Nodes: KSampler With Refiner (Fooocus). The KSampler from Fooocus as a ComfyUI node
NOTE: This patches basic ComfyUI behaviour - don't use together with other samplers. Or perhaps do? Other samplers might profit from those changes ... ymmv.
"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI-Manager/node_db/forked/extension-node-map.json b/custom_nodes/ComfyUI-Manager/node_db/forked/extension-node-map.json
new file mode 100644
index 0000000000000000000000000000000000000000..9e26dfeeb6e641a33dae4961196235bdb965b21b
--- /dev/null
+++ b/custom_nodes/ComfyUI-Manager/node_db/forked/extension-node-map.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI-Manager/node_db/forked/model-list.json b/custom_nodes/ComfyUI-Manager/node_db/forked/model-list.json
new file mode 100644
index 0000000000000000000000000000000000000000..9e26dfeeb6e641a33dae4961196235bdb965b21b
--- /dev/null
+++ b/custom_nodes/ComfyUI-Manager/node_db/forked/model-list.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI-Manager/node_db/forked/scan.sh b/custom_nodes/ComfyUI-Manager/node_db/forked/scan.sh
new file mode 100644
index 0000000000000000000000000000000000000000..5d8d8c48b6e3f48dc1491738c1226f574909c05d
--- /dev/null
+++ b/custom_nodes/ComfyUI-Manager/node_db/forked/scan.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+source ../../../../venv/bin/activate
+rm .tmp/*.py > /dev/null
+python ../../scanner.py
diff --git a/custom_nodes/ComfyUI-Manager/node_db/legacy/alter-list.json b/custom_nodes/ComfyUI-Manager/node_db/legacy/alter-list.json
new file mode 100644
index 0000000000000000000000000000000000000000..9e26dfeeb6e641a33dae4961196235bdb965b21b
--- /dev/null
+++ b/custom_nodes/ComfyUI-Manager/node_db/legacy/alter-list.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI-Manager/node_db/legacy/custom-node-list.json b/custom_nodes/ComfyUI-Manager/node_db/legacy/custom-node-list.json
new file mode 100644
index 0000000000000000000000000000000000000000..4f366cbb1cf3881af41daa4224ccc1e41f155356
--- /dev/null
+++ b/custom_nodes/ComfyUI-Manager/node_db/legacy/custom-node-list.json
@@ -0,0 +1,155 @@
+{
+ "custom_nodes": [
+ {
+ "author": "RockOfFire",
+ "title": "CR Animation Nodes",
+ "reference": "https://github.com/RockOfFire/CR_Animation_Nodes",
+ "files": [
+ "https://github.com/RockOfFire/CR_Animation_Nodes"
+ ],
+ "install_type": "git-clone",
+ "description": "A comprehensive suite of nodes to enhance your animations. These nodes include some features similar to Deforum, and also some new ideas. NOTE: This node is merged into Comfyroll Custom Nodes."
+ },
+ {
+ "author": "tkoenig89",
+ "title": "Load Image with metadata",
+ "reference": "https://github.com/tkoenig89/ComfyUI_Load_Image_With_Metadata",
+ "files": [
+ "https://github.com/tkoenig89/ComfyUI_Load_Image_With_Metadata"
+ ],
+ "install_type": "git-clone",
+ "description": "A custom node for comfy ui to read generation data from images (prompt, seed, size...). This could be used when upscaling generated images to use the original prompt and seed."
+ },
+ {
+ "author": "LucianoCirino",
+ "title": "Efficiency Nodes for ComfyUI [LEGACY]",
+ "reference": "https://github.com/LucianoCirino/efficiency-nodes-comfyui",
+ "files": [
+ "https://github.com/LucianoCirino/efficiency-nodes-comfyui"
+ ],
+ "install_type": "git-clone",
+ "description": "A collection of ComfyUI custom nodes to help streamline workflows and reduce total node count. NOTE: This repository is the original repository but is no longer maintained. Please use the forked version by jags."
+ },
+ {
+ "author": "GeLi1989",
+ "title": "roop nodes for ComfyUI",
+ "reference": "https://github.com/GeLi1989/GK-beifen-ComfyUI_roop",
+ "files": [
+ "https://github.com/GeLi1989/GK-beifen-ComfyUI_roop"
+ ],
+ "install_type": "git-clone",
+ "description": "ComfyUI nodes for the roop A1111 webui script. NOTE: Need to download model to use this node. NOTE: This is removed."
+ },
+ {
+ "author": "ProDALOR",
+ "title": "comfyui_u2net",
+ "reference": "https://github.com/ProDALOR/comfyui_u2net",
+ "files": [
+ "https://github.com/ProDALOR/comfyui_u2net"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes: Load U2Net model, U2Net segmentation, To mask, Segmentation to mask, U2NetBaseNormalization, U2NetMaxNormalization. NOTE: This is removed."
+ },
+ {
+ "author": "FizzleDorf",
+ "title": "AIT",
+ "reference": "https://github.com/FizzleDorf/AIT",
+ "files": [
+ "https://github.com/FizzleDorf/AIT"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes: Load AITemplate, Load AITemplate (ControlNet), VAE Decode (AITemplate), VAE Encode (AITemplate), VAE Encode (AITemplate, Inpaint). Experimental usage of AITemplate. NOTE: This is deprecated extension. Use ComfyUI-AIT instead of this."
+ },
+ {
+ "author": "chenbaiyujason",
+ "title": "sc-node-comfyui",
+ "reference": "https://github.com/chenbaiyujason/sc-node-comfyui",
+ "files": [
+ "https://github.com/chenbaiyujason/sc-node-comfyui"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes for GPT interaction and text manipulation"
+ },
+ {
+ "author": "asd417",
+ "title": "CheckpointTomeLoader",
+ "reference": "https://github.com/asd417/tomeSD_for_Comfy",
+ "files": [
+ "https://github.com/ltdrdata/ComfyUI-tomeSD-installer"
+ ],
+ "install_type": "git-clone",
+ "description": "tomeSD(https://github.com/dbolya/tomesd) applied to ComfyUI stable diffusion UI using custom node. Note:In vanilla ComfyUI, the TomePatchModel node is provided as a built-in feature."
+ },
+ {
+ "author": "gamert",
+ "title": "ComfyUI_tagger",
+ "reference": "https://github.com/gamert/ComfyUI_tagger",
+ "pip": ["gradio"],
+ "files": [
+ "https://github.com/gamert/ComfyUI_tagger"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes: CLIPTextEncodeTaggerDD, ImageTaggerDD.
WARNING: Installing the current version is causing an issue where ComfyUI fails to start.
"
+ },
+ {
+ "author": "Fannovel16",
+ "title": "ControlNet Preprocessors",
+ "reference": "https://github.com/Fannovel16/comfy_controlnet_preprocessors",
+ "files": [
+ "https://github.com/Fannovel16/comfy_controlnet_preprocessors"
+ ],
+ "install_type": "git-clone",
+ "description": "ControlNet Preprocessors. (To use this extension, you need to download the required model file from Install Models)
NOTE: Please uninstall this custom node and instead install 'ComfyUI's ControlNet Auxiliary Preprocessors' from the default channel. To use nodes belonging to controlnet v1 such as Canny_Edge_Preprocessor, MIDAS_Depth_Map_Preprocessor, Uniformer_SemSegPreprocessor, etc., you need to copy the config.yaml.example file to config.yaml and change skip_v1: True to skip_v1: False.
"
+ },
+ {
+ "author": "comfyanonymous",
+ "title": "ComfyUI_experiments/sampler_tonemap",
+ "reference": "https://github.com/comfyanonymous/ComfyUI_experiments",
+ "files": [
+ "https://github.com/comfyanonymous/ComfyUI_experiments/raw/master/sampler_tonemap.py"
+ ],
+ "install_type": "copy",
+ "description": "ModelSamplerTonemapNoiseTest a node that makes the sampler use a simple tonemapping algorithm to tonemap the noise. It will let you use higher CFG without breaking the image. To using higher CFG lower the multiplier value. Similar to Dynamic Thresholding extension of A1111. "
+ },
+ {
+ "author": "comfyanonymous",
+ "title": "ComfyUI_experiments/sampler_rescalecfg",
+ "reference": "https://github.com/comfyanonymous/ComfyUI_experiments",
+ "files": [
+ "https://github.com/comfyanonymous/ComfyUI_experiments/raw/master/sampler_rescalecfg.py"
+ ],
+ "install_type": "copy",
+ "description": "RescaleClassifierFreeGuidance improves the problem of images being degraded by high CFG.To using higher CFG lower the multiplier value. Similar to Dynamic Thresholding extension of A1111. (reference paper)
It is recommended to use the integrated custom nodes in the default channel for update support rather than installing individual nodes.
"
+ },
+ {
+ "author": "comfyanonymous",
+ "title": "ComfyUI_experiments/advanced_model_merging",
+ "reference": "https://github.com/comfyanonymous/ComfyUI_experiments",
+ "files": [
+ "https://github.com/comfyanonymous/ComfyUI_experiments/raw/master/advanced_model_merging.py"
+ ],
+ "install_type": "copy",
+ "description": "This provides a detailed model merge feature based on block weight. ModelMergeBlock, in vanilla ComfyUI, allows for adjusting the ratios of input/middle/output layers, but this node provides ratio adjustments for all blocks within each layer.
It is recommended to use the integrated custom nodes in the default channel for update support rather than installing individual nodes.
"
+ },
+ {
+ "author": "comfyanonymous",
+ "title": "ComfyUI_experiments/sdxl_model_merging",
+ "reference": "https://github.com/comfyanonymous/ComfyUI_experiments",
+ "files": [
+ "https://github.com/comfyanonymous/ComfyUI_experiments/raw/master/sdxl_model_merging.py"
+ ],
+ "install_type": "copy",
+ "description": "These nodes provide the capability to merge SDXL base models.
It is recommended to use the integrated custom nodes in the default channel for update support rather than installing individual nodes.
It is recommended to use the integrated custom nodes in the default channel for update support rather than installing individual nodes.
"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI-Manager/node_db/legacy/extension-node-map.json b/custom_nodes/ComfyUI-Manager/node_db/legacy/extension-node-map.json
new file mode 100644
index 0000000000000000000000000000000000000000..9e26dfeeb6e641a33dae4961196235bdb965b21b
--- /dev/null
+++ b/custom_nodes/ComfyUI-Manager/node_db/legacy/extension-node-map.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI-Manager/node_db/legacy/model-list.json b/custom_nodes/ComfyUI-Manager/node_db/legacy/model-list.json
new file mode 100644
index 0000000000000000000000000000000000000000..9e26dfeeb6e641a33dae4961196235bdb965b21b
--- /dev/null
+++ b/custom_nodes/ComfyUI-Manager/node_db/legacy/model-list.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI-Manager/node_db/new/alter-list.json b/custom_nodes/ComfyUI-Manager/node_db/new/alter-list.json
new file mode 100644
index 0000000000000000000000000000000000000000..072c3bb5e8bd05b6f14f6df25386dc1e1010a137
--- /dev/null
+++ b/custom_nodes/ComfyUI-Manager/node_db/new/alter-list.json
@@ -0,0 +1,4 @@
+{
+ "items": [
+ ]
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI-Manager/node_db/new/custom-node-list.json b/custom_nodes/ComfyUI-Manager/node_db/new/custom-node-list.json
new file mode 100644
index 0000000000000000000000000000000000000000..3bda1d94eca007508d06672f2c1d5f8cfa4a1511
--- /dev/null
+++ b/custom_nodes/ComfyUI-Manager/node_db/new/custom-node-list.json
@@ -0,0 +1,876 @@
+{
+ "custom_nodes": [
+ {
+ "author": "jtrue",
+ "title": "ComfyUI-JaRue",
+ "reference": "https://github.com/jtrue/ComfyUI-JaRue",
+ "files": [
+ "https://github.com/jtrue/ComfyUI-JaRue"
+ ],
+ "install_type": "git-clone",
+ "description": "A collection of nodes powering a tensor oracle on a home network with automation"
+ },
+ {
+ "author": "filliptm",
+ "title": "ComfyUI_Fill-Nodes",
+ "reference": "https://github.com/filliptm/ComfyUI_Fill-Nodes",
+ "files": [
+ "https://github.com/filliptm/ComfyUI_Fill-Nodes"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:FL Image Randomizer. The start of a pack that I will continue to build out to fill the gaps of nodes and functionality that I feel is missing in comfyUI"
+ },
+ {
+ "author": "thecooltechguy",
+ "title": "ComfyUI-MagicAnimate",
+ "reference": "https://github.com/thecooltechguy/ComfyUI-MagicAnimate",
+ "files": [
+ "https://github.com/thecooltechguy/ComfyUI-MagicAnimate"
+ ],
+ "install_type": "git-clone",
+ "description": "Easily use Magic Animate within ComfyUI!"
+ },
+ {
+ "author": "knuknX",
+ "title": "ComfyUI-Image-Tools",
+ "reference": "https://github.com/knuknX/ComfyUI-Image-Tools",
+ "files": [
+ "https://github.com/knuknX/ComfyUI-Image-Tools"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:BatchImageResizeProcessor, SingleImagePathLoader, SingleImageUrlLoader"
+ },
+ {
+ "author": "11cafe",
+ "title": "ComfyUI Workspace Manager - Comfyspace",
+ "reference": "https://github.com/11cafe/comfyui-workspace-manager",
+ "files": [
+ "https://github.com/11cafe/comfyui-workspace-manager"
+ ],
+ "install_type": "git-clone",
+ "description": "A ComfyUI custom node for project management to centralize the management of all your workflows in one place. Seamlessly switch between workflows, create and update them within a single workspace, like Google Docs."
+ },
+ {
+ "author": "AustinMroz",
+ "title": "SpliceTools",
+ "reference": "https://github.com/AustinMroz/ComfyUI-SpliceTools",
+ "files": [
+ "https://github.com/AustinMroz/ComfyUI-SpliceTools"
+ ],
+ "install_type": "git-clone",
+ "description": "Experimental utility nodes with a focus on manipulation of noised latents"
+ },
+ {
+ "author": "asagi4",
+ "title": "asagi4/comfyui-utility-nodes",
+ "reference": "https://github.com/asagi4/comfyui-utility-nodes",
+ "files": [
+ "https://github.com/asagi4/comfyui-utility-nodes"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:MUJinjaRender, MUSimpleWildcard"
+ },
+ {
+ "author": "asagi4",
+ "title": "ComfyUI-CADS",
+ "reference": "https://github.com/asagi4/ComfyUI-CADS",
+ "files": [
+ "https://github.com/asagi4/ComfyUI-CADS"
+ ],
+ "install_type": "git-clone",
+ "description": "Attempts to implement [a/CADS](https://arxiv.org/abs/2310.17347) for ComfyUI. Credit also to the [a/A1111 implementation](https://github.com/v0xie/sd-webui-cads/tree/main) that I used as a reference."
+ },
+ {
+ "author": "Electrofried",
+ "title": "OpenAINode",
+ "reference": "https://github.com/Electrofried/ComfyUI-OpenAINode",
+ "files": [
+ "https://github.com/Electrofried/ComfyUI-OpenAINode"
+ ],
+ "install_type": "git-clone",
+ "description": "A simply node for hooking in to openAI API based servers via comfyUI"
+ },
+ {
+ "author": "zcfrank1st",
+ "title": "comfyui_visual_anagram",
+ "reference": "https://github.com/zcfrank1st/comfyui_visual_anagrams",
+ "files": [
+ "https://github.com/zcfrank1st/comfyui_visual_anagrams"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:visual_anagrams_sample, visual_anagrams_animate"
+ },
+ {
+ "author": "subtleGradient",
+ "title": "Touchpad two-finger gesture support for macOS",
+ "reference": "https://github.com/subtleGradient/TinkerBot-tech-for-ComfyUI-Touchpad",
+ "files": [
+ "https://github.com/subtleGradient/TinkerBot-tech-for-ComfyUI-Touchpad"
+ ],
+ "install_type": "git-clone",
+ "description": "Two-finger scrolling (vertical and horizontal) to pan the canvas. Two-finger pinch to zoom in and out. Command-scroll up and down to zoom in and out. Fixes [a/comfyanonymous/ComfyUI#2059](https://github.com/comfyanonymous/ComfyUI/issues/2059)."
+ },
+ {
+ "author": "SoftMeng",
+ "title": "ComfyUI_Mexx_Poster",
+ "reference": "https://github.com/SoftMeng/ComfyUI_Mexx_Poster",
+ "files": [
+ "https://github.com/SoftMeng/ComfyUI_Mexx_Poster"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes: ComfyUI_Mexx_Poster"
+ },
+ {
+ "author": "ningxiaoxiao",
+ "title": "comfyui-NDI",
+ "reference": "https://github.com/ningxiaoxiao/comfyui-NDI",
+ "files": [
+ "https://github.com/ningxiaoxiao/comfyui-NDI"
+ ],
+ "pip": ["ndi-python"],
+ "install_type": "git-clone",
+ "description": "Real-time input output node for ComfyUI by NDI. Leveraging the powerful linking capabilities of NDI, you can access NDI video stream frames and send images generated by the model to NDI video streams."
+ },
+ {
+ "author": "Fannovel16",
+ "title": "ComfyUI-Video-Matting",
+ "reference": "https://github.com/Fannovel16/ComfyUI-Video-Matting",
+ "files": [
+ "https://github.com/Fannovel16/ComfyUI-Video-Matting"
+ ],
+ "install_type": "git-clone",
+ "description": "A minimalistic implementation of [a/Robust Video Matting (RVM)](https://github.com/PeterL1n/RobustVideoMatting/) in ComfyUI"
+ },
+ {
+ "author": "Dream Project",
+ "title": "Dream Video Batches",
+ "reference": "https://github.com/alt-key-project/comfyui-dream-video-batches",
+ "files": [
+ "https://github.com/alt-key-project/comfyui-dream-video-batches"
+ ],
+ "install_type": "git-clone",
+ "description": "Provide utilities for batch based video generation workflows (s.a. AnimateDiff and Stable Video Diffusion)."
+ },
+ {
+ "author": "bedovyy",
+ "title": "ComfyUI_NAIDGenerator",
+ "reference": "https://github.com/bedovyy/ComfyUI_NAIDGenerator",
+ "files": [
+ "https://github.com/bedovyy/ComfyUI_NAIDGenerator"
+ ],
+ "install_type": "git-clone",
+ "description": "This extension helps generate images through NAI."
+ },
+ {
+ "author": "jags111",
+ "title": "ComfyUI_Jags_Audiotools",
+ "reference": "https://github.com/jags111/ComfyUI_Jags_Audiotools",
+ "files": [
+ "https://github.com/jags111/ComfyUI_Jags_Audiotools"
+ ],
+ "install_type": "git-clone",
+ "description": "A collection amazing audio tools for working with audio and sound files in comfyUI"
+ },
+ {
+ "author": "Haoming02",
+ "title": "ComfyUI Diffusion Color Grading",
+ "reference": "https://github.com/Haoming02/comfyui-diffusion-cg",
+ "files": [
+ "https://github.com/Haoming02/comfyui-diffusion-cg"
+ ],
+ "install_type": "git-clone",
+ "description": "This is the ComfyUI port of the joint research between me and TimothyAlexisVass. For more information, check out the original [a/Extension](https://github.com/Haoming02/sd-webui-diffusion-cg) for Automatic1111."
+ },
+ {
+ "author": "Scholar01",
+ "title": "SComfyUI-Keyframe",
+ "reference": "https://github.com/Scholar01/ComfyUI-Keyframe",
+ "files": [
+ "https://github.com/Scholar01/ComfyUI-Keyframe"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:Keyframe Part, Keyframe Interpolation Part, Keyframe Apply."
+ },
+ {
+ "author": "WebDev9000",
+ "title": "WebDev9000-Nodes",
+ "reference": "https://github.com/WebDev9000/WebDev9000-Nodes",
+ "files": [
+ "https://github.com/WebDev9000/WebDev9000-Nodes"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:Ignore Braces, Settings Switch."
+ },
+ {
+ "author": "vanillacode314",
+ "title": "Simple Wildcard",
+ "reference": "https://github.com/vanillacode314/SimpleWildcardsComfyUI",
+ "files": ["https://github.com/vanillacode314/SimpleWildcardsComfyUI"],
+ "install_type": "git-clone",
+ "pip": ["pipe"],
+ "description": "A simple wildcard node for ComfyUI. Can also be used a style prompt node."
+ },
+ {
+ "author": "DrJKL",
+ "title": "ComfyUI-Anchors",
+ "reference": "https://github.com/DrJKL/ComfyUI-Anchors",
+ "files": [
+ "https://github.com/DrJKL/ComfyUI-Anchors"
+ ],
+ "install_type": "git-clone",
+ "description": "A ComfyUI extension to add spatial anchors/waypoints to better navigate large workflows."
+ },
+ {
+ "author": "wmatson",
+ "title": "easy-comfy-nodes",
+ "reference": "https://github.com/wmatson/easy-comfy-nodes",
+ "files": [
+ "https://github.com/wmatson/easy-comfy-nodes"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes: HTTP POST, Empty Dict, Assoc Str, Assoc Dict, Assoc Img, Load Img From URL (EZ), Load Img Batch From URLs (EZ), Video Combine + upload (EZ), ..."
+ },
+ {
+ "author": "SoftMeng",
+ "title": "ComfyUI_Mexx_Styler",
+ "reference": "https://github.com/SoftMeng/ComfyUI_Mexx_Styler",
+ "files": [
+ "https://github.com/SoftMeng/ComfyUI_Mexx_Styler"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes: ComfyUI Mexx Styler, ComfyUI Mexx Styler Advanced"
+ },
+ {
+ "author": "zcfrank1st",
+ "title": "ComfyUI Yolov8",
+ "reference": "https://github.com/zcfrank1st/Comfyui-Yolov8",
+ "files": [
+ "https://github.com/zcfrank1st/Comfyui-Yolov8"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes: Yolov8Detection, Yolov8Segmentation. Deadly simple yolov8 comfyui plugin"
+ },
+ {
+ "author": "discopixel-studio",
+ "title": "ComfyUI Discopixel Nodes",
+ "reference": "https://github.com/discopixel-studio/comfyui-discopixel",
+ "files": [
+ "https://github.com/discopixel-studio/comfyui-discopixel"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:TransformTemplateOntoFaceMask, ..."
+ },
+ {
+ "author": "zhuanqianfish",
+ "title": "EasyCaptureNode for ComfyUI",
+ "reference": "https://github.com/zhuanqianfish/ComfyUI-EasyNode",
+ "files": [
+ "https://github.com/zhuanqianfish/ComfyUI-EasyNode"
+ ],
+ "install_type": "git-clone",
+ "description": "Capture window content from other programs, easyway combined with LCM for real-time painting"
+ },
+ {
+ "author": "AbdullahAlfaraj",
+ "title": "Comfy-Photoshop-SD",
+ "reference": "https://github.com/AbdullahAlfaraj/Comfy-Photoshop-SD",
+ "files": [
+ "https://github.com/AbdullahAlfaraj/Comfy-Photoshop-SD"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes: load Image with metadata, get config data, load image from base64 string, Load Loras From Prompt, Generate Latent Noise, Combine Two Latents Into Batch, General Purpose Controlnet Unit, ControlNet Script, Content Mask Latent, Auto-Photoshop-SD Seed, Expand and Blur the Mask"
+ },
+ {
+ "author": "80sVectorz",
+ "title": "ComfyUI-Static-Primitives",
+ "reference": "https://github.com/80sVectorz/ComfyUI-Static-Primitives",
+ "files": [
+ "https://github.com/80sVectorz/ComfyUI-Static-Primitives"
+ ],
+ "install_type": "git-clone",
+ "description": "Adds Static Primitives to ComfyUI. Mostly to work with reroute nodes"
+ },
+ {
+ "author": "kenjiqq",
+ "title": "qq-nodes-comfyui",
+ "reference": "https://github.com/kenjiqq/qq-nodes-comfyui",
+ "files": [
+ "https://github.com/kenjiqq/qq-nodes-comfyui"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:Any List, Image Accumulator Start, Image Accumulator End, Load Lines From Text File, XY Grid Helper, Slice List, Axis To String/Int/Float/Model, ..."
+ },
+ {
+ "author": "fearnworks",
+ "title": "Fearnworks Custom Nodes",
+ "reference": "https://github.com/fearnworks/ComfyUI_FearnworksNodes",
+ "files": [
+ "https://github.com/fearnworks/ComfyUI_FearnworksNodes/raw/main/fw_nodes.py"
+ ],
+ "install_type": "copy",
+ "description": "A collection of ComfyUI nodes. These nodes are tailored for specific tasks, such as counting files in directories and sorting text segments based on token counts. Currently this is only tested on SDXL 1.0 models. An additional swich is needed to hand 1.x"
+ },
+ {
+ "author": "hayden-fr",
+ "title": "ComfyUI-Image-Browsing",
+ "reference": "https://github.com/hayden-fr/ComfyUI-Image-Browsing",
+ "files": [
+ "https://github.com/hayden-fr/ComfyUI-Image-Browsing"
+ ],
+ "install_type": "git-clone",
+ "description": "Image Browsing: browsing, download and delete."
+ },
+ {
+ "author": "kinfolk0117",
+ "title": "TiledIPAdapter",
+ "reference": "https://github.com/kinfolk0117/ComfyUI_TiledIPAdapter",
+ "files": [
+ "https://github.com/kinfolk0117/ComfyUI_TiledIPAdapter"
+ ],
+ "install_type": "git-clone",
+ "description": "Proof of concent on how to use IPAdapter to control tiled upscaling. NOTE: You need to have 'ComfyUI_IPAdapter_plus' installed."
+ },
+ {
+ "author": "komojini",
+ "title": "ComfyUI_SDXL_DreamBooth_LoRA_CustomNodes",
+ "reference": "https://github.com/komojini/ComfyUI_SDXL_DreamBooth_LoRA_CustomNodes",
+ "files": [
+ "https://github.com/komojini/ComfyUI_SDXL_DreamBooth_LoRA_CustomNodes"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:XL DreamBooth LoRA, S3 Bucket LoRA"
+ },
+ {
+ "author": "42lux",
+ "title": "ComfyUI-safety-checker",
+ "reference": "https://github.com/42lux/ComfyUI-safety-checker",
+ "files": [
+ "https://github.com/42lux/ComfyUI-safety-checker"
+ ],
+ "install_type": "git-clone",
+ "description": "A NSFW/Safety Checker Node for ComfyUI."
+ },
+ {
+ "author": "ZHO-ZHO-ZHO",
+ "title": "ComfyUI-Text_Image-Composite",
+ "reference": "https://github.com/ZHO-ZHO-ZHO/ComfyUI-Text_Image-Composite",
+ "files": [
+ "https://github.com/ZHO-ZHO-ZHO/ComfyUI-Text_Image-Composite"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:Text_Image_Zho, Text_Image_Multiline_Zho, RGB_Image_Zho, AlphaChanelAddByMask, ImageComposite_Zho, ..."
+ },
+ {
+ "author": "sergekatzmann",
+ "title": "ComfyUI_Nimbus-Pack",
+ "reference": "https://github.com/sergekatzmann/ComfyUI_Nimbus-Pack",
+ "files": [
+ "https://github.com/sergekatzmann/ComfyUI_Nimbus-Pack"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:Image Square Adapter Node, Image Resize And Crop Node"
+ },
+ {
+ "author": "Danand",
+ "title": "ComfyUI-ComfyCouple",
+ "reference": "https://github.com/Danand/ComfyUI-ComfyCouple",
+ "files": [
+ "https://github.com/Danand/ComfyUI-ComfyCouple"
+ ],
+ "install_type": "git-clone",
+ "description": " Simple custom node which helps to generate images of actual couples."
+ },
+ {
+ "author": "thecooltechguy",
+ "title": "ComfyUI Stable Video Diffusion",
+ "reference": "https://github.com/thecooltechguy/ComfyUI-Stable-Video-Diffusion",
+ "files": [
+ "https://github.com/thecooltechguy/ComfyUI-Stable-Video-Diffusion"
+ ],
+ "install_type": "git-clone",
+ "description": "Easily use Stable Video Diffusion inside ComfyUI!"
+ },
+ {
+ "author": "toyxyz",
+ "title": "ComfyUI_toyxyz_test_nodes",
+ "reference": "https://github.com/toyxyz/ComfyUI_toyxyz_test_nodes",
+ "files": [
+ "https://github.com/toyxyz/ComfyUI_toyxyz_test_nodes"
+ ],
+ "install_type": "git-clone",
+ "description": "This node was created to send a webcam to ComfyUI in real time. This node is recommended for use with LCM."
+ },
+ {
+ "author": "kijai",
+ "title": "ComfyUI-SVD",
+ "reference": "https://github.com/kijai/ComfyUI-SVD",
+ "files": [
+ "https://github.com/kijai/ComfyUI-SVD"
+ ],
+ "install_type": "git-clone",
+ "description": "Preliminary use of SVD in ComfyUI.\nNOTE: Quick Implementation, Unstable. See details on repositories."
+ },
+ {
+ "author": "bronkula",
+ "title": "comfyui-fitsize",
+ "reference": "https://github.com/bronkula/comfyui-fitsize",
+ "files": [
+ "https://github.com/bronkula/comfyui-fitsize"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:Fit Size From Int/Image/Resize, Load Image And Resize To Fit, Pick Image From Batch/List, Crop Image Into Even Pieces, Image Region To Mask... A simple set of nodes for making an image fit within a bounding box"
+ },
+ {
+ "author": "drago87",
+ "title": "ComfyUI_Dragos_Nodes",
+ "reference": "https://github.com/drago87/ComfyUI_Dragos_Nodes",
+ "files": [
+ "https://github.com/drago87/ComfyUI_Dragos_Nodes"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:File Padding, Image Info, VAE Loader With Name"
+ },
+ {
+ "author": "ansonkao",
+ "title": "comfyui-geometry",
+ "reference": "https://github.com/ansonkao/comfyui-geometry",
+ "files": [
+ "https://github.com/ansonkao/comfyui-geometry"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes: Mask to Centroid, Mask to Eigenvector. A small collection of custom nodes for use with ComfyUI, for geometry calculations"
+ },
+ {
+ "author": "GTSuya-Studio",
+ "title": "ComfyUI-GTSuya-Nodes",
+ "reference": "https://github.com/GTSuya-Studio/ComfyUI-Gtsuya-Nodes",
+ "files": [
+ "https://github.com/GTSuya-Studio/ComfyUI-Gtsuya-Nodes"
+ ],
+ "install_type": "git-clone",
+ "description": "ComfyUI-GTSuya-Nodes is a ComyUI extension designed to add several wildcards supports into ComfyUI. Wildcards allow you to use __name__ syntax in your prompt to get a random line from a file named name.txt in a wildcards directory."
+ },
+ {
+ "author": "jojkaart",
+ "title": "ComfyUI-sampler-lcm-alternative",
+ "reference": "https://github.com/jojkaart/ComfyUI-sampler-lcm-alternative",
+ "files": [
+ "https://github.com/jojkaart/ComfyUI-sampler-lcm-alternative"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:LCMScheduler, SamplerLCMAlternative, SamplerLCMCycle. ComfyUI Custom Sampler nodes that add a new improved LCM sampler functions"
+ },
+ {
+ "author": "LonicaMewinsky",
+ "title": "ComfyUI-RawSaver",
+ "reference": "https://github.com/LonicaMewinsky/ComfyUI-RawSaver",
+ "files": [
+ "https://github.com/LonicaMewinsky/ComfyUI-RawSaver"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:SaveTifImage. ComfyUI custom node for purpose of saving image as uint16 tif file."
+ },
+ {
+ "author": "natto-maki",
+ "title": "ComfyUI-NegiTools",
+ "reference": "https://github.com/natto-maki/ComfyUI-NegiTools",
+ "files": [
+ "https://github.com/natto-maki/ComfyUI-NegiTools"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:OpenAI DALLe3, OpenAI Translate to English, String Function, Seed Generator"
+ },
+ {
+ "author": "wutipong",
+ "title": "ComfyUI-TextUtils",
+ "reference": "https://github.com/wutipong/ComfyUI-TextUtils",
+ "files": [
+ "https://github.com/wutipong/ComfyUI-TextUtils"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:Create N-Token String"
+ },
+ {
+ "author": "Feidorian",
+ "title": "feidorian-ComfyNodes",
+ "reference": "https://github.com/Feidorian/feidorian-ComfyNodes",
+ "nodename_pattern": "^Feidorian_",
+ "files": [
+ "https://github.com/Feidorian/feidorian-ComfyNodes"
+ ],
+ "install_type": "git-clone",
+ "description": "This extension provides various custom nodes. literals, loaders, logic, output, switches"
+ },
+ {
+ "author": "kinfolk0117",
+ "title": "ComfyUI_GradientDeepShrink",
+ "reference": "https://github.com/kinfolk0117/ComfyUI_GradientDeepShrink",
+ "files": [
+ "https://github.com/kinfolk0117/ComfyUI_GradientDeepShrink"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:GradientPatchModelAddDownscale (Kohya Deep Shrink)."
+ },
+ {
+ "author": "Niutonian",
+ "title": "ComfyUi-NoodleWebcam",
+ "reference": "https://github.com/Niutonian/ComfyUi-NoodleWebcam",
+ "files": [
+ "https://github.com/Niutonian/ComfyUi-NoodleWebcam"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:Noodle webcam is a node that records frames and send them to your favourite node."
+ },
+ {
+ "author": "Umikaze-job",
+ "title": "select_folder_path_easy",
+ "reference": "https://github.com/Umikaze-job/select_folder_path_easy",
+ "files": [
+ "https://github.com/Umikaze-job/select_folder_path_easy"
+ ],
+ "install_type": "git-clone",
+ "description": "This extension simply connects the nodes and specifies the output path of the generated images to a manageable path."
+ },
+ {
+ "author": "amorano",
+ "title": "Jovimetrix Composition Nodes",
+ "reference": "https://github.com/Amorano/Jovimetrix",
+ "files": [
+ "https://github.com/Amorano/Jovimetrix"
+ ],
+ "nodename_pattern": " \\(jov\\)$",
+ "install_type": "git-clone",
+ "description": "Compose like Substance Designer. Webcams, Media Streams (in/out), Tick animation, Color correction, Geometry manipulation, Pixel shader, Polygonal shape generator, Remap images gometry and color, Heavily inspired by WAS and MTB Node Suites"
+ },
+ {
+ "author": "romeobuilderotti",
+ "title": "ComfyUI PNG Metadata",
+ "reference": "https://github.com/romeobuilderotti/ComfyUI-PNG-Metadata",
+ "files": [
+ "https://github.com/romeobuilderotti/ComfyUI-PNG-Metadata"
+ ],
+ "install_type": "git-clone",
+ "description": "Add custom Metadata fields to your saved PNG files."
+ },
+ {
+ "author": "ka-puna",
+ "title": "comfyui-yanc",
+ "reference": "https://github.com/ka-puna/comfyui-yanc",
+ "files": [
+ "https://github.com/ka-puna/comfyui-yanc"
+ ],
+ "install_type": "git-clone",
+ "description": "NOTE: Concatenate Strings, Format Datetime String, Integer Caster, Multiline String, Truncate String. Yet Another Node Collection, a repository of simple nodes for ComfyUI. This repository eases the addition or removal of custom nodes to itself."
+ },
+ {
+ "author": "TheBarret",
+ "title": "ZSuite",
+ "reference": "https://github.com/TheBarret/ZSuite",
+ "files": [
+ "https://github.com/TheBarret/ZSuite"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:Prompter, RF Noise, SeedMod."
+ },
+ {
+ "author": "palant",
+ "title": "Extended Save Image for ComfyUI",
+ "reference": "https://github.com/palant/extended-saveimage-comfyui",
+ "files": [
+ "https://github.com/palant/extended-saveimage-comfyui"
+ ],
+ "install_type": "git-clone",
+ "description": "This custom node is largely identical to the usual Save Image but allows saving images also in JPEG and WEBP formats, the latter with both lossless and lossy compression. Metadata is embedded in the images as usual, and the resulting images can be used to load a workflow."
+ },
+ {
+ "author": "LonicaMewinsky",
+ "title": "ComfyBreakAnim",
+ "reference": "https://github.com/LonicaMewinsky/ComfyUI-MakeFrame",
+ "files": [
+ "https://github.com/LonicaMewinsky/ComfyUI-MakeFrame"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:BreakFrames, GetKeyFrames, MakeGrid."
+ },
+ {
+ "author": "gemell1",
+ "title": "ComfyUI_GMIC",
+ "reference": "https://github.com/gemell1/ComfyUI_GMIC",
+ "files": [
+ "https://github.com/gemell1/ComfyUI_GMIC"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:GMIC Image Processing."
+ },
+ {
+ "author": "peteromallet",
+ "title": "ComfyUI-Creative-Interpolation [Beta]",
+ "reference": "https://github.com/peteromallet/ComfyUI-Creative-Interpolation",
+ "files": [
+ "https://github.com/peteromallet/ComfyUI-Creative-Interpolation"
+ ],
+ "install_type": "git-clone",
+ "description": "This a ComfyUI node for batch creative interpolation. The goal is to allow you to input a batch of images, and to provide a range of simple settings to control how the images are interpolated between."
+ },
+ {
+ "author": "martijnat",
+ "title": "comfyui-previewlatent",
+ "reference": "https://github.com/martijnat/comfyui-previewlatent",
+ "files": [
+ "https://github.com/martijnat/comfyui-previewlatent"
+ ],
+ "install_type": "git-clone",
+ "description": "a ComfyUI plugin for previewing latents without vae decoding. Useful for showing intermediate results and can be used a faster 'preview image' if you don't wan't to use vae decode."
+ },
+ {
+ "author": "whmc76",
+ "title": "ComfyUI-Openpose-Editor-Plus",
+ "reference": "https://github.com/whmc76/ComfyUI-Openpose-Editor-Plus",
+ "files": [
+ "https://github.com/whmc76/ComfyUI-Openpose-Editor-Plus"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:Openpose Editor Plus"
+ },
+ {
+ "author": "Off-Live",
+ "title": "ComfyUI-off-suite",
+ "reference": "https://github.com/Off-Live/ComfyUI-off-suite",
+ "files": [
+ "https://github.com/Off-Live/ComfyUI-off-suite"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:Image Crop Fit, OFF SEGS to Image, Crop Center wigh SEGS, Watermarking, GW Number Formatting Node."
+ },
+ {
+ "author": "laksjdjf",
+ "title": "LCMSampler-ComfyUI",
+ "reference": "https://github.com/laksjdjf/LCMSampler-ComfyUI",
+ "files": [
+ "https://github.com/laksjdjf/LCMSampler-ComfyUI"
+ ],
+ "install_type": "git-clone",
+ "description": "This extension node is intended for the use of LCM conversion for SSD-1B-anime. It does not guarantee operation with the original LCM (as it cannot load weights in the current version). To take advantage of fast generation with LCM, a node for using TAESD as a decoder is also provided. This is inspired by ComfyUI-OtherVAEs."
+ },
+ {
+ "author": "palant",
+ "title": "Integrated Nodes for ComfyUI",
+ "reference": "https://github.com/palant/integrated-nodes-comfyui",
+ "files": [
+ "https://github.com/palant/integrated-nodes-comfyui"
+ ],
+ "install_type": "git-clone",
+ "description": "This tool will turn entire workflows or parts of them into single integrated nodes. In a way, it is similar to the Node Templates functionality but hides the inner structure. This is useful if all you want is to reuse and quickly configure a bunch of nodes without caring how they are interconnected."
+ },
+ {
+ "author": "palant",
+ "title": "Image Resize for ComfyUI",
+ "reference": "https://github.com/palant/image-resize-comfyui",
+ "files": [
+ "https://github.com/palant/image-resize-comfyui"
+ ],
+ "install_type": "git-clone",
+ "description": "This custom node provides various tools for resizing images. The goal is resizing without distorting proportions, yet without having to perform any calculations with the size of the original image. If a mask is present, it is resized and modified along with the image."
+ },
+ {
+ "author": "M1kep",
+ "title": "ComfyUI-KepOpenAI",
+ "reference": "https://github.com/M1kep/ComfyUI-KepOpenAI",
+ "files": [
+ "https://github.com/M1kep/ComfyUI-KepOpenAI"
+ ],
+ "install_type": "git-clone",
+ "description": "ComfyUI-KepOpenAI is a user-friendly node that serves as an interface to the GPT-4 with Vision (GPT-4V) API. This integration facilitates the processing of images coupled with text prompts, leveraging the capabilities of the OpenAI API to generate text completions that are contextually relevant to the provided inputs."
+ },
+ {
+ "author": "bmad4ever",
+ "title": "comfyui_ab_sampler",
+ "reference": "https://github.com/bmad4ever/comfyui_ab_samplercustom",
+ "files": [
+ "https://github.com/bmad4ever/comfyui_ab_samplercustom"
+ ],
+ "install_type": "git-clone",
+ "description": "Experimental sampler node. Sampling alternates between A and B inputs until only one remains, starting with A. B steps run over a 2x2 grid, where 3/4's of the grid are copies of the original input latent. When the optional mask is used, the region outside the defined roi is copied from the original latent at the end of every step."
+ },
+ {
+ "author": "AbyssYuan0",
+ "title": "ComfyUI_BadgerTools",
+ "reference": "https://github.com/AbyssYuan0/ComfyUI_BadgerTools",
+ "files": [
+ "https://github.com/AbyssYuan0/ComfyUI_BadgerTools"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:ImageOverlap-badger, FloatToInt-badger, IntToString-badger, FloatToString-badger, ImageNormalization-badger, ImageScaleToSide-badger, NovelToFizz-badger."
+ },
+ {
+ "author": "fexli",
+ "title": "fexli-util-node-comfyui",
+ "reference": "https://github.com/fexli/fexli-util-node-comfyui",
+ "files": [
+ "https://github.com/fexli/fexli-util-node-comfyui"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:FEImagePadForOutpaint, FEColorOut, FEColor2Image, FERandomizedColor2Image"
+ },
+ {
+ "author": "nagolinc",
+ "title": "ComfyUI_FastVAEDecorder_SDXL",
+ "reference": "https://github.com/nagolinc/ComfyUI_FastVAEDecorder_SDXL",
+ "files": [
+ "https://github.com/nagolinc/ComfyUI_FastVAEDecorder_SDXL"
+ ],
+ "install_type": "git-clone",
+ "description": "Based off of: [a/Birch-san/diffusers-play/approx_vae](https://github.com/Birch-san/diffusers-play/tree/main/approx_vae). This ComfyUI node allows you to quickly preview SDXL 1.0 latents."
+ },
+ {
+ "author": "jags111",
+ "title": "ComfyUI_Jags_VectorMagic",
+ "reference": "https://github.com/jags111/ComfyUI_Jags_VectorMagic",
+ "files": [
+ "https://github.com/jags111/ComfyUI_Jags_VectorMagic"
+ ],
+ "install_type": "git-clone",
+ "description": "a collection of nodes to explore Vector and image manipulation"
+ },
+ {
+ "author": "Trung0246",
+ "title": "ComfyUI-0246",
+ "reference": "https://github.com/Trung0246/ComfyUI-0246",
+ "files": [
+ "https://github.com/Trung0246/ComfyUI-0246"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes: Highway, Junction. Random nodes for ComfyUI I made to solve my struggle with ComfyUI. Have varying quality."
+ },
+ {
+ "author": "PCMonsterx",
+ "title": "ComfyUI-CSV-Loader",
+ "reference": "https://github.com/PCMonsterx/ComfyUI-CSV-Loader",
+ "files": [
+ "https://github.com/PCMonsterx/ComfyUI-CSV-Loader"
+ ],
+ "install_type": "git-clone",
+ "description": "CSV Loader for prompt building within ComfyUI interface. Allows access to positive/negative prompts associated with a name. Selections are being pulled from CSV files."
+ },
+ {
+ "author": "IAmMatan.com",
+ "title": "ComfyUI Serving toolkit",
+ "reference": "https://github.com/matan1905/ComfyUI-Serving-Toolkit",
+ "files": [
+ "https://github.com/matan1905/ComfyUI-Serving-Toolkit"
+ ],
+ "install_type": "git-clone",
+ "description": "This extension adds nodes that allow you to easily serve your workflow (for example using a discord bot) "
+ },
+ {
+ "author": "ParmanBabra",
+ "title": "ComfyUI-Malefish-Custom-Scripts",
+ "reference": "https://github.com/ParmanBabra/ComfyUI-Malefish-Custom-Scripts",
+ "files": [
+ "https://github.com/ParmanBabra/ComfyUI-Malefish-Custom-Scripts"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:Multi Lora Loader, Random (Prompt), Combine (Prompt), CSV Prompts Loader"
+ },
+ {
+ "author": "mikkel",
+ "title": "ComfyUI - Mask Bounding Box",
+ "reference": "https://github.com/mikkel/comfyui-mask-boundingbox",
+ "files": [
+ "https://github.com/mikkel/comfyui-mask-boundingbox"
+ ],
+ "install_type": "git-clone",
+ "description": "The ComfyUI Mask Bounding Box Plugin provides functionalities for selecting a specific size mask from an image. Can be combined with ClipSEG to replace any aspect of an SDXL image with an SD1.5 output."
+ },
+ {
+ "author": "THtianhao",
+ "title": "ComfyUI-FaceChain",
+ "reference": "https://github.com/THtianhao/ComfyUI-FaceChain",
+ "files": [
+ "https://github.com/THtianhao/ComfyUI-FaceChain"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:FC_LoraMerge."
+ },
+ {
+ "author": "noEmbryo",
+ "title": "noEmbryo nodes",
+ "reference": "https://github.com/noembryo/ComfyUI-noEmbryo",
+ "files": [
+ "https://github.com/noembryo/ComfyUI-noEmbryo"
+ ],
+ "install_type": "git-clone",
+ "description": "PromptTermList (1-6): are some nodes that help with the creation of Prompts inside ComfyUI. Resolution Scale outputs image dimensions using a scale factor. Regex Text Chopper outputs the chopped parts of a text using RegEx."
+ },
+ {
+ "author": "aianimation55",
+ "title": "Comfy UI FatLabels",
+ "reference": "https://github.com/aianimation55/ComfyUI-FatLabels",
+ "files": [
+ "https://github.com/aianimation55/ComfyUI-FatLabels"
+ ],
+ "install_type": "git-clone",
+ "description": "It's a super simple custom node for Comfy UI, to generate text, with a font size option. Useful for bigger labelling of nodes, helpful for wider screen captures or tutorials. Plus you can of course use the text within your generations."
+ },
+ {
+ "author": "idrirap",
+ "title": "ComfyUI-Lora-Auto-Trigger-Words",
+ "reference": "https://github.com/idrirap/ComfyUI-Lora-Auto-Trigger-Words",
+ "files": [
+ "https://github.com/idrirap/ComfyUI-Lora-Auto-Trigger-Words"
+ ],
+ "install_type": "git-clone",
+ "description": "This project is a fork of [a/https://github.com/Extraltodeus/LoadLoraWithTags](https://github.com/Extraltodeus/LoadLoraWithTags) The aim of these custom nodes is to get an easy access to the tags used to trigger a lora."
+ },
+ {
+ "author": "jags111",
+ "title": "Efficiency Nodes for ComfyUI Version 2.0+",
+ "reference": "https://github.com/jags111/efficiency-nodes-comfyui",
+ "files": [
+ "https://github.com/jags111/efficiency-nodes-comfyui"
+ ],
+ "install_type": "git-clone",
+ "description": "A collection of ComfyUI custom nodes to help streamline workflows and reduce total node count.[w/NOTE: This node is originally created by LucianoCirino, but the [a/original repository](https://github.com/LucianoCirino/efficiency-nodes-comfyui) is no longer maintained and has been forked by a new maintainer. To use the forked version, you should uninstall the original version and **REINSTALL** this one.]"
+ },
+ {
+ "author": "M1kep",
+ "title": "ComfyUI-OtherVAEs",
+ "reference": "https://github.com/M1kep/ComfyUI-OtherVAEs",
+ "files": [
+ "https://github.com/M1kep/ComfyUI-OtherVAEs"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes: TAESD VAE Decode"
+ },
+ {
+ "author": "Fictiverse",
+ "title": "ComfyUI Fictiverse Nodes",
+ "reference": "https://github.com/Fictiverse/ComfyUI_Fictiverse",
+ "files": [
+ "https://github.com/Fictiverse/ComfyUI_Fictiverse"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:Color correction."
+ },
+ {
+ "author": "kinfolk0117",
+ "title": "SimpleTiles",
+ "reference": "https://github.com/kinfolk0117/ComfyUI_SimpleTiles",
+ "files": [
+ "https://github.com/kinfolk0117/ComfyUI_SimpleTiles"
+ ],
+ "install_type": "git-clone",
+ "description": "Nodes:TileSplit, TileMerge."
+ },
+ {
+ "author": "CaptainGrock",
+ "title": "ComfyUIInvisibleWatermark",
+ "reference": "https://github.com/CaptainGrock/ComfyUIInvisibleWatermark",
+ "files": [
+ "https://github.com/CaptainGrock/ComfyUIInvisibleWatermark/raw/main/Invisible%20Watermark.py"
+ ],
+ "install_type": "copy",
+ "description": "Nodes:Apply Invisible Watermark, Extract Watermark. Adds up to 12 characters encoded into an image that can be extracted."
+ }
+ ]
+}
diff --git a/custom_nodes/ComfyUI-Manager/node_db/new/extension-node-map.json b/custom_nodes/ComfyUI-Manager/node_db/new/extension-node-map.json
new file mode 100644
index 0000000000000000000000000000000000000000..8ae906e8dedadbb163850187623efb229562539d
--- /dev/null
+++ b/custom_nodes/ComfyUI-Manager/node_db/new/extension-node-map.json
@@ -0,0 +1,5495 @@
+{
+ "https://gist.github.com/alkemann/7361b8eb966f29c8238fd323409efb68/raw/f9605be0b38d38d3e3a2988f89248ff557010076/alkemann.py": [
+ [
+ "Int to Text",
+ "Save A1 Image",
+ "Seed With Text"
+ ],
+ {
+ "title_aux": "alkemann nodes"
+ }
+ ],
+ "https://github.com/0xbitches/ComfyUI-LCM": [
+ [
+ "LCM_Sampler",
+ "LCM_Sampler_Advanced",
+ "LCM_img2img_Sampler",
+ "LCM_img2img_Sampler_Advanced"
+ ],
+ {
+ "title_aux": "Latent Consistency Model for ComfyUI"
+ }
+ ],
+ "https://github.com/42lux/ComfyUI-safety-checker": [
+ [
+ "Safety Checker"
+ ],
+ {
+ "title_aux": "ComfyUI-safety-checker"
+ }
+ ],
+ "https://github.com/80sVectorz/ComfyUI-Static-Primitives": [
+ [
+ "FloatStaticPrimitive",
+ "IntStaticPrimitive",
+ "StringMlStaticPrimitive",
+ "StringStaticPrimitive"
+ ],
+ {
+ "title_aux": "ComfyUI-Static-Primitives"
+ }
+ ],
+ "https://github.com/AIrjen/OneButtonPrompt": [
+ [
+ "CreatePromptVariant",
+ "OneButtonPrompt",
+ "SavePromptToFile"
+ ],
+ {
+ "title_aux": "One Button Prompt"
+ }
+ ],
+ "https://github.com/AbdullahAlfaraj/Comfy-Photoshop-SD": [
+ [
+ "APS_LatentBatch",
+ "APS_Seed",
+ "ContentMaskLatent",
+ "ControlNetScript",
+ "ControlnetUnit",
+ "GaussianLatentImage",
+ "GetConfig",
+ "LoadImageBase64",
+ "LoadImageWithMetaData",
+ "LoadLorasFromPrompt",
+ "MaskExpansion"
+ ],
+ {
+ "title_aux": "Comfy-Photoshop-SD"
+ }
+ ],
+ "https://github.com/AbyssYuan0/ComfyUI_BadgerTools": [
+ [
+ "FloatToInt-badger",
+ "FloatToString-badger",
+ "ImageNormalization-badger",
+ "ImageOverlap-badger",
+ "ImageScaleToSide-badger",
+ "IntToString-badger",
+ "StringToFizz-badger",
+ "TextListToString-badger"
+ ],
+ {
+ "title_aux": "ComfyUI_BadgerTools"
+ }
+ ],
+ "https://github.com/Acly/comfyui-tooling-nodes": [
+ [
+ "ETN_ApplyMaskToImage",
+ "ETN_CropImage",
+ "ETN_LoadImageBase64",
+ "ETN_LoadMaskBase64",
+ "ETN_SendImageWebSocket"
+ ],
+ {
+ "title_aux": "ComfyUI Nodes for External Tooling"
+ }
+ ],
+ "https://github.com/Amorano/Jovimetrix": [
+ [],
+ {
+ "author": "amorano",
+ "description": "Procedural & Compositing. Includes a Webcam node.",
+ "nodename_pattern": " \\(jov\\)$",
+ "title": "Jovimetrix Composition Pack",
+ "title_aux": "Jovimetrix Composition Nodes"
+ }
+ ],
+ "https://github.com/ArtBot2023/CharacterFaceSwap": [
+ [
+ "Color Blend",
+ "Crop Face",
+ "Exclude Facial Feature",
+ "Generation Parameter Input",
+ "Generation Parameter Output",
+ "Image Full BBox",
+ "Load BiseNet",
+ "Load RetinaFace",
+ "Mask Contour",
+ "Segment Face",
+ "Uncrop Face"
+ ],
+ {
+ "title_aux": "Character Face Swap"
+ }
+ ],
+ "https://github.com/ArtVentureX/comfyui-animatediff": [
+ [
+ "AnimateDiffCombine",
+ "AnimateDiffLoraLoader",
+ "AnimateDiffModuleLoader",
+ "AnimateDiffSampler",
+ "AnimateDiffSlidingWindowOptions",
+ "ImageSizeAndBatchSize",
+ "LoadVideo"
+ ],
+ {
+ "title_aux": "AnimateDiff"
+ }
+ ],
+ "https://github.com/AustinMroz/ComfyUI-SpliceTools": [
+ [
+ "LogSigmas",
+ "SpliceDenoised",
+ "SpliceLatents",
+ "TemporalSplice"
+ ],
+ {
+ "title_aux": "SpliceTools"
+ }
+ ],
+ "https://github.com/BadCafeCode/masquerade-nodes-comfyui": [
+ [
+ "Blur",
+ "Change Channel Count",
+ "Combine Masks",
+ "Constant Mask",
+ "Convert Color Space",
+ "Create QR Code",
+ "Create Rect Mask",
+ "Cut By Mask",
+ "Get Image Size",
+ "Image To Mask",
+ "Make Image Batch",
+ "Mask By Text",
+ "Mask Morphology",
+ "Mask To Region",
+ "MasqueradeIncrementer",
+ "Mix Color By Mask",
+ "Mix Images By Mask",
+ "Paste By Mask",
+ "Prune By Mask",
+ "Separate Mask Components",
+ "Unary Image Op",
+ "Unary Mask Op"
+ ],
+ {
+ "title_aux": "Masquerade Nodes"
+ }
+ ],
+ "https://github.com/Beinsezii/bsz-cui-extras": [
+ [
+ "BSZAbsoluteHires",
+ "BSZAspectHires",
+ "BSZColoredLatentImageXL",
+ "BSZCombinedHires",
+ "BSZHueChromaXL",
+ "BSZInjectionKSampler",
+ "BSZLatentDebug",
+ "BSZLatentFill",
+ "BSZLatentGradient",
+ "BSZLatentHSVAImage",
+ "BSZLatentOffsetXL",
+ "BSZLatentRGBAImage",
+ "BSZLatentbuster",
+ "BSZPixelbuster",
+ "BSZPixelbusterHelp",
+ "BSZPrincipledConditioning",
+ "BSZPrincipledSampler",
+ "BSZPrincipledScale",
+ "BSZStrangeResample"
+ ],
+ {
+ "title_aux": "bsz-cui-extras"
+ }
+ ],
+ "https://github.com/Bikecicle/ComfyUI-Waveform-Extensions/raw/main/EXT_AudioManipulation.py": [
+ [
+ "BatchJoinAudio",
+ "CutAudio",
+ "DuplicateAudio",
+ "JoinAudio",
+ "ResampleAudio",
+ "ReverseAudio",
+ "StretchAudio"
+ ],
+ {
+ "title_aux": "Waveform Extensions"
+ }
+ ],
+ "https://github.com/BlenderNeko/ComfyUI_ADV_CLIP_emb": [
+ [
+ "BNK_AddCLIPSDXLParams",
+ "BNK_AddCLIPSDXLRParams",
+ "BNK_CLIPTextEncodeAdvanced",
+ "BNK_CLIPTextEncodeSDXLAdvanced"
+ ],
+ {
+ "title_aux": "Advanced CLIP Text Encode"
+ }
+ ],
+ "https://github.com/BlenderNeko/ComfyUI_Cutoff": [
+ [
+ "BNK_CutoffBasePrompt",
+ "BNK_CutoffRegionsToConditioning",
+ "BNK_CutoffRegionsToConditioning_ADV",
+ "BNK_CutoffSetRegions"
+ ],
+ {
+ "title_aux": "ComfyUI Cutoff"
+ }
+ ],
+ "https://github.com/BlenderNeko/ComfyUI_Noise": [
+ [
+ "BNK_DuplicateBatchIndex",
+ "BNK_GetSigma",
+ "BNK_InjectNoise",
+ "BNK_NoisyLatentImage",
+ "BNK_SlerpLatent",
+ "BNK_Unsampler"
+ ],
+ {
+ "title_aux": "ComfyUI Noise"
+ }
+ ],
+ "https://github.com/BlenderNeko/ComfyUI_SeeCoder": [
+ [
+ "ConcatConditioning",
+ "SEECoderImageEncode"
+ ],
+ {
+ "title_aux": "SeeCoder [WIP]"
+ }
+ ],
+ "https://github.com/BlenderNeko/ComfyUI_TiledKSampler": [
+ [
+ "BNK_TiledKSampler",
+ "BNK_TiledKSamplerAdvanced"
+ ],
+ {
+ "title_aux": "Tiled sampling for ComfyUI"
+ }
+ ],
+ "https://github.com/CaptainGrock/ComfyUIInvisibleWatermark/raw/main/Invisible%20Watermark.py": [
+ [
+ "Apply Invisible Watermark",
+ "Extract Watermark"
+ ],
+ {
+ "title_aux": "ComfyUIInvisibleWatermark"
+ }
+ ],
+ "https://github.com/Chaoses-Ib/ComfyUI_Ib_CustomNodes": [
+ [
+ "LoadImageFromPath"
+ ],
+ {
+ "title_aux": "ComfyUI_Ib_CustomNodes"
+ }
+ ],
+ "https://github.com/Clybius/ComfyUI-Latent-Modifiers": [
+ [
+ "Latent Diffusion Mega Modifier"
+ ],
+ {
+ "title_aux": "ComfyUI-Latent-Modifiers"
+ }
+ ],
+ "https://github.com/Danand/ComfyUI-ComfyCouple": [
+ [
+ "Attention couple",
+ "Comfy Couple"
+ ],
+ {
+ "author": "Rei D.",
+ "description": "If you want to draw two different characters together without blending their features, so you could try to check out this custom node.",
+ "nickname": "Danand",
+ "title": "Comfy Couple",
+ "title_aux": "ComfyUI-ComfyCouple"
+ }
+ ],
+ "https://github.com/Davemane42/ComfyUI_Dave_CustomNode": [
+ [
+ "ABGRemover",
+ "ConditioningStretch",
+ "ConditioningUpscale",
+ "MultiAreaConditioning",
+ "MultiLatentComposite"
+ ],
+ {
+ "title_aux": "Visual Area Conditioning / Latent composition"
+ }
+ ],
+ "https://github.com/Derfuu/Derfuu_ComfyUI_ModdedNodes": [
+ [
+ "ABSNode_DF",
+ "Absolute value",
+ "Ceil",
+ "CeilNode_DF",
+ "Conditioning area scale by ratio",
+ "ConditioningSetArea with tuples",
+ "ConditioningSetAreaEXT_DF",
+ "ConditioningSetArea_DF",
+ "CosNode_DF",
+ "Cosines",
+ "Divide",
+ "DivideNode_DF",
+ "EmptyLatentImage_DF",
+ "Float",
+ "Float debug print",
+ "Float2Tuple_DF",
+ "FloatDebugPrint_DF",
+ "FloatNode_DF",
+ "Floor",
+ "FloorNode_DF",
+ "Get image size",
+ "Get latent size",
+ "GetImageSize_DF",
+ "GetLatentSize_DF",
+ "Image scale by ratio",
+ "Image scale to side",
+ "ImageScale_Ratio_DF",
+ "ImageScale_Side_DF",
+ "Int debug print",
+ "Int to float",
+ "Int to tuple",
+ "Int2Float_DF",
+ "IntDebugPrint_DF",
+ "Integer",
+ "IntegerNode_DF",
+ "Latent Scale by ratio",
+ "Latent Scale to side",
+ "LatentComposite with tuples",
+ "LatentScale_Ratio_DF",
+ "LatentScale_Side_DF",
+ "MultilineStringNode_DF",
+ "Multiply",
+ "MultiplyNode_DF",
+ "PowNode_DF",
+ "Power",
+ "Random",
+ "RandomFloat_DF",
+ "SinNode_DF",
+ "Sinus",
+ "SqrtNode_DF",
+ "Square root",
+ "String debug print",
+ "StringNode_DF",
+ "Subtract",
+ "SubtractNode_DF",
+ "Sum",
+ "SumNode_DF",
+ "TanNode_DF",
+ "Tangent",
+ "Text",
+ "Text box",
+ "Tuple",
+ "Tuple debug print",
+ "Tuple multiply",
+ "Tuple swap",
+ "Tuple to floats",
+ "Tuple to ints",
+ "Tuple2Float_DF",
+ "TupleDebugPrint_DF",
+ "TupleNode_DF"
+ ],
+ {
+ "title_aux": "Derfuu_ComfyUI_ModdedNodes"
+ }
+ ],
+ "https://github.com/Electrofried/ComfyUI-OpenAINode": [
+ [
+ "OpenAINode"
+ ],
+ {
+ "title_aux": "OpenAINode"
+ }
+ ],
+ "https://github.com/EllangoK/ComfyUI-post-processing-nodes": [
+ [
+ "ArithmeticBlend",
+ "AsciiArt",
+ "Blend",
+ "Blur",
+ "CannyEdgeMask",
+ "ChromaticAberration",
+ "ColorCorrect",
+ "ColorTint",
+ "Dissolve",
+ "Dither",
+ "DodgeAndBurn",
+ "FilmGrain",
+ "Glow",
+ "HSVThresholdMask",
+ "KMeansQuantize",
+ "KuwaharaBlur",
+ "Parabolize",
+ "PencilSketch",
+ "PixelSort",
+ "Pixelize",
+ "Quantize",
+ "Sharpen",
+ "SineWave",
+ "Solarize",
+ "Vignette"
+ ],
+ {
+ "title_aux": "ComfyUI-post-processing-nodes"
+ }
+ ],
+ "https://github.com/Extraltodeus/LoadLoraWithTags": [
+ [
+ "LoraLoaderTagsQuery"
+ ],
+ {
+ "title_aux": "LoadLoraWithTags"
+ }
+ ],
+ "https://github.com/Extraltodeus/noise_latent_perlinpinpin": [
+ [
+ "NoisyLatentPerlin"
+ ],
+ {
+ "title_aux": "noise latent perlinpinpin"
+ }
+ ],
+ "https://github.com/Fannovel16/ComfyUI-Frame-Interpolation": [
+ [
+ "AMT VFI",
+ "CAIN VFI",
+ "EISAI VFI",
+ "FILM VFI",
+ "FLAVR VFI",
+ "GMFSS Fortuna VFI",
+ "IFRNet VFI",
+ "IFUnet VFI",
+ "KSampler Gradually Adding More Denoise (efficient)",
+ "M2M VFI",
+ "Make Interpolation State List",
+ "RIFE VFI",
+ "STMFNet VFI",
+ "Sepconv VFI"
+ ],
+ {
+ "title_aux": "ComfyUI Frame Interpolation"
+ }
+ ],
+ "https://github.com/Fannovel16/ComfyUI-Loopchain": [
+ [
+ "EmptyLatentImageLoop",
+ "FolderToImageStorage",
+ "ImageStorageExportLoop",
+ "ImageStorageImport",
+ "ImageStorageReset",
+ "LatentStorageExportLoop",
+ "LatentStorageImport",
+ "LatentStorageReset"
+ ],
+ {
+ "title_aux": "ComfyUI Loopchain"
+ }
+ ],
+ "https://github.com/Fannovel16/ComfyUI-MotionDiff": [
+ [
+ "EmptyMotionData",
+ "ExportSMPLTo3DSoftware",
+ "MotionCLIPTextEncode",
+ "MotionDataVisualizer",
+ "MotionDiffLoader",
+ "MotionDiffSimpleSampler",
+ "RenderSMPLMesh",
+ "SMPLLoader",
+ "SaveSMPL",
+ "SmplifyMotionData"
+ ],
+ {
+ "title_aux": "ComfyUI MotionDiff"
+ }
+ ],
+ "https://github.com/Fannovel16/ComfyUI-Video-Matting": [
+ [
+ "Robust Video Matting"
+ ],
+ {
+ "title_aux": "ComfyUI-Video-Matting"
+ }
+ ],
+ "https://github.com/Fannovel16/comfyui_controlnet_aux": [
+ [
+ "AIO_Preprocessor",
+ "AnimalPosePreprocessor",
+ "AnimeFace_SemSegPreprocessor",
+ "AnimeLineArtPreprocessor",
+ "BAE-NormalMapPreprocessor",
+ "BinaryPreprocessor",
+ "CannyEdgePreprocessor",
+ "ColorPreprocessor",
+ "DWPreprocessor",
+ "DensePosePreprocessor",
+ "FakeScribblePreprocessor",
+ "HEDPreprocessor",
+ "HintImageEnchance",
+ "ImageGenResolutionFromImage",
+ "ImageGenResolutionFromLatent",
+ "InpaintPreprocessor",
+ "LeReS-DepthMapPreprocessor",
+ "LineArtPreprocessor",
+ "M-LSDPreprocessor",
+ "Manga2Anime_LineArt_Preprocessor",
+ "MediaPipe-FaceMeshPreprocessor",
+ "MiDaS-DepthMapPreprocessor",
+ "MiDaS-NormalMapPreprocessor",
+ "OneFormer-ADE20K-SemSegPreprocessor",
+ "OneFormer-COCO-SemSegPreprocessor",
+ "OpenposePreprocessor",
+ "PiDiNetPreprocessor",
+ "PixelPerfectResolution",
+ "SAMPreprocessor",
+ "ScribblePreprocessor",
+ "Scribble_XDoG_Preprocessor",
+ "SemSegPreprocessor",
+ "ShufflePreprocessor",
+ "TilePreprocessor",
+ "UniFormer-SemSegPreprocessor",
+ "Zoe-DepthMapPreprocessor"
+ ],
+ {
+ "author": "tstandley",
+ "title_aux": "ComfyUI's ControlNet Auxiliary Preprocessors"
+ }
+ ],
+ "https://github.com/Feidorian/feidorian-ComfyNodes": [
+ [],
+ {
+ "nodename_pattern": "^Feidorian_",
+ "title_aux": "feidorian-ComfyNodes"
+ }
+ ],
+ "https://github.com/Fictiverse/ComfyUI_Fictiverse": [
+ [
+ "Add Noise to Image with Mask",
+ "Color correction",
+ "Displace Image with Depth",
+ "Displace Images with Mask",
+ "Zoom Image with Depth"
+ ],
+ {
+ "title_aux": "ComfyUI Fictiverse Nodes"
+ }
+ ],
+ "https://github.com/FizzleDorf/ComfyUI-AIT": [
+ [
+ "AIT_Unet_Loader",
+ "AIT_VAE_Encode_Loader"
+ ],
+ {
+ "title_aux": "ComfyUI-AIT"
+ }
+ ],
+ "https://github.com/FizzleDorf/ComfyUI_FizzNodes": [
+ [
+ "AbsCosWave",
+ "AbsSinWave",
+ "BatchGLIGENSchedule",
+ "BatchPromptSchedule",
+ "BatchPromptScheduleEncodeSDXL",
+ "BatchPromptScheduleLatentInput",
+ "BatchPromptScheduleNodeFlowEnd",
+ "BatchPromptScheduleSDXLLatentInput",
+ "BatchStringSchedule",
+ "BatchValueSchedule",
+ "BatchValueScheduleLatentInput",
+ "CalculateFrameOffset",
+ "ConcatStringSingle",
+ "CosWave",
+ "FizzFrame",
+ "FizzFrameConcatenate",
+ "Init FizzFrame",
+ "InvCosWave",
+ "InvSinWave",
+ "Lerp",
+ "PromptSchedule",
+ "PromptScheduleEncodeSDXL",
+ "PromptScheduleNodeFlow",
+ "PromptScheduleNodeFlowEnd",
+ "SawtoothWave",
+ "SinWave",
+ "SquareWave",
+ "StringConcatenate",
+ "StringSchedule",
+ "TriangleWave",
+ "ValueSchedule",
+ "convertKeyframeKeysToBatchKeys"
+ ],
+ {
+ "title_aux": "FizzNodes"
+ }
+ ],
+ "https://github.com/GMapeSplat/ComfyUI_ezXY": [
+ [
+ "ConcatenateString",
+ "ItemFromDropdown",
+ "IterationDriver",
+ "JoinImages",
+ "LineToConsole",
+ "NumberFromList",
+ "NumbersToList",
+ "PlotImages",
+ "StringFromList",
+ "StringToLabel",
+ "StringsToList",
+ "ezMath",
+ "ezXY_AssemblePlot",
+ "ezXY_Driver"
+ ],
+ {
+ "title_aux": "ezXY scripts and nodes"
+ }
+ ],
+ "https://github.com/GTSuya-Studio/ComfyUI-Gtsuya-Nodes": [
+ [
+ "Danbooru (ID)",
+ "Danbooru (Random)",
+ "Replace Strings",
+ "Simple Wildcards",
+ "Simple Wildcards (Dir.)",
+ "Wildcards Nodes"
+ ],
+ {
+ "title_aux": "ComfyUI-GTSuya-Nodes"
+ }
+ ],
+ "https://github.com/Gourieff/comfyui-reactor-node": [
+ [
+ "ReActorFaceSwap",
+ "ReActorLoadFaceModel",
+ "ReActorSaveFaceModel"
+ ],
+ {
+ "title_aux": "ReActor Node for ComfyUI"
+ }
+ ],
+ "https://github.com/Haoming02/comfyui-diffusion-cg": [
+ [
+ "Hook Recenter",
+ "Hook Recenter XL",
+ "Normalization",
+ "NormalizationXL",
+ "Tensor Debug",
+ "Unhook Recenter"
+ ],
+ {
+ "title_aux": "ComfyUI Diffusion Color Grading"
+ }
+ ],
+ "https://github.com/JPS-GER/ComfyUI_JPS-Nodes": [
+ [
+ "Conditioning Switch (JPS)",
+ "ControlNet Switch (JPS)",
+ "Crop Image Square (JPS)",
+ "Crop Image TargetSize (JPS)",
+ "Disable Enable Switch (JPS)",
+ "Enable Disable Switch (JPS)",
+ "Generation Settings (JPS)",
+ "Generation Settings Pipe (JPS)",
+ "Generation TXT IMG Settings (JPS)",
+ "Get Date Time String (JPS)",
+ "Get Image Size (JPS)",
+ "IP Adapter Settings (JPS)",
+ "IP Adapter Settings Pipe (JPS)",
+ "Image Switch (JPS)",
+ "Images Masks MultiPipe (JPS)",
+ "Integer Switch (JPS)",
+ "Largest Int (JPS)",
+ "Latent Switch (JPS)",
+ "Lora Loader (JPS)",
+ "Mask Switch (JPS)",
+ "Model Switch (JPS)",
+ "Multiply Float Float (JPS)",
+ "Multiply Int Float (JPS)",
+ "Multiply Int Int (JPS)",
+ "Resolution Multiply (JPS)",
+ "Revision Settings (JPS)",
+ "Revision Settings Pipe (JPS)",
+ "SDXL Basic Settings (JPS)",
+ "SDXL Basic Settings Pipe (JPS)",
+ "SDXL Fundamentals MultiPipe (JPS)",
+ "SDXL Prompt Handling (JPS)",
+ "SDXL Prompt Handling Plus (JPS)",
+ "SDXL Prompt Styler (JPS)",
+ "SDXL Recommended Resolution Calc (JPS)",
+ "SDXL Resolutions (JPS)",
+ "Sampler Scheduler Settings (JPS)",
+ "Substract Int Int (JPS)",
+ "Text Concatenate (JPS)",
+ "VAE Switch (JPS)"
+ ],
+ {
+ "author": "JPS",
+ "description": "Various nodes to handle SDXL Resolutions, SDXL Basic Settings, IP Adapter Settings, Revision Settings, SDXL Prompt Styler, Crop Image to Square, Crop Image to Target Size, Get Date-Time String, Resolution Multiply, Largest Integer, 5-to-1 Switches for Integer, Images, Latents, Conditioning, Model, VAE, ControlNet",
+ "nickname": "JPS Custom Nodes",
+ "title": "JPS Custom Nodes for ComfyUI",
+ "title_aux": "JPS Custom Nodes for ComfyUI"
+ }
+ ],
+ "https://github.com/Jcd1230/rembg-comfyui-node": [
+ [
+ "Image Remove Background (rembg)"
+ ],
+ {
+ "title_aux": "Rembg Background Removal Node for ComfyUI"
+ }
+ ],
+ "https://github.com/Jordach/comfy-plasma": [
+ [
+ "JDC_AutoContrast",
+ "JDC_BlendImages",
+ "JDC_BrownNoise",
+ "JDC_Contrast",
+ "JDC_EqualizeGrey",
+ "JDC_GaussianBlur",
+ "JDC_GreyNoise",
+ "JDC_Greyscale",
+ "JDC_ImageLoader",
+ "JDC_ImageLoaderMeta",
+ "JDC_PinkNoise",
+ "JDC_Plasma",
+ "JDC_PlasmaSampler",
+ "JDC_PowerImage",
+ "JDC_RandNoise",
+ "JDC_ResizeFactor"
+ ],
+ {
+ "title_aux": "comfy-plasma"
+ }
+ ],
+ "https://github.com/Kaharos94/ComfyUI-Saveaswebp": [
+ [
+ "Save_as_webp"
+ ],
+ {
+ "title_aux": "ComfyUI-Saveaswebp"
+ }
+ ],
+ "https://github.com/Kosinkadink/ComfyUI-Advanced-ControlNet": [
+ [
+ "ACN_AdvancedControlNetApply",
+ "ACN_DefaultUniversalWeights",
+ "ControlNetLoaderAdvanced",
+ "CustomControlNetWeights",
+ "CustomT2IAdapterWeights",
+ "DiffControlNetLoaderAdvanced",
+ "LatentKeyframe",
+ "LatentKeyframeBatchedGroup",
+ "LatentKeyframeGroup",
+ "LatentKeyframeTiming",
+ "LoadImagesFromDirectory",
+ "ScaledSoftControlNetWeights",
+ "ScaledSoftMaskedUniversalWeights",
+ "SoftControlNetWeights",
+ "SoftT2IAdapterWeights",
+ "TimestepKeyframe"
+ ],
+ {
+ "title_aux": "ComfyUI-Advanced-ControlNet"
+ }
+ ],
+ "https://github.com/Kosinkadink/ComfyUI-AnimateDiff-Evolved": [
+ [
+ "ADE_AnimateDiffCombine",
+ "ADE_AnimateDiffLoRALoader",
+ "ADE_AnimateDiffLoaderV1Advanced",
+ "ADE_AnimateDiffLoaderWithContext",
+ "ADE_AnimateDiffModelSettings",
+ "ADE_AnimateDiffModelSettingsAdvancedAttnStrengths",
+ "ADE_AnimateDiffModelSettingsSimple",
+ "ADE_AnimateDiffModelSettings_Release",
+ "ADE_AnimateDiffUniformContextOptions",
+ "ADE_AnimateDiffUniformContextOptionsExperimental",
+ "ADE_AnimateDiffUnload",
+ "ADE_EmptyLatentImageLarge",
+ "AnimateDiffLoaderV1",
+ "CheckpointLoaderSimpleWithNoiseSelect"
+ ],
+ {
+ "title_aux": "AnimateDiff Evolved"
+ }
+ ],
+ "https://github.com/Kosinkadink/ComfyUI-VideoHelperSuite": [
+ [
+ "VHS_DuplicateImages",
+ "VHS_DuplicateLatents",
+ "VHS_GetImageCount",
+ "VHS_GetLatentCount",
+ "VHS_LoadImages",
+ "VHS_LoadImagesPath",
+ "VHS_LoadVideo",
+ "VHS_LoadVideoPath",
+ "VHS_MergeImages",
+ "VHS_MergeLatents",
+ "VHS_SelectEveryNthImage",
+ "VHS_SelectEveryNthLatent",
+ "VHS_SplitImages",
+ "VHS_SplitLatents",
+ "VHS_VideoCombine"
+ ],
+ {
+ "title_aux": "ComfyUI-VideoHelperSuite"
+ }
+ ],
+ "https://github.com/LEv145/images-grid-comfy-plugin": [
+ [
+ "GridAnnotation",
+ "ImageCombine",
+ "ImagesGridByColumns",
+ "ImagesGridByRows",
+ "LatentCombine"
+ ],
+ {
+ "title_aux": "ImagesGrid"
+ }
+ ],
+ "https://github.com/Lerc/canvas_tab": [
+ [
+ "Canvas_Tab",
+ "Send_To_Editor"
+ ],
+ {
+ "author": "Lerc",
+ "description": "This extension provides a full page image editor with mask support. There are two nodes, one to receive images from the editor and one to send images to the editor.",
+ "nickname": "Canvas Tab",
+ "title": "Canvas Tab",
+ "title_aux": "Canvas Tab"
+ }
+ ],
+ "https://github.com/LonicaMewinsky/ComfyUI-MakeFrame": [
+ [
+ "BreakFrames",
+ "BreakGrid",
+ "GetKeyFrames",
+ "MakeGrid",
+ "RandomImageFromDir"
+ ],
+ {
+ "title_aux": "ComfyBreakAnim"
+ }
+ ],
+ "https://github.com/LonicaMewinsky/ComfyUI-RawSaver": [
+ [
+ "SaveTifImage"
+ ],
+ {
+ "title_aux": "ComfyUI-RawSaver"
+ }
+ ],
+ "https://github.com/M1kep/ComfyLiterals": [
+ [
+ "Checkpoint",
+ "Float",
+ "Int",
+ "KepStringLiteral",
+ "Lora",
+ "Operation",
+ "String"
+ ],
+ {
+ "title_aux": "ComfyLiterals"
+ }
+ ],
+ "https://github.com/M1kep/ComfyUI-KepOpenAI": [
+ [
+ "KepOpenAI_ImageWithPrompt"
+ ],
+ {
+ "title_aux": "ComfyUI-KepOpenAI"
+ }
+ ],
+ "https://github.com/M1kep/ComfyUI-OtherVAEs": [
+ [
+ "OtherVAE_Taesd"
+ ],
+ {
+ "title_aux": "ComfyUI-OtherVAEs"
+ }
+ ],
+ "https://github.com/M1kep/Comfy_KepKitchenSink": [
+ [
+ "KepRotateImage"
+ ],
+ {
+ "title_aux": "Comfy_KepKitchenSink"
+ }
+ ],
+ "https://github.com/M1kep/Comfy_KepListStuff": [
+ [
+ "Empty Images",
+ "Image Overlay",
+ "ImageListLoader",
+ "Join Float Lists",
+ "Join Image Lists",
+ "KepStringList",
+ "KepStringListFromNewline",
+ "Kep_JoinListAny",
+ "Kep_RepeatList",
+ "Kep_ReverseList",
+ "Kep_VariableImageBuilder",
+ "List Length",
+ "Range(Num Steps) - Float",
+ "Range(Num Steps) - Int",
+ "Range(Step) - Float",
+ "Range(Step) - Int",
+ "Stack Images",
+ "XYAny",
+ "XYImage"
+ ],
+ {
+ "title_aux": "Comfy_KepListStuff"
+ }
+ ],
+ "https://github.com/M1kep/Comfy_KepMatteAnything": [
+ [
+ "MatteAnything_DinoBoxes",
+ "MatteAnything_GenerateVITMatte",
+ "MatteAnything_InitSamPredictor",
+ "MatteAnything_LoadDINO",
+ "MatteAnything_LoadVITMatteModel",
+ "MatteAnything_SAMLoader",
+ "MatteAnything_SAMMaskFromBoxes",
+ "MatteAnything_ToTrimap"
+ ],
+ {
+ "title_aux": "Comfy_KepMatteAnything"
+ }
+ ],
+ "https://github.com/M1kep/KepPromptLang": [
+ [
+ "Build Gif",
+ "Special CLIP Loader"
+ ],
+ {
+ "title_aux": "KepPromptLang"
+ }
+ ],
+ "https://github.com/ManglerFTW/ComfyI2I": [
+ [
+ "Color Transfer",
+ "Combine and Paste",
+ "Inpaint Segments",
+ "Mask Ops"
+ ],
+ {
+ "author": "ManglerFTW",
+ "title": "ComfyI2I",
+ "title_aux": "ComfyI2I"
+ }
+ ],
+ "https://github.com/NicholasMcCarthy/ComfyUI_TravelSuite": [
+ [
+ "LatentTravel"
+ ],
+ {
+ "title_aux": "ComfyUI_TravelSuite"
+ }
+ ],
+ "https://github.com/Niutonian/ComfyUi-NoodleWebcam": [
+ [
+ "WebcamNode"
+ ],
+ {
+ "title_aux": "ComfyUi-NoodleWebcam"
+ }
+ ],
+ "https://github.com/Nourepide/ComfyUI-Allor": [
+ [
+ "AlphaChanelAdd",
+ "AlphaChanelAddByMask",
+ "AlphaChanelAsMask",
+ "AlphaChanelRemove",
+ "AlphaChanelRestore",
+ "ClipClamp",
+ "ClipVisionClamp",
+ "ClipVisionOutputClamp",
+ "ConditioningClamp",
+ "ControlNetClamp",
+ "GligenClamp",
+ "ImageBatchCopy",
+ "ImageBatchFork",
+ "ImageBatchGet",
+ "ImageBatchJoin",
+ "ImageBatchPermute",
+ "ImageBatchRemove",
+ "ImageClamp",
+ "ImageCompositeAbsolute",
+ "ImageCompositeAbsoluteByContainer",
+ "ImageCompositeRelative",
+ "ImageCompositeRelativeByContainer",
+ "ImageContainer",
+ "ImageContainerInheritanceAdd",
+ "ImageContainerInheritanceMax",
+ "ImageContainerInheritanceScale",
+ "ImageContainerInheritanceSum",
+ "ImageDrawArc",
+ "ImageDrawArcByContainer",
+ "ImageDrawChord",
+ "ImageDrawChordByContainer",
+ "ImageDrawEllipse",
+ "ImageDrawEllipseByContainer",
+ "ImageDrawLine",
+ "ImageDrawLineByContainer",
+ "ImageDrawPieslice",
+ "ImageDrawPiesliceByContainer",
+ "ImageDrawPolygon",
+ "ImageDrawRectangle",
+ "ImageDrawRectangleByContainer",
+ "ImageDrawRectangleRounded",
+ "ImageDrawRectangleRoundedByContainer",
+ "ImageEffectsAdjustment",
+ "ImageEffectsGrayscale",
+ "ImageEffectsLensBokeh",
+ "ImageEffectsLensChromaticAberration",
+ "ImageEffectsLensOpticAxis",
+ "ImageEffectsLensVignette",
+ "ImageEffectsLensZoomBurst",
+ "ImageEffectsNegative",
+ "ImageEffectsSepia",
+ "ImageFilterBilateralBlur",
+ "ImageFilterBlur",
+ "ImageFilterBoxBlur",
+ "ImageFilterContour",
+ "ImageFilterDetail",
+ "ImageFilterEdgeEnhance",
+ "ImageFilterEdgeEnhanceMore",
+ "ImageFilterEmboss",
+ "ImageFilterFindEdges",
+ "ImageFilterGaussianBlur",
+ "ImageFilterGaussianBlurAdvanced",
+ "ImageFilterMax",
+ "ImageFilterMedianBlur",
+ "ImageFilterMin",
+ "ImageFilterMode",
+ "ImageFilterRank",
+ "ImageFilterSharpen",
+ "ImageFilterSmooth",
+ "ImageFilterSmoothMore",
+ "ImageFilterStackBlur",
+ "ImageNoiseBeta",
+ "ImageNoiseBinomial",
+ "ImageNoiseBytes",
+ "ImageNoiseGaussian",
+ "ImageSegmentation",
+ "ImageSegmentationCustom",
+ "ImageSegmentationCustomAdvanced",
+ "ImageText",
+ "ImageTextMultiline",
+ "ImageTextMultilineOutlined",
+ "ImageTextOutlined",
+ "ImageTransformCropAbsolute",
+ "ImageTransformCropCorners",
+ "ImageTransformCropRelative",
+ "ImageTransformPaddingAbsolute",
+ "ImageTransformPaddingRelative",
+ "ImageTransformResizeAbsolute",
+ "ImageTransformResizeClip",
+ "ImageTransformResizeRelative",
+ "ImageTransformRotate",
+ "ImageTransformTranspose",
+ "LatentClamp",
+ "MaskClamp",
+ "ModelClamp",
+ "StyleModelClamp",
+ "UpscaleModelClamp",
+ "VaeClamp"
+ ],
+ {
+ "title_aux": "Allor Plugin"
+ }
+ ],
+ "https://github.com/Nuked88/ComfyUI-N-Nodes": [
+ [
+ "DynamicPrompt",
+ "Float Variable",
+ "FrameInterpolator",
+ "GPT Loader Simple",
+ "GPTSampler",
+ "Integer Variable",
+ "LoadFramesFromFolder",
+ "LoadVideo",
+ "SaveVideo",
+ "SetMetadataForSaveVideo",
+ "String Variable"
+ ],
+ {
+ "title_aux": "ComfyUI-N-Nodes"
+ }
+ ],
+ "https://github.com/Off-Live/ComfyUI-off-suite": [
+ [
+ "Cached Image Load From URL",
+ "Crop Center wigh SEGS",
+ "Crop Center with SEGS",
+ "GW Number Formatting",
+ "Image Crop Fit",
+ "Image Resize Fit",
+ "OFF SEGS to Image",
+ "Watermarking"
+ ],
+ {
+ "title_aux": "ComfyUI-off-suite"
+ }
+ ],
+ "https://github.com/Onierous/QRNG_Node_ComfyUI/raw/main/qrng_node.py": [
+ [
+ "QRNG_Node_CSV"
+ ],
+ {
+ "title_aux": "QRNG_Node_ComfyUI"
+ }
+ ],
+ "https://github.com/PCMonsterx/ComfyUI-CSV-Loader": [
+ [
+ "Load Artists CSV",
+ "Load Artmovements CSV",
+ "Load Characters CSV",
+ "Load Colors CSV",
+ "Load Composition CSV",
+ "Load Lighting CSV",
+ "Load Negative CSV",
+ "Load Positive CSV",
+ "Load Settings CSV",
+ "Load Styles CSV"
+ ],
+ {
+ "title_aux": "ComfyUI-CSV-Loader"
+ }
+ ],
+ "https://github.com/ParmanBabra/ComfyUI-Malefish-Custom-Scripts": [
+ [
+ "CSVPromptsLoader",
+ "CombinePrompt",
+ "MultiLoraLoader",
+ "RandomPrompt"
+ ],
+ {
+ "title_aux": "ComfyUI-Malefish-Custom-Scripts"
+ }
+ ],
+ "https://github.com/Pfaeff/pfaeff-comfyui": [
+ [
+ "AstropulsePixelDetector",
+ "BackgroundRemover",
+ "ImagePadForBetterOutpaint",
+ "Inpainting",
+ "InpaintingPipelineLoader"
+ ],
+ {
+ "title_aux": "pfaeff-comfyui"
+ }
+ ],
+ "https://github.com/RockOfFire/ComfyUI_Comfyroll_CustomNodes": [
+ [
+ "CR 3D Camera Drone",
+ "CR 3D Camera Static",
+ "CR 3D Polygon",
+ "CR 3D Solids",
+ "CR Add Annotation",
+ "CR Alternate Latents",
+ "CR Apply Annotations",
+ "CR Apply ControlNet",
+ "CR Apply LoRA Stack",
+ "CR Apply Model Merge",
+ "CR Apply Multi Upscale",
+ "CR Apply Multi-ControlNet",
+ "CR Arabic Text RTL",
+ "CR Aspect Ratio",
+ "CR Aspect Ratio SDXL",
+ "CR Batch Process Switch",
+ "CR Central Schedule",
+ "CR Check Job Complete",
+ "CR Checker Pattern",
+ "CR Clip Input Switch",
+ "CR Color Bars",
+ "CR Color Gradient",
+ "CR Color Panel",
+ "CR Color Tint",
+ "CR Combine Schedules",
+ "CR Comic Panel Templates",
+ "CR Comic Panel Templates (Advanced)",
+ "CR Comic Panel Templates Advanced",
+ "CR Composite Text",
+ "CR Conditioning Input Switch",
+ "CR Conditioning Mixer",
+ "CR Continuous Rotation",
+ "CR Continuous Track",
+ "CR Continuous Zoom",
+ "CR ControlNet Input Switch",
+ "CR Current Frame",
+ "CR Cycle Images",
+ "CR Cycle Images Simple",
+ "CR Cycle LoRAs",
+ "CR Cycle Models",
+ "CR Cycle Styles",
+ "CR Cycle Text",
+ "CR Cycle Text Simple",
+ "CR Debatch Frames",
+ "CR Draw Perspective Text",
+ "CR Draw Text",
+ "CR Encode Scheduled Prompts",
+ "CR Float To Integer",
+ "CR Float To String",
+ "CR Gradient Float",
+ "CR Gradient Integer",
+ "CR Halftone Filter",
+ "CR Halftone Grid",
+ "CR Hires Fix Process Switch",
+ "CR Image Border",
+ "CR Image Grid Panel",
+ "CR Image Input Switch",
+ "CR Image Input Switch (4 way)",
+ "CR Image List",
+ "CR Image List Simple",
+ "CR Image Output",
+ "CR Image Panel",
+ "CR Image Pipe Edit",
+ "CR Image Pipe In",
+ "CR Image Pipe Out",
+ "CR Image Size",
+ "CR Image Transition",
+ "CR Image XY Panel",
+ "CR Img2Img Process Switch",
+ "CR Increment Float",
+ "CR Increment Integer",
+ "CR Index",
+ "CR Index Increment",
+ "CR Index Multiply",
+ "CR Index Reset",
+ "CR Input Text List",
+ "CR Integer Multiple",
+ "CR Integer To String",
+ "CR Interpolate Latents",
+ "CR Interpolate Prompt Weights",
+ "CR Interpolate Rotation",
+ "CR Interpolate Track",
+ "CR Interpolate Zoom",
+ "CR Job Current Frame",
+ "CR Job List",
+ "CR Job Scheduler",
+ "CR Keyframe List",
+ "CR Latent Batch Size",
+ "CR Latent Input Switch",
+ "CR LoRA List",
+ "CR LoRA Stack",
+ "CR Load Animation Frames",
+ "CR Load Flow Frames",
+ "CR Load LoRA",
+ "CR Load Prompt Style",
+ "CR Load Schedule From File",
+ "CR Load Scheduled ControlNets",
+ "CR Load Scheduled LoRAs",
+ "CR Load Scheduled Models",
+ "CR Load Workflow",
+ "CR Load XY Annotation From File",
+ "CR Mask Text",
+ "CR Model Input Switch",
+ "CR Model List",
+ "CR Model Merge Stack",
+ "CR Module Input",
+ "CR Module Output",
+ "CR Module Pipe Loader",
+ "CR Multi Upscale Stack",
+ "CR Multi-ControlNet Stack",
+ "CR Multi-Panel Meme Template",
+ "CR Output Flow Frames",
+ "CR Output Schedule To File",
+ "CR Overlay Text",
+ "CR Overlay Transparent Image",
+ "CR Page Layout",
+ "CR Pipe Switch",
+ "CR Polygons",
+ "CR Popular Meme Templates",
+ "CR Prompt List",
+ "CR Prompt List Keyframes",
+ "CR Prompt Scheduler",
+ "CR Prompt Text",
+ "CR Prompt Weight Scheduler",
+ "CR Radial Gradient",
+ "CR Random Hex Color",
+ "CR Random RGB",
+ "CR SD1.5 Aspect Ratio",
+ "CR SDXL Aspect Ratio",
+ "CR SDXL Base Prompt Encoder",
+ "CR SDXL Prompt Mix Presets",
+ "CR SDXL Style Text",
+ "CR Schedule Camera Movements",
+ "CR Schedule ControlNets",
+ "CR Schedule Input Switch",
+ "CR Schedule Styles",
+ "CR Schedule To ScheduleList",
+ "CR Seed",
+ "CR Seed to Int",
+ "CR Select Model",
+ "CR Simple Annotations",
+ "CR Simple Image Watermark",
+ "CR Simple Meme Template",
+ "CR Simple Prompt List",
+ "CR Simple Prompt List Keyframes",
+ "CR Simple Prompt Scheduler",
+ "CR Simple Schedule",
+ "CR Simple Text Panel",
+ "CR Simple Text Scheduler",
+ "CR Simple Text Watermark",
+ "CR Simple Value Scheduler",
+ "CR Spawn Workflow Instance",
+ "CR Split String",
+ "CR Starburst Colors",
+ "CR Starburst Lines",
+ "CR String To Combo",
+ "CR String To Number",
+ "CR Strobe Images",
+ "CR Style Bars",
+ "CR Style List",
+ "CR Switch Model and CLIP",
+ "CR Text Input Switch",
+ "CR Text Input Switch (4 way)",
+ "CR Text List",
+ "CR Text List Cross Join",
+ "CR Text List Simple",
+ "CR Text List To String",
+ "CR Text Scheduler",
+ "CR Trigger",
+ "CR Upscale Image",
+ "CR VAE Input Switch",
+ "CR Value",
+ "CR Value Scheduler",
+ "CR XY From Folder",
+ "CR XY Grid",
+ "CR XY Index",
+ "CR XY Interpolate",
+ "CR XY List",
+ "CR XY Save Grid Image",
+ "CR XYZ Index",
+ "CR XYZ Interpolate",
+ "CR XYZ List"
+ ],
+ {
+ "author": "RockOfFire",
+ "description": "Custom nodes for SDXL and SD1.5 including Multi-ControlNet, LoRA, Aspect Ratio, Process Switches, and many more nodes.",
+ "nickname": "Comfyroll Custom Nodes",
+ "title": "Comfyroll Custom Nodes",
+ "title_aux": "ComfyUI_Comfyroll_CustomNodes"
+ }
+ ],
+ "https://github.com/SLAPaper/ComfyUI-Image-Selector": [
+ [
+ "ImageDuplicator",
+ "ImageSelector",
+ "LatentDuplicator",
+ "LatentSelector"
+ ],
+ {
+ "title_aux": "ComfyUI-Image-Selector"
+ }
+ ],
+ "https://github.com/SOELexicon/ComfyUI-LexMSDBNodes": [
+ [
+ "MSSqlSelectNode",
+ "MSSqlTableNode"
+ ],
+ {
+ "title_aux": "LexMSDBNodes"
+ }
+ ],
+ "https://github.com/SOELexicon/ComfyUI-LexTools": [
+ [
+ "AgeClassifierNode",
+ "ArtOrHumanClassifierNode",
+ "DocumentClassificationNode",
+ "FoodCategoryClassifierNode",
+ "ImageAspectPadNode",
+ "ImageCaptioning",
+ "ImageFilterByFloatScoreNode",
+ "ImageFilterByIntScoreNode",
+ "ImageQualityScoreNode",
+ "ImageRankingNode",
+ "ImageScaleToMin",
+ "MD5ImageHashNode",
+ "SamplerPropertiesNode",
+ "ScoreConverterNode",
+ "SeedIncrementerNode",
+ "SegformerNode",
+ "SegformerNodeMasks",
+ "SegformerNodeMergeSegments",
+ "StepCfgIncrementNode"
+ ],
+ {
+ "title_aux": "ComfyUI-LexTools"
+ }
+ ],
+ "https://github.com/SadaleNet/CLIPTextEncodeA1111-ComfyUI/raw/master/custom_nodes/clip_text_encoder_a1111.py": [
+ [
+ "CLIPTextEncodeA1111",
+ "RerouteTextForCLIPTextEncodeA1111"
+ ],
+ {
+ "title_aux": "ComfyUI A1111-like Prompt Custom Node Solution"
+ }
+ ],
+ "https://github.com/Scholar01/ComfyUI-Keyframe": [
+ [
+ "KeyframeApply",
+ "KeyframeInterpolationPart",
+ "KeyframePart"
+ ],
+ {
+ "title_aux": "SComfyUI-Keyframe"
+ }
+ ],
+ "https://github.com/SeargeDP/SeargeSDXL": [
+ [
+ "SeargeAdvancedParameters",
+ "SeargeCheckpointLoader",
+ "SeargeConditionMixing",
+ "SeargeConditioningMuxer2",
+ "SeargeConditioningMuxer5",
+ "SeargeConditioningParameters",
+ "SeargeControlnetAdapterV2",
+ "SeargeControlnetModels",
+ "SeargeCustomAfterUpscaling",
+ "SeargeCustomAfterVaeDecode",
+ "SeargeCustomPromptMode",
+ "SeargeDebugPrinter",
+ "SeargeEnablerInputs",
+ "SeargeFloatConstant",
+ "SeargeFloatMath",
+ "SeargeFloatPair",
+ "SeargeFreeU",
+ "SeargeGenerated1",
+ "SeargeGenerationParameters",
+ "SeargeHighResolution",
+ "SeargeImage2ImageAndInpainting",
+ "SeargeImageAdapterV2",
+ "SeargeImageSave",
+ "SeargeImageSaving",
+ "SeargeInput1",
+ "SeargeInput2",
+ "SeargeInput3",
+ "SeargeInput4",
+ "SeargeInput5",
+ "SeargeInput6",
+ "SeargeInput7",
+ "SeargeIntegerConstant",
+ "SeargeIntegerMath",
+ "SeargeIntegerPair",
+ "SeargeIntegerScaler",
+ "SeargeLatentMuxer3",
+ "SeargeLoraLoader",
+ "SeargeLoras",
+ "SeargeMagicBox",
+ "SeargeModelSelector",
+ "SeargeOperatingMode",
+ "SeargeOutput1",
+ "SeargeOutput2",
+ "SeargeOutput3",
+ "SeargeOutput4",
+ "SeargeOutput5",
+ "SeargeOutput6",
+ "SeargeOutput7",
+ "SeargeParameterProcessor",
+ "SeargePipelineStart",
+ "SeargePipelineTerminator",
+ "SeargePreviewImage",
+ "SeargePromptAdapterV2",
+ "SeargePromptCombiner",
+ "SeargePromptStyles",
+ "SeargePromptText",
+ "SeargeSDXLBasePromptEncoder",
+ "SeargeSDXLImage2ImageSampler",
+ "SeargeSDXLImage2ImageSampler2",
+ "SeargeSDXLPromptEncoder",
+ "SeargeSDXLRefinerPromptEncoder",
+ "SeargeSDXLSampler",
+ "SeargeSDXLSampler2",
+ "SeargeSDXLSamplerV3",
+ "SeargeSamplerAdvanced",
+ "SeargeSamplerInputs",
+ "SeargeSaveFolderInputs",
+ "SeargeSeparator",
+ "SeargeStylePreprocessor",
+ "SeargeTextInputV2",
+ "SeargeUpscaleModelLoader",
+ "SeargeUpscaleModels",
+ "SeargeVAELoader"
+ ],
+ {
+ "title_aux": "SeargeSDXL"
+ }
+ ],
+ "https://github.com/Ser-Hilary/SDXL_sizing/raw/main/conditioning_sizing_for_SDXL.py": [
+ [
+ "get_aspect_from_image",
+ "get_aspect_from_ints",
+ "sizing_node",
+ "sizing_node_basic",
+ "sizing_node_unparsed"
+ ],
+ {
+ "title_aux": "SDXL_sizing"
+ }
+ ],
+ "https://github.com/Smuzzies/comfyui_chatbox_overlay/raw/main/chatbox_overlay.py": [
+ [
+ "Chatbox Overlay"
+ ],
+ {
+ "title_aux": "Chatbox Overlay node for ComfyUI"
+ }
+ ],
+ "https://github.com/SoftMeng/ComfyUI_Mexx_Poster": [
+ [
+ "ComfyUI_Mexx_Poster"
+ ],
+ {
+ "title_aux": "ComfyUI_Mexx_Poster"
+ }
+ ],
+ "https://github.com/SoftMeng/ComfyUI_Mexx_Styler": [
+ [
+ "MexxSDXLPromptStyler",
+ "MexxSDXLPromptStylerAdvanced"
+ ],
+ {
+ "title_aux": "ComfyUI_Mexx_Styler"
+ }
+ ],
+ "https://github.com/Stability-AI/stability-ComfyUI-nodes": [
+ [
+ "ColorBlend",
+ "ControlLoraSave",
+ "GetImageSize"
+ ],
+ {
+ "title_aux": "stability-ComfyUI-nodes"
+ }
+ ],
+ "https://github.com/Sxela/ComfyWarp": [
+ [
+ "ExtractOpticalFlow",
+ "LoadFrame",
+ "LoadFrameFromDataset",
+ "LoadFrameFromFolder",
+ "LoadFramePairFromDataset",
+ "LoadFrameSequence",
+ "MakeFrameDataset",
+ "MixConsistencyMaps",
+ "OffsetNumber",
+ "ResizeToFit",
+ "SaveFrame",
+ "WarpFrame"
+ ],
+ {
+ "title_aux": "ComfyWarp"
+ }
+ ],
+ "https://github.com/TGu-97/ComfyUI-TGu-utils": [
+ [
+ "MPNReroute",
+ "MPNSwitch",
+ "PNSwitch"
+ ],
+ {
+ "title_aux": "TGu Utilities"
+ }
+ ],
+ "https://github.com/THtianhao/ComfyUI-FaceChain": [
+ [
+ "FCStyleLoraLoad",
+ "FC_CropAndPaste",
+ "FC_CropBottom",
+ "FC_CropFace",
+ "FC_CropMask",
+ "FC_FaceDetection",
+ "FC_FaceFusion",
+ "FC_MaskOP",
+ "FC_ReplaceImage",
+ "FC_Segment",
+ "FC_StyleLoraLoad"
+ ],
+ {
+ "title_aux": "ComfyUI-FaceChain"
+ }
+ ],
+ "https://github.com/THtianhao/ComfyUI-Portrait-Maker": [
+ [
+ "PM_BoxCropImage",
+ "PM_ColorTransfer",
+ "PM_ExpandMaskBox",
+ "PM_FaceFusion",
+ "PM_FaceShapMatch",
+ "PM_FaceSkin",
+ "PM_GetImageInfo",
+ "PM_ImageResizeTarget",
+ "PM_ImageScaleShort",
+ "PM_MakeUpTransfer",
+ "PM_MaskDilateErode",
+ "PM_MaskMerge2Image",
+ "PM_PortraitEnhancement",
+ "PM_RatioMerge2Image",
+ "PM_ReplaceBoxImg",
+ "PM_RetinaFace",
+ "PM_SkinRetouching",
+ "PM_SuperColorTransfer",
+ "PM_SuperMakeUpTransfer"
+ ],
+ {
+ "title_aux": "ComfyUI-Portrait-Maker"
+ }
+ ],
+ "https://github.com/TRI3D-LC/tri3d-comfyui-nodes": [
+ [
+ "tri3d-atr-parse",
+ "tri3d-atr-parse-batch",
+ "tri3d-dwpose",
+ "tri3d-extract-hand",
+ "tri3d-extract-parts-batch",
+ "tri3d-extract-parts-batch2",
+ "tri3d-extract-parts-mask-batch",
+ "tri3d-fuzzification",
+ "tri3d-interaction-canny",
+ "tri3d-pose-adaption",
+ "tri3d-pose-to-image",
+ "tri3d-position-hands",
+ "tri3d-position-parts-batch",
+ "tri3d-skin-feathered-padded-mask",
+ "tri3d-swap-pixels"
+ ],
+ {
+ "title_aux": "tri3d-comfyui-nodes"
+ }
+ ],
+ "https://github.com/TeaCrab/ComfyUI-TeaNodes": [
+ [
+ "TC_ColorFill",
+ "TC_EqualizeCLAHE",
+ "TC_ImageResize",
+ "TC_ImageScale",
+ "TC_MaskBG_DIS",
+ "TC_RandomColorFill",
+ "TC_SizeApproximation"
+ ],
+ {
+ "title_aux": "ComfyUI-TeaNodes"
+ }
+ ],
+ "https://github.com/TheBarret/ZSuite": [
+ [
+ "ZSuite: Prompter",
+ "ZSuite: RF Noise",
+ "ZSuite: SeedMod"
+ ],
+ {
+ "title_aux": "ZSuite"
+ }
+ ],
+ "https://github.com/TinyTerra/ComfyUI_tinyterraNodes": [
+ [
+ "ttN busIN",
+ "ttN busOUT",
+ "ttN compareInput",
+ "ttN concat",
+ "ttN debugInput",
+ "ttN float",
+ "ttN hiresfixScale",
+ "ttN imageOutput",
+ "ttN imageREMBG",
+ "ttN int",
+ "ttN multiModelMerge",
+ "ttN pipe2BASIC",
+ "ttN pipe2DETAILER",
+ "ttN pipeEDIT",
+ "ttN pipeEncodeConcat",
+ "ttN pipeIN",
+ "ttN pipeKSampler",
+ "ttN pipeKSamplerAdvanced",
+ "ttN pipeKSamplerSDXL",
+ "ttN pipeLoader",
+ "ttN pipeLoaderSDXL",
+ "ttN pipeLoraStack",
+ "ttN pipeOUT",
+ "ttN seed",
+ "ttN seedDebug",
+ "ttN text",
+ "ttN text3BOX_3WAYconcat",
+ "ttN text7BOX_concat",
+ "ttN textDebug",
+ "ttN xyPlot"
+ ],
+ {
+ "author": "tinyterra",
+ "description": "This extension offers various pipe nodes, fullscreen image viewer based on node history, dynamic widgets, interface customization, and more.",
+ "nickname": "ttNodes",
+ "nodename_pattern": "^ttN ",
+ "title": "tinyterraNodes",
+ "title_aux": "tinyterraNodes"
+ }
+ ],
+ "https://github.com/Tropfchen/ComfyUI-Embedding_Picker": [
+ [
+ "EmbeddingPicker"
+ ],
+ {
+ "title_aux": "Embedding Picker"
+ }
+ ],
+ "https://github.com/Tropfchen/ComfyUI-yaResolutionSelector": [
+ [
+ "YARS",
+ "YARSAdv"
+ ],
+ {
+ "title_aux": "YARS: Yet Another Resolution Selector"
+ }
+ ],
+ "https://github.com/Trung0246/ComfyUI-0246": [
+ [
+ "0246.Beautify",
+ "0246.Convert",
+ "0246.Count",
+ "0246.Highway",
+ "0246.Hold",
+ "0246.Junction",
+ "0246.JunctionBatch",
+ "0246.Loop",
+ "0246.Mimic",
+ "0246.RandomInt",
+ "0246.Stringify"
+ ],
+ {
+ "title_aux": "ComfyUI-0246"
+ }
+ ],
+ "https://github.com/Ttl/ComfyUi_NNLatentUpscale": [
+ [
+ "NNLatentUpscale"
+ ],
+ {
+ "title_aux": "ComfyUI Neural network latent upscale custom node"
+ }
+ ],
+ "https://github.com/Umikaze-job/select_folder_path_easy": [
+ [
+ "SelectFolderPathEasy"
+ ],
+ {
+ "title_aux": "select_folder_path_easy"
+ }
+ ],
+ "https://github.com/WASasquatch/ASTERR": [
+ [
+ "ASTERR",
+ "SaveASTERR"
+ ],
+ {
+ "title_aux": "ASTERR"
+ }
+ ],
+ "https://github.com/WASasquatch/ComfyUI_Preset_Merger": [
+ [
+ "Preset_Model_Merge"
+ ],
+ {
+ "title_aux": "ComfyUI Preset Merger"
+ }
+ ],
+ "https://github.com/WASasquatch/FreeU_Advanced": [
+ [
+ "FreeU (Advanced)"
+ ],
+ {
+ "title_aux": "FreeU_Advanced"
+ }
+ ],
+ "https://github.com/WASasquatch/PPF_Noise_ComfyUI": [
+ [
+ "Blend Latents (PPF Noise)",
+ "Cross-Hatch Power Fractal (PPF Noise)",
+ "Images as Latents (PPF Noise)",
+ "Perlin Power Fractal Latent (PPF Noise)"
+ ],
+ {
+ "title_aux": "PPF_Noise_ComfyUI"
+ }
+ ],
+ "https://github.com/WASasquatch/PowerNoiseSuite": [
+ [
+ "Blend Latents (PPF Noise)",
+ "Cross-Hatch Power Fractal (PPF Noise)",
+ "Cross-Hatch Power Fractal Settings (PPF Noise)",
+ "Images as Latents (PPF Noise)",
+ "Latent Adjustment (PPF Noise)",
+ "Latents to CPU (PPF Noise)",
+ "Linear Cross-Hatch Power Fractal (PPF Noise)",
+ "Perlin Power Fractal Latent (PPF Noise)",
+ "Perlin Power Fractal Settings (PPF Noise)",
+ "Power KSampler Advanced (PPF Noise)",
+ "Power-Law Noise (PPF Noise)"
+ ],
+ {
+ "title_aux": "Power Noise Suite for ComfyUI"
+ }
+ ],
+ "https://github.com/WASasquatch/WAS_Extras": [
+ [
+ "BLVAEEncode",
+ "CLIPTextEncodeList",
+ "CLIPTextEncodeSequence2",
+ "ConditioningBlend",
+ "DebugInput",
+ "KSamplerSeq",
+ "KSamplerSeq2",
+ "VAEEncodeForInpaint (WAS)",
+ "VividSharpen"
+ ],
+ {
+ "title_aux": "WAS_Extras"
+ }
+ ],
+ "https://github.com/WASasquatch/was-node-suite-comfyui": [
+ [
+ "BLIP Analyze Image",
+ "BLIP Model Loader",
+ "Blend Latents",
+ "Bounded Image Blend",
+ "Bounded Image Blend with Mask",
+ "Bounded Image Crop",
+ "Bounded Image Crop with Mask",
+ "Bus Node",
+ "CLIP Input Switch",
+ "CLIP Vision Input Switch",
+ "CLIPSeg Batch Masking",
+ "CLIPSeg Masking",
+ "CLIPSeg Model Loader",
+ "CLIPTextEncode (BlenderNeko Advanced + NSP)",
+ "CLIPTextEncode (NSP)",
+ "Cache Node",
+ "Checkpoint Loader",
+ "Checkpoint Loader (Simple)",
+ "Conditioning Input Switch",
+ "Constant Number",
+ "Control Net Model Input Switch",
+ "Convert Masks to Images",
+ "Create Grid Image",
+ "Create Grid Image from Batch",
+ "Create Morph Image",
+ "Create Morph Image from Path",
+ "Create Video from Path",
+ "Debug Number to Console",
+ "Dictionary to Console",
+ "Diffusers Hub Model Down-Loader",
+ "Diffusers Model Loader",
+ "Export API",
+ "Image Analyze",
+ "Image Aspect Ratio",
+ "Image Batch",
+ "Image Blank",
+ "Image Blend",
+ "Image Blend by Mask",
+ "Image Blending Mode",
+ "Image Bloom Filter",
+ "Image Bounds",
+ "Image Bounds to Console",
+ "Image Canny Filter",
+ "Image Chromatic Aberration",
+ "Image Color Palette",
+ "Image Crop Face",
+ "Image Crop Location",
+ "Image Crop Square Location",
+ "Image Displacement Warp",
+ "Image Dragan Photography Filter",
+ "Image Edge Detection Filter",
+ "Image Film Grain",
+ "Image Filter Adjustments",
+ "Image Flip",
+ "Image Generate Gradient",
+ "Image Gradient Map",
+ "Image High Pass Filter",
+ "Image History Loader",
+ "Image Input Switch",
+ "Image Levels Adjustment",
+ "Image Load",
+ "Image Lucy Sharpen",
+ "Image Median Filter",
+ "Image Mix RGB Channels",
+ "Image Monitor Effects Filter",
+ "Image Nova Filter",
+ "Image Padding",
+ "Image Paste Crop",
+ "Image Paste Crop by Location",
+ "Image Paste Face",
+ "Image Perlin Noise",
+ "Image Perlin Power Fractal",
+ "Image Pixelate",
+ "Image Power Noise",
+ "Image Rembg (Remove Background)",
+ "Image Remove Background (Alpha)",
+ "Image Remove Color",
+ "Image Resize",
+ "Image Rotate",
+ "Image Rotate Hue",
+ "Image SSAO (Ambient Occlusion)",
+ "Image SSDO (Direct Occlusion)",
+ "Image Save",
+ "Image Seamless Texture",
+ "Image Select Channel",
+ "Image Select Color",
+ "Image Shadows and Highlights",
+ "Image Size to Number",
+ "Image Stitch",
+ "Image Style Filter",
+ "Image Threshold",
+ "Image Tiled",
+ "Image Transpose",
+ "Image Voronoi Noise Filter",
+ "Image fDOF Filter",
+ "Image to Latent Mask",
+ "Image to Noise",
+ "Image to Seed",
+ "Images to Linear",
+ "Images to RGB",
+ "Inset Image Bounds",
+ "Integer place counter",
+ "KSampler (WAS)",
+ "KSampler Cycle",
+ "Latent Input Switch",
+ "Latent Noise Injection",
+ "Latent Size to Number",
+ "Latent Upscale by Factor (WAS)",
+ "Load Cache",
+ "Load Image Batch",
+ "Load Lora",
+ "Load Text File",
+ "Logic Boolean",
+ "Lora Input Switch",
+ "Lora Loader",
+ "Mask Arbitrary Region",
+ "Mask Batch",
+ "Mask Batch to Mask",
+ "Mask Ceiling Region",
+ "Mask Crop Dominant Region",
+ "Mask Crop Minority Region",
+ "Mask Crop Region",
+ "Mask Dilate Region",
+ "Mask Dominant Region",
+ "Mask Erode Region",
+ "Mask Fill Holes",
+ "Mask Floor Region",
+ "Mask Gaussian Region",
+ "Mask Invert",
+ "Mask Minority Region",
+ "Mask Paste Region",
+ "Mask Smooth Region",
+ "Mask Threshold Region",
+ "Masks Add",
+ "Masks Combine Batch",
+ "Masks Combine Regions",
+ "Masks Subtract",
+ "MiDaS Depth Approximation",
+ "MiDaS Mask Image",
+ "MiDaS Model Loader",
+ "Model Input Switch",
+ "Number Counter",
+ "Number Input Condition",
+ "Number Input Switch",
+ "Number Multiple Of",
+ "Number Operation",
+ "Number PI",
+ "Number to Float",
+ "Number to Int",
+ "Number to Seed",
+ "Number to String",
+ "Number to Text",
+ "Prompt Multiple Styles Selector",
+ "Prompt Styles Selector",
+ "Random Number",
+ "SAM Image Mask",
+ "SAM Model Loader",
+ "SAM Parameters",
+ "SAM Parameters Combine",
+ "Samples Passthrough (Stat System)",
+ "Save Text File",
+ "Seed",
+ "String to Text",
+ "Tensor Batch to Image",
+ "Text Add Token by Input",
+ "Text Add Tokens",
+ "Text Compare",
+ "Text Concatenate",
+ "Text Dictionary Update",
+ "Text File History Loader",
+ "Text Find and Replace",
+ "Text Find and Replace Input",
+ "Text Find and Replace by Dictionary",
+ "Text Input Switch",
+ "Text List",
+ "Text List Concatenate",
+ "Text Load Line From File",
+ "Text Multiline",
+ "Text Parse A1111 Embeddings",
+ "Text Parse Noodle Soup Prompts",
+ "Text Parse Tokens",
+ "Text Random Line",
+ "Text Random Prompt",
+ "Text Shuffle",
+ "Text String",
+ "Text String Truncate",
+ "Text to Conditioning",
+ "Text to Console",
+ "Text to Number",
+ "Text to String",
+ "True Random.org Number Generator",
+ "Upscale Model Loader",
+ "Upscale Model Switch",
+ "VAE Input Switch",
+ "Video Dump Frames",
+ "Write to GIF",
+ "Write to Video",
+ "unCLIP Checkpoint Loader"
+ ],
+ {
+ "title_aux": "WAS Node Suite"
+ }
+ ],
+ "https://github.com/WebDev9000/WebDev9000-Nodes": [
+ [
+ "IgnoreBraces",
+ "SettingsSwitch"
+ ],
+ {
+ "title_aux": "WebDev9000-Nodes"
+ }
+ ],
+ "https://github.com/YMC-GitHub/ymc-node-suite-comfyui": [
+ [
+ "Image Save",
+ "Save Text File",
+ "canvas-util-cal-size",
+ "conditioning-util-input-switch",
+ "cutoff-region-util",
+ "hks-util-cal-denoise-step",
+ "img-util-get-image-size",
+ "img-util-switch-input-image",
+ "io-util-file-list-get",
+ "io-util-file-list-get-text",
+ "number-util-random-num",
+ "pipe-util-to-basic-pipe",
+ "region-util-get-by-center-and-size",
+ "region-util-get-by-lt",
+ "region-util-get-crop-location-from-center-size-text",
+ "region-util-get-pad-out-location-by-size",
+ "text-preset-colors",
+ "text-util-join-text",
+ "text-util-loop-text",
+ "text-util-path-list",
+ "text-util-prompt-add-prompt",
+ "text-util-prompt-adv-dup",
+ "text-util-prompt-adv-search",
+ "text-util-prompt-del",
+ "text-util-prompt-dup",
+ "text-util-prompt-join",
+ "text-util-prompt-search",
+ "text-util-prompt-shuffle",
+ "text-util-prompt-std",
+ "text-util-prompt-unweight",
+ "text-util-random-text",
+ "text-util-search-text",
+ "text-util-show-text",
+ "text-util-switch-text",
+ "xyz-util-txt-to-int"
+ ],
+ {
+ "title_aux": "ymc-node-suite-comfyui"
+ }
+ ],
+ "https://github.com/YOUR-WORST-TACO/ComfyUI-TacoNodes": [
+ [
+ "Example",
+ "TacoAnimatedLoader",
+ "TacoGifMaker",
+ "TacoImg2ImgAnimatedLoader",
+ "TacoImg2ImgAnimatedProcessor",
+ "TacoLatent"
+ ],
+ {
+ "title_aux": "ComfyUI-TacoNodes"
+ }
+ ],
+ "https://github.com/YinBailiang/MergeBlockWeighted_fo_ComfyUI": [
+ [
+ "MergeBlockWeighted"
+ ],
+ {
+ "title_aux": "MergeBlockWeighted_fo_ComfyUI"
+ }
+ ],
+ "https://github.com/ZHO-ZHO-ZHO/ComfyUI-Text_Image-Composite": [
+ [
+ "AlphaChanelAddByMask",
+ "ImageCompositeBy_BG_Zho",
+ "ImageCompositeBy_Zho",
+ "ImageComposite_BG_Zho",
+ "ImageComposite_Zho",
+ "RGB_Image_Zho",
+ "Text_Image_Frame_Zho",
+ "Text_Image_Multiline_Zho",
+ "Text_Image_Zho"
+ ],
+ {
+ "title_aux": "ComfyUI-Text_Image-Composite"
+ }
+ ],
+ "https://github.com/ZaneA/ComfyUI-ImageReward": [
+ [
+ "ImageRewardLoader",
+ "ImageRewardScore"
+ ],
+ {
+ "title_aux": "ImageReward"
+ }
+ ],
+ "https://github.com/Zuellni/ComfyUI-ExLlama": [
+ [
+ "ZuellniExLlamaGenerator",
+ "ZuellniExLlamaLoader",
+ "ZuellniTextPreview",
+ "ZuellniTextReplace"
+ ],
+ {
+ "title_aux": "ComfyUI-ExLlama"
+ }
+ ],
+ "https://github.com/Zuellni/ComfyUI-PickScore-Nodes": [
+ [
+ "ZuellniPickScoreImageProcessor",
+ "ZuellniPickScoreLoader",
+ "ZuellniPickScoreSelector",
+ "ZuellniPickScoreTextProcessor"
+ ],
+ {
+ "title_aux": "ComfyUI PickScore Nodes"
+ }
+ ],
+ "https://github.com/a1lazydog/ComfyUI-AudioScheduler": [
+ [
+ "AmplitudeToGraph",
+ "AmplitudeToNumber",
+ "AudioToAmplitudeGraph",
+ "AudioToFFTs",
+ "BatchAmplitudeSchedule",
+ "ClipAmplitude",
+ "GateNormalizedAmplitude",
+ "LoadAudio",
+ "NormalizeAmplitude",
+ "NormalizedAmplitudeDrivenString",
+ "NormalizedAmplitudeToGraph",
+ "NormalizedAmplitudeToNumber",
+ "TransientAmplitudeBasic"
+ ],
+ {
+ "title_aux": "ComfyUI-AudioScheduler"
+ }
+ ],
+ "https://github.com/adieyal/comfyui-dynamicprompts": [
+ [
+ "DPCombinatorialGenerator",
+ "DPFeelingLucky",
+ "DPJinja",
+ "DPMagicPrompt",
+ "DPOutput",
+ "DPRandomGenerator"
+ ],
+ {
+ "title_aux": "DynamicPrompts Custom Nodes"
+ }
+ ],
+ "https://github.com/aianimation55/ComfyUI-FatLabels": [
+ [
+ "FatLabels"
+ ],
+ {
+ "title_aux": "Comfy UI FatLabels"
+ }
+ ],
+ "https://github.com/alpertunga-bile/prompt-generator-comfyui": [
+ [
+ "Prompt Generator"
+ ],
+ {
+ "title_aux": "prompt-generator"
+ }
+ ],
+ "https://github.com/alsritter/asymmetric-tiling-comfyui": [
+ [
+ "Asymmetric_Tiling_KSampler"
+ ],
+ {
+ "title_aux": "asymmetric-tiling-comfyui"
+ }
+ ],
+ "https://github.com/alt-key-project/comfyui-dream-project": [
+ [
+ "Analyze Palette [Dream]",
+ "Beat Curve [Dream]",
+ "Big Float Switch [Dream]",
+ "Big Image Switch [Dream]",
+ "Big Int Switch [Dream]",
+ "Big Latent Switch [Dream]",
+ "Big Palette Switch [Dream]",
+ "Big Text Switch [Dream]",
+ "Boolean To Float [Dream]",
+ "Boolean To Int [Dream]",
+ "Build Prompt [Dream]",
+ "CSV Curve [Dream]",
+ "CSV Generator [Dream]",
+ "Calculation [Dream]",
+ "Common Frame Dimensions [Dream]",
+ "Compare Palettes [Dream]",
+ "FFMPEG Video Encoder [Dream]",
+ "File Count [Dream]",
+ "Finalize Prompt [Dream]",
+ "Float Input [Dream]",
+ "Float to Log Entry [Dream]",
+ "Frame Count Calculator [Dream]",
+ "Frame Counter (Directory) [Dream]",
+ "Frame Counter (Simple) [Dream]",
+ "Frame Counter Info [Dream]",
+ "Frame Counter Offset [Dream]",
+ "Frame Counter Time Offset [Dream]",
+ "Image Brightness Adjustment [Dream]",
+ "Image Color Shift [Dream]",
+ "Image Contrast Adjustment [Dream]",
+ "Image Motion [Dream]",
+ "Image Sequence Blend [Dream]",
+ "Image Sequence Loader [Dream]",
+ "Image Sequence Saver [Dream]",
+ "Image Sequence Tweening [Dream]",
+ "Int Input [Dream]",
+ "Int to Log Entry [Dream]",
+ "Laboratory [Dream]",
+ "Linear Curve [Dream]",
+ "Log Entry Joiner [Dream]",
+ "Log File [Dream]",
+ "Noise from Area Palettes [Dream]",
+ "Noise from Palette [Dream]",
+ "Palette Color Align [Dream]",
+ "Palette Color Shift [Dream]",
+ "Sample Image Area as Palette [Dream]",
+ "Sample Image as Palette [Dream]",
+ "Saw Curve [Dream]",
+ "Sine Curve [Dream]",
+ "Smooth Event Curve [Dream]",
+ "String Input [Dream]",
+ "String Tokenizer [Dream]",
+ "String to Log Entry [Dream]",
+ "Text Input [Dream]",
+ "Triangle Curve [Dream]",
+ "Triangle Event Curve [Dream]",
+ "WAV Curve [Dream]"
+ ],
+ {
+ "title_aux": "Dream Project Animation Nodes"
+ }
+ ],
+ "https://github.com/alt-key-project/comfyui-dream-video-batches": [
+ [
+ "Blended Transition [DVB]",
+ "Calculation [DVB]",
+ "Create Frame Set [DVB]",
+ "Divide [DVB]",
+ "Fade From Black [DVB]",
+ "Fade To Black [DVB]",
+ "Float Input [DVB]",
+ "For Each Done [DVB]",
+ "For Each Filename [DVB]",
+ "Frame Set Append [DVB]",
+ "Frame Set Frame Dimensions Scaled [DVB]",
+ "Frame Set Index Offset [DVB]",
+ "Frame Set Merger [DVB]",
+ "Frame Set Reindex [DVB]",
+ "Frame Set Repeat [DVB]",
+ "Frame Set Reverse [DVB]",
+ "Frame Set Split Beginning [DVB]",
+ "Frame Set Split End [DVB]",
+ "Frame Set Splitter [DVB]",
+ "Generate Inbetween Frames [DVB]",
+ "Int Input [DVB]",
+ "Linear Camera Pan [DVB]",
+ "Linear Camera Roll [DVB]",
+ "Linear Camera Zoom [DVB]",
+ "Load Image From Path [DVB]",
+ "Multiply [DVB]",
+ "Sine Camera Pan [DVB]",
+ "Sine Camera Roll [DVB]",
+ "Sine Camera Zoom [DVB]",
+ "String Input [DVB]",
+ "Text Input [DVB]",
+ "Trace Memory Allocation [DVB]",
+ "Unwrap Frame Set [DVB]"
+ ],
+ {
+ "title_aux": "Dream Video Batches"
+ }
+ ],
+ "https://github.com/andersxa/comfyui-PromptAttention": [
+ [
+ "CLIPAttentionMaskEncode"
+ ],
+ {
+ "title_aux": "CLIP Directional Prompt Attention"
+ }
+ ],
+ "https://github.com/asagi4/ComfyUI-CADS": [
+ [
+ "CADS"
+ ],
+ {
+ "title_aux": "ComfyUI-CADS"
+ }
+ ],
+ "https://github.com/asagi4/comfyui-prompt-control": [
+ [
+ "EditableCLIPEncode",
+ "FilterSchedule",
+ "LoRAScheduler",
+ "PCSplitSampling",
+ "PromptControlSimple",
+ "PromptToSchedule",
+ "ScheduleToCond",
+ "ScheduleToModel"
+ ],
+ {
+ "title_aux": "ComfyUI prompt control"
+ }
+ ],
+ "https://github.com/asagi4/comfyui-utility-nodes": [
+ [
+ "MUJinjaRender",
+ "MUSimpleWildcard"
+ ],
+ {
+ "title_aux": "asagi4/comfyui-utility-nodes"
+ }
+ ],
+ "https://github.com/aszc-dev/ComfyUI-CoreMLSuite": [
+ [
+ "Core ML Converter",
+ "Core ML LCM Converter",
+ "Core ML LoRA Loader",
+ "CoreMLModelAdapter",
+ "CoreMLSampler",
+ "CoreMLSamplerAdvanced",
+ "CoreMLUNetLoader"
+ ],
+ {
+ "title_aux": "Core ML Suite for ComfyUI"
+ }
+ ],
+ "https://github.com/avatechai/avatar-graph-comfyui": [
+ [
+ "ApplyMeshTransformAsShapeKey",
+ "B_ENUM",
+ "B_VECTOR3",
+ "B_VECTOR4",
+ "CreateShapeFlow",
+ "ExportBlendshapes",
+ "ExportGLTF",
+ "Image Alpha Mask Merge",
+ "ImageBridge",
+ "LoadImageWithAlpha",
+ "SAM MultiLayer",
+ "Save Image With Workflow"
+ ],
+ {
+ "author": "Avatech Limited",
+ "description": "Include nodes for sam + bpy operation, that allows workflow creations for generative 2d character rig.",
+ "nickname": "Avatar Graph",
+ "title": "Avatar Graph",
+ "title_aux": "avatar-graph-comfyui"
+ }
+ ],
+ "https://github.com/azazeal04/ComfyUI-Styles": [
+ [
+ "menus"
+ ],
+ {
+ "title_aux": "ComfyUI-Styles"
+ }
+ ],
+ "https://github.com/badjeff/comfyui_lora_tag_loader": [
+ [
+ "LoraTagLoader"
+ ],
+ {
+ "title_aux": "LoRA Tag Loader for ComfyUI"
+ }
+ ],
+ "https://github.com/bash-j/mikey_nodes": [
+ [
+ "AddMetaData",
+ "Batch Crop Image",
+ "Batch Crop Resize Inplace",
+ "Batch Load Images",
+ "Batch Resize Image for SDXL",
+ "Checkpoint Loader Simple Mikey",
+ "CinematicLook",
+ "Empty Latent Ratio Custom SDXL",
+ "Empty Latent Ratio Select SDXL",
+ "EvalFloats",
+ "FileNamePrefix",
+ "Float to String",
+ "HaldCLUT",
+ "Image Caption",
+ "ImageBorder",
+ "ImageOverlay",
+ "ImagePaste",
+ "Int to String",
+ "LoraSyntaxProcessor",
+ "Mikey Sampler",
+ "Mikey Sampler Base Only",
+ "Mikey Sampler Base Only Advanced",
+ "Mikey Sampler Tiled",
+ "Mikey Sampler Tiled Base Only",
+ "MikeySamplerTiledAdvanced",
+ "MikeySamplerTiledAdvancedBaseOnly",
+ "OobaPrompt",
+ "PresetRatioSelector",
+ "Prompt With SDXL",
+ "Prompt With Style",
+ "Prompt With Style V2",
+ "Prompt With Style V3",
+ "Range Float",
+ "Range Integer",
+ "Ratio Advanced",
+ "Resize Image for SDXL",
+ "Save Image If True",
+ "Save Image With Prompt Data",
+ "Save Images Mikey",
+ "Save Images No Display",
+ "SaveMetaData",
+ "SearchAndReplace",
+ "Seed String",
+ "Style Conditioner",
+ "Style Conditioner Base Only",
+ "Text2InputOr3rdOption",
+ "TextCombinations",
+ "TextCombinations3",
+ "TextPreserve",
+ "Upscale Tile Calculator",
+ "Wildcard Processor",
+ "WildcardAndLoraSyntaxProcessor",
+ "WildcardOobaPrompt"
+ ],
+ {
+ "title_aux": "Mikey Nodes"
+ }
+ ],
+ "https://github.com/bedovyy/ComfyUI_NAIDGenerator": [
+ [
+ "GenerateNAID",
+ "ImageToNAIMask",
+ "Img2ImgOptionNAID",
+ "InpaintingOptionNAID",
+ "ModelOptionNAID"
+ ],
+ {
+ "title_aux": "ComfyUI_NAIDGenerator"
+ }
+ ],
+ "https://github.com/biegert/ComfyUI-CLIPSeg/raw/main/custom_nodes/clipseg.py": [
+ [
+ "CLIPSeg",
+ "CombineSegMasks"
+ ],
+ {
+ "title_aux": "CLIPSeg"
+ }
+ ],
+ "https://github.com/bmad4ever/comfyui_ab_samplercustom": [
+ [
+ "AB SamplerCustom (experimental)"
+ ],
+ {
+ "title_aux": "comfyui_ab_sampler"
+ }
+ ],
+ "https://github.com/bmad4ever/comfyui_bmad_nodes": [
+ [
+ "AdaptiveThresholding",
+ "Add String To Many",
+ "AddAlpha",
+ "AdjustRect",
+ "AnyToAny",
+ "BoundingRect (contours)",
+ "BuildColorRangeAdvanced (hsv)",
+ "BuildColorRangeHSV (hsv)",
+ "CLAHE",
+ "CLIPEncodeMultiple",
+ "CLIPEncodeMultipleAdvanced",
+ "ChameleonMask",
+ "CheckpointLoader (dirty)",
+ "CheckpointLoaderSimple (dirty)",
+ "Color (RGB)",
+ "Color (hexadecimal)",
+ "Color Clip",
+ "Color Clip (advanced)",
+ "Color Clip ADE20k",
+ "ColorDictionary",
+ "ColorDictionary (custom)",
+ "Conditioning (combine multiple)",
+ "Conditioning (combine selective)",
+ "Conditioning Grid (cond)",
+ "Conditioning Grid (string)",
+ "Conditioning Grid (string) Advanced",
+ "Contour To Mask",
+ "Contours",
+ "ControlNetHadamard",
+ "ControlNetHadamard (manual)",
+ "ConvertImg",
+ "CopyMakeBorder",
+ "CreateRequestMetadata",
+ "DistanceTransform",
+ "Draw Contour(s)",
+ "EqualizeHistogram",
+ "FadeMaskEdges",
+ "Filter Contour",
+ "FindComplementaryColor",
+ "FindThreshold",
+ "FlatLatentsIntoSingleGrid",
+ "Framed Mask Grab Cut",
+ "Framed Mask Grab Cut 2",
+ "FromListGet1Color",
+ "FromListGet1Cond",
+ "FromListGet1Float",
+ "FromListGet1Image",
+ "FromListGet1Int",
+ "FromListGet1Latent",
+ "FromListGet1Model",
+ "FromListGet1String",
+ "FromListGetColors",
+ "FromListGetConds",
+ "FromListGetFloats",
+ "FromListGetImages",
+ "FromListGetInts",
+ "FromListGetLatents",
+ "FromListGetModels",
+ "FromListGetStrings",
+ "Get Contour from list",
+ "Get Models",
+ "Get Prompt",
+ "HypernetworkLoader (dirty)",
+ "ImageBatchToList",
+ "InRange (hsv)",
+ "InnerCylinder (remap)",
+ "Inpaint",
+ "Input/String to Int Array",
+ "KMeansColor",
+ "Load 64 Encoded Image",
+ "LoraLoader (dirty)",
+ "MaskGrid N KSamplers Advanced",
+ "Merge Latent Batch Gridwise",
+ "MonoMerge",
+ "MorphologicOperation",
+ "MorphologicSkeletoning",
+ "NaiveAutoKMeansColor",
+ "OtsuThreshold",
+ "OuterCylinder (remap)",
+ "RGB to HSV",
+ "Rect Grab Cut",
+ "Remap",
+ "Repeat Into Grid (image)",
+ "Repeat Into Grid (latent)",
+ "RequestInputs",
+ "SampleColorHSV",
+ "Save Image (api)",
+ "SeamlessClone",
+ "SeamlessClone (simple)",
+ "SetRequestStateToComplete",
+ "String",
+ "String to Float",
+ "String to Integer",
+ "ToColorList",
+ "ToCondList",
+ "ToFloatList",
+ "ToImageList",
+ "ToIntList",
+ "ToLatentList",
+ "ToModelList",
+ "ToStringList",
+ "UnGridify (image)",
+ "VAEEncodeBatch"
+ ],
+ {
+ "title_aux": "Bmad Nodes"
+ }
+ ],
+ "https://github.com/bradsec/ComfyUI_ResolutionSelector": [
+ [
+ "ResolutionSelector"
+ ],
+ {
+ "title_aux": "ResolutionSelector for ComfyUI"
+ }
+ ],
+ "https://github.com/braintacles/braintacles-comfyui-nodes": [
+ [
+ "CLIPTextEncodeSDXL-Multi-IO",
+ "CLIPTextEncodeSDXL-Pipe",
+ "Empty Latent Image from Aspect-Ratio",
+ "Random Find and Replace",
+ "VAE Decode Pipe",
+ "VAE Decode Tiled Pipe",
+ "VAE Encode Pipe",
+ "VAE Encode Tiled Pipe"
+ ],
+ {
+ "title_aux": "braintacles-nodes"
+ }
+ ],
+ "https://github.com/bronkula/comfyui-fitsize": [
+ [
+ "FS: Crop Image Into Even Pieces",
+ "FS: Fit Image And Resize",
+ "FS: Fit Size From Image",
+ "FS: Fit Size From Int",
+ "FS: Image Region To Mask",
+ "FS: Load Image And Resize To Fit",
+ "FS: Pick Image From Batch",
+ "FS: Pick Image From Batches",
+ "FS: Pick Image From List"
+ ],
+ {
+ "title_aux": "comfyui-fitsize"
+ }
+ ],
+ "https://github.com/budihartono/comfyui_otonx_nodes": [
+ [
+ "OTX Integer Multiple Inputs 4",
+ "OTX Integer Multiple Inputs 5",
+ "OTX Integer Multiple Inputs 6",
+ "OTX KSampler Feeder",
+ "OTX Versatile Multiple Inputs 4",
+ "OTX Versatile Multiple Inputs 5",
+ "OTX Versatile Multiple Inputs 6"
+ ],
+ {
+ "title_aux": "Otonx's Custom Nodes"
+ }
+ ],
+ "https://github.com/bvhari/ComfyUI_ImageProcessing": [
+ [
+ "BilateralFilter",
+ "Brightness",
+ "Gamma",
+ "Hue",
+ "Saturation",
+ "SigmoidCorrection",
+ "UnsharpMask"
+ ],
+ {
+ "title_aux": "ImageProcessing"
+ }
+ ],
+ "https://github.com/bvhari/ComfyUI_LatentToRGB": [
+ [
+ "LatentToRGB"
+ ],
+ {
+ "title_aux": "LatentToRGB"
+ }
+ ],
+ "https://github.com/bvhari/ComfyUI_PerpNeg": [
+ [
+ "KSamplerAdvancedPerpNeg"
+ ],
+ {
+ "title_aux": "ComfyUI_PerpNeg [WIP]"
+ }
+ ],
+ "https://github.com/bvhari/ComfyUI_PerpWeight": [
+ [
+ "CLIPTextEncodePerpWeight"
+ ],
+ {
+ "title_aux": "ComfyUI_PerpWeight"
+ }
+ ],
+ "https://github.com/catscandrive/comfyui-imagesubfolders/raw/main/loadImageWithSubfolders.py": [
+ [
+ "LoadImagewithSubfolders"
+ ],
+ {
+ "title_aux": "Image loader with subfolders"
+ }
+ ],
+ "https://github.com/chflame163/ComfyUI_MSSpeech_TTS": [
+ [
+ "MicorsoftSpeech_TTS",
+ "Play Sound"
+ ],
+ {
+ "title_aux": "ComfyUI_MSSpeech_TTS"
+ }
+ ],
+ "https://github.com/chibiace/ComfyUI-Chibi-Nodes": [
+ [
+ "ConditionText",
+ "ConditionTextMulti",
+ "ImageAddText",
+ "ImageSimpleResize",
+ "ImageSizeInfo",
+ "ImageTool",
+ "Int2String",
+ "LoadEmbedding",
+ "LoadImageExtended",
+ "Loader",
+ "Prompts",
+ "SaveImages",
+ "SeedGenerator",
+ "SimpleSampler",
+ "TextSplit",
+ "Textbox",
+ "Wildcards"
+ ],
+ {
+ "title_aux": "ComfyUI-Chibi-Nodes"
+ }
+ ],
+ "https://github.com/chrisgoringe/cg-image-picker": [
+ [
+ "Preview Chooser",
+ "Preview Chooser Fabric"
+ ],
+ {
+ "author": "chrisgoringe",
+ "description": "Custom nodes that preview images and pause the workflow to allow the user to select one or more to progress",
+ "nickname": "Image Chooser",
+ "title": "Image Chooser",
+ "title_aux": "Image chooser"
+ }
+ ],
+ "https://github.com/chrisgoringe/cg-noise": [
+ [
+ "Hijack",
+ "KSampler Advanced with Variations",
+ "KSampler with Variations",
+ "UnHijack"
+ ],
+ {
+ "title_aux": "Variation seeds"
+ }
+ ],
+ "https://github.com/chrisgoringe/cg-use-everywhere": [
+ [
+ "Seed Everywhere"
+ ],
+ {
+ "nodename_pattern": "(^(Prompts|Anything) Everywhere|Simple String)",
+ "title_aux": "Use Everywhere (UE Nodes)"
+ }
+ ],
+ "https://github.com/city96/ComfyUI_ColorMod": [
+ [
+ "ColorModEdges",
+ "ColorModPivot",
+ "LoadImageHighPrec",
+ "PreviewImageHighPrec",
+ "SaveImageHighPrec"
+ ],
+ {
+ "title_aux": "ComfyUI_ColorMod"
+ }
+ ],
+ "https://github.com/city96/ComfyUI_DiT": [
+ [
+ "DiTCheckpointLoader",
+ "DiTCheckpointLoaderSimple",
+ "DiTLabelCombine",
+ "DiTLabelSelect",
+ "DiTSampler"
+ ],
+ {
+ "title_aux": "ComfyUI_DiT [WIP]"
+ }
+ ],
+ "https://github.com/city96/ComfyUI_ExtraModels": [
+ [
+ "DiTCondLabelEmpty",
+ "DiTCondLabelSelect",
+ "DitCheckpointLoader",
+ "ExtraVAELoader",
+ "PixArtCheckpointLoader",
+ "PixArtDPMSampler",
+ "PixArtResolutionSelect",
+ "PixArtT5TextEncode",
+ "T5TextEncode",
+ "T5v11Loader"
+ ],
+ {
+ "title_aux": "Extra Models for ComfyUI"
+ }
+ ],
+ "https://github.com/city96/ComfyUI_NetDist": [
+ [
+ "FetchRemote",
+ "QueueRemote"
+ ],
+ {
+ "title_aux": "ComfyUI_NetDist"
+ }
+ ],
+ "https://github.com/city96/SD-Advanced-Noise": [
+ [
+ "LatentGaussianNoise",
+ "MathEncode"
+ ],
+ {
+ "title_aux": "SD-Advanced-Noise"
+ }
+ ],
+ "https://github.com/city96/SD-Latent-Interposer": [
+ [
+ "LatentInterposer"
+ ],
+ {
+ "title_aux": "Latent-Interposer"
+ }
+ ],
+ "https://github.com/city96/SD-Latent-Upscaler": [
+ [
+ "LatentUpscaler"
+ ],
+ {
+ "title_aux": "SD-Latent-Upscaler"
+ }
+ ],
+ "https://github.com/civitai/comfy-nodes": [
+ [
+ "CivitAI_Checkpoint_Loader",
+ "CivitAI_Lora_Loader"
+ ],
+ {
+ "title_aux": "comfy-nodes"
+ }
+ ],
+ "https://github.com/comfyanonymous/ComfyUI": [
+ [
+ "BasicScheduler",
+ "CLIPLoader",
+ "CLIPMergeSimple",
+ "CLIPSave",
+ "CLIPSetLastLayer",
+ "CLIPTextEncode",
+ "CLIPTextEncodeSDXL",
+ "CLIPTextEncodeSDXLRefiner",
+ "CLIPVisionEncode",
+ "CLIPVisionLoader",
+ "Canny",
+ "CheckpointLoader",
+ "CheckpointLoaderSimple",
+ "CheckpointSave",
+ "ConditioningAverage",
+ "ConditioningCombine",
+ "ConditioningConcat",
+ "ConditioningSetArea",
+ "ConditioningSetAreaPercentage",
+ "ConditioningSetMask",
+ "ConditioningSetTimestepRange",
+ "ConditioningZeroOut",
+ "ControlNetApply",
+ "ControlNetApplyAdvanced",
+ "ControlNetLoader",
+ "CropMask",
+ "DiffControlNetLoader",
+ "DiffusersLoader",
+ "DualCLIPLoader",
+ "EmptyImage",
+ "EmptyLatentImage",
+ "ExponentialScheduler",
+ "FeatherMask",
+ "FlipSigmas",
+ "FreeU",
+ "FreeU_V2",
+ "GLIGENLoader",
+ "GLIGENTextBoxApply",
+ "GrowMask",
+ "HyperTile",
+ "HypernetworkLoader",
+ "ImageBatch",
+ "ImageBlend",
+ "ImageBlur",
+ "ImageColorToMask",
+ "ImageCompositeMasked",
+ "ImageCrop",
+ "ImageInvert",
+ "ImageOnlyCheckpointLoader",
+ "ImagePadForOutpaint",
+ "ImageQuantize",
+ "ImageScale",
+ "ImageScaleBy",
+ "ImageScaleToTotalPixels",
+ "ImageSharpen",
+ "ImageToMask",
+ "ImageUpscaleWithModel",
+ "InvertMask",
+ "JoinImageWithAlpha",
+ "KSampler",
+ "KSamplerAdvanced",
+ "KSamplerSelect",
+ "KarrasScheduler",
+ "LatentAdd",
+ "LatentBlend",
+ "LatentComposite",
+ "LatentCompositeMasked",
+ "LatentCrop",
+ "LatentFlip",
+ "LatentFromBatch",
+ "LatentInterpolate",
+ "LatentMultiply",
+ "LatentRotate",
+ "LatentSubtract",
+ "LatentUpscale",
+ "LatentUpscaleBy",
+ "LoadImage",
+ "LoadImageMask",
+ "LoadLatent",
+ "LoraLoader",
+ "LoraLoaderModelOnly",
+ "MaskComposite",
+ "MaskToImage",
+ "ModelMergeAdd",
+ "ModelMergeBlocks",
+ "ModelMergeSimple",
+ "ModelMergeSubtract",
+ "ModelSamplingContinuousEDM",
+ "ModelSamplingDiscrete",
+ "PatchModelAddDownscale",
+ "PolyexponentialScheduler",
+ "PorterDuffImageComposite",
+ "PreviewImage",
+ "RebatchLatents",
+ "RepeatImageBatch",
+ "RepeatLatentBatch",
+ "RescaleCFG",
+ "SDTurboScheduler",
+ "SVD_img2vid_Conditioning",
+ "SamplerCustom",
+ "SamplerDPMPP_2M_SDE",
+ "SamplerDPMPP_SDE",
+ "SaveAnimatedPNG",
+ "SaveAnimatedWEBP",
+ "SaveImage",
+ "SaveLatent",
+ "SetLatentNoiseMask",
+ "SolidMask",
+ "SplitImageWithAlpha",
+ "SplitSigmas",
+ "StyleModelApply",
+ "StyleModelLoader",
+ "TomePatchModel",
+ "UNETLoader",
+ "UpscaleModelLoader",
+ "VAEDecode",
+ "VAEDecodeTiled",
+ "VAEEncode",
+ "VAEEncodeForInpaint",
+ "VAEEncodeTiled",
+ "VAELoader",
+ "VAESave",
+ "VPScheduler",
+ "VideoLinearCFGGuidance",
+ "unCLIPCheckpointLoader",
+ "unCLIPConditioning"
+ ],
+ {
+ "title_aux": "ComfyUI"
+ }
+ ],
+ "https://github.com/comfyanonymous/ComfyUI_experiments": [
+ [
+ "ModelMergeBlockNumber",
+ "ModelMergeSDXL",
+ "ModelMergeSDXLDetailedTransformers",
+ "ModelMergeSDXLTransformers",
+ "ModelSamplerTonemapNoiseTest",
+ "ReferenceOnlySimple",
+ "RescaleClassifierFreeGuidanceTest",
+ "TonemapNoiseWithRescaleCFG"
+ ],
+ {
+ "title_aux": "ComfyUI_experiments"
+ }
+ ],
+ "https://github.com/coreyryanhanson/ComfyQR": [
+ [
+ "comfy-qr-by-image-size",
+ "comfy-qr-by-module-size",
+ "comfy-qr-by-module-split",
+ "comfy-qr-mask_errors"
+ ],
+ {
+ "title_aux": "ComfyQR"
+ }
+ ],
+ "https://github.com/coreyryanhanson/ComfyQR-scanning-nodes": [
+ [
+ "comfy-qr-read",
+ "comfy-qr-validate"
+ ],
+ {
+ "title_aux": "ComfyQR-scanning-nodes"
+ }
+ ],
+ "https://github.com/cubiq/ComfyUI_IPAdapter_plus": [
+ [
+ "IPAdapterApply",
+ "IPAdapterApplyEncoded",
+ "IPAdapterBatchEmbeds",
+ "IPAdapterEncoder",
+ "IPAdapterLoadEmbeds",
+ "IPAdapterModelLoader",
+ "IPAdapterSaveEmbeds",
+ "PrepImageForClipVision"
+ ],
+ {
+ "title_aux": "ComfyUI_IPAdapter_plus"
+ }
+ ],
+ "https://github.com/cubiq/ComfyUI_SimpleMath": [
+ [
+ "SimpleMath",
+ "SimpleMathDebug"
+ ],
+ {
+ "title_aux": "Simple Math"
+ }
+ ],
+ "https://github.com/cubiq/ComfyUI_essentials": [
+ [
+ "ConsoleDebug+",
+ "GetImageSize+",
+ "ImageCASharpening+",
+ "ImageCrop+",
+ "ImageDesaturate+",
+ "ImageEnhanceDifference+",
+ "ImageExpandBatch+",
+ "ImageFlip+",
+ "ImagePosterize+",
+ "ImageResize+",
+ "MaskBatch+",
+ "MaskBlur+",
+ "MaskExpandBatch+",
+ "MaskFlip+",
+ "MaskPreview+",
+ "ModelCompile+",
+ "SimpleMath+",
+ "TransitionMask+"
+ ],
+ {
+ "title_aux": "ComfyUI Essentials"
+ }
+ ],
+ "https://github.com/dagthomas/comfyui_dagthomas": [
+ [
+ "CSL",
+ "CSVPromptGenerator",
+ "PromptGenerator"
+ ],
+ {
+ "title_aux": "SDXL Auto Prompter"
+ }
+ ],
+ "https://github.com/dawangraoming/ComfyUI_ksampler_gpu/raw/main/ksampler_gpu.py": [
+ [
+ "KSamplerAdvancedGPU",
+ "KSamplerGPU"
+ ],
+ {
+ "title_aux": "KSampler GPU"
+ }
+ ],
+ "https://github.com/daxthin/DZ-FaceDetailer": [
+ [
+ "DZ_Face_Detailer"
+ ],
+ {
+ "title_aux": "DZ-FaceDetailer"
+ }
+ ],
+ "https://github.com/dimtoneff/ComfyUI-PixelArt-Detector": [
+ [
+ "PixelArtAddDitherPattern",
+ "PixelArtDetectorConverter",
+ "PixelArtDetectorSave",
+ "PixelArtDetectorToImage",
+ "PixelArtLoadPalettes"
+ ],
+ {
+ "title_aux": "ComfyUI PixelArt Detector"
+ }
+ ],
+ "https://github.com/diontimmer/ComfyUI-Vextra-Nodes": [
+ [
+ "Add Text To Image",
+ "Apply Instagram Filter",
+ "Create Solid Color",
+ "Flatten Colors",
+ "Generate Noise Image",
+ "GlitchThis Effect",
+ "Hue Rotation",
+ "Load Picture Index",
+ "Pixel Sort",
+ "Play Sound At Execution",
+ "Prettify Prompt Using distilgpt2",
+ "Swap Color Mode"
+ ],
+ {
+ "title_aux": "ComfyUI-Vextra-Nodes"
+ }
+ ],
+ "https://github.com/drago87/ComfyUI_Dragos_Nodes": [
+ [
+ "file_padding",
+ "image_info",
+ "lora_loader",
+ "vae_loader"
+ ],
+ {
+ "title_aux": "ComfyUI_Dragos_Nodes"
+ }
+ ],
+ "https://github.com/drustan-hawk/primitive-types": [
+ [
+ "float",
+ "int",
+ "string",
+ "string_multiline"
+ ],
+ {
+ "title_aux": "primitive-types"
+ }
+ ],
+ "https://github.com/ealkanat/comfyui_easy_padding": [
+ [
+ "comfyui-easy-padding"
+ ],
+ {
+ "title_aux": "ComfyUI Easy Padding"
+ }
+ ],
+ "https://github.com/evanspearman/ComfyMath": [
+ [
+ "CM_BoolBinaryOperation",
+ "CM_BoolToInt",
+ "CM_BoolUnaryOperation",
+ "CM_BreakoutVec2",
+ "CM_BreakoutVec3",
+ "CM_BreakoutVec4",
+ "CM_ComposeVec2",
+ "CM_ComposeVec3",
+ "CM_ComposeVec4",
+ "CM_FloatBinaryCondition",
+ "CM_FloatBinaryOperation",
+ "CM_FloatToInt",
+ "CM_FloatToNumber",
+ "CM_FloatUnaryCondition",
+ "CM_FloatUnaryOperation",
+ "CM_IntBinaryCondition",
+ "CM_IntBinaryOperation",
+ "CM_IntToBool",
+ "CM_IntToFloat",
+ "CM_IntToNumber",
+ "CM_IntUnaryCondition",
+ "CM_IntUnaryOperation",
+ "CM_NearestSDXLResolution",
+ "CM_NumberBinaryCondition",
+ "CM_NumberBinaryOperation",
+ "CM_NumberToFloat",
+ "CM_NumberToInt",
+ "CM_NumberUnaryCondition",
+ "CM_NumberUnaryOperation",
+ "CM_SDXLResolution",
+ "CM_Vec2BinaryCondition",
+ "CM_Vec2BinaryOperation",
+ "CM_Vec2ScalarOperation",
+ "CM_Vec2ToScalarBinaryOperation",
+ "CM_Vec2ToScalarUnaryOperation",
+ "CM_Vec2UnaryCondition",
+ "CM_Vec2UnaryOperation",
+ "CM_Vec3BinaryCondition",
+ "CM_Vec3BinaryOperation",
+ "CM_Vec3ScalarOperation",
+ "CM_Vec3ToScalarBinaryOperation",
+ "CM_Vec3ToScalarUnaryOperation",
+ "CM_Vec3UnaryCondition",
+ "CM_Vec3UnaryOperation",
+ "CM_Vec4BinaryCondition",
+ "CM_Vec4BinaryOperation",
+ "CM_Vec4ScalarOperation",
+ "CM_Vec4ToScalarBinaryOperation",
+ "CM_Vec4ToScalarUnaryOperation",
+ "CM_Vec4UnaryCondition",
+ "CM_Vec4UnaryOperation"
+ ],
+ {
+ "title_aux": "ComfyMath"
+ }
+ ],
+ "https://github.com/fearnworks/ComfyUI_FearnworksNodes/raw/main/fw_nodes.py": [
+ [
+ "Count Files in Directory (FW)",
+ "Count Tokens (FW)",
+ "Token Count Ranker(FW)",
+ "Trim To Tokens (FW)"
+ ],
+ {
+ "title_aux": "Fearnworks Custom Nodes"
+ }
+ ],
+ "https://github.com/fexli/fexli-util-node-comfyui": [
+ [
+ "FEColor2Image",
+ "FEColorOut",
+ "FEImagePadForOutpaint",
+ "FERandomizedColor2Image"
+ ],
+ {
+ "title_aux": "fexli-util-node-comfyui"
+ }
+ ],
+ "https://github.com/filipemeneses/comfy_pixelization": [
+ [
+ "Pixelization"
+ ],
+ {
+ "title_aux": "Pixelization"
+ }
+ ],
+ "https://github.com/filliptm/ComfyUI_Fill-Nodes": [
+ [
+ "FL_ImageRandomizer"
+ ],
+ {
+ "title_aux": "ComfyUI_Fill-Nodes"
+ }
+ ],
+ "https://github.com/fitCorder/fcSuite/raw/main/fcSuite.py": [
+ [
+ "fcFloat",
+ "fcFloatMatic",
+ "fcInteger"
+ ],
+ {
+ "title_aux": "fcSuite"
+ }
+ ],
+ "https://github.com/flyingshutter/As_ComfyUI_CustomNodes": [
+ [
+ "BatchIndex_AS",
+ "CropImage_AS",
+ "ImageMixMasked_As",
+ "ImageToMask_AS",
+ "Increment_AS",
+ "Int2Any_AS",
+ "LatentAdd_AS",
+ "LatentMixMasked_As",
+ "LatentMix_AS",
+ "LatentToImages_AS",
+ "LoadLatent_AS",
+ "MapRange_AS",
+ "MaskToImage_AS",
+ "Math_AS",
+ "NoiseImage_AS",
+ "Number2Float_AS",
+ "Number2Int_AS",
+ "Number_AS",
+ "SaveLatent_AS",
+ "TextToImage_AS",
+ "TextWildcardList_AS"
+ ],
+ {
+ "title_aux": "As_ComfyUI_CustomNodes"
+ }
+ ],
+ "https://github.com/gemell1/ComfyUI_GMIC": [
+ [
+ "GmicCliWrapper"
+ ],
+ {
+ "title_aux": "ComfyUI_GMIC"
+ }
+ ],
+ "https://github.com/giriss/comfy-image-saver": [
+ [
+ "Cfg Literal",
+ "Checkpoint Selector",
+ "Int Literal",
+ "Sampler Selector",
+ "Save Image w/Metadata",
+ "Scheduler Selector",
+ "Seed Generator",
+ "String Literal",
+ "Width/Height Literal"
+ ],
+ {
+ "title_aux": "Save Image with Generation Metadata"
+ }
+ ],
+ "https://github.com/guoyk93/yk-node-suite-comfyui": [
+ [
+ "YKImagePadForOutpaint",
+ "YKMaskToImage"
+ ],
+ {
+ "title_aux": "y.k.'s ComfyUI node suite"
+ }
+ ],
+ "https://github.com/hhhzzyang/Comfyui_Lama": [
+ [
+ "LamaApply",
+ "LamaModelLoader",
+ "YamlConfigLoader"
+ ],
+ {
+ "title_aux": "Comfyui-Lama"
+ }
+ ],
+ "https://github.com/hnmr293/ComfyUI-nodes-hnmr": [
+ [
+ "CLIPIter",
+ "Dict2Model",
+ "GridImage",
+ "ImageBlend2",
+ "KSamplerOverrided",
+ "KSamplerSetting",
+ "KSamplerXYZ",
+ "LatentToHist",
+ "LatentToImage",
+ "ModelIter",
+ "RandomLatentImage",
+ "SaveStateDict",
+ "SaveText",
+ "StateDictLoader",
+ "StateDictMerger",
+ "StateDictMergerBlockWeighted",
+ "StateDictMergerBlockWeightedMulti",
+ "VAEDecodeBatched",
+ "VAEEncodeBatched",
+ "VAEIter"
+ ],
+ {
+ "title_aux": "ComfyUI-nodes-hnmr"
+ }
+ ],
+ "https://github.com/hustille/ComfyUI_Fooocus_KSampler": [
+ [
+ "KSampler With Refiner (Fooocus)"
+ ],
+ {
+ "title_aux": "ComfyUI_Fooocus_KSampler"
+ }
+ ],
+ "https://github.com/hustille/ComfyUI_hus_utils": [
+ [
+ "3way Prompt Styler",
+ "Batch State",
+ "Date Time Format",
+ "Debug Extra",
+ "Fetch widget value",
+ "Text Hash"
+ ],
+ {
+ "title_aux": "hus' utils for ComfyUI"
+ }
+ ],
+ "https://github.com/hylarucoder/ComfyUI-Eagle-PNGInfo": [
+ [
+ "EagleImageNode",
+ "SDXLPromptStyler",
+ "SDXLPromptStylerAdvanced",
+ "SDXLResolutionPresets"
+ ],
+ {
+ "title_aux": "Eagle PNGInfo"
+ }
+ ],
+ "https://github.com/idrirap/ComfyUI-Lora-Auto-Trigger-Words": [
+ [
+ "FusionText",
+ "LoraLoaderAdvanced",
+ "LoraLoaderStackedAdvanced",
+ "LoraLoaderStackedVanilla",
+ "LoraLoaderVanilla",
+ "Randomizer",
+ "TagsFormater",
+ "TagsSelector",
+ "TextInputBasic"
+ ],
+ {
+ "title_aux": "ComfyUI-Lora-Auto-Trigger-Words"
+ }
+ ],
+ "https://github.com/imb101/ComfyUI-FaceSwap": [
+ [
+ "FaceSwapNode"
+ ],
+ {
+ "title_aux": "FaceSwap"
+ }
+ ],
+ "https://github.com/jags111/ComfyUI_Jags_Audiotools": [
+ [
+ "BatchJoinAudio",
+ "BatchToList",
+ "BitCrushAudioFX",
+ "BulkVariation",
+ "ChorusAudioFX",
+ "ClippingAudioFX",
+ "CompressorAudioFX",
+ "ConcatAudioList",
+ "ConvolutionAudioFX",
+ "CutAudio",
+ "DelayAudioFX",
+ "DistortionAudioFX",
+ "DuplicateAudio",
+ "GainAudioFX",
+ "GenerateAudioSample",
+ "GenerateAudioWave",
+ "GetAudioFromFolderIndex",
+ "GetSingle",
+ "GetStringByIndex",
+ "HighShelfFilter",
+ "HighpassFilter",
+ "ImageToSpectral",
+ "InvertAudioFX",
+ "JoinAudio",
+ "LadderFilter",
+ "LimiterAudioFX",
+ "ListToBatch",
+ "LoadAudioDir",
+ "LoadAudioFile",
+ "LoadAudioModel (DD)",
+ "LoadVST3",
+ "LowShelfFilter",
+ "LowpassFilter",
+ "MP3CompressorAudioFX",
+ "MixAudioTensors",
+ "NoiseGateAudioFX",
+ "OTTAudioFX",
+ "PeakFilter",
+ "PhaserEffectAudioFX",
+ "PitchShiftAudioFX",
+ "PlotSpectrogram",
+ "PreviewAudioFile",
+ "PreviewAudioTensor",
+ "ResampleAudio",
+ "ReverbAudioFX",
+ "ReverseAudio",
+ "SaveAudioTensor",
+ "SequenceVariation",
+ "SliceAudio",
+ "StretchAudio"
+ ],
+ {
+ "author": "jags111",
+ "description": "This extension offers various audio generation tools",
+ "nickname": "Audiotools",
+ "title": "Jags_Audiotools",
+ "title_aux": "ComfyUI_Jags_Audiotools"
+ }
+ ],
+ "https://github.com/jags111/ComfyUI_Jags_VectorMagic": [
+ [
+ "CircularVAEDecode",
+ "SVG",
+ "YoloSEGdetectionNode",
+ "YoloSegNode",
+ "color_drop",
+ "my unique name",
+ "xy_Tiling_KSampler"
+ ],
+ {
+ "author": "jags111",
+ "description": "This extension offers various vector manipulation and generation tools",
+ "nickname": "Jags_VectorMagic",
+ "title": "Jags_VectorMagic",
+ "title_aux": "ComfyUI_Jags_VectorMagic"
+ }
+ ],
+ "https://github.com/jags111/efficiency-nodes-comfyui": [
+ [
+ "AnimateDiff Script",
+ "Apply ControlNet Stack",
+ "Control Net Stacker",
+ "Eff. Loader SDXL",
+ "Efficient Loader",
+ "HighRes-Fix Script",
+ "Image Overlay",
+ "Join XY Inputs of Same Type",
+ "KSampler (Efficient)",
+ "KSampler Adv. (Efficient)",
+ "KSampler SDXL (Eff.)",
+ "LatentUpscaler",
+ "LoRA Stacker",
+ "Manual XY Entry Info",
+ "NNLatentUpscale",
+ "Noise Control Script",
+ "Pack SDXL Tuple",
+ "Tiled Upscaler Script",
+ "Unpack SDXL Tuple",
+ "XY Input: Add/Return Noise",
+ "XY Input: Aesthetic Score",
+ "XY Input: CFG Scale",
+ "XY Input: Checkpoint",
+ "XY Input: Clip Skip",
+ "XY Input: Control Net",
+ "XY Input: Control Net Plot",
+ "XY Input: Denoise",
+ "XY Input: LoRA",
+ "XY Input: LoRA Plot",
+ "XY Input: LoRA Stacks",
+ "XY Input: Manual XY Entry",
+ "XY Input: Prompt S/R",
+ "XY Input: Refiner On/Off",
+ "XY Input: Sampler/Scheduler",
+ "XY Input: Seeds++ Batch",
+ "XY Input: Steps",
+ "XY Input: VAE",
+ "XY Plot"
+ ],
+ {
+ "title_aux": "Efficiency Nodes for ComfyUI Version 2.0+"
+ }
+ ],
+ "https://github.com/jamesWalker55/comfyui-various": [
+ [],
+ {
+ "nodename_pattern": "^JW",
+ "title_aux": "Various ComfyUI Nodes by Type"
+ }
+ ],
+ "https://github.com/jjkramhoeft/ComfyUI-Jjk-Nodes": [
+ [
+ "JjkConcat",
+ "JjkShowText",
+ "JjkText",
+ "SDXLRecommendedImageSize"
+ ],
+ {
+ "title_aux": "ComfyUI-Jjk-Nodes"
+ }
+ ],
+ "https://github.com/jojkaart/ComfyUI-sampler-lcm-alternative": [
+ [
+ "LCMScheduler",
+ "SamplerLCMAlternative",
+ "SamplerLCMCycle"
+ ],
+ {
+ "title_aux": "ComfyUI-sampler-lcm-alternative"
+ }
+ ],
+ "https://github.com/jtrue/ComfyUI-JaRue": [
+ [
+ "ConcatStringWithDelimiter_jru",
+ "ConcatString_jru",
+ "Float2Int_jru",
+ "Float2String_jru",
+ "ImageSizer_jru",
+ "Int2FloatMultiply_jru",
+ "Int2String_jru",
+ "String2Int_jru",
+ "YouTube2Prompt_jru"
+ ],
+ {
+ "title_aux": "ComfyUI-JaRue"
+ }
+ ],
+ "https://github.com/ka-puna/comfyui-yanc": [
+ [
+ "YANC.ConcatStrings",
+ "YANC.FormatDatetimeString",
+ "YANC.GetWidgetValueString",
+ "YANC.IntegerCaster",
+ "YANC.MultilineString",
+ "YANC.TruncateString"
+ ],
+ {
+ "title_aux": "comfyui-yanc"
+ }
+ ],
+ "https://github.com/kenjiqq/qq-nodes-comfyui": [
+ [
+ "Any List",
+ "Axis To Float",
+ "Axis To Int",
+ "Axis To Model",
+ "Axis To String",
+ "Image Accumulator End",
+ "Image Accumulator Start",
+ "Load Lines From Text File",
+ "Slice List",
+ "XY Grid Helper"
+ ],
+ {
+ "title_aux": "qq-nodes-comfyui"
+ }
+ ],
+ "https://github.com/kijai/ComfyUI-KJNodes": [
+ [
+ "AddLabel",
+ "BatchCLIPSeg",
+ "BatchCropFromMask",
+ "BatchCropFromMaskAdvanced",
+ "BatchUncrop",
+ "BatchUncropAdvanced",
+ "BboxToInt",
+ "ColorMatch",
+ "ColorToMask",
+ "ConditioningMultiCombine",
+ "ConditioningSetMaskAndCombine",
+ "ConditioningSetMaskAndCombine3",
+ "ConditioningSetMaskAndCombine4",
+ "ConditioningSetMaskAndCombine5",
+ "CreateAudioMask",
+ "CreateFadeMask",
+ "CreateFadeMaskAdvanced",
+ "CreateFluidMask",
+ "CreateGradientMask",
+ "CreateMagicMask",
+ "CreateShapeMask",
+ "CreateTextMask",
+ "CreateVoronoiMask",
+ "CrossFadeImages",
+ "DummyLatentOut",
+ "EmptyLatentImagePresets",
+ "FlipSigmasAdjusted",
+ "FloatConstant",
+ "GetImageRangeFromBatch",
+ "GrowMaskWithBlur",
+ "INTConstant",
+ "ImageBatchTestPattern",
+ "ImageConcanate",
+ "ImageGrabPIL",
+ "ImageGridComposite2x2",
+ "ImageGridComposite3x3",
+ "InjectNoiseToLatent",
+ "NormalizeLatent",
+ "OffsetMask",
+ "ReplaceImagesInBatch",
+ "ResizeMask",
+ "ReverseImageBatch",
+ "RoundMask",
+ "SaveImageWithAlpha",
+ "SomethingToString",
+ "SplitBboxes",
+ "VRAM_Debug",
+ "WidgetToString"
+ ],
+ {
+ "title_aux": "KJNodes for ComfyUI"
+ }
+ ],
+ "https://github.com/kijai/ComfyUI-SVD": [
+ [
+ "SVDimg2vid"
+ ],
+ {
+ "title_aux": "ComfyUI-SVD"
+ }
+ ],
+ "https://github.com/kinfolk0117/ComfyUI_GradientDeepShrink": [
+ [
+ "GradientPatchModelAddDownscale",
+ "GradientPatchModelAddDownscaleAdvanced"
+ ],
+ {
+ "title_aux": "ComfyUI_GradientDeepShrink"
+ }
+ ],
+ "https://github.com/kinfolk0117/ComfyUI_SimpleTiles": [
+ [
+ "TileCalc",
+ "TileMerge",
+ "TileSplit"
+ ],
+ {
+ "title_aux": "SimpleTiles"
+ }
+ ],
+ "https://github.com/kinfolk0117/ComfyUI_TiledIPAdapter": [
+ [
+ "TiledIPAdapter"
+ ],
+ {
+ "title_aux": "TiledIPAdapter"
+ }
+ ],
+ "https://github.com/knuknX/ComfyUI-Image-Tools": [
+ [
+ "ImageBatchSqueezeProcessor",
+ "ImageBgRemoveProcessor",
+ "ImageStandardResizeProcessor",
+ "SingleImagePathLoader",
+ "SingleImageUrlLoader"
+ ],
+ {
+ "title_aux": "ComfyUI-Image-Tools"
+ }
+ ],
+ "https://github.com/kohya-ss/ControlNet-LLLite-ComfyUI": [
+ [
+ "LLLiteLoader"
+ ],
+ {
+ "title_aux": "ControlNet-LLLite-ComfyUI"
+ }
+ ],
+ "https://github.com/komojini/ComfyUI_SDXL_DreamBooth_LoRA_CustomNodes": [
+ [
+ "S3 Bucket LoRA",
+ "S3Bucket_Load_LoRA",
+ "XL DreamBooth LoRA",
+ "XLDB_LoRA"
+ ],
+ {
+ "title_aux": "ComfyUI_SDXL_DreamBooth_LoRA_CustomNodes"
+ }
+ ],
+ "https://github.com/kwaroran/abg-comfyui": [
+ [
+ "Remove Image Background (abg)"
+ ],
+ {
+ "title_aux": "abg-comfyui"
+ }
+ ],
+ "https://github.com/laksjdjf/IPAdapter-ComfyUI": [
+ [
+ "IPAdapter",
+ "ImageCrop"
+ ],
+ {
+ "title_aux": "IPAdapter-ComfyUI"
+ }
+ ],
+ "https://github.com/laksjdjf/LCMSampler-ComfyUI": [
+ [
+ "SamplerLCM",
+ "TAESDLoader"
+ ],
+ {
+ "title_aux": "LCMSampler-ComfyUI"
+ }
+ ],
+ "https://github.com/laksjdjf/LoRA-Merger-ComfyUI": [
+ [
+ "LoraLoaderFromWeight",
+ "LoraLoaderWeightOnly",
+ "LoraMerge",
+ "LoraSave"
+ ],
+ {
+ "title_aux": "LoRA-Merger-ComfyUI"
+ }
+ ],
+ "https://github.com/laksjdjf/attention-couple-ComfyUI": [
+ [
+ "Attention couple"
+ ],
+ {
+ "title_aux": "attention-couple-ComfyUI"
+ }
+ ],
+ "https://github.com/laksjdjf/cd-tuner_negpip-ComfyUI": [
+ [
+ "CDTuner",
+ "Negapip",
+ "Negpip"
+ ],
+ {
+ "title_aux": "cd-tuner_negpip-ComfyUI"
+ }
+ ],
+ "https://github.com/laksjdjf/pfg-ComfyUI": [
+ [
+ "PFG"
+ ],
+ {
+ "title_aux": "pfg-ComfyUI"
+ }
+ ],
+ "https://github.com/lilly1987/ComfyUI_node_Lilly": [
+ [
+ "CheckpointLoaderSimpleText",
+ "LoraLoaderText",
+ "LoraLoaderTextRandom",
+ "Random_Sampler",
+ "VAELoaderDecode"
+ ],
+ {
+ "title_aux": "simple wildcard for ComfyUI"
+ }
+ ],
+ "https://github.com/lordgasmic/ComfyUI-Wildcards/raw/master/wildcards.py": [
+ [
+ "CLIPTextEncodeWithWildcards"
+ ],
+ {
+ "title_aux": "Wildcards"
+ }
+ ],
+ "https://github.com/lrzjason/ComfyUIJasonNode/raw/main/SDXLMixSampler.py": [
+ [
+ "SDXLMixSampler"
+ ],
+ {
+ "title_aux": "ComfyUIJasonNode"
+ }
+ ],
+ "https://github.com/ltdrdata/ComfyUI-Impact-Pack": [
+ [
+ "AddMask",
+ "BasicPipeToDetailerPipe",
+ "BasicPipeToDetailerPipeSDXL",
+ "BboxDetectorCombined",
+ "BboxDetectorCombined_v2",
+ "BboxDetectorForEach",
+ "BboxDetectorSEGS",
+ "BitwiseAndMask",
+ "BitwiseAndMaskForEach",
+ "CLIPSegDetectorProvider",
+ "CfgScheduleHookProvider",
+ "CombineRegionalPrompts",
+ "CoreMLDetailerHookProvider",
+ "DenoiseScheduleHookProvider",
+ "DetailerForEach",
+ "DetailerForEachDebug",
+ "DetailerForEachDebugPipe",
+ "DetailerForEachPipe",
+ "DetailerPipeToBasicPipe",
+ "EditBasicPipe",
+ "EditDetailerPipe",
+ "EditDetailerPipeSDXL",
+ "EmptySegs",
+ "FaceDetailer",
+ "FaceDetailerPipe",
+ "FromBasicPipe",
+ "FromBasicPipe_v2",
+ "FromDetailerPipe",
+ "FromDetailerPipeSDXL",
+ "FromDetailerPipe_v2",
+ "ImageListToImageBatch",
+ "ImageMaskSwitch",
+ "ImageReceiver",
+ "ImageSender",
+ "ImpactAssembleSEGS",
+ "ImpactCombineConditionings",
+ "ImpactCompare",
+ "ImpactConditionalBranch",
+ "ImpactConditionalStopIteration",
+ "ImpactControlBridge",
+ "ImpactControlNetApplySEGS",
+ "ImpactDecomposeSEGS",
+ "ImpactDilateMask",
+ "ImpactDilate_Mask_SEG_ELT",
+ "ImpactDummyInput",
+ "ImpactEdit_SEG_ELT",
+ "ImpactFloat",
+ "ImpactFrom_SEG_ELT",
+ "ImpactHFTransformersClassifierProvider",
+ "ImpactImageBatchToImageList",
+ "ImpactImageInfo",
+ "ImpactInt",
+ "ImpactInversedSwitch",
+ "ImpactIsNotEmptySEGS",
+ "ImpactKSamplerAdvancedBasicPipe",
+ "ImpactKSamplerBasicPipe",
+ "ImpactLogger",
+ "ImpactMakeImageBatch",
+ "ImpactMakeImageList",
+ "ImpactMinMax",
+ "ImpactNeg",
+ "ImpactNodeSetMuteState",
+ "ImpactQueueTrigger",
+ "ImpactQueueTriggerCountdown",
+ "ImpactSEGSClassify",
+ "ImpactSEGSConcat",
+ "ImpactSEGSLabelFilter",
+ "ImpactSEGSOrderedFilter",
+ "ImpactSEGSPicker",
+ "ImpactSEGSRangeFilter",
+ "ImpactSEGSToMaskBatch",
+ "ImpactSEGSToMaskList",
+ "ImpactScaleBy_BBOX_SEG_ELT",
+ "ImpactSegsAndMask",
+ "ImpactSegsAndMaskForEach",
+ "ImpactSetWidgetValue",
+ "ImpactSimpleDetectorSEGS",
+ "ImpactSimpleDetectorSEGSPipe",
+ "ImpactSimpleDetectorSEGS_for_AD",
+ "ImpactSleep",
+ "ImpactStringSelector",
+ "ImpactSwitch",
+ "ImpactValueReceiver",
+ "ImpactValueSender",
+ "ImpactWildcardEncode",
+ "ImpactWildcardProcessor",
+ "IterativeImageUpscale",
+ "IterativeLatentUpscale",
+ "KSamplerAdvancedProvider",
+ "KSamplerProvider",
+ "LatentPixelScale",
+ "LatentReceiver",
+ "LatentSender",
+ "LatentSwitch",
+ "MMDetDetectorProvider",
+ "MMDetLoader",
+ "MaskDetailerPipe",
+ "MaskListToMaskBatch",
+ "MaskPainter",
+ "MaskToSEGS",
+ "MaskToSEGS_for_AnimateDiff",
+ "MasksToMaskList",
+ "MediaPipeFaceMeshToSEGS",
+ "NoiseInjectionDetailerHookProvider",
+ "NoiseInjectionHookProvider",
+ "ONNXDetectorProvider",
+ "ONNXDetectorSEGS",
+ "PixelKSampleHookCombine",
+ "PixelKSampleUpscalerProvider",
+ "PixelKSampleUpscalerProviderPipe",
+ "PixelTiledKSampleUpscalerProvider",
+ "PixelTiledKSampleUpscalerProviderPipe",
+ "PreviewBridge",
+ "ReencodeLatent",
+ "ReencodeLatentPipe",
+ "RegionalPrompt",
+ "RegionalSampler",
+ "RegionalSamplerAdvanced",
+ "RemoveNoiseMask",
+ "SAMDetectorCombined",
+ "SAMDetectorSegmented",
+ "SAMLoader",
+ "SEGSDetailer",
+ "SEGSDetailerForAnimateDiff",
+ "SEGSPaste",
+ "SEGSPreview",
+ "SEGSSwitch",
+ "SEGSToImageList",
+ "SegmDetectorCombined",
+ "SegmDetectorCombined_v2",
+ "SegmDetectorForEach",
+ "SegmDetectorSEGS",
+ "Segs Mask",
+ "Segs Mask ForEach",
+ "SegsMaskCombine",
+ "SegsToCombinedMask",
+ "SubtractMask",
+ "SubtractMaskForEach",
+ "TiledKSamplerProvider",
+ "ToBasicPipe",
+ "ToBinaryMask",
+ "ToDetailerPipe",
+ "ToDetailerPipeSDXL",
+ "TwoAdvancedSamplersForMask",
+ "TwoSamplersForMask",
+ "TwoSamplersForMaskUpscalerProvider",
+ "TwoSamplersForMaskUpscalerProviderPipe",
+ "UltralyticsDetectorProvider"
+ ],
+ {
+ "author": "Dr.Lt.Data",
+ "description": "This extension offers various detector nodes and detailer nodes that allow you to configure a workflow that automatically enhances facial details. And provide iterative upscaler.",
+ "nickname": "Impact Pack",
+ "title": "Impact Pack",
+ "title_aux": "ComfyUI Impact Pack"
+ }
+ ],
+ "https://github.com/ltdrdata/ComfyUI-Inspire-Pack": [
+ [
+ "AnimeLineArt_Preprocessor_Provider_for_SEGS //Inspire",
+ "ApplyRegionalIPAdapters //Inspire",
+ "BindImageListPromptList //Inspire",
+ "CacheBackendData //Inspire",
+ "CacheBackendDataList //Inspire",
+ "CacheBackendDataNumberKey //Inspire",
+ "CacheBackendDataNumberKeyList //Inspire",
+ "Canny_Preprocessor_Provider_for_SEGS //Inspire",
+ "ChangeImageBatchSize //Inspire",
+ "Color_Preprocessor_Provider_for_SEGS //Inspire",
+ "DWPreprocessor_Provider_for_SEGS //Inspire",
+ "FakeScribblePreprocessor_Provider_for_SEGS //Inspire",
+ "FloatRange //Inspire",
+ "FromIPAdapterPipe //Inspire",
+ "GlobalSeed //Inspire",
+ "HEDPreprocessor_Provider_for_SEGS //Inspire",
+ "InpaintPreprocessor_Provider_for_SEGS //Inspire",
+ "KSampler //Inspire",
+ "KSamplerAdvanced //Inspire",
+ "KSamplerAdvancedProgress //Inspire",
+ "KSamplerProgress //Inspire",
+ "LeRes_DepthMap_Preprocessor_Provider_for_SEGS //Inspire",
+ "LineArt_Preprocessor_Provider_for_SEGS //Inspire",
+ "ListCounter //Inspire",
+ "LoadImage //Inspire",
+ "LoadImageListFromDir //Inspire",
+ "LoadImagesFromDir //Inspire",
+ "LoadPromptsFromDir //Inspire",
+ "LoadPromptsFromFile //Inspire",
+ "LoraBlockInfo //Inspire",
+ "LoraLoaderBlockWeight //Inspire",
+ "Manga2Anime_LineArt_Preprocessor_Provider_for_SEGS //Inspire",
+ "MediaPipeFaceMeshDetectorProvider //Inspire",
+ "MediaPipe_FaceMesh_Preprocessor_Provider_for_SEGS //Inspire",
+ "MiDaS_DepthMap_Preprocessor_Provider_for_SEGS //Inspire",
+ "OpenPose_Preprocessor_Provider_for_SEGS //Inspire",
+ "PromptBuilder //Inspire",
+ "PromptExtractor //Inspire",
+ "RegionalConditioningColorMask //Inspire",
+ "RegionalConditioningSimple //Inspire",
+ "RegionalIPAdapterColorMask //Inspire",
+ "RegionalIPAdapterEncodedColorMask //Inspire",
+ "RegionalIPAdapterEncodedMask //Inspire",
+ "RegionalIPAdapterMask //Inspire",
+ "RegionalPromptColorMask //Inspire",
+ "RegionalPromptSimple //Inspire",
+ "RegionalSeedExplorerColorMask //Inspire",
+ "RegionalSeedExplorerMask //Inspire",
+ "RemoveBackendData //Inspire",
+ "RemoveBackendDataNumberKey //Inspire",
+ "RetrieveBackendData //Inspire",
+ "RetrieveBackendDataNumberKey //Inspire",
+ "SeedExplorer //Inspire",
+ "ShowCachedInfo //Inspire",
+ "TilePreprocessor_Provider_for_SEGS //Inspire",
+ "ToIPAdapterPipe //Inspire",
+ "UnzipPrompt //Inspire",
+ "WildcardEncode //Inspire",
+ "XY Input: Lora Block Weight //Inspire",
+ "ZipPrompt //Inspire",
+ "Zoe_DepthMap_Preprocessor_Provider_for_SEGS //Inspire"
+ ],
+ {
+ "author": "Dr.Lt.Data",
+ "description": "This extension provides various nodes to support Lora Block Weight and the Impact Pack.",
+ "nickname": "Inspire Pack",
+ "nodename_pattern": "Inspire$",
+ "title": "Inspire Pack",
+ "title_aux": "ComfyUI Inspire Pack"
+ }
+ ],
+ "https://github.com/m-sokes/ComfyUI-Sokes-Nodes": [
+ [
+ "Custom Date Format | sokes \ud83e\uddac",
+ "Latent Switch x9 | sokes \ud83e\uddac"
+ ],
+ {
+ "title_aux": "ComfyUI Sokes Nodes"
+ }
+ ],
+ "https://github.com/m957ymj75urz/ComfyUI-Custom-Nodes/raw/main/clip-text-encode-split/clip_text_encode_split.py": [
+ [
+ "RawText",
+ "RawTextCombine",
+ "RawTextEncode",
+ "RawTextReplace"
+ ],
+ {
+ "title_aux": "m957ymj75urz/ComfyUI-Custom-Nodes"
+ }
+ ],
+ "https://github.com/marhensa/sdxl-recommended-res-calc": [
+ [
+ "RecommendedResCalc"
+ ],
+ {
+ "title_aux": "Recommended Resolution Calculator"
+ }
+ ],
+ "https://github.com/martijnat/comfyui-previewlatent": [
+ [
+ "PreviewLatent",
+ "PreviewLatentAdvanced"
+ ],
+ {
+ "title_aux": "comfyui-previewlatent"
+ }
+ ],
+ "https://github.com/matan1905/ComfyUI-Serving-Toolkit": [
+ [
+ "DiscordServing",
+ "ServingInputNumber",
+ "ServingInputText",
+ "ServingOutput",
+ "WebSocketServing"
+ ],
+ {
+ "title_aux": "ComfyUI Serving toolkit"
+ }
+ ],
+ "https://github.com/mav-rik/facerestore_cf": [
+ [
+ "CropFace",
+ "FaceRestoreCFWithModel",
+ "FaceRestoreModelLoader"
+ ],
+ {
+ "title_aux": "Facerestore CF (Code Former)"
+ }
+ ],
+ "https://github.com/mcmonkeyprojects/sd-dynamic-thresholding": [
+ [
+ "DynamicThresholdingFull",
+ "DynamicThresholdingSimple"
+ ],
+ {
+ "title_aux": "Stable Diffusion Dynamic Thresholding (CFG Scale Fix)"
+ }
+ ],
+ "https://github.com/meap158/ComfyUI-GPU-temperature-protection": [
+ [
+ "GPUTemperatureProtection"
+ ],
+ {
+ "title_aux": "GPU temperature protection"
+ }
+ ],
+ "https://github.com/meap158/ComfyUI-Prompt-Expansion": [
+ [
+ "PromptExpansion"
+ ],
+ {
+ "title_aux": "ComfyUI-Prompt-Expansion"
+ }
+ ],
+ "https://github.com/melMass/comfy_mtb": [
+ [
+ "Animation Builder (mtb)",
+ "Any To String (mtb)",
+ "Batch Float (mtb)",
+ "Batch Float Assemble (mtb)",
+ "Batch Float Fill (mtb)",
+ "Batch Make (mtb)",
+ "Batch Merge (mtb)",
+ "Batch Shake (mtb)",
+ "Batch Shape (mtb)",
+ "Batch Transform (mtb)",
+ "Bbox (mtb)",
+ "Bbox From Mask (mtb)",
+ "Blur (mtb)",
+ "Color Correct (mtb)",
+ "Colored Image (mtb)",
+ "Concat Images (mtb)",
+ "Crop (mtb)",
+ "Debug (mtb)",
+ "Deep Bump (mtb)",
+ "Export With Ffmpeg (mtb)",
+ "Face Swap (mtb)",
+ "Film Interpolation (mtb)",
+ "Fit Number (mtb)",
+ "Float To Number (mtb)",
+ "Get Batch From History (mtb)",
+ "Image Compare (mtb)",
+ "Image Premultiply (mtb)",
+ "Image Remove Background Rembg (mtb)",
+ "Image Resize Factor (mtb)",
+ "Image Tile Offset (mtb)",
+ "Int To Bool (mtb)",
+ "Int To Number (mtb)",
+ "Interpolate Clip Sequential (mtb)",
+ "Latent Lerp (mtb)",
+ "Load Face Analysis Model (mtb)",
+ "Load Face Enhance Model (mtb)",
+ "Load Face Swap Model (mtb)",
+ "Load Film Model (mtb)",
+ "Load Image From Url (mtb)",
+ "Load Image Sequence (mtb)",
+ "Mask To Image (mtb)",
+ "Math Expression (mtb)",
+ "Model Patch Seamless (mtb)",
+ "Pick From Batch (mtb)",
+ "Qr Code (mtb)",
+ "Restore Face (mtb)",
+ "Save Gif (mtb)",
+ "Save Image Grid (mtb)",
+ "Save Image Sequence (mtb)",
+ "Save Tensors (mtb)",
+ "Sharpen (mtb)",
+ "Smart Step (mtb)",
+ "Stack Images (mtb)",
+ "String Replace (mtb)",
+ "Styles Loader (mtb)",
+ "Text To Image (mtb)",
+ "Transform Image (mtb)",
+ "Uncrop (mtb)",
+ "Unsplash Image (mtb)",
+ "Vae Decode (mtb)"
+ ],
+ {
+ "nodename_pattern": "\\(mtb\\)$",
+ "title_aux": "MTB Nodes"
+ }
+ ],
+ "https://github.com/mihaiiancu/ComfyUI_Inpaint": [
+ [
+ "InpaintMediapipe"
+ ],
+ {
+ "title_aux": "mihaiiancu/Inpaint"
+ }
+ ],
+ "https://github.com/mikkel/ComfyUI-text-overlay": [
+ [
+ "Image Text Overlay"
+ ],
+ {
+ "title_aux": "ComfyUI - Text Overlay Plugin"
+ }
+ ],
+ "https://github.com/mikkel/comfyui-mask-boundingbox": [
+ [
+ "Mask Bounding Box"
+ ],
+ {
+ "title_aux": "ComfyUI - Mask Bounding Box"
+ }
+ ],
+ "https://github.com/mlinmg/ComfyUI-LaMA-Preprocessor": [
+ [
+ "LaMaPreprocessor",
+ "lamaPreprocessor"
+ ],
+ {
+ "title_aux": "LaMa Preprocessor [WIP]"
+ }
+ ],
+ "https://github.com/mpiquero7164/ComfyUI-SaveImgPrompt": [
+ [
+ "Save IMG Prompt"
+ ],
+ {
+ "title_aux": "SaveImgPrompt"
+ }
+ ],
+ "https://github.com/nagolinc/ComfyUI_FastVAEDecorder_SDXL": [
+ [
+ "FastLatentToImage"
+ ],
+ {
+ "title_aux": "ComfyUI_FastVAEDecorder_SDXL"
+ }
+ ],
+ "https://github.com/natto-maki/ComfyUI-NegiTools": [
+ [
+ "NegiTools_CompositeImages",
+ "NegiTools_ImageProperties",
+ "NegiTools_LatentProperties",
+ "NegiTools_NoiseImageGenerator",
+ "NegiTools_OpenAiDalle3",
+ "NegiTools_OpenAiTranslate",
+ "NegiTools_SeedGenerator",
+ "NegiTools_StringFunction"
+ ],
+ {
+ "title_aux": "ComfyUI-NegiTools"
+ }
+ ],
+ "https://github.com/nicolai256/comfyUI_Nodes_nicolai256/raw/main/yugioh-presets.py": [
+ [
+ "yugioh_Presets"
+ ],
+ {
+ "title_aux": "comfyUI_Nodes_nicolai256"
+ }
+ ],
+ "https://github.com/ningxiaoxiao/comfyui-NDI": [
+ [
+ "NDI_LoadImage",
+ "NDI_SendImage"
+ ],
+ {
+ "title_aux": "comfyui-NDI"
+ }
+ ],
+ "https://github.com/noembryo/ComfyUI-noEmbryo": [
+ [
+ "PromptTermList1",
+ "PromptTermList2",
+ "PromptTermList3",
+ "PromptTermList4",
+ "PromptTermList5",
+ "PromptTermList6"
+ ],
+ {
+ "author": "noEmbryo",
+ "description": "Some useful nodes for ComfyUI",
+ "nickname": "noEmbryo",
+ "title": "noEmbryo nodes for ComfyUI",
+ "title_aux": "noEmbryo nodes"
+ }
+ ],
+ "https://github.com/noxinias/ComfyUI_NoxinNodes": [
+ [
+ "NoxinChime",
+ "NoxinPromptLoad",
+ "NoxinPromptSave",
+ "NoxinScaledResolution",
+ "NoxinSimpleMath",
+ "NoxinSplitPrompt"
+ ],
+ {
+ "title_aux": "ComfyUI_NoxinNodes"
+ }
+ ],
+ "https://github.com/ntdviet/comfyui-ext/raw/main/custom_nodes/gcLatentTunnel/gcLatentTunnel.py": [
+ [
+ "gcLatentTunnel"
+ ],
+ {
+ "title_aux": "ntdviet/comfyui-ext"
+ }
+ ],
+ "https://github.com/omar92/ComfyUI-QualityOfLifeSuit_Omar92": [
+ [
+ "CLIPStringEncode _O",
+ "Chat completion _O",
+ "ChatGPT Simple _O",
+ "ChatGPT _O",
+ "ChatGPT compact _O",
+ "Chat_Completion _O",
+ "Chat_Message _O",
+ "Chat_Message_fromString _O",
+ "Concat Text _O",
+ "ConcatRandomNSP_O",
+ "Debug String _O",
+ "Debug Text _O",
+ "Debug Text route _O",
+ "Edit_image _O",
+ "Equation1param _O",
+ "Equation2params _O",
+ "GetImage_(Width&Height) _O",
+ "GetLatent_(Width&Height) _O",
+ "ImageScaleFactor _O",
+ "ImageScaleFactorSimple _O",
+ "LatentUpscaleFactor _O",
+ "LatentUpscaleFactorSimple _O",
+ "LatentUpscaleMultiply",
+ "Note _O",
+ "RandomNSP _O",
+ "Replace Text _O",
+ "String _O",
+ "Text _O",
+ "Text2Image _O",
+ "Trim Text _O",
+ "VAEDecodeParallel _O",
+ "combine_chat_messages _O",
+ "compine_chat_messages _O",
+ "concat Strings _O",
+ "create image _O",
+ "create_image _O",
+ "debug Completeion _O",
+ "debug messages_O",
+ "float _O",
+ "floatToInt _O",
+ "floatToText _O",
+ "int _O",
+ "intToFloat _O",
+ "load_openAI _O",
+ "replace String _O",
+ "replace String advanced _O",
+ "saveTextToFile _O",
+ "seed _O",
+ "selectLatentFromBatch _O",
+ "string2Image _O",
+ "trim String _O",
+ "variation_image _O"
+ ],
+ {
+ "title_aux": "Quality of life Suit:V2"
+ }
+ ],
+ "https://github.com/ostris/ostris_nodes_comfyui": [
+ [
+ "LLM Pipe Loader - Ostris",
+ "LLM Prompt Upsampling - Ostris",
+ "One Seed - Ostris",
+ "Text Box - Ostris"
+ ],
+ {
+ "nodename_pattern": "- Ostris$",
+ "title_aux": "Ostris Nodes ComfyUI"
+ }
+ ],
+ "https://github.com/oyvindg/ComfyUI-TrollSuite": [
+ [
+ "BinaryImageMask",
+ "ImagePadding",
+ "LoadLastImage",
+ "RandomMask",
+ "TransparentImage"
+ ],
+ {
+ "title_aux": "ComfyUI-TrollSuite"
+ }
+ ],
+ "https://github.com/palant/extended-saveimage-comfyui": [
+ [
+ "SaveImageExtended"
+ ],
+ {
+ "title_aux": "Extended Save Image for ComfyUI"
+ }
+ ],
+ "https://github.com/palant/image-resize-comfyui": [
+ [
+ "ImageResize"
+ ],
+ {
+ "title_aux": "Image Resize for ComfyUI"
+ }
+ ],
+ "https://github.com/pants007/comfy-pants": [
+ [
+ "CLIPTextEncodeAIO",
+ "Image Make Square"
+ ],
+ {
+ "title_aux": "pants"
+ }
+ ],
+ "https://github.com/paulo-coronado/comfy_clip_blip_node": [
+ [
+ "CLIPTextEncodeBLIP",
+ "CLIPTextEncodeBLIP-2",
+ "Example"
+ ],
+ {
+ "title_aux": "comfy_clip_blip_node"
+ }
+ ],
+ "https://github.com/peteromallet/ComfyUI-Creative-Interpolation": [
+ [
+ "BatchCreativeInterpolation"
+ ],
+ {
+ "title_aux": "ComfyUI-Creative-Interpolation [Beta]"
+ }
+ ],
+ "https://github.com/picturesonpictures/comfy_PoP": [
+ [
+ "AdaptiveCannyDetector_PoP",
+ "AnyAspectRatio",
+ "ConditioningMultiplier_PoP",
+ "ConditioningNormalizer_PoP",
+ "LoadImageResizer_PoP",
+ "LoraStackLoader10_PoP",
+ "LoraStackLoader_PoP",
+ "VAEDecoderPoP",
+ "VAEEncoderPoP"
+ ],
+ {
+ "title_aux": "comfy_PoP"
+ }
+ ],
+ "https://github.com/pythongosssss/ComfyUI-Custom-Scripts": [
+ [
+ "CheckpointLoader|pysssss",
+ "ConstrainImage|pysssss",
+ "LoadText|pysssss",
+ "LoraLoader|pysssss",
+ "MathExpression|pysssss",
+ "MultiPrimitive|pysssss",
+ "PlaySound|pysssss",
+ "Repeater|pysssss",
+ "ReroutePrimitive|pysssss",
+ "SaveText|pysssss",
+ "ShowText|pysssss",
+ "StringFunction|pysssss"
+ ],
+ {
+ "title_aux": "pythongosssss/ComfyUI-Custom-Scripts"
+ }
+ ],
+ "https://github.com/pythongosssss/ComfyUI-WD14-Tagger": [
+ [
+ "WD14Tagger|pysssss"
+ ],
+ {
+ "title_aux": "ComfyUI WD 1.4 Tagger"
+ }
+ ],
+ "https://github.com/ramyma/A8R8_ComfyUI_nodes": [
+ [
+ "Base64ImageInput",
+ "Base64ImageOutput"
+ ],
+ {
+ "title_aux": "A8R8 ComfyUI Nodes"
+ }
+ ],
+ "https://github.com/receyuki/comfyui-prompt-reader-node": [
+ [
+ "SDBatchLoader",
+ "SDParameterGenerator",
+ "SDPromptMerger",
+ "SDPromptReader",
+ "SDPromptSaver",
+ "SDTypeConverter"
+ ],
+ {
+ "author": "receyuki",
+ "description": "ComfyUI node version of the SD Prompt Reader",
+ "nickname": "SD Prompt Reader",
+ "title": "SD Prompt Reader",
+ "title_aux": "comfyui-prompt-reader-node"
+ }
+ ],
+ "https://github.com/rgthree/rgthree-comfy": [
+ [],
+ {
+ "author": "rgthree",
+ "description": "A bunch of nodes I created that I also find useful.",
+ "nickname": "rgthree",
+ "nodename_pattern": " \\(rgthree\\)$",
+ "title": "Comfy Nodes",
+ "title_aux": "rgthree's ComfyUi Nodes"
+ }
+ ],
+ "https://github.com/richinsley/Comfy-LFO": [
+ [
+ "LFO_Pulse",
+ "LFO_Sawtooth",
+ "LFO_Sine",
+ "LFO_Square",
+ "LFO_Triangle"
+ ],
+ {
+ "title_aux": "Comfy-LFO"
+ }
+ ],
+ "https://github.com/rklaffehn/rk-comfy-nodes": [
+ [
+ "RK_CivitAIAddHashes",
+ "RK_CivitAIMetaChecker"
+ ],
+ {
+ "title_aux": "rk-comfy-nodes"
+ }
+ ],
+ "https://github.com/romeobuilderotti/ComfyUI-PNG-Metadata": [
+ [
+ "SetMetadataAll",
+ "SetMetadataString"
+ ],
+ {
+ "title_aux": "ComfyUI PNG Metadata"
+ }
+ ],
+ "https://github.com/s1dlx/comfy_meh/raw/main/meh.py": [
+ [
+ "MergingExecutionHelper"
+ ],
+ {
+ "title_aux": "comfy_meh"
+ }
+ ],
+ "https://github.com/seanlynch/comfyui-optical-flow": [
+ [
+ "Apply optical flow",
+ "Compute optical flow",
+ "Visualize optical flow"
+ ],
+ {
+ "title_aux": "ComfyUI Optical Flow"
+ }
+ ],
+ "https://github.com/seanlynch/srl-nodes": [
+ [
+ "SRL Conditional Interrrupt",
+ "SRL Eval",
+ "SRL Filter Image List",
+ "SRL Format String"
+ ],
+ {
+ "title_aux": "SRL's nodes"
+ }
+ ],
+ "https://github.com/sergekatzmann/ComfyUI_Nimbus-Pack": [
+ [
+ "ImageResizeAndCropNode",
+ "ImageSquareAdapterNode"
+ ],
+ {
+ "title_aux": "ComfyUI_Nimbus-Pack"
+ }
+ ],
+ "https://github.com/shadowcz007/comfyui-mixlab-nodes": [
+ [
+ "AreaToMask",
+ "CLIPSeg",
+ "CLIPSeg_",
+ "CharacterInText",
+ "ChatGPTOpenAI",
+ "CombineMasks_",
+ "CombineSegMasks",
+ "EditLayer",
+ "EmptyLayer",
+ "EnhanceImage",
+ "FaceToMask",
+ "FeatheredMask",
+ "FloatingVideo",
+ "ImageCropByAlpha",
+ "LoadImagesFromPath",
+ "MergeLayers",
+ "NewLayer",
+ "RandomPrompt",
+ "ScreenShare",
+ "ShowTextForGPT",
+ "SmoothMask",
+ "SplitLongMask",
+ "SvgImage",
+ "TextImage",
+ "TransparentImage",
+ "VAEDecodeConsistencyDecoder",
+ "VAELoaderConsistencyDecoder"
+ ],
+ {
+ "title_aux": "comfyui-mixlab-nodes [WIP]"
+ }
+ ],
+ "https://github.com/shiimizu/ComfyUI_smZNodes": [
+ [
+ "smZ CLIPTextEncode",
+ "smZ Settings"
+ ],
+ {
+ "title_aux": "smZNodes"
+ }
+ ],
+ "https://github.com/shingo1228/ComfyUI-SDXL-EmptyLatentImage": [
+ [
+ "SDXL Empty Latent Image"
+ ],
+ {
+ "title_aux": "ComfyUI-SDXL-EmptyLatentImage"
+ }
+ ],
+ "https://github.com/shingo1228/ComfyUI-send-eagle-slim": [
+ [
+ "Send Webp Image to Eagle"
+ ],
+ {
+ "title_aux": "ComfyUI-send-Eagle(slim)"
+ }
+ ],
+ "https://github.com/shockz0rz/ComfyUI_InterpolateEverything": [
+ [
+ "OpenposePreprocessorInterpolate"
+ ],
+ {
+ "title_aux": "InterpolateEverything"
+ }
+ ],
+ "https://github.com/sipherxyz/comfyui-art-venture": [
+ [
+ "AV_CheckpointMerge",
+ "AV_CheckpointModelsToParametersPipe",
+ "AV_CheckpointSave",
+ "AV_ControlNetEfficientLoader",
+ "AV_ControlNetEfficientLoaderAdvanced",
+ "AV_ControlNetEfficientStacker",
+ "AV_ControlNetEfficientStackerSimple",
+ "AV_ControlNetLoader",
+ "AV_ControlNetPreprocessor",
+ "AV_LoraListLoader",
+ "AV_LoraListStacker",
+ "AV_LoraLoader",
+ "AV_ParametersPipeToCheckpointModels",
+ "AV_ParametersPipeToPrompts",
+ "AV_PromptsToParametersPipe",
+ "AV_SAMLoader",
+ "AV_VAELoader",
+ "AspectRatioSelector",
+ "BLIPCaption",
+ "BLIPLoader",
+ "BooleanPrimitive",
+ "ColorBlend",
+ "ColorCorrect",
+ "DeepDanbooruCaption",
+ "DependenciesEdit",
+ "Fooocus_KSampler",
+ "Fooocus_KSamplerAdvanced",
+ "GetBoolFromJson",
+ "GetFloatFromJson",
+ "GetIntFromJson",
+ "GetObjectFromJson",
+ "GetSAMEmbedding",
+ "GetTextFromJson",
+ "ISNetLoader",
+ "ISNetSegment",
+ "ImageAlphaComposite",
+ "ImageApplyChannel",
+ "ImageExtractChannel",
+ "ImageGaussianBlur",
+ "ImageMuxer",
+ "ImageRepeat",
+ "ImageScaleDown",
+ "ImageScaleDownBy",
+ "ImageScaleDownToSize",
+ "ImageScaleToMegapixels",
+ "LaMaInpaint",
+ "LoadImageAsMaskFromUrl",
+ "LoadImageFromUrl",
+ "LoadJsonFromUrl",
+ "MergeModels",
+ "NumberScaler",
+ "OverlayInpaintedImage",
+ "OverlayInpaintedLatent",
+ "PrepareImageAndMaskForInpaint",
+ "QRCodeGenerator",
+ "RandomFloat",
+ "RandomInt",
+ "SAMEmbeddingToImage",
+ "SDXLAspectRatioSelector",
+ "SDXLPromptStyler",
+ "SeedSelector",
+ "StringToInt",
+ "StringToNumber"
+ ],
+ {
+ "title_aux": "comfyui-art-venture"
+ }
+ ],
+ "https://github.com/skfoo/ComfyUI-Coziness": [
+ [
+ "LoraTextExtractor-b1f83aa2",
+ "MultiLoraLoader-70bf3d77"
+ ],
+ {
+ "title_aux": "ComfyUI-Coziness"
+ }
+ ],
+ "https://github.com/space-nuko/ComfyUI-Disco-Diffusion": [
+ [
+ "DiscoDiffusion_DiscoDiffusion",
+ "DiscoDiffusion_DiscoDiffusionExtraSettings",
+ "DiscoDiffusion_GuidedDiffusionLoader",
+ "DiscoDiffusion_OpenAICLIPLoader"
+ ],
+ {
+ "title_aux": "Disco Diffusion"
+ }
+ ],
+ "https://github.com/space-nuko/ComfyUI-OpenPose-Editor": [
+ [
+ "Nui.OpenPoseEditor"
+ ],
+ {
+ "title_aux": "OpenPose Editor"
+ }
+ ],
+ "https://github.com/space-nuko/nui-suite": [
+ [
+ "Nui.DynamicPromptsTextGen",
+ "Nui.FeelingLuckyTextGen",
+ "Nui.OutputString"
+ ],
+ {
+ "title_aux": "nui suite"
+ }
+ ],
+ "https://github.com/spacepxl/ComfyUI-HQ-Image-Save": [
+ [
+ "LoadLatentEXR",
+ "SaveEXR",
+ "SaveLatentEXR",
+ "SaveTiff"
+ ],
+ {
+ "title_aux": "ComfyUI-HQ-Image-Save"
+ }
+ ],
+ "https://github.com/spinagon/ComfyUI-seam-carving": [
+ [
+ "SeamCarving"
+ ],
+ {
+ "title_aux": "ComfyUI-seam-carving"
+ }
+ ],
+ "https://github.com/spinagon/ComfyUI-seamless-tiling": [
+ [
+ "CircularVAEDecode",
+ "MakeCircularVAE",
+ "OffsetImage",
+ "SeamlessTile"
+ ],
+ {
+ "title_aux": "Seamless tiling Node for ComfyUI"
+ }
+ ],
+ "https://github.com/spro/comfyui-mirror": [
+ [
+ "LatentMirror"
+ ],
+ {
+ "title_aux": "Latent Mirror node for ComfyUI"
+ }
+ ],
+ "https://github.com/ssitu/ComfyUI_UltimateSDUpscale": [
+ [
+ "UltimateSDUpscale",
+ "UltimateSDUpscaleNoUpscale"
+ ],
+ {
+ "title_aux": "UltimateSDUpscale"
+ }
+ ],
+ "https://github.com/ssitu/ComfyUI_fabric": [
+ [
+ "FABRICPatchModel",
+ "FABRICPatchModelAdv",
+ "KSamplerAdvFABRICAdv",
+ "KSamplerFABRIC",
+ "KSamplerFABRICAdv",
+ "LatentBatch"
+ ],
+ {
+ "title_aux": "ComfyUI fabric"
+ }
+ ],
+ "https://github.com/ssitu/ComfyUI_restart_sampling": [
+ [
+ "KRestartSampler",
+ "KRestartSamplerAdv",
+ "KRestartSamplerSimple"
+ ],
+ {
+ "title_aux": "Restart Sampling"
+ }
+ ],
+ "https://github.com/ssitu/ComfyUI_roop": [
+ [
+ "RoopImproved",
+ "roop"
+ ],
+ {
+ "title_aux": "ComfyUI roop"
+ }
+ ],
+ "https://github.com/storyicon/comfyui_segment_anything": [
+ [
+ "GroundingDinoModelLoader (segment anything)",
+ "GroundingDinoSAMSegment (segment anything)",
+ "InvertMask (segment anything)",
+ "SAMModelLoader (segment anything)"
+ ],
+ {
+ "title_aux": "segment anything"
+ }
+ ],
+ "https://github.com/strimmlarn/ComfyUI_Strimmlarns_aesthetic_score": [
+ [
+ "AesthetlcScoreSorter",
+ "CalculateAestheticScore",
+ "LoadAesteticModel",
+ "ScoreToNumber"
+ ],
+ {
+ "title_aux": "ComfyUI_Strimmlarns_aesthetic_score"
+ }
+ ],
+ "https://github.com/syllebra/bilbox-comfyui": [
+ [
+ "BilboXLut",
+ "BilboXPhotoPrompt",
+ "BilboXVignette"
+ ],
+ {
+ "title_aux": "BilboX's ComfyUI Custom Nodes"
+ }
+ ],
+ "https://github.com/sylym/comfy_vid2vid": [
+ [
+ "CheckpointLoaderSimpleSequence",
+ "DdimInversionSequence",
+ "KSamplerSequence",
+ "LoadImageMaskSequence",
+ "LoadImageSequence",
+ "LoraLoaderSequence",
+ "SetLatentNoiseSequence",
+ "TrainUnetSequence",
+ "VAEEncodeForInpaintSequence"
+ ],
+ {
+ "title_aux": "Vid2vid"
+ }
+ ],
+ "https://github.com/szhublox/ambw_comfyui": [
+ [
+ "Auto Merge Block Weighted",
+ "CLIPMergeSimple",
+ "CheckpointSave",
+ "ModelMergeBlocks",
+ "ModelMergeSimple"
+ ],
+ {
+ "title_aux": "Auto-MBW"
+ }
+ ],
+ "https://github.com/taabata/Comfy_Syrian_Falcon_Nodes/raw/main/SyrianFalconNodes.py": [
+ [
+ "CompositeImage",
+ "KSamplerAlternate",
+ "KSamplerPromptEdit",
+ "KSamplerPromptEditAndAlternate",
+ "LoopBack",
+ "QRGenerate",
+ "WordAsImage"
+ ],
+ {
+ "title_aux": "Syrian Falcon Nodes"
+ }
+ ],
+ "https://github.com/taabata/LCM_Inpaint-Outpaint_Comfy": [
+ [
+ "FreeU_LCM",
+ "ImageOutputToComfyNodes",
+ "ImageShuffle",
+ "LCMGenerate",
+ "LCMGenerate_ReferenceOnly",
+ "LCMGenerate_SDTurbo",
+ "LCMGenerate_img2img",
+ "LCMGenerate_img2img_IPAdapter",
+ "LCMGenerate_img2img_controlnet",
+ "LCMGenerate_inpaintv2",
+ "LCMGenerate_inpaintv3",
+ "LCMLoader",
+ "LCMLoader_RefInpaint",
+ "LCMLoader_ReferenceOnly",
+ "LCMLoader_SDTurbo",
+ "LCMLoader_controlnet",
+ "LCMLoader_controlnet_inpaint",
+ "LCMLoader_img2img",
+ "LCMLoraLoader_inpaint",
+ "LCMLora_inpaint",
+ "LCMT2IAdapter",
+ "LCM_IPAdapter",
+ "LCM_IPAdapter_inpaint",
+ "LCM_outpaint_prep",
+ "LoadImageNode_LCM",
+ "OutpaintCanvasTool",
+ "SaveImage_LCM",
+ "stitch"
+ ],
+ {
+ "title_aux": "LCM_Inpaint-Outpaint_Comfy"
+ }
+ ],
+ "https://github.com/theUpsider/ComfyUI-Logic": [
+ [
+ "Compare",
+ "DebugPrint",
+ "If ANY execute A else B",
+ "Int",
+ "String"
+ ],
+ {
+ "title_aux": "ComfyUI-Logic"
+ }
+ ],
+ "https://github.com/theUpsider/ComfyUI-Styles_CSV_Loader": [
+ [
+ "Load Styles CSV"
+ ],
+ {
+ "title_aux": "Styles CSV Loader Extension for ComfyUI"
+ }
+ ],
+ "https://github.com/thecooltechguy/ComfyUI-MagicAnimate": [
+ [
+ "MagicAnimate",
+ "MagicAnimateModelLoader"
+ ],
+ {
+ "title_aux": "ComfyUI-MagicAnimate"
+ }
+ ],
+ "https://github.com/thecooltechguy/ComfyUI-Stable-Video-Diffusion": [
+ [
+ "SVDDecoder",
+ "SVDModelLoader",
+ "SVDSampler",
+ "SVDSimpleImg2Vid"
+ ],
+ {
+ "title_aux": "ComfyUI Stable Video Diffusion"
+ }
+ ],
+ "https://github.com/thedyze/save-image-extended-comfyui": [
+ [
+ "SaveImageExtended"
+ ],
+ {
+ "title_aux": "Save Image Extended for ComfyUI"
+ }
+ ],
+ "https://github.com/toyxyz/ComfyUI_toyxyz_test_nodes": [
+ [
+ "CaptureWebcam",
+ "LoadWebcamImage",
+ "SaveImagetoPath"
+ ],
+ {
+ "title_aux": "ComfyUI_toyxyz_test_nodes"
+ }
+ ],
+ "https://github.com/trojblue/trNodes": [
+ [
+ "JpgConvertNode",
+ "trColorCorrection",
+ "trLayering",
+ "trRouter",
+ "trRouterLonger"
+ ],
+ {
+ "title_aux": "trNodes"
+ }
+ ],
+ "https://github.com/tudal/Hakkun-ComfyUI-nodes/raw/main/hakkun_nodes.py": [
+ [
+ "Any Converter",
+ "Calculate Upscale",
+ "Image Resize To Height",
+ "Image Resize To Width",
+ "Image size to string",
+ "Load Random Image",
+ "Load Text",
+ "Multi Text Merge",
+ "Prompt Parser",
+ "Random Line",
+ "Random Line 4"
+ ],
+ {
+ "nodename_pattern": "\\(mtb\\)$",
+ "title_aux": "Hakkun-ComfyUI-nodes"
+ }
+ ],
+ "https://github.com/tusharbhutt/Endless-Nodes": [
+ [
+ "ESS Aesthetic Scoring",
+ "ESS Aesthetic Scoring Auto",
+ "ESS Combo Parameterizer",
+ "ESS Combo Parameterizer & Prompts",
+ "ESS Eight Input Random",
+ "ESS Eight Input Text Switch",
+ "ESS Float to Integer",
+ "ESS Float to Number",
+ "ESS Float to String",
+ "ESS Float to X",
+ "ESS Global Envoy",
+ "ESS Image Reward",
+ "ESS Image Reward Auto",
+ "ESS Image Saver with JSON",
+ "ESS Integer to Float",
+ "ESS Integer to Number",
+ "ESS Integer to String",
+ "ESS Integer to X",
+ "ESS Number to Float",
+ "ESS Number to Integer",
+ "ESS Number to String",
+ "ESS Number to X",
+ "ESS Parameterizer",
+ "ESS Parameterizer & Prompts",
+ "ESS Six Float Output",
+ "ESS Six Input Random",
+ "ESS Six Input Text Switch",
+ "ESS Six Integer IO Switch",
+ "ESS Six Integer IO Widget",
+ "ESS String to Float",
+ "ESS String to Integer",
+ "ESS String to Num",
+ "ESS String to X",
+ "\u267e\ufe0f\ud83c\udf0a\u2728 Image Saver with JSON"
+ ],
+ {
+ "author": "BiffMunky",
+ "description": "A small set of nodes I created for various numerical and text inputs. Features image saver with ability to have JSON saved to separate folder, parameter collection nodes, two aesthetic scoring models, switches for text and numbers, and conversion of string to numeric and vice versa.",
+ "nickname": "\u267e\ufe0f\ud83c\udf0a\u2728",
+ "title": "Endless \ufe0f\ud83c\udf0a\u2728 Nodes",
+ "title_aux": "Endless \ufe0f\ud83c\udf0a\u2728 Nodes"
+ }
+ ],
+ "https://github.com/twri/sdxl_prompt_styler": [
+ [
+ "SDXLPromptStyler",
+ "SDXLPromptStylerAdvanced"
+ ],
+ {
+ "title_aux": "SDXL Prompt Styler"
+ }
+ ],
+ "https://github.com/uarefans/ComfyUI-Fans": [
+ [
+ "Fans Prompt Styler Negative",
+ "Fans Prompt Styler Positive",
+ "Fans Styler",
+ "Fans Text Concatenate"
+ ],
+ {
+ "title_aux": "ComfyUI-Fans"
+ }
+ ],
+ "https://github.com/vanillacode314/SimpleWildcardsComfyUI": [
+ [
+ "SimpleConcat",
+ "SimpleWildcard"
+ ],
+ {
+ "author": "VanillaCode314",
+ "description": "A simple wildcard node for ComfyUI. Can also be used a style prompt node.",
+ "nickname": "Simple Wildcard",
+ "title": "Simple Wildcard",
+ "title_aux": "Simple Wildcard"
+ }
+ ],
+ "https://github.com/wallish77/wlsh_nodes": [
+ [
+ "Alternating KSampler (WLSH)",
+ "Build Filename String (WLSH)",
+ "CLIP +/- w/Text Unified (WLSH)",
+ "CLIP Positive-Negative (WLSH)",
+ "CLIP Positive-Negative XL (WLSH)",
+ "CLIP Positive-Negative XL w/Text (WLSH)",
+ "CLIP Positive-Negative w/Text (WLSH)",
+ "Checkpoint Loader w/Name (WLSH)",
+ "Empty Latent by Pixels (WLSH)",
+ "Empty Latent by Ratio (WLSH)",
+ "Empty Latent by Size (WLSH)",
+ "Generate Border Mask (WLSH)",
+ "Grayscale Image (WLSH)",
+ "Image Load with Metadata (WLSH)",
+ "Image Save with Prompt (WLSH)",
+ "Image Save with Prompt File (WLSH)",
+ "Image Save with Prompt/Info (WLSH)",
+ "Image Save with Prompt/Info File (WLSH)",
+ "Image Scale By Factor (WLSH)",
+ "Image Scale by Shortside (WLSH)",
+ "KSamplerAdvanced (WLSH)",
+ "Multiply Integer (WLSH)",
+ "Outpaint to Image (WLSH)",
+ "Prompt Weight (WLSH)",
+ "Quick Resolution Multiply (WLSH)",
+ "Resolutions by Ratio (WLSH)",
+ "SDXL Quick Empty Latent (WLSH)",
+ "SDXL Quick Image Scale (WLSH)",
+ "SDXL Resolutions (WLSH)",
+ "SDXL Steps (WLSH)",
+ "Save Positive Prompt(WLSH)",
+ "Save Prompt (WLSH)",
+ "Save Prompt/Info (WLSH)",
+ "Seed and Int (WLSH)",
+ "Seed to Number (WLSH)",
+ "Simple Pattern Replace (WLSH)",
+ "Simple String Combine (WLSH)",
+ "Time String (WLSH)",
+ "Upscale by Factor with Model (WLSH)",
+ "VAE Encode for Inpaint w/Padding (WLSH)"
+ ],
+ {
+ "title_aux": "wlsh_nodes"
+ }
+ ],
+ "https://github.com/whatbirdisthat/cyberdolphin": [
+ [
+ "\ud83d\udc2c Gradio ChatInterface",
+ "\ud83d\udc2c OpenAI Advanced",
+ "\ud83d\udc2c OpenAI Compatible",
+ "\ud83d\udc2c OpenAI DALL\u00b7E",
+ "\ud83d\udc2c OpenAI Simple"
+ ],
+ {
+ "title_aux": "cyberdolphin"
+ }
+ ],
+ "https://github.com/whmc76/ComfyUI-Openpose-Editor-Plus": [
+ [
+ "CDL.OpenPoseEditorPlus"
+ ],
+ {
+ "title_aux": "ComfyUI-Openpose-Editor-Plus"
+ }
+ ],
+ "https://github.com/wmatson/easy-comfy-nodes": [
+ [
+ "EZAssocDictNode",
+ "EZAssocImgNode",
+ "EZAssocStrNode",
+ "EZEmptyDictNode",
+ "EZHttpPostNode",
+ "EZLoadImgBatchFromUrlsNode",
+ "EZLoadImgFromUrlNode",
+ "EZVideoCombiner"
+ ],
+ {
+ "title_aux": "easy-comfy-nodes"
+ }
+ ],
+ "https://github.com/wolfden/ComfyUi_PromptStylers": [
+ [
+ "SDXLPromptStylerAll",
+ "SDXLPromptStylerHorror",
+ "SDXLPromptStylerMisc",
+ "SDXLPromptStylerbyArtist",
+ "SDXLPromptStylerbyCamera",
+ "SDXLPromptStylerbyComposition",
+ "SDXLPromptStylerbyCyberpunkSurrealism",
+ "SDXLPromptStylerbyDepth",
+ "SDXLPromptStylerbyEnvironment",
+ "SDXLPromptStylerbyFantasySetting",
+ "SDXLPromptStylerbyFilter",
+ "SDXLPromptStylerbyFocus",
+ "SDXLPromptStylerbyImpressionism",
+ "SDXLPromptStylerbyLighting",
+ "SDXLPromptStylerbyMileHigh",
+ "SDXLPromptStylerbyMood",
+ "SDXLPromptStylerbyMythicalCreature",
+ "SDXLPromptStylerbyOriginal",
+ "SDXLPromptStylerbyQuantumRealism",
+ "SDXLPromptStylerbySteamPunkRealism",
+ "SDXLPromptStylerbySubject",
+ "SDXLPromptStylerbySurrealism",
+ "SDXLPromptStylerbyTheme",
+ "SDXLPromptStylerbyTimeofDay",
+ "SDXLPromptStylerbyWyvern",
+ "SDXLPromptbyCelticArt",
+ "SDXLPromptbyContemporaryNordicArt",
+ "SDXLPromptbyFashionArt",
+ "SDXLPromptbyGothicRevival",
+ "SDXLPromptbyIrishFolkArt",
+ "SDXLPromptbyRomanticNationalismArt",
+ "SDXLPromptbySportsArt",
+ "SDXLPromptbyStreetArt",
+ "SDXLPromptbyVikingArt",
+ "SDXLPromptbyWildlifeArt"
+ ],
+ {
+ "title_aux": "SDXL Prompt Styler (customized version by wolfden)"
+ }
+ ],
+ "https://github.com/wolfden/ComfyUi_String_Function_Tree": [
+ [
+ "StringFunction"
+ ],
+ {
+ "title_aux": "ComfyUi_String_Function_Tree"
+ }
+ ],
+ "https://github.com/wsippel/comfyui_ws/raw/main/sdxl_utility.py": [
+ [
+ "SDXLResolutionPresets"
+ ],
+ {
+ "title_aux": "SDXLResolutionPresets"
+ }
+ ],
+ "https://github.com/wutipong/ComfyUI-TextUtils": [
+ [
+ "Text Utils - Join N-Elements of String List",
+ "Text Utils - Join String List",
+ "Text Utils - Join Strings",
+ "Text Utils - Split String to List"
+ ],
+ {
+ "title_aux": "ComfyUI-TextUtils"
+ }
+ ],
+ "https://github.com/xXAdonesXx/NodeGPT": [
+ [
+ "AppendAgent",
+ "Assistant",
+ "Chat",
+ "ChatGPT",
+ "CombineInput",
+ "Conditioning",
+ "CostumeAgent_1",
+ "CostumeAgent_2",
+ "CostumeMaster_1",
+ "Critic",
+ "DisplayString",
+ "DisplayTextAsImage",
+ "EVAL",
+ "Engineer",
+ "Executor",
+ "GroupChat",
+ "Image_generation_Conditioning",
+ "LM_Studio",
+ "LoadAPIconfig",
+ "LoadTXT",
+ "MemGPT",
+ "Memory_Excel",
+ "Model_1",
+ "Ollama",
+ "Output2String",
+ "Planner",
+ "Scientist",
+ "TextCombine",
+ "TextGeneration",
+ "TextGenerator",
+ "TextInput",
+ "TextOutput",
+ "UserProxy",
+ "llama-cpp",
+ "llava",
+ "oobaboogaOpenAI"
+ ],
+ {
+ "title_aux": "NodeGPT"
+ }
+ ],
+ "https://github.com/yolanother/DTAIComfyImageSubmit": [
+ [
+ "DTSimpleSubmitImage",
+ "DTSubmitImage"
+ ],
+ {
+ "title_aux": "Comfy AI DoubTech.ai Image Sumission Node"
+ }
+ ],
+ "https://github.com/yolanother/DTAIComfyLoaders": [
+ [
+ "DTCLIPLoader",
+ "DTCLIPVisionLoader",
+ "DTCheckpointLoader",
+ "DTCheckpointLoaderSimple",
+ "DTControlNetLoader",
+ "DTDiffControlNetLoader",
+ "DTDiffusersLoader",
+ "DTGLIGENLoader",
+ "DTLoadImage",
+ "DTLoadImageMask",
+ "DTLoadLatent",
+ "DTLoraLoader",
+ "DTLorasLoader",
+ "DTStyleModelLoader",
+ "DTUpscaleModelLoader",
+ "DTVAELoader",
+ "DTunCLIPCheckpointLoader"
+ ],
+ {
+ "title_aux": "Comfy UI Online Loaders"
+ }
+ ],
+ "https://github.com/yolanother/DTAIComfyPromptAgent": [
+ [
+ "DTPromptAgent",
+ "DTPromptAgentString"
+ ],
+ {
+ "title_aux": "Comfy UI Prompt Agent"
+ }
+ ],
+ "https://github.com/yolanother/DTAIComfyQRCodes": [
+ [
+ "QRCode"
+ ],
+ {
+ "title_aux": "Comfy UI QR Codes"
+ }
+ ],
+ "https://github.com/yolanother/DTAIComfyVariables": [
+ [
+ "DTCLIPTextEncode",
+ "DTSingleLineStringVariable",
+ "DTSingleLineStringVariableNoClip",
+ "FloatVariable",
+ "IntVariable",
+ "StringFormat",
+ "StringFormatSingleLine",
+ "StringVariable"
+ ],
+ {
+ "title_aux": "Variables for Comfy UI"
+ }
+ ],
+ "https://github.com/yolanother/DTAIImageToTextNode": [
+ [
+ "DTAIImageToTextNode",
+ "DTAIImageUrlToTextNode"
+ ],
+ {
+ "title_aux": "Image to Text Node"
+ }
+ ],
+ "https://github.com/youyegit/tdxh_node_comfyui": [
+ [
+ "TdxhBoolNumber",
+ "TdxhClipVison",
+ "TdxhControlNetApply",
+ "TdxhControlNetProcessor",
+ "TdxhFloatInput",
+ "TdxhImageToSize",
+ "TdxhImageToSizeAdvanced",
+ "TdxhImg2ImgLatent",
+ "TdxhIntInput",
+ "TdxhLoraLoader",
+ "TdxhOnOrOff",
+ "TdxhReference",
+ "TdxhStringInput",
+ "TdxhStringInputTranslator"
+ ],
+ {
+ "title_aux": "tdxh_node_comfyui"
+ }
+ ],
+ "https://github.com/zcfrank1st/Comfyui-Yolov8": [
+ [
+ "Yolov8Detection",
+ "Yolov8Segmentation"
+ ],
+ {
+ "title_aux": "ComfyUI Yolov8"
+ }
+ ],
+ "https://github.com/zcfrank1st/comfyui_visual_anagrams": [
+ [
+ "VisualAnagramsAnimate",
+ "VisualAnagramsSample"
+ ],
+ {
+ "title_aux": "comfyui_visual_anagram"
+ }
+ ],
+ "https://github.com/zer0TF/cute-comfy": [
+ [
+ "Cute.Placeholder"
+ ],
+ {
+ "title_aux": "Cute Comfy"
+ }
+ ],
+ "https://github.com/zhuanqianfish/ComfyUI-EasyNode": [
+ [
+ "EasyCaptureNode",
+ "EasyVideoOutputNode",
+ "SendImageWebSocket"
+ ],
+ {
+ "title_aux": "EasyCaptureNode for ComfyUI"
+ }
+ ],
+ "https://raw.githubusercontent.com/throttlekitty/SDXLCustomAspectRatio/main/SDXLAspectRatio.py": [
+ [
+ "SDXLAspectRatio"
+ ],
+ {
+ "title_aux": "SDXLCustomAspectRatio"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI-Manager/node_db/new/model-list.json b/custom_nodes/ComfyUI-Manager/node_db/new/model-list.json
new file mode 100644
index 0000000000000000000000000000000000000000..8868e76d536dc93d5e18270127db206d80dee68e
--- /dev/null
+++ b/custom_nodes/ComfyUI-Manager/node_db/new/model-list.json
@@ -0,0 +1,671 @@
+{
+ "models": [
+ {
+ "name": "SDXL-Turbo 1.0 (fp16)",
+ "type": "checkpoints",
+ "base": "SDXL",
+ "save_path": "checkpoints/SDXL-TURBO",
+ "description": "[6.9GB] SDXL-Turbo 1.0 fp16",
+ "reference": "https://huggingface.co/stabilityai/sdxl-turbo",
+ "filename": "sd_xl_turbo_1.0_fp16.safetensors",
+ "url": "https://huggingface.co/stabilityai/sdxl-turbo/resolve/main/sd_xl_turbo_1.0_fp16.safetensors"
+ },
+ {
+ "name": "SDXL-Turbo 1.0",
+ "type": "checkpoints",
+ "base": "SDXL",
+ "save_path": "checkpoints/SDXL-TURBO",
+ "description": "[13.9GB] SDXL-Turbo 1.0",
+ "reference": "https://huggingface.co/stabilityai/sdxl-turbo",
+ "filename": "sd_xl_turbo_1.0.safetensors",
+ "url": "https://huggingface.co/stabilityai/sdxl-turbo/resolve/main/sd_xl_turbo_1.0.safetensors"
+ },
+ {
+ "name": "Stable Video Diffusion Image-to-Video",
+ "type": "checkpoints",
+ "base": "SVD",
+ "save_path": "checkpoints/SVD",
+ "description": "Stable Video Diffusion (SVD) Image-to-Video is a diffusion model that takes in a still image as a conditioning frame, and generates a video from it. NOTE: 14 frames @ 576x1024",
+ "reference": "https://huggingface.co/stabilityai/stable-video-diffusion-img2vid",
+ "filename": "svd.safetensors",
+ "url": "https://huggingface.co/stabilityai/stable-video-diffusion-img2vid/resolve/main/svd.safetensors"
+ },
+ {
+ "name": "Stable Video Diffusion Image-to-Video (XT)",
+ "type": "checkpoints",
+ "base": "SVD",
+ "save_path": "checkpoints/SVD",
+ "description": "Stable Video Diffusion (SVD) Image-to-Video is a diffusion model that takes in a still image as a conditioning frame, and generates a video from it. NOTE: 25 frames @ 576x1024 ",
+ "reference": "https://huggingface.co/stabilityai/stable-video-diffusion-img2vid-xt",
+ "filename": "svd_xt.safetensors",
+ "url": "https://huggingface.co/stabilityai/stable-video-diffusion-img2vid-xt/resolve/main/svd_xt.safetensors"
+ },
+
+ {
+ "name": "animatediff/mm_sdxl_v10_beta.ckpt (ComfyUI-AnimateDiff-Evolved)",
+ "type": "animatediff",
+ "base": "SDXL",
+ "save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/models",
+ "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/guoyww/animatediff",
+ "filename": "mm_sdxl_v10_beta.ckpt",
+ "url": "https://huggingface.co/guoyww/animatediff/resolve/main/mm_sdxl_v10_beta.ckpt"
+ },
+ {
+ "name": "animatediff/v2_lora_PanLeft.ckpt (ComfyUI-AnimateDiff-Evolved)",
+ "type": "motion lora",
+ "base": "SD1.x",
+ "save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/motion_lora",
+ "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/guoyww/animatediff",
+ "filename": "v2_lora_PanLeft.ckpt",
+ "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_PanLeft.ckpt"
+ },
+ {
+ "name": "animatediff/v2_lora_PanRight.ckpt (ComfyUI-AnimateDiff-Evolved)",
+ "type": "motion lora",
+ "base": "SD1.x",
+ "save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/motion_lora",
+ "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/guoyww/animatediff",
+ "filename": "v2_lora_PanRight.ckpt",
+ "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_PanRight.ckpt"
+ },
+ {
+ "name": "animatediff/v2_lora_RollingAnticlockwise.ckpt (ComfyUI-AnimateDiff-Evolved)",
+ "type": "motion lora",
+ "base": "SD1.x",
+ "save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/motion_lora",
+ "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/guoyww/animatediff",
+ "filename": "v2_lora_RollingAnticlockwise.ckpt",
+ "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_RollingAnticlockwise.ckpt"
+ },
+ {
+ "name": "animatediff/v2_lora_RollingClockwise.ckpt (ComfyUI-AnimateDiff-Evolved)",
+ "type": "motion lora",
+ "base": "SD1.x",
+ "save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/motion_lora",
+ "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/guoyww/animatediff",
+ "filename": "v2_lora_RollingClockwise.ckpt",
+ "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_RollingClockwise.ckpt"
+ },
+ {
+ "name": "animatediff/v2_lora_TiltDown.ckpt (ComfyUI-AnimateDiff-Evolved)",
+ "type": "motion lora",
+ "base": "SD1.x",
+ "save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/motion_lora",
+ "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/guoyww/animatediff",
+ "filename": "v2_lora_TiltDown.ckpt",
+ "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_TiltDown.ckpt"
+ },
+ {
+ "name": "animatediff/v2_lora_TiltUp.ckpt (ComfyUI-AnimateDiff-Evolved)",
+ "type": "motion lora",
+ "base": "SD1.x",
+ "save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/motion_lora",
+ "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/guoyww/animatediff",
+ "filename": "v2_lora_TiltUp.ckpt",
+ "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_TiltUp.ckpt"
+ },
+ {
+ "name": "animatediff/v2_lora_ZoomIn.ckpt (ComfyUI-AnimateDiff-Evolved)",
+ "type": "motion lora",
+ "base": "SD1.x",
+ "save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/motion_lora",
+ "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/guoyww/animatediff",
+ "filename": "v2_lora_ZoomIn.ckpt",
+ "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_ZoomIn.ckpt"
+ },
+ {
+ "name": "animatediff/v2_lora_ZoomOut.ckpt (ComfyUI-AnimateDiff-Evolved)",
+ "type": "motion lora",
+ "base": "SD1.x",
+ "save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/motion_lora",
+ "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/guoyww/animatediff",
+ "filename": "v2_lora_ZoomOut.ckpt",
+ "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_ZoomOut.ckpt"
+ },
+
+ {
+ "name": "CiaraRowles/TemporalNet1XL (1.0)",
+ "type": "controlnet",
+ "base": "SD1.5",
+ "save_path": "controlnet/TemporalNet1XL",
+ "description": "This is TemporalNet1XL, it is a re-train of the controlnet TemporalNet1 with Stable Diffusion XL.",
+ "reference": "https://huggingface.co/CiaraRowles/controlnet-temporalnet-sdxl-1.0",
+ "filename": "diffusion_pytorch_model.safetensors",
+ "url": "https://huggingface.co/CiaraRowles/controlnet-temporalnet-sdxl-1.0/resolve/main/diffusion_pytorch_model.safetensors"
+ },
+
+ {
+ "name": "LCM LoRA SD1.5",
+ "type": "lora",
+ "base": "SD1.5",
+ "save_path": "loras/lcm/SD1.5",
+ "description": "Latent Consistency LoRA for SD1.5",
+ "reference": "https://huggingface.co/latent-consistency/lcm-lora-sdv1-5",
+ "filename": "pytorch_lora_weights.safetensors",
+ "url": "https://huggingface.co/latent-consistency/lcm-lora-sdv1-5/resolve/main/pytorch_lora_weights.safetensors"
+ },
+ {
+ "name": "LCM LoRA SSD-1B",
+ "type": "lora",
+ "base": "SSD-1B",
+ "save_path": "loras/lcm/SSD-1B",
+ "description": "Latent Consistency LoRA for SSD-1B",
+ "reference": "https://huggingface.co/latent-consistency/lcm-lora-ssd-1b",
+ "filename": "pytorch_lora_weights.safetensors",
+ "url": "https://huggingface.co/latent-consistency/lcm-lora-ssd-1b/resolve/main/pytorch_lora_weights.safetensors"
+ },
+ {
+ "name": "LCM LoRA SDXL",
+ "type": "lora",
+ "base": "SSD-1B",
+ "save_path": "loras/lcm/SDXL",
+ "description": "Latent Consistency LoRA for SDXL",
+ "reference": "https://huggingface.co/latent-consistency/lcm-lora-sdxl",
+ "filename": "pytorch_lora_weights.safetensors",
+ "url": "https://huggingface.co/latent-consistency/lcm-lora-sdxl/resolve/main/pytorch_lora_weights.safetensors"
+ },
+
+ {
+ "name": "face_yolov8m-seg_60.pt (segm)",
+ "type": "Ultralytics",
+ "base": "Ultralytics",
+ "save_path": "ultralytics/segm",
+ "description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.",
+ "reference": "https://github.com/hben35096/assets/releases/tag/yolo8",
+ "filename": "face_yolov8m-seg_60.pt",
+ "url": "https://github.com/hben35096/assets/releases/download/yolo8/face_yolov8m-seg_60.pt"
+ },
+ {
+ "name": "face_yolov8n-seg2_60.pt (segm)",
+ "type": "Ultralytics",
+ "base": "Ultralytics",
+ "save_path": "ultralytics/segm",
+ "description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.",
+ "reference": "https://github.com/hben35096/assets/releases/tag/yolo8",
+ "filename": "face_yolov8n-seg2_60.pt",
+ "url": "https://github.com/hben35096/assets/releases/download/yolo8/face_yolov8n-seg2_60.pt"
+ },
+ {
+ "name": "hair_yolov8n-seg_60.pt (segm)",
+ "type": "Ultralytics",
+ "base": "Ultralytics",
+ "save_path": "ultralytics/segm",
+ "description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.",
+ "reference": "https://github.com/hben35096/assets/releases/tag/yolo8",
+ "filename": "hair_yolov8n-seg_60.pt",
+ "url": "https://github.com/hben35096/assets/releases/download/yolo8/hair_yolov8n-seg_60.pt"
+ },
+ {
+ "name": "skin_yolov8m-seg_400.pt (segm)",
+ "type": "Ultralytics",
+ "base": "Ultralytics",
+ "save_path": "ultralytics/segm",
+ "description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.",
+ "reference": "https://github.com/hben35096/assets/releases/tag/yolo8",
+ "filename": "skin_yolov8m-seg_400.pt",
+ "url": "https://github.com/hben35096/assets/releases/download/yolo8/skin_yolov8m-seg_400.pt"
+ },
+ {
+ "name": "skin_yolov8n-seg_400.pt (segm)",
+ "type": "Ultralytics",
+ "base": "Ultralytics",
+ "save_path": "ultralytics/segm",
+ "description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.",
+ "reference": "https://github.com/hben35096/assets/releases/tag/yolo8",
+ "filename": "skin_yolov8n-seg_400.pt",
+ "url": "https://github.com/hben35096/assets/releases/download/yolo8/skin_yolov8n-seg_400.pt"
+ },
+ {
+ "name": "skin_yolov8n-seg_800.pt (segm)",
+ "type": "Ultralytics",
+ "base": "Ultralytics",
+ "save_path": "ultralytics/segm",
+ "description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.",
+ "reference": "https://github.com/hben35096/assets/releases/tag/yolo8",
+ "filename": "skin_yolov8n-seg_800.pt",
+ "url": "https://github.com/hben35096/assets/releases/download/yolo8/skin_yolov8n-seg_800.pt"
+ },
+
+ {
+ "name": "ip-adapter-plus_sdxl_vit-h.bin (install to ComfyUI_IPAdapter_plus)",
+ "type": "IP-Adapter",
+ "base": "SDXL",
+ "save_path": "custom_nodes/ComfyUI_IPAdapter_plus/models",
+ "description": "Pressing 'install' directly downloads the model from the ComfyUI_IPAdapter_plus/models extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/h94/IP-Adapter",
+ "filename": "ip-adapter-plus_sdxl_vit-h.bin",
+ "url": "https://huggingface.co/h94/IP-Adapter/resolve/main/sdxl_models/ip-adapter-plus_sdxl_vit-h.bin"
+ },
+ {
+ "name": "ip-adapter_sdxl_vit-h.bin (install to ComfyUI_IPAdapter_plus)",
+ "type": "IP-Adapter",
+ "base": "SDXL",
+ "save_path": "custom_nodes/ComfyUI_IPAdapter_plus/models",
+ "description": "Pressing 'install' directly downloads the model from the ComfyUI_IPAdapter_plus/models extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/h94/IP-Adapter",
+ "filename": "ip-adapter_sdxl_vit-h.bin",
+ "url": "https://huggingface.co/h94/IP-Adapter/resolve/main/sdxl_models/ip-adapter_sdxl_vit-h.bin"
+ },
+ {
+ "name": "CiaraRowles/temporaldiff-v1-animatediff.ckpt (ComfyUI-AnimateDiff-Evolved)",
+ "type": "animatediff",
+ "base": "SD1.x",
+ "save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/models",
+ "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/CiaraRowles/TemporalDiff",
+ "filename": "temporaldiff-v1-animatediff.ckpt",
+ "url": "https://huggingface.co/CiaraRowles/TemporalDiff/resolve/main/temporaldiff-v1-animatediff.ckpt"
+ },
+ {
+ "name": "animatediff/mm_sd_v15_v2.ckpt (ComfyUI-AnimateDiff-Evolved)",
+ "type": "animatediff",
+ "base": "SD1.x",
+ "save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/models",
+ "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/guoyww/animatediff",
+ "filename": "mm_sd_v15_v2.ckpt",
+ "url": "https://huggingface.co/guoyww/animatediff/resolve/main/mm_sd_v15_v2.ckpt"
+ },
+ {
+ "name": "AD_Stabilized_Motion/mm-Stabilized_high.pth (ComfyUI-AnimateDiff-Evolved)",
+ "type": "animatediff",
+ "base": "SD1.x",
+ "save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/models",
+ "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/manshoety/AD_Stabilized_Motion",
+ "filename": "mm-Stabilized_high.pth",
+ "url": "https://huggingface.co/manshoety/AD_Stabilized_Motion/resolve/main/mm-Stabilized_high.pth"
+ },
+ {
+ "name": "AD_Stabilized_Motion/mm-Stabilized_mid.pth (ComfyUI-AnimateDiff-Evolved)",
+ "type": "animatediff",
+ "base": "SD1.x",
+ "save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/models",
+ "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/manshoety/AD_Stabilized_Motion",
+ "filename": "mm-Stabilized_mid.pth",
+ "url": "https://huggingface.co/manshoety/AD_Stabilized_Motion/resolve/main/mm-Stabilized_mid.pth"
+ },
+
+ {
+ "name": "GFPGANv1.4.pth",
+ "type": "GFPGAN",
+ "base": "GFPGAN",
+ "save_path": "facerestore_models",
+ "description": "Face Restoration Models. Download the model required for using the 'Facerestore CF (Code Former)' custom node.",
+ "reference": "https://github.com/TencentARC/GFPGAN/releases",
+ "filename": "GFPGANv1.4.pth",
+ "url": "https://github.com/TencentARC/GFPGAN/releases/download/v1.3.4/GFPGANv1.4.pth"
+ },
+ {
+ "name": "codeformer.pth",
+ "type": "CodeFormer",
+ "base": "CodeFormer",
+ "save_path": "facerestore_models",
+ "description": "Face Restoration Models. Download the model required for using the 'Facerestore CF (Code Former)' custom node.",
+ "reference": "https://github.com/sczhou/CodeFormer/releases",
+ "filename": "codeformer.pth",
+ "url": "https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/codeformer.pth"
+ },
+ {
+ "name": "detection_Resnet50_Final.pth",
+ "type": "facexlib",
+ "base": "facexlib",
+ "save_path": "facerestore_models",
+ "description": "Face Detection Models. Download the model required for using the 'Facerestore CF (Code Former)' custom node.",
+ "reference": "https://github.com/xinntao/facexlib",
+ "filename": "detection_Resnet50_Final.pth",
+ "url": "https://github.com/xinntao/facexlib/releases/download/v0.1.0/detection_Resnet50_Final.pth"
+ },
+ {
+ "name": "detection_mobilenet0.25_Final.pth",
+ "type": "facexlib",
+ "base": "facexlib",
+ "save_path": "facerestore_models",
+ "description": "Face Detection Models. Download the model required for using the 'Facerestore CF (Code Former)' custom node.",
+ "reference": "https://github.com/xinntao/facexlib",
+ "filename": "detection_mobilenet0.25_Final.pth",
+ "url": "https://github.com/xinntao/facexlib/releases/download/v0.1.0/detection_mobilenet0.25_Final.pth"
+ },
+ {
+ "name": "yolov5l-face.pth",
+ "type": "facexlib",
+ "base": "facexlib",
+ "save_path": "facedetection",
+ "description": "Face Detection Models. Download the model required for using the 'Facerestore CF (Code Former)' custom node.",
+ "reference": "https://github.com/xinntao/facexlib",
+ "filename": "yolov5l-face.pth",
+ "url": "https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/yolov5l-face.pth"
+ },
+ {
+ "name": "yolov5n-face.pth",
+ "type": "facexlib",
+ "base": "facexlib",
+ "save_path": "facedetection",
+ "description": "Face Detection Models. Download the model required for using the 'Facerestore CF (Code Former)' custom node.",
+ "reference": "https://github.com/xinntao/facexlib",
+ "filename": "yolov5n-face.pth",
+ "url": "https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/yolov5n-face.pth"
+ },
+ {
+ "name": "ip-adapter-plus_sd15.bin (install to IPAdapter-ComfyUI)",
+ "type": "IP-Adapter",
+ "base": "SD1.5",
+ "save_path": "custom_nodes/IPAdapter-ComfyUI/models",
+ "description": "Pressing 'install' directly downloads the model from the IPAdapter-ComfyUI/models extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/h94/IP-Adapter",
+ "filename": "ip-adapter-plus_sd15.bin",
+ "url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter-plus_sd15.bin"
+ },
+ {
+ "name": "ip-adapter-plus-face_sd15.bin (install to IPAdapter-ComfyUI)",
+ "type": "IP-Adapter",
+ "base": "SD1.5",
+ "save_path": "custom_nodes/IPAdapter-ComfyUI/models",
+ "description": "Pressing 'install' directly downloads the model from the IPAdapter-ComfyUI/models extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/h94/IP-Adapter",
+ "filename": "ip-adapter-plus-face_sd15.bin",
+ "url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter-plus-face_sd15.bin"
+ },
+
+ {
+ "name": "diffusers/stable-diffusion-xl-1.0-inpainting-0.1 (UNET/fp16)",
+ "type": "unet",
+ "base": "SDXL",
+ "save_path": "unet/xl-inpaint-0.1",
+ "description": "[5.14GB] Stable Diffusion XL inpainting model 0.1. You need UNETLoader instead of CheckpointLoader.",
+ "reference": "https://huggingface.co/diffusers/stable-diffusion-xl-1.0-inpainting-0.1",
+ "filename": "diffusion_pytorch_model.fp16.safetensors",
+ "url": "https://huggingface.co/diffusers/stable-diffusion-xl-1.0-inpainting-0.1/resolve/main/unet/diffusion_pytorch_model.fp16.safetensors"
+ },
+ {
+ "name": "diffusers/stable-diffusion-xl-1.0-inpainting-0.1 (UNET)",
+ "type": "unet",
+ "base": "SDXL",
+ "save_path": "unet/xl-inpaint-0.1",
+ "description": "[10.3GB] Stable Diffusion XL inpainting model 0.1. You need UNETLoader instead of CheckpointLoader.",
+ "reference": "https://huggingface.co/diffusers/stable-diffusion-xl-1.0-inpainting-0.1",
+ "filename": "diffusion_pytorch_model.safetensors",
+ "url": "https://huggingface.co/diffusers/stable-diffusion-xl-1.0-inpainting-0.1/resolve/main/unet/diffusion_pytorch_model.safetensors"
+ },
+ {
+ "name": "wd15_ip_adapter_plus.bin",
+ "type": "IP-Adapter",
+ "base": "SDXL",
+ "save_path": "custom_nodes/IPAdapter-ComfyUI/models",
+ "description": "Pressing 'install' directly downloads the model from the IPAdapter-ComfyUI/models extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/h94/IP-Adapter",
+ "filename": "wd15_ip_adapter_plus.bin",
+ "url": "https://huggingface.co/furusu/IP-Adapter/resolve/main/wd15_ip_adapter_plus.bin"
+ },
+
+ {
+ "name": "Inswapper (face swap)",
+ "type": "insightface",
+ "base" : "inswapper",
+ "save_path": "insightface",
+ "description": "Checkpoint of the insightface swapper model (used by Comfy-Roop and comfy_mtb)",
+ "reference": "https://huggingface.co/deepinsight/inswapper/",
+ "filename": "inswapper_128.onnx",
+ "url": "https://huggingface.co/deepinsight/inswapper/resolve/main/inswapper_128.onnx"
+ },
+
+ {
+ "name": "CLIPVision model (stabilityai/clip_vision_g)",
+ "type": "clip_vision",
+ "base": "SDXL",
+ "save_path": "clip_vision/SDXL",
+ "description": "[3.69GB] clip_g vision model",
+ "reference": "https://huggingface.co/stabilityai/control-lora",
+ "filename": "clip_vision_g.safetensors",
+ "url": "https://huggingface.co/stabilityai/control-lora/resolve/main/revision/clip_vision_g.safetensors"
+ },
+
+ {
+ "name": "CLIPVision model (IP-Adapter)",
+ "type": "clip_vision",
+ "base": "SD1.5",
+ "save_path": "clip_vision/SD1.5",
+ "description": "[2.5GB] CLIPVision model (needed for IP-Adapter)",
+ "reference": "https://huggingface.co/h94/IP-Adapter",
+ "filename": "pytorch_model.bin",
+ "url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/image_encoder/pytorch_model.bin"
+ },
+ {
+ "name": "CLIPVision model (IP-Adapter)",
+ "type": "clip_vision",
+ "base": "SDXL",
+ "save_path": "clip_vision/SDXL",
+ "description": "[3.69GB] CLIPVision model (needed for IP-Adapter)",
+ "reference": "https://huggingface.co/h94/IP-Adapter",
+ "filename": "pytorch_model.bin",
+ "url": "https://huggingface.co/h94/IP-Adapter/resolve/main/sdxl_models/image_encoder/pytorch_model.bin"
+ },
+
+ {
+ "name": "stabilityai/control-lora-canny-rank128.safetensors",
+ "type": "controlnet",
+ "base": "SDXL",
+ "save_path": "default",
+ "description": "Control-LoRA: canny rank128",
+ "reference": "https://huggingface.co/stabilityai/control-lora",
+ "filename": "control-lora-canny-rank128.safetensors",
+ "url": "https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank128/control-lora-canny-rank128.safetensors"
+ },
+ {
+ "name": "stabilityai/control-lora-depth-rank128.safetensors",
+ "type": "controlnet",
+ "base": "SDXL",
+ "save_path": "default",
+ "description": "Control-LoRA: depth rank128",
+ "reference": "https://huggingface.co/stabilityai/control-lora",
+ "filename": "control-lora-depth-rank128.safetensors",
+ "url": "https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank128/control-lora-depth-rank128.safetensors"
+ },
+ {
+ "name": "stabilityai/control-lora-recolor-rank128.safetensors",
+ "type": "controlnet",
+ "base": "SDXL",
+ "save_path": "default",
+ "description": "Control-LoRA: recolor rank128",
+ "reference": "https://huggingface.co/stabilityai/control-lora",
+ "filename": "control-lora-recolor-rank128.safetensors",
+ "url": "https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank128/control-lora-recolor-rank128.safetensors"
+ },
+ {
+ "name": "stabilityai/control-lora-sketch-rank128-metadata.safetensors",
+ "type": "controlnet",
+ "base": "SDXL",
+ "save_path": "default",
+ "description": "Control-LoRA: sketch rank128 metadata",
+ "reference": "https://huggingface.co/stabilityai/control-lora",
+ "filename": "control-lora-sketch-rank128-metadata.safetensors",
+ "url": "https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank128/control-lora-sketch-rank128-metadata.safetensors"
+ },
+
+ {
+ "name": "stabilityai/control-lora-canny-rank256.safetensors",
+ "type": "controlnet",
+ "base": "SDXL",
+ "save_path": "default",
+ "description": "Control-LoRA: canny rank256",
+ "reference": "https://huggingface.co/stabilityai/control-lora",
+ "filename": "control-lora-canny-rank256.safetensors",
+ "url": "https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-canny-rank256.safetensors"
+ },
+ {
+ "name": "stabilityai/control-lora-depth-rank256.safetensors",
+ "type": "controlnet",
+ "base": "SDXL",
+ "save_path": "default",
+ "description": "Control-LoRA: depth rank256",
+ "reference": "https://huggingface.co/stabilityai/control-lora",
+ "filename": "control-lora-depth-rank256.safetensors",
+ "url": "https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-depth-rank256.safetensors"
+ },
+ {
+ "name": "stabilityai/control-lora-recolor-rank256.safetensors",
+ "type": "controlnet",
+ "base": "SDXL",
+ "save_path": "default",
+ "description": "Control-LoRA: recolor rank256",
+ "reference": "https://huggingface.co/stabilityai/control-lora",
+ "filename": "control-lora-recolor-rank256.safetensors",
+ "url": "https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-recolor-rank256.safetensors"
+ },
+ {
+ "name": "stabilityai/control-lora-sketch-rank256.safetensors",
+ "type": "controlnet",
+ "base": "SDXL",
+ "save_path": "default",
+ "description": "Control-LoRA: sketch rank256",
+ "reference": "https://huggingface.co/stabilityai/control-lora",
+ "filename": "control-lora-sketch-rank256.safetensors",
+ "url": "https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-sketch-rank256.safetensors"
+ },
+
+ {
+ "name": "kohya-ss/ControlNet-LLLite: SDXL Canny Anime",
+ "type": "controlnet",
+ "base": "SDXL",
+ "save_path": "custom_nodes/ControlNet-LLLite-ComfyUI/models",
+ "description": "[46.2MB] An extremely compactly designed controlnet model (a.k.a. ControlNet-LLLite). Note: The model structure is highly experimental and may be subject to change in the future.",
+ "reference": "https://huggingface.co/kohya-ss/controlnet-lllite",
+ "filename": "controllllite_v01032064e_sdxl_canny_anime.safetensors",
+ "url": "https://huggingface.co/kohya-ss/controlnet-lllite/resolve/main/controllllite_v01032064e_sdxl_canny_anime.safetensors"
+ },
+
+ {
+ "name": "SDXL-controlnet: OpenPose (v2)",
+ "type": "controlnet",
+ "base": "SDXL",
+ "save_path": "default",
+ "description": "ControlNet openpose model for SDXL",
+ "reference": "https://huggingface.co/thibaud/controlnet-openpose-sdxl-1.0",
+ "filename": "OpenPoseXL2.safetensors",
+ "url": "https://huggingface.co/thibaud/controlnet-openpose-sdxl-1.0/resolve/main/OpenPoseXL2.safetensors"
+ },
+ {
+ "name": "controlnet-SargeZT/controlnet-sd-xl-1.0-softedge-dexined",
+ "type": "controlnet",
+ "base": "SDXL",
+ "save_path": "default",
+ "description": "ControlNet softedge model for SDXL",
+ "reference": "https://huggingface.co/SargeZT/controlnet-sd-xl-1.0-softedge-dexined",
+ "filename": "controlnet-sd-xl-1.0-softedge-dexined.safetensors",
+ "url": "https://huggingface.co/SargeZT/controlnet-sd-xl-1.0-softedge-dexined/resolve/main/controlnet-sd-xl-1.0-softedge-dexined.safetensors"
+ },
+ {
+ "name": "controlnet-SargeZT/controlnet-sd-xl-1.0-depth-16bit-zoe",
+ "type": "controlnet",
+ "base": "SDXL",
+ "save_path": "default",
+ "description": "ControlNet depth-zoe model for SDXL",
+ "reference": "https://huggingface.co/SargeZT/controlnet-sd-xl-1.0-depth-16bit-zoe",
+ "filename": "depth-zoe-xl-v1.0-controlnet.safetensors",
+ "url": "https://huggingface.co/SargeZT/controlnet-sd-xl-1.0-depth-16bit-zoe/resolve/main/depth-zoe-xl-v1.0-controlnet.safetensors"
+ },
+
+ {
+ "name": "animatediff/mmd_sd_v14.ckpt (comfyui-animatediff)",
+ "type": "animatediff",
+ "base": "SD1.x",
+ "save_path": "custom_nodes/comfyui-animatediff/models",
+ "description": "Pressing 'install' directly downloads the model from the ArtVentureX/AnimateDiff extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/guoyww/animatediff",
+ "filename": "mm_sd_v14.ckpt",
+ "url": "https://huggingface.co/guoyww/animatediff/resolve/main/mm_sd_v14.ckpt"
+ },
+ {
+ "name": "animatediff/mm_sd_v15.ckpt (comfyui-animatediff)",
+ "type": "animatediff",
+ "base": "SD1.x",
+ "save_path": "custom_nodes/comfyui-animatediff/models",
+ "description": "Pressing 'install' directly downloads the model from the ArtVentureX/AnimateDiff extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/guoyww/animatediff",
+ "filename": "mm_sd_v15.ckpt",
+ "url": "https://huggingface.co/guoyww/animatediff/resolve/main/mm_sd_v15.ckpt"
+ },
+
+ {
+ "name": "animatediff/mmd_sd_v14.ckpt (ComfyUI-AnimateDiff-Evolved)",
+ "type": "animatediff",
+ "base": "SD1.x",
+ "save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/models",
+ "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/guoyww/animatediff",
+ "filename": "mm_sd_v14.ckpt",
+ "url": "https://huggingface.co/guoyww/animatediff/resolve/main/mm_sd_v14.ckpt"
+ },
+ {
+ "name": "animatediff/mm_sd_v15.ckpt (ComfyUI-AnimateDiff-Evolved)",
+ "type": "animatediff",
+ "base": "SD1.x",
+ "save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/models",
+ "description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/guoyww/animatediff",
+ "filename": "mm_sd_v15.ckpt",
+ "url": "https://huggingface.co/guoyww/animatediff/resolve/main/mm_sd_v15.ckpt"
+ },
+
+ {
+ "name": "ip-adapter_sdxl.bin (install to IPAdapter-ComfyUI)",
+ "type": "IP-Adapter",
+ "base": "SDXL",
+ "save_path": "custom_nodes/IPAdapter-ComfyUI/models",
+ "description": "Pressing 'install' directly downloads the model from the IPAdapter-ComfyUI/models extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/h94/IP-Adapter",
+ "filename": "ip-adapter_sdxl.bin",
+ "url": "https://huggingface.co/h94/IP-Adapter/resolve/main/sdxl_models/ip-adapter_sdxl.bin"
+ },
+ {
+ "name": "ip-adapter_sd15.bin (install to IPAdapter-ComfyUI)",
+ "type": "IP-Adapter",
+ "base": "SD1.5",
+ "save_path": "custom_nodes/IPAdapter-ComfyUI/models",
+ "description": "Pressing 'install' directly downloads the model from the IPAdapter-ComfyUI/models extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/h94/IP-Adapter",
+ "filename": "ip-adapter_sd15.bin",
+ "url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter_sd15.bin"
+ },
+ {
+ "name": "pfg-novel-n10.pt",
+ "type": "PFG",
+ "base": "SD1.5",
+ "save_path": "custom_nodes/pfg-ComfyUI/models",
+ "description": "Pressing 'install' directly downloads the model from the pfg-ComfyUI/models extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/furusu/PFG",
+ "filename": "pfg-novel-n10.pt",
+ "url": "https://huggingface.co/furusu/PFG/resolve/main/pfg-novel-n10.pt"
+ },
+ {
+ "name": "pfg-wd14-n10.pt",
+ "type": "PFG",
+ "base": "SD1.5",
+ "save_path": "custom_nodes/pfg-ComfyUI/models",
+ "description": "Pressing 'install' directly downloads the model from the pfg-ComfyUI/models extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/furusu/PFG",
+ "filename": "pfg-wd14-n10.pt",
+ "url": "https://huggingface.co/furusu/PFG/resolve/main/pfg-wd14-n10.pt"
+ },
+ {
+ "name": "pfg-wd15beta2-n10.pt",
+ "type": "PFG",
+ "base": "SD1.5",
+ "save_path": "custom_nodes/pfg-ComfyUI/models",
+ "description": "Pressing 'install' directly downloads the model from the pfg-ComfyUI/models extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
+ "reference": "https://huggingface.co/furusu/PFG",
+ "filename": "pfg-wd15beta2-n10.pt",
+ "url": "https://huggingface.co/furusu/PFG/resolve/main/pfg-wd15beta2-n10.pt"
+ }
+ ]
+}
diff --git a/custom_nodes/ComfyUI-Manager/notebooks/comfyui_colab_with_manager.ipynb b/custom_nodes/ComfyUI-Manager/notebooks/comfyui_colab_with_manager.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..d4e2665666d9dc5e56075cc8c9fde9435059550b
--- /dev/null
+++ b/custom_nodes/ComfyUI-Manager/notebooks/comfyui_colab_with_manager.ipynb
@@ -0,0 +1,350 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "aaaaaaaaaa"
+ },
+ "source": [
+ "Git clone the repo and install the requirements. (ignore the pip errors about protobuf)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "bbbbbbbbbb"
+ },
+ "outputs": [],
+ "source": [
+ "# #@title Environment Setup\n",
+ "\n",
+ "from pathlib import Path\n",
+ "\n",
+ "OPTIONS = {}\n",
+ "\n",
+ "USE_GOOGLE_DRIVE = True #@param {type:\"boolean\"}\n",
+ "UPDATE_COMFY_UI = True #@param {type:\"boolean\"}\n",
+ "USE_COMFYUI_MANAGER = True #@param {type:\"boolean\"}\n",
+ "INSTALL_CUSTOM_NODES_DEPENDENCIES = True #@param {type:\"boolean\"}\n",
+ "OPTIONS['USE_GOOGLE_DRIVE'] = USE_GOOGLE_DRIVE\n",
+ "OPTIONS['UPDATE_COMFY_UI'] = UPDATE_COMFY_UI\n",
+ "OPTIONS['USE_COMFYUI_MANAGER'] = USE_COMFYUI_MANAGER\n",
+ "OPTIONS['INSTALL_CUSTOM_NODES_DEPENDENCIES'] = INSTALL_CUSTOM_NODES_DEPENDENCIES\n",
+ "\n",
+ "current_dir = !pwd\n",
+ "WORKSPACE = f\"{current_dir[0]}/ComfyUI\"\n",
+ "\n",
+ "if OPTIONS['USE_GOOGLE_DRIVE']:\n",
+ " !echo \"Mounting Google Drive...\"\n",
+ " %cd /\n",
+ "\n",
+ " from google.colab import drive\n",
+ " drive.mount('/content/drive')\n",
+ "\n",
+ " WORKSPACE = \"/content/drive/MyDrive/ComfyUI\"\n",
+ " %cd /content/drive/MyDrive\n",
+ "\n",
+ "![ ! -d $WORKSPACE ] && echo -= Initial setup ComfyUI =- && git clone https://github.com/comfyanonymous/ComfyUI\n",
+ "%cd $WORKSPACE\n",
+ "\n",
+ "if OPTIONS['UPDATE_COMFY_UI']:\n",
+ " !echo -= Updating ComfyUI =-\n",
+ " !git pull\n",
+ "\n",
+ "!echo -= Install dependencies =-\n",
+ "#Remove cu121 as it causes issues in Colab.\n",
+ "#!pip install xformers!=0.0.18 -r requirements.txt --extra-index-url https://download.pytorch.org/whl/cu121 --extra-index-url https://download.pytorch.org/whl/cu118 --extra-index-url https://download.pytorch.org/whl/cu117\n",
+ "!pip install xformers!=0.0.18 -r requirements.txt --extra-index-url https://download.pytorch.org/whl/cu118 --extra-index-url https://download.pytorch.org/whl/cu117\n",
+ "\n",
+ "if OPTIONS['USE_COMFYUI_MANAGER']:\n",
+ " %cd custom_nodes\n",
+ " ![ ! -d ComfyUI-Manager ] && echo -= Initial setup ComfyUI-Manager =- && git clone https://github.com/ltdrdata/ComfyUI-Manager\n",
+ " %cd ComfyUI-Manager\n",
+ " !git pull\n",
+ "\n",
+ "%cd $WORKSPACE\n",
+ "\n",
+ "if OPTIONS['INSTALL_CUSTOM_NODES_DEPENDENCIES']:\n",
+ " !pwd\n",
+ " !echo -= Install custom nodes dependencies =-\n",
+ " ![ -f \"custom_nodes/ComfyUI-Manager/scripts/colab-dependencies.py\" ] && python \"custom_nodes/ComfyUI-Manager/scripts/colab-dependencies.py\"\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "cccccccccc"
+ },
+ "source": [
+ "Download some models/checkpoints/vae or custom comfyui nodes (uncomment the commands for the ones you want)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "dddddddddd"
+ },
+ "outputs": [],
+ "source": [
+ "# Checkpoints\n",
+ "\n",
+ "### SDXL\n",
+ "### I recommend these workflow examples: https://comfyanonymous.github.io/ComfyUI_examples/sdxl/\n",
+ "\n",
+ "#!wget -c https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0.safetensors -P ./models/checkpoints/\n",
+ "#!wget -c https://huggingface.co/stabilityai/stable-diffusion-xl-refiner-1.0/resolve/main/sd_xl_refiner_1.0.safetensors -P ./models/checkpoints/\n",
+ "\n",
+ "# SDXL ReVision\n",
+ "#!wget -c https://huggingface.co/comfyanonymous/clip_vision_g/resolve/main/clip_vision_g.safetensors -P ./models/clip_vision/\n",
+ "\n",
+ "# SD1.5\n",
+ "!wget -c https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.ckpt -P ./models/checkpoints/\n",
+ "\n",
+ "# SD2\n",
+ "#!wget -c https://huggingface.co/stabilityai/stable-diffusion-2-1-base/resolve/main/v2-1_512-ema-pruned.safetensors -P ./models/checkpoints/\n",
+ "#!wget -c https://huggingface.co/stabilityai/stable-diffusion-2-1/resolve/main/v2-1_768-ema-pruned.safetensors -P ./models/checkpoints/\n",
+ "\n",
+ "# Some SD1.5 anime style\n",
+ "#!wget -c https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/AbyssOrangeMix2/AbyssOrangeMix2_hard.safetensors -P ./models/checkpoints/\n",
+ "#!wget -c https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/AbyssOrangeMix3/AOM3A1_orangemixs.safetensors -P ./models/checkpoints/\n",
+ "#!wget -c https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/AbyssOrangeMix3/AOM3A3_orangemixs.safetensors -P ./models/checkpoints/\n",
+ "#!wget -c https://huggingface.co/Linaqruf/anything-v3.0/resolve/main/anything-v3-fp16-pruned.safetensors -P ./models/checkpoints/\n",
+ "\n",
+ "# Waifu Diffusion 1.5 (anime style SD2.x 768-v)\n",
+ "#!wget -c https://huggingface.co/waifu-diffusion/wd-1-5-beta3/resolve/main/wd-illusion-fp16.safetensors -P ./models/checkpoints/\n",
+ "\n",
+ "\n",
+ "# unCLIP models\n",
+ "#!wget -c https://huggingface.co/comfyanonymous/illuminatiDiffusionV1_v11_unCLIP/resolve/main/illuminatiDiffusionV1_v11-unclip-h-fp16.safetensors -P ./models/checkpoints/\n",
+ "#!wget -c https://huggingface.co/comfyanonymous/wd-1.5-beta2_unCLIP/resolve/main/wd-1-5-beta2-aesthetic-unclip-h-fp16.safetensors -P ./models/checkpoints/\n",
+ "\n",
+ "\n",
+ "# VAE\n",
+ "!wget -c https://huggingface.co/stabilityai/sd-vae-ft-mse-original/resolve/main/vae-ft-mse-840000-ema-pruned.safetensors -P ./models/vae/\n",
+ "#!wget -c https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/VAEs/orangemix.vae.pt -P ./models/vae/\n",
+ "#!wget -c https://huggingface.co/hakurei/waifu-diffusion-v1-4/resolve/main/vae/kl-f8-anime2.ckpt -P ./models/vae/\n",
+ "\n",
+ "\n",
+ "# Loras\n",
+ "#!wget -c https://civitai.com/api/download/models/10350 -O ./models/loras/theovercomer8sContrastFix_sd21768.safetensors #theovercomer8sContrastFix SD2.x 768-v\n",
+ "#!wget -c https://civitai.com/api/download/models/10638 -O ./models/loras/theovercomer8sContrastFix_sd15.safetensors #theovercomer8sContrastFix SD1.x\n",
+ "#!wget -c https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_offset_example-lora_1.0.safetensors -P ./models/loras/ #SDXL offset noise lora\n",
+ "\n",
+ "\n",
+ "# T2I-Adapter\n",
+ "#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_depth_sd14v1.pth -P ./models/controlnet/\n",
+ "#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_seg_sd14v1.pth -P ./models/controlnet/\n",
+ "#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_sketch_sd14v1.pth -P ./models/controlnet/\n",
+ "#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_keypose_sd14v1.pth -P ./models/controlnet/\n",
+ "#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_openpose_sd14v1.pth -P ./models/controlnet/\n",
+ "#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_color_sd14v1.pth -P ./models/controlnet/\n",
+ "#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_canny_sd14v1.pth -P ./models/controlnet/\n",
+ "\n",
+ "# T2I Styles Model\n",
+ "#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_style_sd14v1.pth -P ./models/style_models/\n",
+ "\n",
+ "# CLIPVision model (needed for styles model)\n",
+ "#!wget -c https://huggingface.co/openai/clip-vit-large-patch14/resolve/main/pytorch_model.bin -O ./models/clip_vision/clip_vit14.bin\n",
+ "\n",
+ "\n",
+ "# ControlNet\n",
+ "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11e_sd15_ip2p_fp16.safetensors -P ./models/controlnet/\n",
+ "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11e_sd15_shuffle_fp16.safetensors -P ./models/controlnet/\n",
+ "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_canny_fp16.safetensors -P ./models/controlnet/\n",
+ "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11f1p_sd15_depth_fp16.safetensors -P ./models/controlnet/\n",
+ "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_inpaint_fp16.safetensors -P ./models/controlnet/\n",
+ "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_lineart_fp16.safetensors -P ./models/controlnet/\n",
+ "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_mlsd_fp16.safetensors -P ./models/controlnet/\n",
+ "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_normalbae_fp16.safetensors -P ./models/controlnet/\n",
+ "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_openpose_fp16.safetensors -P ./models/controlnet/\n",
+ "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_scribble_fp16.safetensors -P ./models/controlnet/\n",
+ "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_seg_fp16.safetensors -P ./models/controlnet/\n",
+ "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_softedge_fp16.safetensors -P ./models/controlnet/\n",
+ "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15s2_lineart_anime_fp16.safetensors -P ./models/controlnet/\n",
+ "#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11u_sd15_tile_fp16.safetensors -P ./models/controlnet/\n",
+ "\n",
+ "# ControlNet SDXL\n",
+ "#!wget -c https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-canny-rank256.safetensors -P ./models/controlnet/\n",
+ "#!wget -c https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-depth-rank256.safetensors -P ./models/controlnet/\n",
+ "#!wget -c https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-recolor-rank256.safetensors -P ./models/controlnet/\n",
+ "#!wget -c https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-sketch-rank256.safetensors -P ./models/controlnet/\n",
+ "\n",
+ "# Controlnet Preprocessor nodes by Fannovel16\n",
+ "#!cd custom_nodes && git clone https://github.com/Fannovel16/comfy_controlnet_preprocessors; cd comfy_controlnet_preprocessors && python install.py\n",
+ "\n",
+ "\n",
+ "# GLIGEN\n",
+ "#!wget -c https://huggingface.co/comfyanonymous/GLIGEN_pruned_safetensors/resolve/main/gligen_sd14_textbox_pruned_fp16.safetensors -P ./models/gligen/\n",
+ "\n",
+ "\n",
+ "# ESRGAN upscale model\n",
+ "#!wget -c https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth -P ./models/upscale_models/\n",
+ "#!wget -c https://huggingface.co/sberbank-ai/Real-ESRGAN/resolve/main/RealESRGAN_x2.pth -P ./models/upscale_models/\n",
+ "#!wget -c https://huggingface.co/sberbank-ai/Real-ESRGAN/resolve/main/RealESRGAN_x4.pth -P ./models/upscale_models/\n",
+ "\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "kkkkkkkkkkkkkkk"
+ },
+ "source": [
+ "### Run ComfyUI with cloudflared (Recommended Way)\n",
+ "\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "jjjjjjjjjjjjjj"
+ },
+ "outputs": [],
+ "source": [
+ "!wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb\n",
+ "!dpkg -i cloudflared-linux-amd64.deb\n",
+ "\n",
+ "import subprocess\n",
+ "import threading\n",
+ "import time\n",
+ "import socket\n",
+ "import urllib.request\n",
+ "\n",
+ "def iframe_thread(port):\n",
+ " while True:\n",
+ " time.sleep(0.5)\n",
+ " sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n",
+ " result = sock.connect_ex(('127.0.0.1', port))\n",
+ " if result == 0:\n",
+ " break\n",
+ " sock.close()\n",
+ " print(\"\\nComfyUI finished loading, trying to launch cloudflared (if it gets stuck here cloudflared is having issues)\\n\")\n",
+ "\n",
+ " p = subprocess.Popen([\"cloudflared\", \"tunnel\", \"--url\", \"http://127.0.0.1:{}\".format(port)], stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n",
+ " for line in p.stderr:\n",
+ " l = line.decode()\n",
+ " if \"trycloudflare.com \" in l:\n",
+ " print(\"This is the URL to access ComfyUI:\", l[l.find(\"http\"):], end='')\n",
+ " #print(l, end='')\n",
+ "\n",
+ "\n",
+ "threading.Thread(target=iframe_thread, daemon=True, args=(8188,)).start()\n",
+ "\n",
+ "!python main.py --dont-print-server"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "kkkkkkkkkkkkkk"
+ },
+ "source": [
+ "### Run ComfyUI with localtunnel\n",
+ "\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "jjjjjjjjjjjjj"
+ },
+ "outputs": [],
+ "source": [
+ "!npm install -g localtunnel\n",
+ "\n",
+ "import subprocess\n",
+ "import threading\n",
+ "import time\n",
+ "import socket\n",
+ "import urllib.request\n",
+ "\n",
+ "def iframe_thread(port):\n",
+ " while True:\n",
+ " time.sleep(0.5)\n",
+ " sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n",
+ " result = sock.connect_ex(('127.0.0.1', port))\n",
+ " if result == 0:\n",
+ " break\n",
+ " sock.close()\n",
+ " print(\"\\nComfyUI finished loading, trying to launch localtunnel (if it gets stuck here localtunnel is having issues)\\n\")\n",
+ "\n",
+ " print(\"The password/enpoint ip for localtunnel is:\", urllib.request.urlopen('https://ipv4.icanhazip.com').read().decode('utf8').strip(\"\\n\"))\n",
+ " p = subprocess.Popen([\"lt\", \"--port\", \"{}\".format(port)], stdout=subprocess.PIPE)\n",
+ " for line in p.stdout:\n",
+ " print(line.decode(), end='')\n",
+ "\n",
+ "\n",
+ "threading.Thread(target=iframe_thread, daemon=True, args=(8188,)).start()\n",
+ "\n",
+ "!python main.py --dont-print-server"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "gggggggggg"
+ },
+ "source": [
+ "### Run ComfyUI with colab iframe (use only in case the previous way with localtunnel doesn't work)\n",
+ "\n",
+ "You should see the ui appear in an iframe. If you get a 403 error, it's your firefox settings or an extension that's messing things up.\n",
+ "\n",
+ "If you want to open it in another window use the link.\n",
+ "\n",
+ "Note that some UI features like live image previews won't work because the colab iframe blocks websockets."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "hhhhhhhhhh"
+ },
+ "outputs": [],
+ "source": [
+ "import threading\n",
+ "import time\n",
+ "import socket\n",
+ "def iframe_thread(port):\n",
+ " while True:\n",
+ " time.sleep(0.5)\n",
+ " sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n",
+ " result = sock.connect_ex(('127.0.0.1', port))\n",
+ " if result == 0:\n",
+ " break\n",
+ " sock.close()\n",
+ " from google.colab import output\n",
+ " output.serve_kernel_port_as_iframe(port, height=1024)\n",
+ " print(\"to open it in a window you can open this link here:\")\n",
+ " output.serve_kernel_port_as_window(port)\n",
+ "\n",
+ "threading.Thread(target=iframe_thread, daemon=True, args=(8188,)).start()\n",
+ "\n",
+ "!python main.py --dont-print-server"
+ ]
+ }
+ ],
+ "metadata": {
+ "accelerator": "GPU",
+ "colab": {
+ "provenance": []
+ },
+ "gpuClass": "standard",
+ "kernelspec": {
+ "display_name": "Python 3",
+ "name": "python3"
+ },
+ "language_info": {
+ "name": "python"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
diff --git a/custom_nodes/ComfyUI-Manager/prestartup_script.py b/custom_nodes/ComfyUI-Manager/prestartup_script.py
new file mode 100644
index 0000000000000000000000000000000000000000..9dfc013832f86fc6b7067be5bf10a2c69ae0c1be
--- /dev/null
+++ b/custom_nodes/ComfyUI-Manager/prestartup_script.py
@@ -0,0 +1,398 @@
+import datetime
+import os
+import subprocess
+import sys
+import atexit
+import threading
+import re
+import locale
+
+
+message_collapses = []
+import_failed_extensions = set()
+
+
+def register_message_collapse(f):
+ global message_collapses
+ message_collapses.append(f)
+
+
+def is_import_failed_extension(x):
+ global import_failed_extensions
+ return x in import_failed_extensions
+
+
+sys.__comfyui_manager_register_message_collapse = register_message_collapse
+sys.__comfyui_manager_is_import_failed_extension = is_import_failed_extension
+
+comfyui_manager_path = os.path.dirname(__file__)
+custom_nodes_path = os.path.abspath(os.path.join(comfyui_manager_path, ".."))
+startup_script_path = os.path.join(comfyui_manager_path, "startup-scripts")
+restore_snapshot_path = os.path.join(startup_script_path, "restore-snapshot.json")
+git_script_path = os.path.join(comfyui_manager_path, "git_helper.py")
+
+
+def handle_stream(stream, prefix):
+ stream.reconfigure(encoding=locale.getpreferredencoding(), errors='replace')
+ for msg in stream:
+ if prefix == '[!]' and ('it/s]' in msg or 's/it]' in msg) and ('%|' in msg or 'it [' in msg):
+ if msg.startswith('100%'):
+ print('\r' + msg, end="", file=sys.stderr),
+ else:
+ print('\r' + msg[:-1], end="", file=sys.stderr),
+ else:
+ if prefix == '[!]':
+ print(prefix, msg, end="", file=sys.stderr)
+ else:
+ print(prefix, msg, end="")
+
+
+def process_wrap(cmd_str, cwd_path, handler=None):
+ process = subprocess.Popen(cmd_str, cwd=cwd_path, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1)
+
+ if handler is None:
+ handler = handle_stream
+
+ stdout_thread = threading.Thread(target=handler, args=(process.stdout, ""))
+ stderr_thread = threading.Thread(target=handler, args=(process.stderr, "[!]"))
+
+ stdout_thread.start()
+ stderr_thread.start()
+
+ stdout_thread.join()
+ stderr_thread.join()
+
+ return process.wait()
+
+
+try:
+ if '--port' in sys.argv:
+ port_index = sys.argv.index('--port')
+ if port_index + 1 < len(sys.argv):
+ port = int(sys.argv[port_index + 1])
+ postfix = f"_{port}"
+ else:
+ postfix = ""
+
+ # Logger setup
+ if os.path.exists(f"comfyui{postfix}.log"):
+ if os.path.exists(f"comfyui{postfix}.prev.log"):
+ if os.path.exists(f"comfyui{postfix}.prev2.log"):
+ os.remove(f"comfyui{postfix}.prev2.log")
+ os.rename(f"comfyui{postfix}.prev.log", f"comfyui{postfix}.prev2.log")
+ os.rename(f"comfyui{postfix}.log", f"comfyui{postfix}.prev.log")
+
+ original_stdout = sys.stdout
+ original_stderr = sys.stderr
+
+ pat_tqdm = r'\d+%.*\[(.*?)\]'
+ pat_import_fail = r'seconds \(IMPORT FAILED\):'
+ pat_custom_node = r'[/\\]custom_nodes[/\\](.*)$'
+
+ is_start_mode = True
+ is_import_fail_mode = False
+
+ log_file = open(f"comfyui{postfix}.log", "w", encoding="utf-8")
+ log_lock = threading.Lock()
+
+ class ComfyUIManagerLogger:
+ def __init__(self, is_stdout):
+ self.is_stdout = is_stdout
+ self.encoding = "utf-8"
+
+ def fileno(self):
+ try:
+ if self.is_stdout:
+ return original_stdout.fileno()
+ else:
+ return original_stderr.fileno()
+ except AttributeError:
+ # Handle error
+ raise ValueError("The object does not have a fileno method")
+
+ def write(self, message):
+ global is_start_mode
+ global is_import_fail_mode
+
+ if any(f(message) for f in message_collapses):
+ return
+
+ if is_start_mode:
+ if is_import_fail_mode:
+ match = re.search(pat_custom_node, message)
+ if match:
+ import_failed_extensions.add(match.group(1))
+ is_import_fail_mode = False
+ else:
+ match = re.search(pat_import_fail, message)
+ if match:
+ is_import_fail_mode = True
+ else:
+ is_import_fail_mode = False
+
+ if 'Starting server' in message:
+ is_start_mode = False
+
+ if not self.is_stdout:
+ match = re.search(pat_tqdm, message)
+ if match:
+ message = re.sub(r'([#|])\d', r'\1▌', message)
+ message = re.sub('#', '█', message)
+ if '100%' in message:
+ self.sync_write(message)
+ else:
+ original_stderr.write(message)
+ original_stderr.flush()
+ else:
+ self.sync_write(message)
+ else:
+ self.sync_write(message)
+
+ def sync_write(self, message):
+ with log_lock:
+ log_file.write(message)
+ log_file.flush()
+
+ if self.is_stdout:
+ original_stdout.write(message)
+ original_stdout.flush()
+ else:
+ original_stderr.write(message)
+ original_stderr.flush()
+
+ def flush(self):
+ log_file.flush()
+ if self.is_stdout:
+ original_stdout.flush()
+ else:
+ original_stderr.flush()
+
+ def close(self):
+ self.flush()
+ pass
+
+ def reconfigure(self, *args, **kwargs):
+ pass
+
+ # You can close through sys.stderr.close_log()
+ def close_log(self):
+ sys.stderr = original_stderr
+ sys.stdout = original_stdout
+ log_file.close()
+
+ def close_log():
+ sys.stderr = original_stderr
+ sys.stdout = original_stdout
+ log_file.close()
+
+ sys.stdout = ComfyUIManagerLogger(True)
+ sys.stderr = ComfyUIManagerLogger(False)
+
+ atexit.register(close_log)
+except Exception as e:
+ print(f"[ComfyUI-Manager] Logging failed: {e}")
+
+
+print("** ComfyUI start up time:", datetime.datetime.now())
+
+
+def check_bypass_ssl():
+ try:
+ import configparser
+ import ssl
+ config_path = os.path.join(os.path.dirname(__file__), "config.ini")
+ config = configparser.ConfigParser()
+ config.read(config_path)
+ default_conf = config['default']
+
+ if 'bypass_ssl' in default_conf and default_conf['bypass_ssl'].lower() == 'true':
+ print(f"[ComfyUI-Manager] WARN: Unsafe - SSL verification bypass option is Enabled. (see ComfyUI-Manager/config.ini)")
+ ssl._create_default_https_context = ssl._create_unverified_context # SSL certificate error fix.
+ except Exception:
+ pass
+
+
+check_bypass_ssl()
+
+
+# Perform install
+processed_install = set()
+script_list_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "startup-scripts", "install-scripts.txt")
+pip_list = None
+
+
+def get_installed_packages():
+ global pip_list
+
+ if pip_list is None:
+ try:
+ result = subprocess.check_output([sys.executable, '-m', 'pip', 'list'], universal_newlines=True)
+ pip_list = set([line.split()[0].lower() for line in result.split('\n') if line.strip()])
+ except subprocess.CalledProcessError as e:
+ print(f"[ComfyUI-Manager] Failed to retrieve the information of installed pip packages.")
+ return set()
+
+ return pip_list
+
+
+def is_installed(name):
+ name = name.strip()
+
+ if name.startswith('#'):
+ return True
+
+ pattern = r'([^<>!=]+)([<>!=]=?)'
+ match = re.search(pattern, name)
+
+ if match:
+ name = match.group(1)
+
+ return name.lower() in get_installed_packages()
+
+
+if os.path.exists(restore_snapshot_path):
+ try:
+ import json
+
+ cloned_repos = []
+
+ def msg_capture(stream, prefix):
+ stream.reconfigure(encoding=locale.getpreferredencoding(), errors='replace')
+ for msg in stream:
+ if msg.startswith("CLONE: "):
+ cloned_repos.append(msg[7:])
+ if prefix == '[!]':
+ print(prefix, msg, end="", file=sys.stderr)
+ else:
+ print(prefix, msg, end="")
+
+ elif prefix == '[!]' and ('it/s]' in msg or 's/it]' in msg) and ('%|' in msg or 'it [' in msg):
+ if msg.startswith('100%'):
+ print('\r' + msg, end="", file=sys.stderr),
+ else:
+ print('\r'+msg[:-1], end="", file=sys.stderr),
+ else:
+ if prefix == '[!]':
+ print(prefix, msg, end="", file=sys.stderr)
+ else:
+ print(prefix, msg, end="")
+
+ print(f"[ComfyUI-Manager] Restore snapshot.")
+ cmd_str = [sys.executable, git_script_path, '--apply-snapshot', restore_snapshot_path]
+ exit_code = process_wrap(cmd_str, custom_nodes_path, handler=msg_capture)
+
+ with open(restore_snapshot_path, 'r', encoding="UTF-8") as json_file:
+ info = json.load(json_file)
+ for url in cloned_repos:
+ try:
+ repository_name = url.split("/")[-1].strip()
+ repo_path = os.path.join(custom_nodes_path, repository_name)
+ repo_path = os.path.abspath(repo_path)
+
+ requirements_path = os.path.join(repo_path, 'requirements.txt')
+ install_script_path = os.path.join(repo_path, 'install.py')
+
+ this_exit_code = 0
+
+ if os.path.exists(requirements_path):
+ with open(requirements_path, 'r', encoding="UTF-8") as file:
+ for line in file:
+ package_name = line.strip()
+ if package_name and not is_installed(package_name):
+ install_cmd = [sys.executable, "-m", "pip", "install", package_name]
+ this_exit_code += process_wrap(install_cmd, repo_path)
+
+ if os.path.exists(install_script_path) and f'{repo_path}/install.py' not in processed_install:
+ processed_install.add(f'{repo_path}/install.py')
+ install_cmd = [sys.executable, install_script_path]
+ print(f">>> {install_cmd} / {repo_path}")
+ this_exit_code += process_wrap(install_cmd, repo_path)
+
+ if this_exit_code != 0:
+ print(f"[ComfyUI-Manager] Restoring '{repository_name}' is failed.")
+
+ except Exception as e:
+ print(e)
+ print(f"[ComfyUI-Manager] Restoring '{repository_name}' is failed.")
+
+ if exit_code != 0:
+ print(f"[ComfyUI-Manager] Restore snapshot failed.")
+ else:
+ print(f"[ComfyUI-Manager] Restore snapshot done.")
+
+ except Exception as e:
+ print(e)
+ print(f"[ComfyUI-Manager] Restore snapshot failed.")
+
+ os.remove(restore_snapshot_path)
+
+
+def execute_lazy_install_script(repo_path, executable):
+ global processed_install
+
+ install_script_path = os.path.join(repo_path, "install.py")
+ requirements_path = os.path.join(repo_path, "requirements.txt")
+
+ if os.path.exists(requirements_path):
+ print(f"Install: pip packages for '{repo_path}'")
+ with open(requirements_path, "r") as requirements_file:
+ for line in requirements_file:
+ package_name = line.strip()
+ if package_name and not is_installed(package_name):
+ install_cmd = [executable, "-m", "pip", "install", package_name]
+ process_wrap(install_cmd, repo_path)
+
+ if os.path.exists(install_script_path) and f'{repo_path}/install.py' not in processed_install:
+ processed_install.add(f'{repo_path}/install.py')
+ print(f"Install: install script for '{repo_path}'")
+ install_cmd = [executable, "install.py"]
+ process_wrap(install_cmd, repo_path)
+
+
+# Check if script_list_path exists
+if os.path.exists(script_list_path):
+ print("\n#######################################################################")
+ print("[ComfyUI-Manager] Starting dependency installation/(de)activation for the extension\n")
+
+ executed = set()
+ # Read each line from the file and convert it to a list using eval
+ with open(script_list_path, 'r', encoding="UTF-8") as file:
+ for line in file:
+ if line in executed:
+ continue
+
+ executed.add(line)
+
+ try:
+ script = eval(line)
+
+ if script[1].startswith('#'):
+ if script[1] == "#LAZY-INSTALL-SCRIPT":
+ execute_lazy_install_script(script[0], script[2])
+
+ elif os.path.exists(script[0]):
+ if 'pip' in script[1:] and 'install' in script[1:] and is_installed(script[-1]):
+ continue
+
+ print(f"\n## ComfyUI-Manager: EXECUTE => {script[1:]}")
+ print(f"\n## Execute install/(de)activation script for '{script[0]}'")
+
+ exit_code = process_wrap(script[1:], script[0])
+
+ if exit_code != 0:
+ print(f"install/(de)activation script failed: {script[0]}")
+ else:
+ print(f"\n## ComfyUI-Manager: CANCELED => {script[1:]}")
+
+ except Exception as e:
+ print(f"[ERROR] Failed to execute install/(de)activation script: {line} / {e}")
+
+ # Remove the script_list_path file
+ if os.path.exists(script_list_path):
+ os.remove(script_list_path)
+
+ print("\n[ComfyUI-Manager] Startup script completed.")
+ print("#######################################################################\n")
+
+del processed_install
+del pip_list
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI-Manager/requirements.txt b/custom_nodes/ComfyUI-Manager/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3467b3e51c131ba4bbf1db1ae8317aa45cc3ef53
--- /dev/null
+++ b/custom_nodes/ComfyUI-Manager/requirements.txt
@@ -0,0 +1,2 @@
+GitPython
+matrix-client==0.4.0
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI-Manager/scan.sh b/custom_nodes/ComfyUI-Manager/scan.sh
new file mode 100644
index 0000000000000000000000000000000000000000..a169cd488a476f95d8b134914f42c7e578a5397b
--- /dev/null
+++ b/custom_nodes/ComfyUI-Manager/scan.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+source ../../venv/bin/activate
+rm .tmp/*.py > /dev/null
+python scanner.py
+cp extension-node-map.json node_db/new/.
diff --git a/custom_nodes/ComfyUI-Manager/scanner.py b/custom_nodes/ComfyUI-Manager/scanner.py
new file mode 100644
index 0000000000000000000000000000000000000000..9c9304191f133e83eb6f6b94a971733dff28017d
--- /dev/null
+++ b/custom_nodes/ComfyUI-Manager/scanner.py
@@ -0,0 +1,296 @@
+import re
+import os
+import json
+from git import Repo
+from torchvision.datasets.utils import download_url
+import concurrent
+
+builtin_nodes = set()
+
+
+def scan_in_file(filename, is_builtin=False):
+ global builtin_nodes
+
+ try:
+ with open(filename, encoding='utf-8') as file:
+ code = file.read()
+ except UnicodeDecodeError:
+ with open(filename, encoding='cp949') as file:
+ code = file.read()
+
+ pattern = r"_CLASS_MAPPINGS\s*=\s*{([^}]*)}"
+ regex = re.compile(pattern, re.MULTILINE | re.DOTALL)
+
+ nodes = set()
+ class_dict = {}
+
+ pattern2 = r'^[^=]*_CLASS_MAPPINGS\["(.*?)"\]'
+ keys = re.findall(pattern2, code)
+ for key in keys:
+ nodes.add(key.strip())
+
+ pattern3 = r'^[^=]*_CLASS_MAPPINGS\[\'(.*?)\'\]'
+ keys = re.findall(pattern3, code)
+ for key in keys:
+ nodes.add(key.strip())
+
+ matches = regex.findall(code)
+ for match in matches:
+ dict_text = match
+
+ key_value_pairs = re.findall(r"\"([^\"]*)\"\s*:\s*([^,\n]*)", dict_text)
+ for key, value in key_value_pairs:
+ class_dict[key.strip()] = value.strip()
+
+ key_value_pairs = re.findall(r"'([^']*)'\s*:\s*([^,\n]*)", dict_text)
+ for key, value in key_value_pairs:
+ class_dict[key.strip()] = value.strip()
+
+ for key, value in class_dict.items():
+ nodes.add(key.strip())
+
+ update_pattern = r"_CLASS_MAPPINGS.update\s*\({([^}]*)}\)"
+ update_match = re.search(update_pattern, code)
+ if update_match:
+ update_dict_text = update_match.group(1)
+ update_key_value_pairs = re.findall(r"\"([^\"]*)\"\s*:\s*([^,\n]*)", update_dict_text)
+ for key, value in update_key_value_pairs:
+ class_dict[key.strip()] = value.strip()
+ nodes.add(key.strip())
+
+ metadata = {}
+ lines = code.strip().split('\n')
+ for line in lines:
+ if line.startswith('@'):
+ if line.startswith("@author:") or line.startswith("@title:") or line.startswith("@nickname:") or line.startswith("@description:"):
+ key, value = line[1:].strip().split(':')
+ metadata[key.strip()] = value.strip()
+
+ if is_builtin:
+ builtin_nodes += set(nodes)
+ else:
+ for x in builtin_nodes:
+ if x in nodes:
+ nodes.remove(x)
+
+ return nodes, metadata
+
+
+def get_py_file_paths(dirname):
+ file_paths = []
+
+ for root, dirs, files in os.walk(dirname):
+ if ".git" in root or "__pycache__" in root:
+ continue
+
+ for file in files:
+ if file.endswith(".py"):
+ file_path = os.path.join(root, file)
+ file_paths.append(file_path)
+
+ return file_paths
+
+
+def get_nodes(target_dir):
+ py_files = []
+ directories = []
+
+ for item in os.listdir(target_dir):
+ if ".git" in item or "__pycache__" in item:
+ continue
+
+ path = os.path.abspath(os.path.join(target_dir, item))
+
+ if os.path.isfile(path) and item.endswith(".py"):
+ py_files.append(path)
+ elif os.path.isdir(path):
+ directories.append(path)
+
+ return py_files, directories
+
+
+def get_git_urls_from_json(json_file):
+ with open(json_file, encoding='utf-8') as file:
+ data = json.load(file)
+
+ custom_nodes = data.get('custom_nodes', [])
+ git_clone_files = []
+ for node in custom_nodes:
+ if node.get('install_type') == 'git-clone':
+ files = node.get('files', [])
+ if files:
+ git_clone_files.append((files[0], node.get('title'), node.get('nodename_pattern')))
+
+ git_clone_files.append(("https://github.com/comfyanonymous/ComfyUI", "ComfyUI", None))
+
+ return git_clone_files
+
+
+def get_py_urls_from_json(json_file):
+ with open(json_file, encoding='utf-8') as file:
+ data = json.load(file)
+
+ custom_nodes = data.get('custom_nodes', [])
+ py_files = []
+ for node in custom_nodes:
+ if node.get('install_type') == 'copy':
+ files = node.get('files', [])
+ if files:
+ py_files.append((files[0], node.get('title'), node.get('nodename_pattern')))
+
+ return py_files
+
+
+def clone_or_pull_git_repository(git_url):
+ repo_name = git_url.split("/")[-1].split(".")[0]
+ repo_dir = os.path.join(os.getcwd(), ".tmp", repo_name)
+
+ if os.path.exists(repo_dir):
+ try:
+ repo = Repo(repo_dir)
+ origin = repo.remote(name="origin")
+ origin.pull(rebase=True)
+ repo.git.submodule('update', '--init', '--recursive')
+ print(f"Pulling {repo_name}...")
+ except Exception as e:
+ print(f"Pulling {repo_name} failed: {e}")
+ else:
+ try:
+ Repo.clone_from(git_url, repo_dir, recursive=True)
+ print(f"Cloning {repo_name}...")
+ except Exception as e:
+ print(f"Cloning {repo_name} failed: {e}")
+
+
+def update_custom_nodes():
+ tmp_dir = os.path.join(os.getcwd(), ".tmp")
+ if not os.path.exists(tmp_dir):
+ os.makedirs(tmp_dir)
+
+ node_info = {}
+
+ git_url_titles = get_git_urls_from_json('custom-node-list.json')
+
+ def process_git_url_title(url, title, node_pattern):
+ name = os.path.basename(url)
+ if name.endswith(".git"):
+ name = name[:-4]
+
+ node_info[name] = (url, title, node_pattern)
+ clone_or_pull_git_repository(url)
+
+ with concurrent.futures.ThreadPoolExecutor(10) as executor:
+ for url, title, node_pattern in git_url_titles:
+ executor.submit(process_git_url_title, url, title, node_pattern)
+
+ py_url_titles_and_pattern = get_py_urls_from_json('custom-node-list.json')
+
+ def download_and_store_info(url_title_and_pattern):
+ url, title, node_pattern = url_title_and_pattern
+ name = os.path.basename(url)
+ if name.endswith(".py"):
+ node_info[name] = (url, title, node_pattern)
+
+ try:
+ download_url(url, ".tmp")
+ except:
+ print(f"[ERROR] Cannot download '{url}'")
+
+ with concurrent.futures.ThreadPoolExecutor(10) as executor:
+ executor.map(download_and_store_info, py_url_titles_and_pattern)
+
+ return node_info
+
+
+def gen_json(node_info):
+ # scan from .py file
+ node_files, node_dirs = get_nodes(".tmp")
+
+ comfyui_path = os.path.abspath(os.path.join('.tmp', "ComfyUI"))
+ node_dirs.remove(comfyui_path)
+ node_dirs = [comfyui_path] + node_dirs
+
+ data = {}
+ for dirname in node_dirs:
+ py_files = get_py_file_paths(dirname)
+ metadata = {}
+
+ nodes = set()
+ for py in py_files:
+ nodes_in_file, metadata_in_file = scan_in_file(py, dirname == "ComfyUI")
+ nodes.update(nodes_in_file)
+ metadata.update(metadata_in_file)
+
+ dirname = os.path.basename(dirname)
+
+ if len(nodes) > 0 or (dirname in node_info and node_info[dirname][2] is not None):
+ nodes = list(nodes)
+ nodes.sort()
+
+ if dirname in node_info:
+ git_url, title, node_pattern = node_info[dirname]
+ metadata['title_aux'] = title
+ if node_pattern is not None:
+ metadata['nodename_pattern'] = node_pattern
+ data[git_url] = (nodes, metadata)
+ else:
+ print(f"WARN: {dirname} is removed from custom-node-list.json")
+
+ for file in node_files:
+ nodes, metadata = scan_in_file(file)
+
+ if len(nodes) > 0 or (dirname in node_info and node_info[dirname][2] is not None):
+ nodes = list(nodes)
+ nodes.sort()
+
+ file = os.path.basename(file)
+
+ if file in node_info:
+ url, title, node_pattern = node_info[file]
+ metadata['title_aux'] = title
+ if node_pattern is not None:
+ metadata['nodename_pattern'] = node_pattern
+ data[url] = (nodes, metadata)
+ else:
+ print(f"Missing info: {url}")
+
+ # scan from node_list.json file
+ extensions = [name for name in os.listdir('.tmp') if os.path.isdir(os.path.join('.tmp', name))]
+
+ for extension in extensions:
+ node_list_json_path = os.path.join('.tmp', extension, 'node_list.json')
+ if os.path.exists(node_list_json_path):
+ git_url, title, node_pattern = node_info[extension]
+
+ with open(node_list_json_path, 'r', encoding='utf-8') as f:
+ node_list_json = json.load(f)
+
+ metadata_in_url = {}
+ if git_url not in data:
+ nodes = set()
+ else:
+ nodes_in_url, metadata_in_url = data[git_url]
+ nodes = set(nodes_in_url)
+
+ for x, desc in node_list_json.items():
+ nodes.add(x.strip())
+
+ metadata_in_url['title_aux'] = title
+ if node_pattern is not None:
+ metadata['nodename_pattern'] = node_pattern
+ nodes = list(nodes)
+ nodes.sort()
+ data[git_url] = (nodes, metadata_in_url)
+
+ json_path = f"extension-node-map.json"
+ with open(json_path, "w", encoding='utf-8') as file:
+ json.dump(data, file, indent=4, sort_keys=True)
+
+
+print("### ComfyUI Manager Node Scanner ###")
+
+print("\n# Updating extensions\n")
+updated_node_info = update_custom_nodes()
+
+print("\n# 'extension-node-map.json' file is generated.\n")
+gen_json(updated_node_info)
diff --git a/custom_nodes/ComfyUI-Manager/scripts/colab-dependencies.py b/custom_nodes/ComfyUI-Manager/scripts/colab-dependencies.py
new file mode 100644
index 0000000000000000000000000000000000000000..d5a70ed6dd92ba90e8084e07fbb9097fe3096ea5
--- /dev/null
+++ b/custom_nodes/ComfyUI-Manager/scripts/colab-dependencies.py
@@ -0,0 +1,39 @@
+import os
+import subprocess
+
+
+def get_enabled_subdirectories_with_files(base_directory):
+ subdirs_with_files = []
+ for subdir in os.listdir(base_directory):
+ try:
+ full_path = os.path.join(base_directory, subdir)
+ if os.path.isdir(full_path) and not subdir.endswith(".disabled") and not subdir.startswith('.') and subdir != '__pycache__':
+ print(f"## Install dependencies for '{subdir}'")
+ requirements_file = os.path.join(full_path, "requirements.txt")
+ install_script = os.path.join(full_path, "install.py")
+
+ if os.path.exists(requirements_file) or os.path.exists(install_script):
+ subdirs_with_files.append((full_path, requirements_file, install_script))
+ except Exception as e:
+ print(f"EXCEPTION During Dependencies INSTALL on '{subdir}':\n{e}")
+
+ return subdirs_with_files
+
+
+def install_requirements(requirements_file_path):
+ if os.path.exists(requirements_file_path):
+ subprocess.run(["pip", "install", "-r", requirements_file_path])
+
+
+def run_install_script(install_script_path):
+ if os.path.exists(install_script_path):
+ subprocess.run(["python", install_script_path])
+
+
+custom_nodes_directory = "custom_nodes"
+subdirs_with_files = get_enabled_subdirectories_with_files(custom_nodes_directory)
+
+
+for subdir, requirements_file, install_script in subdirs_with_files:
+ install_requirements(requirements_file)
+ run_install_script(install_script)
diff --git a/custom_nodes/ComfyUI-Manager/scripts/install-comfyui-venv-linux.sh b/custom_nodes/ComfyUI-Manager/scripts/install-comfyui-venv-linux.sh
new file mode 100644
index 0000000000000000000000000000000000000000..be473dc66f8eeb36c48d409945eb5ae83a030171
--- /dev/null
+++ b/custom_nodes/ComfyUI-Manager/scripts/install-comfyui-venv-linux.sh
@@ -0,0 +1,21 @@
+git clone https://github.com/comfyanonymous/ComfyUI
+cd ComfyUI/custom_nodes
+git clone https://github.com/ltdrdata/ComfyUI-Manager
+cd ..
+python -m venv venv
+source venv/bin/activate
+python -m pip install -r requirements.txt
+python -m pip install -r custom_nodes/ComfyUI-Manager/requirements.txt
+python -m pip install torchvision
+cd ..
+echo "#!/bin/bash" > run_gpu.sh
+echo "cd ComfyUI" >> run_gpu.sh
+echo "source venv/bin/activate" >> run_gpu.sh
+echo "python main.py --preview-method auto" >> run_gpu.sh
+chmod +x run_gpu.sh
+
+echo "#!/bin/bash" > run_cpu.sh
+echo "cd ComfyUI" >> run_cpu.sh
+echo "source venv/bin/activate" >> run_cpu.sh
+echo "python main.py --preview-method auto --cpu" >> run_cpu.sh
+chmod +x run_cpu.sh
diff --git a/custom_nodes/ComfyUI-Manager/scripts/install-comfyui-venv-win.bat b/custom_nodes/ComfyUI-Manager/scripts/install-comfyui-venv-win.bat
new file mode 100644
index 0000000000000000000000000000000000000000..6bb0e8364b5170530c2a85341ad754764c6788ae
--- /dev/null
+++ b/custom_nodes/ComfyUI-Manager/scripts/install-comfyui-venv-win.bat
@@ -0,0 +1,20 @@
+git clone https://github.com/comfyanonymous/ComfyUI
+cd ComfyUI/custom_nodes
+git clone https://github.com/ltdrdata/ComfyUI-Manager
+cd ..
+python -m venv venv
+call venv/Scripts/activate
+python -m pip install -r requirements.txt
+python -m pip install -r custom_nodes/ComfyUI-Manager/requirements.txt
+python -m pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu118 xformers
+cd ..
+echo "cd ComfyUI" >> run_gpu.sh
+echo "call venv/Scripts/activate" >> run_gpu.sh
+echo "python main.py" >> run_gpu.sh
+chmod +x run_gpu.sh
+
+echo "#!/bin/bash" > run_cpu.sh
+echo "cd ComfyUI" >> run_cpu.sh
+echo "call venv/Scripts/activate" >> run_cpu.sh
+echo "python main.py --cpu" >> run_cpu.sh
+chmod +x run_cpu.sh
diff --git a/custom_nodes/ComfyUI-Manager/scripts/install-manager-for-portable-version.bat b/custom_nodes/ComfyUI-Manager/scripts/install-manager-for-portable-version.bat
new file mode 100644
index 0000000000000000000000000000000000000000..7b067dfd770d197ccd68e760087536552223f260
--- /dev/null
+++ b/custom_nodes/ComfyUI-Manager/scripts/install-manager-for-portable-version.bat
@@ -0,0 +1,2 @@
+.\python_embeded\python.exe -s -m pip install gitpython
+.\python_embeded\python.exe -c "import git; git.Repo.clone_from('https://github.com/ltdrdata/ComfyUI-Manager', './ComfyUI/custom_nodes/ComfyUI-Manager')"
diff --git a/custom_nodes/ComfyUI-Manager/scripts/update-fix.py b/custom_nodes/ComfyUI-Manager/scripts/update-fix.py
new file mode 100644
index 0000000000000000000000000000000000000000..d2ac10074607544d0b9cdaf4372e43c7f62bb8d0
--- /dev/null
+++ b/custom_nodes/ComfyUI-Manager/scripts/update-fix.py
@@ -0,0 +1,12 @@
+import git
+
+commit_hash = "a361cc1"
+
+repo = git.Repo('.')
+
+if repo.is_dirty():
+ repo.git.stash()
+
+repo.git.update_ref("refs/remotes/origin/main", commit_hash)
+repo.remotes.origin.fetch()
+repo.git.pull("origin", "main")
diff --git a/custom_nodes/ComfyUI-Manager/snapshots/2023-11-21_14-29-27_autosave.json b/custom_nodes/ComfyUI-Manager/snapshots/2023-11-21_14-29-27_autosave.json
new file mode 100644
index 0000000000000000000000000000000000000000..88a9f255fc3146228757e259a07497cf29bfc169
--- /dev/null
+++ b/custom_nodes/ComfyUI-Manager/snapshots/2023-11-21_14-29-27_autosave.json
@@ -0,0 +1,70 @@
+{
+ "comfyui": "2dd5b4dd78fc0a30f3d5baa0b99a6b10f002d917",
+ "git_custom_nodes": {
+ "https://github.com/laksjdjf/cd-tuner_negpip-ComfyUI": {
+ "hash": "c2aad505427d2d8ae3bc3c8650e1f407fac7cfec",
+ "disabled": false
+ },
+ "https://github.com/Kosinkadink/ComfyUI-Advanced-ControlNet": {
+ "hash": "19ea71ad886677b5fa19a2c4bbdc879ff04078db",
+ "disabled": false
+ },
+ "https://github.com/pythongosssss/ComfyUI-Custom-Scripts": {
+ "hash": "27555d4f71bb4e24b87571f89eab2b4a06677bb6",
+ "disabled": false
+ },
+ "https://github.com/alt-key-project/comfyui-dream-project": {
+ "hash": "25cbdff39c5b74c9055c876532316d4926a64948",
+ "disabled": false
+ },
+ "https://github.com/ltdrdata/ComfyUI-Impact-Pack": {
+ "hash": "dffa779e11420f8ca57180897e15fcaab439ebca",
+ "disabled": false
+ },
+ "https://github.com/idrirap/ComfyUI-Lora-Auto-Trigger-Words": {
+ "hash": "05de5959e17c22352fa0218a6ae9b952f2901faa",
+ "disabled": false
+ },
+ "https://github.com/ltdrdata/ComfyUI-Manager.git": {
+ "hash": "7529774f0bc0f2af36db69b50d0aace4e3f93054",
+ "disabled": false
+ },
+ "https://github.com/pythongosssss/ComfyUI-WD14-Tagger": {
+ "hash": "b8a63949bc58df940aa306befd5f84534b80459d",
+ "disabled": false
+ },
+ "https://github.com/Fannovel16/comfyui_controlnet_aux": {
+ "hash": "e0a5be0890b3a6230f1f3edb0b15ba949039757f",
+ "disabled": false
+ },
+ "https://github.com/comfyanonymous/ComfyUI_experiments": {
+ "hash": "934dba9d206e4738e0dac26a09b51f1dffcb4e44",
+ "disabled": false
+ },
+ "https://github.com/lilly1987/ComfyUI_node_Lilly": {
+ "hash": "87d6fd0c35cd34159639a22300c7ac8e7eef9886",
+ "disabled": false
+ },
+ "https://github.com/BlenderNeko/ComfyUI_TiledKSampler": {
+ "hash": "6d7604c9b28f06a6337bc83d555825e362cece7a",
+ "disabled": false
+ },
+ "https://github.com/WASasquatch/FreeU_Advanced": {
+ "hash": "120c23a3f48618aaf9478076552261b5f111cf4b",
+ "disabled": false
+ },
+ "https://github.com/laksjdjf/LCMSampler-ComfyUI": {
+ "hash": "c2ab561f8ae7598b2313e7f3f6c91b106851b193",
+ "disabled": false
+ },
+ "https://github.com/city96/SD-Latent-Upscaler": {
+ "hash": "82b7b817a34ad9021137e1f08de287440f48eb03",
+ "disabled": false
+ },
+ "https://github.com/WASasquatch/was-node-suite-comfyui": {
+ "hash": "a55684c738a4a547f39bbb83658c8163a1cf4f5d",
+ "disabled": false
+ }
+ },
+ "file_custom_nodes": []
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI-Manager/snapshots/2023-12-08_21-22-44_autosave.json b/custom_nodes/ComfyUI-Manager/snapshots/2023-12-08_21-22-44_autosave.json
new file mode 100644
index 0000000000000000000000000000000000000000..82ebc8268f232041e655ac5dee4f4fe338e14c5b
--- /dev/null
+++ b/custom_nodes/ComfyUI-Manager/snapshots/2023-12-08_21-22-44_autosave.json
@@ -0,0 +1,86 @@
+{
+ "comfyui": "a4ec54a40d978c4249dc6a7e2d5133657d1fd109",
+ "git_custom_nodes": {
+ "https://github.com/laksjdjf/cd-tuner_negpip-ComfyUI": {
+ "hash": "c2aad505427d2d8ae3bc3c8650e1f407fac7cfec",
+ "disabled": false
+ },
+ "https://github.com/Kosinkadink/ComfyUI-Advanced-ControlNet": {
+ "hash": "b5e77ecc3f8cd274f13996bf05816c601d90006f",
+ "disabled": false
+ },
+ "https://github.com/pythongosssss/ComfyUI-Custom-Scripts": {
+ "hash": "27555d4f71bb4e24b87571f89eab2b4a06677bb6",
+ "disabled": false
+ },
+ "https://github.com/alt-key-project/comfyui-dream-project": {
+ "hash": "25cbdff39c5b74c9055c876532316d4926a64948",
+ "disabled": false
+ },
+ "https://github.com/ltdrdata/ComfyUI-Impact-Pack": {
+ "hash": "2111b2fedf05d8285cb88bc0367e523e077c34da",
+ "disabled": false
+ },
+ "https://github.com/idrirap/ComfyUI-Lora-Auto-Trigger-Words": {
+ "hash": "b63862f2e3004be7cd255b1d86349500a555ee1d",
+ "disabled": false
+ },
+ "https://github.com/ltdrdata/ComfyUI-Manager.git": {
+ "hash": "1d21359a5aade09956113aa58be2812bd5427d80",
+ "disabled": false
+ },
+ "https://github.com/jojkaart/ComfyUI-sampler-lcm-alternative.git": {
+ "hash": "0fc84c7d72d57763c39efa8b47eaa9aa83e2758e",
+ "disabled": false
+ },
+ "https://github.com/matan1905/ComfyUI-Serving-Toolkit": {
+ "hash": "4eae42c65e19d42252de2e2441e15a53bc9e1369",
+ "disabled": false
+ },
+ "https://github.com/pythongosssss/ComfyUI-WD14-Tagger": {
+ "hash": "b8a63949bc58df940aa306befd5f84534b80459d",
+ "disabled": false
+ },
+ "https://github.com/Fannovel16/comfyui_controlnet_aux": {
+ "hash": "e2fc116be0ccc4016dcd81224d61a9c9176de223",
+ "disabled": false
+ },
+ "https://github.com/comfyanonymous/ComfyUI_experiments": {
+ "hash": "934dba9d206e4738e0dac26a09b51f1dffcb4e44",
+ "disabled": false
+ },
+ "https://github.com/lilly1987/ComfyUI_node_Lilly": {
+ "hash": "87d6fd0c35cd34159639a22300c7ac8e7eef9886",
+ "disabled": false
+ },
+ "https://github.com/shiimizu/ComfyUI_smZNodes": {
+ "hash": "b1defa02d4d160e77c3d69d83212afd76de42e43",
+ "disabled": false
+ },
+ "https://github.com/BlenderNeko/ComfyUI_TiledKSampler": {
+ "hash": "25d1cd2145268b617b12e7a98f28aeb5f3752bbc",
+ "disabled": false
+ },
+ "https://github.com/kohya-ss/ControlNet-LLLite-ComfyUI": {
+ "hash": "103a4457d44fe0d3be586c6d6b32ec5ccdbd0793",
+ "disabled": false
+ },
+ "https://github.com/WASasquatch/FreeU_Advanced": {
+ "hash": "120c23a3f48618aaf9478076552261b5f111cf4b",
+ "disabled": false
+ },
+ "https://github.com/laksjdjf/IPAdapter-ComfyUI": {
+ "hash": "c39015c384cd433d38b4bbd3de276682ab74e0fc",
+ "disabled": false
+ },
+ "https://github.com/city96/SD-Latent-Upscaler": {
+ "hash": "82b7b817a34ad9021137e1f08de287440f48eb03",
+ "disabled": false
+ },
+ "https://github.com/WASasquatch/was-node-suite-comfyui": {
+ "hash": "4e53775e650a7e2d2d1d73056bb914d7edc57f69",
+ "disabled": false
+ }
+ },
+ "file_custom_nodes": []
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI-Manager/snapshots/the_snapshot_files_are_located_here b/custom_nodes/ComfyUI-Manager/snapshots/the_snapshot_files_are_located_here
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/custom_nodes/ComfyUI-Serving-Toolkit/.gitignore b/custom_nodes/ComfyUI-Serving-Toolkit/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..763624ebe547350200a2b9d538bdff0b90536f61
--- /dev/null
+++ b/custom_nodes/ComfyUI-Serving-Toolkit/.gitignore
@@ -0,0 +1 @@
+__pycache__/*
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI-Serving-Toolkit/README.md b/custom_nodes/ComfyUI-Serving-Toolkit/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..df8ef30dca5cad8f9ede4a2edf482be58cd32f10
--- /dev/null
+++ b/custom_nodes/ComfyUI-Serving-Toolkit/README.md
@@ -0,0 +1,107 @@
+# ComfyUI Serving Toolkit
+Welcome to the ComfyUI Serving Toolkit, a powerful tool for serving image generation workflows in Discord and other platforms (soon).
+This toolkit is designed to simplify the process of serving your ComfyUI workflow, making image generation bots easier than ever before.
+You can serve on discord, or on websockets. Checkout https://comfypixels.com for online serving
+
+
+## Features
+* Allows both Images or videos (when in batch mode, such as animatediff - if you return more than one image it will create a video)
+* Add arguments with default values, then allow your users to use them
+* Serve from your own computer, workflow is not inserted into the images so your secrets are 100% safe
+
+## Installation
+[Use ComfyUI Manager](https://github.com/ltdrdata/ComfyUI-Manager)
+or put all the files inside custom_nodes and run:
+```
+..\..\..\python_embeded\python.exe -s -m pip install -r requirements.txt
+```
+
+## The simplest configuration
+Here a simple workflow that will get a !generate \ and resond with an image
+
+
+You can copy the workflow json:
+[discordserv.json](https://github.com/matan1905/ComfyUI-Serving-Toolkit/files/13248566/discordserv.json)
+
+
+
+## Running
+After setting up your workflow, In order for the serving to always be up, you need to allow auto queue, here is an image to help you do that:
+
+
+
+#### This will require you to keep your ComfyUI and computer running. If you want to host your workflow, you can use [vast.ai](https://cloud.vast.ai/?ref_id=93071)
+
+
+## Nodes
+**DiscordServing**
+
+This node is an essencial part of the serving, queueing the prompt it will wait for a single message, process it and optionally return the image.
+Note that in order for it to work for all messages you would have to mark `Auto Queue` (details above in the running section)
+
+Inputs:
+* discord_token - [here is how you get one](https://www.writebots.com/discord-bot-token/) , make sure to enable message viewing intent
+* command_name - the command used to generate, without the '!'. defaults to generate (so you would have to do !generate \ --your_argument1 \
+
+Outputs:
+* Serving Config - A basic reference for this serving, used by the other nodes of this toolkit to get arguments and return images.
+
+**WebsocketServing**
+
+This will connect to a websocket and wait for JSON of {_requestId, prompt, arguments} and will return a json of {_requestId, base64_img}
+You can see an example ws server over at examples/websocket.js
+
+Inputs:
+* websocket_url - the url of the websocket you connect to, if you use the example it will be ws://localhost:8080
+
+Outputs:
+* Serving Config - A basic reference for this serving, used by the other nodes of this toolkit to get arguments and return images.
+
+
+
+**ServingInputText**
+
+Allows you to grab a text arguments from the request
+
+Discord example:
+
+When a user types: !generate 4k epic realism portrait --negative drawing
+you could set the argument=negative and then recieve the value of "drawing" inside the output text.
+
+
+Inputs:
+* serving_config - a config made by a serving node
+* argument - the argument name, the prompt itself will be inside the "prompt" argument. When using discord serving, you can access attachments url using 'attachment_url_0' (and attachment_url_1 etc). then you can use nodes like WAS Image Load to download these images
+* default - the default value of this argument
+
+Outputs:
+text - the value of the argument
+
+
+
+
+**ServingInputNumber**
+
+similar to ServingInputText, this one is for numbers. it is important to set the minimum, maximum and step to the right values in order to avoid errors (for example when trying a width that does isn't divisable by 16)
+Inputs that are not in ServingInputText:
+* max_value - the maximum value of this argument
+* min_value - the minimum value of this argument
+* step - the steps of this value (setting this to 1 will ensure only whole numbers, 0.5 will allow jumps of half etc)
+
+**ServingOutput**
+
+Allows you to return an image/video back to the request
+Inputs:
+* image - the generated image. note that if this is more than one image (for example in the case of batches or animatediff frames) it will return a video
+* duration - in the case of a video, what is the time in miliseconds each frame should appear? if you have an FPS number you can use 1000/FPS to calculate the duration value
+
+
+
+
+
+
+
+
+
+
+
diff --git a/custom_nodes/ComfyUI-Serving-Toolkit/__init__.py b/custom_nodes/ComfyUI-Serving-Toolkit/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..261c1c1403d665fa8ad9cb5fc7a717a880f3e484
--- /dev/null
+++ b/custom_nodes/ComfyUI-Serving-Toolkit/__init__.py
@@ -0,0 +1,6 @@
+import __main__
+
+from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
+
+
+__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
diff --git a/custom_nodes/ComfyUI-Serving-Toolkit/__pycache__/__init__.cpython-311.pyc b/custom_nodes/ComfyUI-Serving-Toolkit/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..88370f83ebc8454f23e7e093661e1d26d5d12bca
Binary files /dev/null and b/custom_nodes/ComfyUI-Serving-Toolkit/__pycache__/__init__.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI-Serving-Toolkit/__pycache__/discord_client.cpython-311.pyc b/custom_nodes/ComfyUI-Serving-Toolkit/__pycache__/discord_client.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..006573d6538a6c173687c0de5bc463cb496fb05b
Binary files /dev/null and b/custom_nodes/ComfyUI-Serving-Toolkit/__pycache__/discord_client.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI-Serving-Toolkit/__pycache__/nodes.cpython-311.pyc b/custom_nodes/ComfyUI-Serving-Toolkit/__pycache__/nodes.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..39fb3d4e7ee5a31c02b645f242f49416b0d1180b
Binary files /dev/null and b/custom_nodes/ComfyUI-Serving-Toolkit/__pycache__/nodes.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI-Serving-Toolkit/__pycache__/utils.cpython-311.pyc b/custom_nodes/ComfyUI-Serving-Toolkit/__pycache__/utils.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7dc49011a6e4174423868dbdc1d704f353786443
Binary files /dev/null and b/custom_nodes/ComfyUI-Serving-Toolkit/__pycache__/utils.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI-Serving-Toolkit/discord_client.py b/custom_nodes/ComfyUI-Serving-Toolkit/discord_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..bf82bfc95ed2b18486d0fc4a51b478830b1bc95d
--- /dev/null
+++ b/custom_nodes/ComfyUI-Serving-Toolkit/discord_client.py
@@ -0,0 +1,16 @@
+import discord
+from discord.ext import commands
+
+
+intents = discord.Intents.default()
+intents.message_content = True
+discord_client = commands.Bot(command_prefix='!', intents=intents)
+
+
+
+
+# Event handler for when the bot is ready
+@discord_client.event
+async def on_ready():
+ print(f'Logged in as {discord_client.user.name}. Ready to take requests!')
+
diff --git a/custom_nodes/ComfyUI-Serving-Toolkit/examples/websocket.js b/custom_nodes/ComfyUI-Serving-Toolkit/examples/websocket.js
new file mode 100644
index 0000000000000000000000000000000000000000..92a8105a20f0e33ba5f24f4bb105ee132f17b09e
--- /dev/null
+++ b/custom_nodes/ComfyUI-Serving-Toolkit/examples/websocket.js
@@ -0,0 +1,55 @@
+import WebSocket, { WebSocketServer } from 'ws';
+import fs from 'fs';
+
+
+const wss = new WebSocketServer({
+ port: 8080,
+});
+wss.on("connection", (ws) => {
+ console.log("WebSocket client connected");
+
+ // Event listener for receiving messages from the client
+ ws.on("message", (message) => {
+ try {
+ const parsed = JSON.parse(message)
+ saveBase64Image(parsed.base64_img, parsed._requestId + '_image.webp');
+ } catch (e) {
+ console.log("Error occured when getting a message", e)
+ }
+ });
+
+ // Event listener for the WebSocket connection closing
+ ws.on("close", () => {
+ console.log("WebSocket client disconnected");
+ });
+});
+
+function saveBase64Image(base64String, filePath) {
+ const binaryData = Buffer.from(base64String, 'base64');
+
+ fs.writeFile(filePath, binaryData, 'binary', (err) => {
+ if (err) {
+ console.error('Error saving the image:', err);
+ } else {
+ console.log('Image saved successfully:', filePath);
+ }
+ });
+}
+console.log("Listening on 8080")
+
+function sendMessage(message) {
+ wss.clients.forEach((client) => {
+ console.log("Messaging Everyone a hi")
+
+ if (client.readyState === WebSocket.OPEN) {
+ client.send(message);
+ }
+ });
+}
+
+
+let i = 0
+setInterval(() => sendMessage(JSON.stringify({
+ _requestId: ++i,
+ prompt: "Cow"
+})), 5000)
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI-Serving-Toolkit/nodes.py b/custom_nodes/ComfyUI-Serving-Toolkit/nodes.py
new file mode 100644
index 0000000000000000000000000000000000000000..bdb7a94e6c3cdcabd3e4489ec9c8e9c6594fbe5d
--- /dev/null
+++ b/custom_nodes/ComfyUI-Serving-Toolkit/nodes.py
@@ -0,0 +1,297 @@
+import time
+import threading
+from .discord_client import discord_client
+import threading
+from collections import deque
+from .utils import parse_command_string, tensorToImageConversion
+import discord
+import asyncio
+import websocket
+import json
+import base64
+
+
+
+class ServingOutput:
+ def __init__(self):
+ # start listening to api/discord
+ # when something happen, pass to serving manager with the details
+ pass
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "serving_config": ("SERVING_CONFIG",),
+ "image": ("IMAGE",),
+ "frame_duration": ("INT", {"default": 30, "min": 1, "step": 1, "max": 9999999}),
+ },
+ }
+
+ RETURN_TYPES = ()
+ # RETURN_NAMES = ("image_output_name",)
+
+ FUNCTION = "out"
+
+ OUTPUT_NODE = True
+
+ CATEGORY = "Serving-Toolkit"
+
+ def out(self, image,serving_config,frame_duration):
+ serving_config["serve_image_function"](image,frame_duration)
+ return {}
+
+
+class ServingInputText:
+ def __init__(self):
+ # start listening to api/discord
+ # when something happen, pass to serving manager with the details
+ pass
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "serving_config": ("SERVING_CONFIG",),
+ "argument": ("STRING", {
+ "multiline": False,
+ "default": "prompt"
+ }),
+ "default": ("STRING", {
+ "multiline": True,
+ "default": ""
+ }),
+ }
+ }
+
+ RETURN_TYPES = ("STRING",)
+ RETURN_NAMES = ("text",)
+
+ FUNCTION = "out"
+
+ CATEGORY = "Serving-Toolkit"
+
+ def out(self, serving_config, argument,default):
+ if argument not in serving_config:
+ return (default,)
+ return (serving_config[argument],)
+
+class ServingInputNumber:
+ def __init__(self):
+ # start listening to api/discord
+ # when something happen, pass to serving manager with the details
+ pass
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "serving_config": ("SERVING_CONFIG",),
+ "argument": ("STRING", {
+ "multiline": False,
+ "default": "number"
+ }),
+ "default": ("FLOAT", {"default": 0.0, "min": -999999.0, "max": 999999.0, "step": 0.0001}),
+ "min_value": ("FLOAT", {"default": -999999.0, "min": -999999.0, "max": 999999.0, "step": 0.0001}),
+ "max_value": ("FLOAT", {"default": 999999.0, "min": -999999.0, "max": 999999.0, "step": 0.0001}),
+ "step": ("FLOAT", {"default": 0.1, "min": -999999.0, "max": 999999.0, "step": 0.0001}),
+ }
+ }
+
+ RETURN_TYPES = ("FLOAT", "INT")
+
+ FUNCTION = "out"
+
+ CATEGORY = "Serving-Toolkit"
+
+ def out(self, serving_config, argument,default, min_value, max_value, step):
+ val = default
+ if argument in serving_config and serving_config[argument].replace('.','',1).isdigit():
+ val = serving_config[argument]
+ valFloat = min(max(float(val), min_value), max_value) // step * step
+ valInt = round(valFloat)
+ return (valFloat,valInt)
+
+
+class DiscordServing():
+ discord_running = False
+ def __init__(self):
+ self.registered_command = False
+ self.data_ready = threading.Event()
+ self.data = deque()
+ self.discord_token = None
+ pass
+
+ def discord_runner(self):
+ discord_client.run(self.discord_token)
+
+ def get_data(self):
+ if not self.data:
+ self.data_ready.wait()
+ data = self.data.popleft()
+ self.data_ready.clear()
+ return data
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "discord_token": ("STRING", {
+ "multiline": True,
+ "default": ""
+ }),
+ "command_name": ("STRING", {
+ "multiline": False,
+ "default": "generate"
+ })
+ }
+ }
+
+ RETURN_TYPES = ("SERVING_CONFIG",)
+ RETURN_NAMES = ("Serving config",)
+
+ FUNCTION = "serve"
+
+ @classmethod
+ def IS_CHANGED(cls, **kwargs):
+ return float("NaN")
+ # OUTPUT_NODE = False
+
+ CATEGORY = "Serving-Toolkit"
+
+ def serve(self, command_name, discord_token):
+ if not DiscordServing.discord_running:
+ self.discord_token = discord_token
+ run_discord = threading.Thread(target=self.discord_runner)
+ run_discord.start()
+ print("Client running")
+ DiscordServing.discord_running = True
+ if not self.registered_command:
+ self.registered_command = True
+ @discord_client.command(name=command_name)
+ async def execute(ctx):
+ parsed_data = parse_command_string(ctx.message.content,command_name)
+ def serve_image_function(image, frame_duration):
+ image_file = tensorToImageConversion(image, frame_duration)
+ asyncio.run_coroutine_threadsafe(ctx.reply(file=discord.File(image_file, filename='image.webp')), discord_client.loop)
+ parsed_data["serve_image_function"] = serve_image_function
+ parsed_data.update({f"attachment_url_{i}": attachment.url for i, attachment in enumerate(ctx.message.attachments)}) # populates all the attachments urls
+ self.data.append(parsed_data)
+ self.data_ready.set()
+
+ data = self.get_data()
+
+ return (data,)
+
+class WebSocketServing():
+ def __init__(self):
+ self.data_ready = threading.Event()
+ self.data = deque()
+ self.ws_running = False
+ self.websocket_url= None
+ self.ws = None
+ pass
+ def on_message(self,ws,message):
+ try:
+ parsed = json.loads(message)
+ self.data.append(parsed)
+ self.data_ready.set()
+ except Exception as e:
+ print("Error parsing JSON", e)
+
+
+ def on_close(self,ws):
+ print("WS Client closed!")
+
+ def on_error(self,ws,error):
+ print("WS Client error: ", error)
+ # Try to reconnect
+ time.sleep(1)
+ self.ws_runner()
+
+ def ws_runner(self):
+ print("Starting WS Client...")
+ self.ws = websocket.WebSocketApp( self.websocket_url,
+
+ on_message=self.on_message, on_close= self.on_close, on_error=self.on_error)
+ while True:
+ try:
+ self.ws.run_forever(reconnect=1,
+ ping_interval=10,
+ ping_timeout=5,)
+ except Exception as e:
+ print("WS Client error: ", e)
+ time.sleep(5)
+ continue
+ def get_data(self):
+ if not self.data:
+ self.data_ready.wait()
+ data = self.data.popleft()
+ self.data_ready.clear()
+ return data
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "websocket_url": ("STRING", {
+ "multiline": False,
+ "default": ""
+ })
+ }
+ }
+
+ RETURN_TYPES = ("SERVING_CONFIG",)
+ RETURN_NAMES = ("Serving config",)
+
+ FUNCTION = "serve"
+
+ @classmethod
+ def IS_CHANGED(cls, **kwargs):
+ return float("NaN")
+ # OUTPUT_NODE = False
+
+ CATEGORY = "Serving-Toolkit"
+
+ def serve(self, websocket_url):
+ if not self.ws_running:
+ self.websocket_url = websocket_url
+ threading.Thread(target=self.ws_runner).start()
+ print("WS Client running")
+ self.ws_running = True
+
+ data = self.get_data()
+ def serve_image_function(image, frame_duration):
+ image_file = tensorToImageConversion(image, frame_duration)
+ base64_img = base64.b64encode(image_file.read()).decode('utf-8')
+ response= {
+ "base64_img":base64_img,
+ "_requestId":data["_requestId"] # It's assumed that it will exist.
+ }
+ self.ws.send(json.dumps(response))
+ data["serve_image_function"] = serve_image_function
+
+ return (data,)
+
+
+# A dictionary that contains all nodes you want to export with their names
+# NOTE: names should be globally unique
+NODE_CLASS_MAPPINGS = {
+ "ServingOutput": ServingOutput,
+ "ServingInputText": ServingInputText,
+ "ServingInputNumber": ServingInputNumber,
+ "DiscordServing": DiscordServing,
+ "WebSocketServing": WebSocketServing
+}
+
+# A dictionary that contains the friendly/humanly readable titles for the nodes
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "ServingOutput": "Serving Output",
+ "DiscordServing": "Discord Serving",
+ "WebSocketServing": "WebSocket Serving",
+ "ServingInputText": "Serving Input Text",
+ "ServingInputNumber": "Serving Input Number",
+}
+
+
+# input - simply a push
diff --git a/custom_nodes/ComfyUI-Serving-Toolkit/requirements.txt b/custom_nodes/ComfyUI-Serving-Toolkit/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..cdd0bc95d7b932daaae1718cb85c36afbe468cb4
--- /dev/null
+++ b/custom_nodes/ComfyUI-Serving-Toolkit/requirements.txt
@@ -0,0 +1,3 @@
+discord.py
+websocket-client
+rel
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI-Serving-Toolkit/utils.py b/custom_nodes/ComfyUI-Serving-Toolkit/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..eedf58cc1b4a6f4a7ccaea6783123200d1675408
--- /dev/null
+++ b/custom_nodes/ComfyUI-Serving-Toolkit/utils.py
@@ -0,0 +1,40 @@
+def parse_command_string(command_string, command_name):
+ textAndArgs = command_string[1+ len(command_name):].strip().split('--')
+ result = {}
+ text = textAndArgs[0].strip()
+ args = textAndArgs[1:]
+ print(args)
+ # The first element is the "freeText" part, remove any leading or trailing whitespace.
+ result["prompt"] = text.strip()
+
+ for arg in args:
+ parts = arg.split()
+ if len(parts) > 1:
+ # Extract the argument name and value
+ arg_name = parts[0].strip()
+ arg_value = ' '.join(parts[1:]).strip()
+ result[arg_name] = arg_value
+
+
+ return result
+
+import io
+from PIL import Image, ImageSequence
+import numpy as np
+def tensorToImageConversion(images, duration):
+ # Create a list to store each image as a frame
+ frames = []
+
+ for img_tensor in images:
+ i = 255. * img_tensor.cpu().numpy()
+ img_pil = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
+ frames.append(img_pil)
+
+ # Create a GIF from the list of frames
+ img_byte_array = io.BytesIO()
+ frames[0].save(img_byte_array, save_all=True, append_images=frames[1:], format='WEBP', duration=duration, loop=0, quality=100, lossless=True)
+
+ img_byte_array.seek(0)
+ return img_byte_array
+
+
diff --git a/custom_nodes/ComfyUI-WD14-Tagger/.gitignore b/custom_nodes/ComfyUI-WD14-Tagger/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..10a149b2025c679b581013f18ed39fb03778247a
--- /dev/null
+++ b/custom_nodes/ComfyUI-WD14-Tagger/.gitignore
@@ -0,0 +1,2 @@
+models
+__pycache__
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI-WD14-Tagger/README.md b/custom_nodes/ComfyUI-WD14-Tagger/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..e153e43460219372937499da7a1c5a19daf31d7d
--- /dev/null
+++ b/custom_nodes/ComfyUI-WD14-Tagger/README.md
@@ -0,0 +1,49 @@
+# ComfyUI WD 1.4 Tagger
+
+A [ComfyUI](https://github.com/comfyanonymous/ComfyUI) extension allowing the interrogation of booru tags from images.
+
+Based on [SmilingWolf/wd-v1-4-tags](https://huggingface.co/spaces/SmilingWolf/wd-v1-4-tags) and [toriato/stable-diffusion-webui-wd14-tagger](https://github.com/toriato/stable-diffusion-webui-wd14-tagger)
+All models created by [SmilingWolf](https://huggingface.co/SmilingWolf)
+
+## Installation
+1. `git clone https://github.com/pythongosssss/ComfyUI-WD14-Tagger` into the `custom_nodes` folder
+ - e.g. `custom_nodes\ComfyUI-WD14-Tagger`
+2. Open a Command Prompt/Terminal/etc
+3. Change to the `custom_nodes\ComfyUI-WD14-Tagger` folder you just created
+ - e.g. `cd C:\ComfyUI_windows_portable\ComfyUI\custom_nodes\ComfyUI-WD14-Tagger` or wherever you have it installed
+4. Install python packages
+ - **Windows Standalone installation** (embedded python):
+ `../../../python_embeded/python.exe -s -m pip install -r requirements.txt`
+ - **Manual/non-Windows installation**
+ `pip install -r requirement.txt`
+
+## Usage
+Add the node via `image` -> `WD14Tagger|pysssss`
+
+Models are automatically downloaded at runtime if missing.
+
+Supports tagging and outputting multiple batched inputs.
+- **model**: The interrogation model to use. You can try them out here [WaifuDiffusion v1.4 Tags](https://huggingface.co/spaces/SmilingWolf/wd-v1-4-tags). The newest model (as of writing) is `MOAT` and the most popular is `ConvNextV2`.
+- **threshold**: The score for the tag to be considered valid
+- **character_threshold**: The score for the character tag to be considered valid
+- **exclude_tags** A comma separated list of tags that should not be included in the results
+
+Quick interrogation of images is also available on any node that is displaying an image, e.g. a `LoadImage`, `SaveImage`, `PreviewImage` node.
+Simply right click on the node (or if displaying multiple images, on the image you want to interrogate) and select `WD14 Tagger` from the menu
+
+
+Settings used for this are in the `settings` section of `pysssss.json`.
+
+### Offline Use
+Simplest way is to use it online, interrogate an image, and the model will be downloaded and cached, however if you want to manually download the models:
+- Create a `models` folder (in same folder as the `wd14tagger.py`)
+- Use URLs for models from the list in `pysssss.json`
+- Download `model.onnx` and name it with the model name e.g. `wd-v1-4-convnext-tagger-v2.onnx`
+- Download `selected_tags.csv` and name it with the model name e.g. `wd-v1-4-convnext-tagger-v2.csv`
+
+## Requirements
+`onnxruntime` (recommended, interrogation is still fast on CPU, included in requirements.txt)
+or `onnxruntime-gpu` (allows use of GPU, many people have issues with this, if you try I can't provide support for this)
+
+## Changelog
+- 2023-05-14 - Moved to own repo, add downloading models, support multiple inputs
diff --git a/custom_nodes/ComfyUI-WD14-Tagger/__init__.py b/custom_nodes/ComfyUI-WD14-Tagger/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..efe56a97a58e1e8841fc6a3eb2a71a872284c6ce
--- /dev/null
+++ b/custom_nodes/ComfyUI-WD14-Tagger/__init__.py
@@ -0,0 +1,5 @@
+from .pysssss import init
+
+if init(check_imports=["onnxruntime"]):
+ from .wd14tagger import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
+ __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
diff --git a/custom_nodes/ComfyUI-WD14-Tagger/__pycache__/__init__.cpython-310.pyc b/custom_nodes/ComfyUI-WD14-Tagger/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f0fa5f2f24a4ef860477b9d536ebfb20e6c8e23d
Binary files /dev/null and b/custom_nodes/ComfyUI-WD14-Tagger/__pycache__/__init__.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI-WD14-Tagger/__pycache__/__init__.cpython-311.pyc b/custom_nodes/ComfyUI-WD14-Tagger/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b9ce5a736466ab79b6298e5cff897fa44c1201ae
Binary files /dev/null and b/custom_nodes/ComfyUI-WD14-Tagger/__pycache__/__init__.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI-WD14-Tagger/__pycache__/pysssss.cpython-310.pyc b/custom_nodes/ComfyUI-WD14-Tagger/__pycache__/pysssss.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5da29ec6e9dd77d501db350339d0deb82b7b8bc7
Binary files /dev/null and b/custom_nodes/ComfyUI-WD14-Tagger/__pycache__/pysssss.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI-WD14-Tagger/__pycache__/pysssss.cpython-311.pyc b/custom_nodes/ComfyUI-WD14-Tagger/__pycache__/pysssss.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1d37188d6c4d06892026bf891ff5d45399a24992
Binary files /dev/null and b/custom_nodes/ComfyUI-WD14-Tagger/__pycache__/pysssss.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI-WD14-Tagger/__pycache__/wd14tagger.cpython-310.pyc b/custom_nodes/ComfyUI-WD14-Tagger/__pycache__/wd14tagger.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..cb220da57de6f661ae45e50b743242ee26325403
Binary files /dev/null and b/custom_nodes/ComfyUI-WD14-Tagger/__pycache__/wd14tagger.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI-WD14-Tagger/__pycache__/wd14tagger.cpython-311.pyc b/custom_nodes/ComfyUI-WD14-Tagger/__pycache__/wd14tagger.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..bfcd2543a999f3e298976b089d362dd52d828cd5
Binary files /dev/null and b/custom_nodes/ComfyUI-WD14-Tagger/__pycache__/wd14tagger.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI-WD14-Tagger/js/wd14tagger.js b/custom_nodes/ComfyUI-WD14-Tagger/js/wd14tagger.js
new file mode 100644
index 0000000000000000000000000000000000000000..346951b2138da8306cda71b5efb5570c07c3d117
--- /dev/null
+++ b/custom_nodes/ComfyUI-WD14-Tagger/js/wd14tagger.js
@@ -0,0 +1,141 @@
+import { app } from "/scripts/app.js";
+import { ComfyWidgets } from "/scripts/widgets.js";
+import { api } from "/scripts/api.js";
+
+class Pysssss {
+ constructor() {
+ if (!window.__pysssss__) {
+ window.__pysssss__ = Symbol("__pysssss__");
+ }
+ this.symbol = window.__pysssss__;
+ }
+
+ getState(node) {
+ return node[this.symbol] || {};
+ }
+
+ setState(node, state) {
+ node[this.symbol] = state;
+ app.canvas.setDirty(true);
+ }
+
+ addStatusTagHandler(nodeType) {
+ if (nodeType[this.symbol]?.statusTagHandler) {
+ return;
+ }
+ if (!nodeType[this.symbol]) {
+ nodeType[this.symbol] = {};
+ }
+ nodeType[this.symbol] = {
+ statusTagHandler: true,
+ };
+
+ api.addEventListener("pysssss/update_status", ({ detail }) => {
+ let { node, progress, text } = detail;
+ const n = app.graph.getNodeById(+(node || app.runningNodeId));
+ if (!n) return;
+ const state = this.getState(n);
+ state.status = Object.assign(state.status || {}, { progress: text ? progress : null, text: text || null });
+ this.setState(n, state);
+ });
+
+ const self = this;
+ const onDrawForeground = nodeType.prototype.onDrawForeground;
+ nodeType.prototype.onDrawForeground = function (ctx) {
+ const r = onDrawForeground?.apply?.(this, arguments);
+ const state = self.getState(this);
+ if (!state?.status?.text) {
+ return r;
+ }
+
+ const { fgColor, bgColor, text, progress, progressColor } = { ...state.status };
+
+ ctx.save();
+ ctx.font = "12px sans-serif";
+ const sz = ctx.measureText(text);
+ ctx.fillStyle = bgColor || "dodgerblue";
+ ctx.beginPath();
+ ctx.roundRect(0, -LiteGraph.NODE_TITLE_HEIGHT - 20, sz.width + 12, 20, 5);
+ ctx.fill();
+
+ if (progress) {
+ ctx.fillStyle = progressColor || "green";
+ ctx.beginPath();
+ ctx.roundRect(0, -LiteGraph.NODE_TITLE_HEIGHT - 20, (sz.width + 12) * progress, 20, 5);
+ ctx.fill();
+ }
+
+ ctx.fillStyle = fgColor || "#fff";
+ ctx.fillText(text, 6, -LiteGraph.NODE_TITLE_HEIGHT - 6);
+ ctx.restore();
+ return r;
+ };
+ }
+}
+
+const pysssss = new Pysssss();
+
+app.registerExtension({
+ name: "pysssss.Wd14Tagger",
+ async beforeRegisterNodeDef(nodeType, nodeData, app) {
+ pysssss.addStatusTagHandler(nodeType);
+
+ if (nodeData.name === "WD14Tagger|pysssss") {
+ const onExecuted = nodeType.prototype.onExecuted;
+ nodeType.prototype.onExecuted = function (message) {
+ const r = onExecuted?.apply?.(this, arguments);
+
+ const pos = this.widgets.findIndex((w) => w.name === "tags");
+ if (pos !== -1) {
+ for (let i = pos; i < this.widgets.length; i++) {
+ this.widgets[i].onRemove?.();
+ }
+ this.widgets.length = pos;
+ }
+
+ for (const list of message.tags) {
+ const w = ComfyWidgets["STRING"](this, "tags", ["STRING", { multiline: true }], app).widget;
+ w.inputEl.readOnly = true;
+ w.inputEl.style.opacity = 0.6;
+ w.value = list;
+ }
+
+ this.onResize?.(this.size);
+
+ return r;
+ };
+ } else {
+ const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
+ nodeType.prototype.getExtraMenuOptions = function (_, options) {
+ const r = getExtraMenuOptions?.apply?.(this, arguments);
+ let img;
+ if (this.imageIndex != null) {
+ // An image is selected so select that
+ img = this.imgs[this.imageIndex];
+ } else if (this.overIndex != null) {
+ // No image is selected but one is hovered
+ img = this.imgs[this.overIndex];
+ }
+ if (img) {
+ let pos = options.findIndex((o) => o.content === "Save Image");
+ if (pos === -1) {
+ pos = 0;
+ } else {
+ pos++;
+ }
+ options.splice(pos, 0, {
+ content: "WD14 Tagger",
+ callback: async () => {
+ let src = img.src;
+ src = src.replace("/view?", `/pysssss/wd14tagger/tag?node=${this.id}&clientId=${api.clientId}&`);
+ const res = await (await fetch(src)).json();
+ alert(res);
+ },
+ });
+ }
+
+ return r;
+ };
+ }
+ },
+});
diff --git a/custom_nodes/ComfyUI-WD14-Tagger/pysssss.json b/custom_nodes/ComfyUI-WD14-Tagger/pysssss.json
new file mode 100644
index 0000000000000000000000000000000000000000..e785dc3707c0504d680369d7a24dcfe7653df966
--- /dev/null
+++ b/custom_nodes/ComfyUI-WD14-Tagger/pysssss.json
@@ -0,0 +1,19 @@
+{
+ "name": "WD14Tagger",
+ "logging": false,
+ "settings": {
+ "model": "wd-v1-4-moat-tagger-v2",
+ "threshold": 0.35,
+ "character_threshold": 0.85,
+ "exclude_tags": ""
+ },
+ "models": [
+ {
+ "wd-v1-4-moat-tagger-v2": "https://huggingface.co/SmilingWolf/wd-v1-4-moat-tagger-v2",
+ "wd-v1-4-convnextv2-tagger-v2": "https://huggingface.co/SmilingWolf/wd-v1-4-convnextv2-tagger-v2",
+ "wd-v1-4-convnext-tagger-v2": "https://huggingface.co/SmilingWolf/wd-v1-4-convnext-tagger-v2",
+ "wd-v1-4-convnext-tagger": "https://huggingface.co/SmilingWolf/wd-v1-4-convnext-tagger",
+ "wd-v1-4-vit-tagger-v2": "https://huggingface.co/SmilingWolf/wd-v1-4-vit-tagger-v2"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI-WD14-Tagger/pysssss.py b/custom_nodes/ComfyUI-WD14-Tagger/pysssss.py
new file mode 100644
index 0000000000000000000000000000000000000000..5886a74aa550d0e273580f77102c813a2073064d
--- /dev/null
+++ b/custom_nodes/ComfyUI-WD14-Tagger/pysssss.py
@@ -0,0 +1,202 @@
+import asyncio
+import os
+import json
+import shutil
+import inspect
+import aiohttp
+from server import PromptServer
+from tqdm import tqdm
+
+config = None
+
+
+def is_logging_enabled():
+ config = get_extension_config()
+ if "logging" not in config:
+ return False
+ return config["logging"]
+
+
+def log(message, type=None, always=False):
+ if not always and not is_logging_enabled():
+ return
+
+ if type is not None:
+ message = f"[{type}] {message}"
+
+ name = get_extension_config()["name"]
+
+ print(f"(pysssss:{name}) {message}")
+
+
+def get_ext_dir(subpath=None, mkdir=False):
+ dir = os.path.dirname(__file__)
+ if subpath is not None:
+ dir = os.path.join(dir, subpath)
+
+ dir = os.path.abspath(dir)
+
+ if mkdir and not os.path.exists(dir):
+ os.makedirs(dir)
+ return dir
+
+
+def get_comfy_dir(subpath=None):
+ dir = os.path.dirname(inspect.getfile(PromptServer))
+ if subpath is not None:
+ dir = os.path.join(dir, subpath)
+
+ dir = os.path.abspath(dir)
+
+ return dir
+
+
+def get_web_ext_dir():
+ config = get_extension_config()
+ name = config["name"]
+ dir = get_comfy_dir("web/extensions/pysssss")
+ if not os.path.exists(dir):
+ os.makedirs(dir)
+ dir += "/" + name
+ return dir
+
+
+def get_extension_config(reload=False):
+ global config
+ if reload == False and config is not None:
+ return config
+
+ config_path = get_ext_dir("pysssss.json")
+ if not os.path.exists(config_path):
+ log("Missing pysssss.json, this extension may not work correctly. Please reinstall the extension.",
+ type="ERROR", always=True)
+ print(f"Extension path: {get_ext_dir()}")
+ return {"name": "Unknown", "version": -1}
+ with open(config_path, "r") as f:
+ config = json.loads(f.read())
+ return config
+
+
+def link_js(src, dst):
+ try:
+ os.symlink(src, dst)
+ return True
+ except:
+ return False
+
+
+def install_js():
+ src_dir = get_ext_dir("js")
+ if not os.path.exists(src_dir):
+ log("No JS")
+ return
+
+ dst_dir = get_web_ext_dir()
+
+ if os.path.exists(dst_dir):
+ if os.path.islink(dst_dir):
+ log("JS already linked")
+ return
+ elif link_js(src_dir, dst_dir):
+ log("JS linked")
+ return
+
+ log("Copying JS files")
+ shutil.copytree(src_dir, dst_dir, dirs_exist_ok=True)
+
+
+def init(check_imports):
+ log("Init")
+
+ if check_imports is not None:
+ import importlib.util
+ for imp in check_imports:
+ spec = importlib.util.find_spec(imp)
+ if spec is None:
+ log(f"{imp} is required, please check requirements are installed.", type="ERROR", always=True)
+ return False
+
+ install_js()
+ return True
+
+
+async def download_to_file(url, destination, update_callback, is_ext_subpath=True, session=None):
+ close_session = False
+ if session is None:
+ close_session = True
+ loop = None
+ try:
+ loop = asyncio.get_event_loop()
+ except:
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
+
+ session = aiohttp.ClientSession(loop=loop)
+ if is_ext_subpath:
+ destination = get_ext_dir(destination)
+ try:
+ async with session.get(url) as response:
+ size = int(response.headers.get('content-length', 0)) or None
+
+ with tqdm(
+ unit='B', unit_scale=True, miniters=1, desc=url.split('/')[-1], total=size,
+ ) as progressbar:
+ with open(destination, mode='wb') as f:
+ perc = 0
+ async for chunk in response.content.iter_chunked(2048):
+ f.write(chunk)
+ progressbar.update(len(chunk))
+ if update_callback is not None and progressbar.total is not None and progressbar.total != 0:
+ last = perc
+ perc = round(progressbar.n / progressbar.total, 2)
+ if perc != last:
+ last = perc
+ await update_callback(perc)
+ finally:
+ if close_session and session is not None:
+ await session.close()
+
+
+def wait_for_async(async_fn, loop=None):
+ res = []
+
+ async def run_async():
+ r = await async_fn()
+ res.append(r)
+
+ if loop is None:
+ try:
+ loop = asyncio.get_event_loop()
+ except:
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
+
+ loop.run_until_complete(run_async())
+
+ return res[0]
+
+def update_node_status(client_id, node, text, progress=None):
+ if client_id is None:
+ client_id = PromptServer.instance.client_id
+
+ if client_id is None:
+ return
+
+ PromptServer.instance.send_sync("pysssss/update_status", {
+ "node": node,
+ "progress": progress,
+ "text": text
+ }, client_id)
+
+async def update_node_status_async(client_id, node, text, progress=None):
+ if client_id is None:
+ client_id = PromptServer.instance.client_id
+
+ if client_id is None:
+ return
+
+ await PromptServer.instance.send("pysssss/update_status", {
+ "node": node,
+ "progress": progress,
+ "text": text
+ }, client_id)
diff --git a/custom_nodes/ComfyUI-WD14-Tagger/requirements.txt b/custom_nodes/ComfyUI-WD14-Tagger/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..51decf87adad3e5697ac7a7e325a119339eab6b9
--- /dev/null
+++ b/custom_nodes/ComfyUI-WD14-Tagger/requirements.txt
@@ -0,0 +1 @@
+onnxruntime
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI-WD14-Tagger/wd14tagger.py b/custom_nodes/ComfyUI-WD14-Tagger/wd14tagger.py
new file mode 100644
index 0000000000000000000000000000000000000000..045b6f832ca75d81a0d6ddf686b8683e048ce3ab
--- /dev/null
+++ b/custom_nodes/ComfyUI-WD14-Tagger/wd14tagger.py
@@ -0,0 +1,179 @@
+# https://huggingface.co/spaces/SmilingWolf/wd-v1-4-tags
+
+import comfy.utils
+import asyncio
+import aiohttp
+import numpy as np
+import csv
+import os
+import sys
+import onnxruntime as ort
+from onnxruntime import InferenceSession
+from PIL import Image
+from server import PromptServer
+from aiohttp import web
+from .pysssss import get_ext_dir, get_comfy_dir, download_to_file, update_node_status, wait_for_async, get_extension_config
+sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), "comfy"))
+
+
+config = get_extension_config()
+
+defaults = {
+ "model": "wd-v1-4-moat-tagger-v2",
+ "threshold": 0.35,
+ "character_threshold": 0.85,
+ "exclude_tags": ""
+}
+defaults.update(config.get("settings", {}))
+
+models_dir = get_ext_dir("models", mkdir=True)
+all_models = ("wd-v1-4-moat-tagger-v2",
+ "wd-v1-4-convnext-tagger-v2", "wd-v1-4-convnext-tagger",
+ "wd-v1-4-convnextv2-tagger-v2", "wd-v1-4-vit-tagger-v2")
+
+
+def get_installed_models():
+ return filter(lambda x: x.endswith(".onnx"), os.listdir(models_dir))
+
+
+async def tag(image, model_name, threshold=0.35, character_threshold=0.85, exclude_tags="", client_id=None, node=None):
+ if model_name.endswith(".onnx"):
+ model_name = model_name[0:-5]
+ installed = list(get_installed_models())
+ if not any(model_name + ".onnx" in s for s in installed):
+ await download_model(model_name, client_id, node)
+
+ name = os.path.join(models_dir, model_name + ".onnx")
+ model = InferenceSession(name, providers=ort.get_available_providers())
+
+ input = model.get_inputs()[0]
+ height = input.shape[1]
+
+ # Reduce to max size and pad with white
+ ratio = float(height)/max(image.size)
+ new_size = tuple([int(x*ratio) for x in image.size])
+ image = image.resize(new_size, Image.LANCZOS)
+ square = Image.new("RGB", (height, height), (255, 255, 255))
+ square.paste(image, ((height-new_size[0])//2, (height-new_size[1])//2))
+
+ image = np.array(square).astype(np.float32)
+ image = image[:, :, ::-1] # RGB -> BGR
+ image = np.expand_dims(image, 0)
+
+ # Read all tags from csv and locate start of each category
+ tags = []
+ general_index = None
+ character_index = None
+ with open(os.path.join(models_dir, model_name + ".csv")) as f:
+ reader = csv.reader(f)
+ next(reader)
+ for row in reader:
+ if general_index is None and row[2] == "0":
+ general_index = reader.line_num - 2
+ elif character_index is None and row[2] == "4":
+ character_index = reader.line_num - 2
+ tags.append(row[1])
+
+ label_name = model.get_outputs()[0].name
+ probs = model.run([label_name], {input.name: image})[0]
+
+ result = list(zip(tags, probs[0]))
+
+ # rating = max(result[:general_index], key=lambda x: x[1])
+ general = [item for item in result[general_index:character_index] if item[1] > threshold]
+ character = [item for item in result[character_index:] if item[1] > character_threshold]
+
+ all = character + general
+ remove = [s.strip() for s in exclude_tags.lower().split(",")]
+ all = [tag for tag in all if tag[0] not in remove]
+
+ res = ", ".join((item[0].replace("(", "\\(").replace(")", "\\)") for item in all))
+
+ print(res)
+ return res
+
+
+async def download_model(model, client_id, node):
+ url = f"https://huggingface.co/SmilingWolf/{model}/resolve/main/"
+ async with aiohttp.ClientSession(loop=asyncio.get_event_loop()) as session:
+ async def update_callback(perc):
+ nonlocal client_id
+ message = ""
+ if perc < 100:
+ message = f"Downloading {model}"
+ update_node_status(client_id, node, message, perc)
+
+ await download_to_file(
+ f"{url}model.onnx", os.path.join("models",f"{model}.onnx"), update_callback, session=session)
+ await download_to_file(
+ f"{url}selected_tags.csv", os.path.join("models",f"{model}.csv"), update_callback, session=session)
+
+ update_node_status(client_id, node, None)
+
+ return web.Response(status=200)
+
+
+@PromptServer.instance.routes.get("/pysssss/wd14tagger/tag")
+async def get_tags(request):
+ if "filename" not in request.rel_url.query:
+ return web.Response(status=404)
+
+ type = request.query.get("type", "output")
+ if type not in ["output", "input", "temp"]:
+ return web.Response(status=400)
+
+ target_dir = get_comfy_dir(type)
+ image_path = os.path.abspath(os.path.join(
+ target_dir, request.query.get("subfolder", ""), request.query["filename"]))
+ c = os.path.commonpath((image_path, target_dir))
+ if os.path.commonpath((image_path, target_dir)) != target_dir:
+ return web.Response(status=403)
+
+ if not os.path.isfile(image_path):
+ return web.Response(status=404)
+
+ image = Image.open(image_path)
+
+ models = get_installed_models()
+ model = next(models, defaults["model"])
+
+ return web.json_response(await tag(image, model, client_id=request.rel_url.query.get("clientId", ""), node=request.rel_url.query.get("node", "")))
+
+
+class WD14Tagger:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {
+ "image": ("IMAGE", ),
+ "model": (all_models, ),
+ "threshold": ("FLOAT", {"default": defaults["threshold"], "min": 0.0, "max": 1, "step": 0.05}),
+ "character_threshold": ("FLOAT", {"default": defaults["character_threshold"], "min": 0.0, "max": 1, "step": 0.05}),
+ "exclude_tags": ("STRING", {"default": defaults["exclude_tags"]}),
+ }}
+
+ RETURN_TYPES = ("STRING",)
+ OUTPUT_IS_LIST = (True,)
+ FUNCTION = "tag"
+ OUTPUT_NODE = True
+
+ CATEGORY = "image"
+
+ def tag(self, image, model, threshold, character_threshold, exclude_tags=""):
+ tensor = image*255
+ tensor = np.array(tensor, dtype=np.uint8)
+
+ pbar = comfy.utils.ProgressBar(tensor.shape[0])
+ tags = []
+ for i in range(tensor.shape[0]):
+ image = Image.fromarray(tensor[i])
+ tags.append(wait_for_async(lambda: tag(image, model, threshold, character_threshold, exclude_tags)))
+ pbar.update(1)
+ return {"ui": {"tags": tags}, "result": (tags,)}
+
+
+NODE_CLASS_MAPPINGS = {
+ "WD14Tagger|pysssss": WD14Tagger,
+}
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "WD14Tagger|pysssss": "WD14 Tagger 🐍",
+}
diff --git a/custom_nodes/ComfyUI-sampler-lcm-alternative/.gitignore b/custom_nodes/ComfyUI-sampler-lcm-alternative/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..68bc17f9ff2104a9d7b6777058bb4c343ca72609
--- /dev/null
+++ b/custom_nodes/ComfyUI-sampler-lcm-alternative/.gitignore
@@ -0,0 +1,160 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/#use-with-ide
+.pdm.toml
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
diff --git a/custom_nodes/ComfyUI-sampler-lcm-alternative/LICENSE b/custom_nodes/ComfyUI-sampler-lcm-alternative/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..f288702d2fa16d3cdf0035b15a9fcbc552cd88e7
--- /dev/null
+++ b/custom_nodes/ComfyUI-sampler-lcm-alternative/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/custom_nodes/ComfyUI-sampler-lcm-alternative/README.md b/custom_nodes/ComfyUI-sampler-lcm-alternative/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..ffeba323f218e44d41d8afc936f351bd34654948
--- /dev/null
+++ b/custom_nodes/ComfyUI-sampler-lcm-alternative/README.md
@@ -0,0 +1,23 @@
+# ComfyUI-sampler-lcm-alternative
+ComfyUI Custom Sampler nodes that add a new improved LCM sampler functions
+
+This custom node repository adds three new nodes for ComfyUI to the Custom Sampler category. SamplerLCMAlternative, SamplerLCMCycle and LCMScheduler (just to save a few clicks, as you could also use the BasicScheduler and choose smg_uniform).
+Just clone it into your custom_nodes folder and you can start using it as soon as you restart ComfyUI.
+
+SamplerLCMAlternative has two extra parameters.
+- `euler_steps`, which tells the sampler to use Euler sampling for the first n steps (or skip euler only for last n steps if n is negative).
+- `ancestral`, If you give this a value above 0.0, the Euler steps get some fresh randomness injected each step. The value controls how much.
+
+With default parameters, this sampler acts exactly like the original LCM sampler from ComfyUI. When you start tuning, I recommend starting by setting `euler_steps` to half of the total step count this sampler will be handling. going higher will increase details/sharpness and lower will decrease both.
+
+SamplerLCMCycle has three extra parameters. This sampler repeats a cycle of Euler and LCM sampling steps until inference is done.
+If you're doing txt2img with LCM and feel like LCM is giving boring or artificial looking images, give this sampler a try.
+- `euler_steps`, sets the number of euler steps per cycle
+- `lcm_steps`, sets the number of lcm steps per cycle
+- `ancestral`, same as with SamplerLCMAlternative
+
+The default settings should work fine. I recommend using at least 6 steps to allow for 2 full cycles, that said, this sampler seems to really benefit from extra steps.
+
+Here's an example workflow for how to use SamplerLCMCycle:
+
+
diff --git a/custom_nodes/ComfyUI-sampler-lcm-alternative/SamplerLCMCycle-example.png b/custom_nodes/ComfyUI-sampler-lcm-alternative/SamplerLCMCycle-example.png
new file mode 100644
index 0000000000000000000000000000000000000000..4523b3f9ac70b5618ec8137886232f9a5e03a553
--- /dev/null
+++ b/custom_nodes/ComfyUI-sampler-lcm-alternative/SamplerLCMCycle-example.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6956e88128295cdb8ecf440e4558171f88beb796961f4bd9eadbf24a1996aea6
+size 1794813
diff --git a/custom_nodes/ComfyUI-sampler-lcm-alternative/__init__.py b/custom_nodes/ComfyUI-sampler-lcm-alternative/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..a23196efd7da5a58f92ef2e3919cbe02ece52190
--- /dev/null
+++ b/custom_nodes/ComfyUI-sampler-lcm-alternative/__init__.py
@@ -0,0 +1,3 @@
+from .sampler_lcm_alt import NODE_CLASS_MAPPINGS
+
+__all__ = ['NODE_CLASS_MAPPINGS']
diff --git a/custom_nodes/ComfyUI-sampler-lcm-alternative/__pycache__/__init__.cpython-311.pyc b/custom_nodes/ComfyUI-sampler-lcm-alternative/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d4160c78fc7ba7d635f19ec665df4b78d2834de8
Binary files /dev/null and b/custom_nodes/ComfyUI-sampler-lcm-alternative/__pycache__/__init__.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI-sampler-lcm-alternative/__pycache__/sampler_lcm_alt.cpython-311.pyc b/custom_nodes/ComfyUI-sampler-lcm-alternative/__pycache__/sampler_lcm_alt.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0f7056c0b885bf58cc5146f7bd6f1eb3a731c3d4
Binary files /dev/null and b/custom_nodes/ComfyUI-sampler-lcm-alternative/__pycache__/sampler_lcm_alt.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI-sampler-lcm-alternative/sampler_lcm_alt.py b/custom_nodes/ComfyUI-sampler-lcm-alternative/sampler_lcm_alt.py
new file mode 100644
index 0000000000000000000000000000000000000000..b9221189e3b27d016a3e11f8bad6d1d56ab84dde
--- /dev/null
+++ b/custom_nodes/ComfyUI-sampler-lcm-alternative/sampler_lcm_alt.py
@@ -0,0 +1,120 @@
+import comfy.samplers
+from comfy.k_diffusion.sampling import default_noise_sampler
+from tqdm.auto import trange, tqdm
+from itertools import product
+import torch
+
+@torch.no_grad()
+def sample_lcm_alt(model, x, sigmas, extra_args=None, callback=None, disable=None, noise_sampler=None, euler_steps=0, ancestral=0.0, noise_mult = 1.0):
+ extra_args = {} if extra_args is None else extra_args
+ noise_sampler = default_noise_sampler(x) if noise_sampler is None else noise_sampler
+ s_in = x.new_ones([x.shape[0]])
+ steps = len(sigmas)-1
+ euler_limit = euler_steps%steps
+ loop_control = [True] * euler_limit + [False] * (steps - euler_limit)
+ return sample_lcm_backbone(model, x, sigmas, extra_args, callback, disable, noise_sampler, loop_control, ancestral, noise_mult)
+
+@torch.no_grad()
+def sample_lcm_cycle(model, x, sigmas, extra_args=None, callback=None, disable=None, noise_sampler=None, euler_steps = 1, lcm_steps = 1, tweak_sigmas = False, ancestral=0.0):
+ extra_args = {} if extra_args is None else extra_args
+ noise_sampler = default_noise_sampler(x) if noise_sampler is None else noise_sampler
+ s_in = x.new_ones([x.shape[0]])
+ steps = len(sigmas) - 2
+ cycle_length = euler_steps + lcm_steps
+ repeats = steps // (cycle_length)
+ leftover = steps % (cycle_length)
+ cycle = [True] * euler_steps + [False] * lcm_steps
+ loop_control = cycle * repeats + cycle[-leftover:] #+ [False]
+ if tweak_sigmas:
+ index_map = torch.tensor([i + j * repeats for i,j in product(range(repeats),range(cycle_length))] +
+ list(range(cycle_length*repeats,len(sigmas)))).to(sigmas.device)
+ sigmas = torch.index_select(sigmas, 0, index_map)
+ return sample_lcm_backbone(model, x, sigmas, extra_args, callback, disable, noise_sampler, loop_control, ancestral)
+
+@torch.no_grad()
+def sample_lcm_backbone(model, x, sigmas, extra_args, callback, disable, noise_sampler, loop_control, ancestral, noise_mult = 1.0):
+ s_in = x.new_ones([x.shape[0]])
+ for i in trange(len(sigmas) - 1, disable=disable):
+ denoised = model(x, sigmas[i] * s_in, **extra_args)
+ if callback is not None:
+ callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigmas[i], 'denoised': denoised})
+
+ if sigmas[i + 1] > 0:
+ if loop_control[i]:
+ if ancestral < 1.0:
+ removed_noise = (x - denoised) / sigmas[i]
+ if ancestral > 0.0:
+ noise = noise_sampler(sigmas[i], sigmas[i + 1])
+ if ancestral < 1.0:
+ noise = (ancestral**0.5) * noise + ((1.0 - ancestral)**0.5) * removed_noise
+ elif ancestral == 0.0:
+ noise = removed_noise*noise_mult
+ else:
+ noise = noise_sampler(sigmas[i], sigmas[i + 1])
+ else:
+ noise = None
+ x = denoised
+ if noise is not None:
+ x += sigmas[i + 1] * noise
+ return x
+
+class LCMScheduler:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required":
+ {"model": ("MODEL",),
+ "steps": ("INT", {"default": 8, "min": 1, "max": 10000}),
+ }
+ }
+ RETURN_TYPES = ("SIGMAS",)
+ CATEGORY = "sampling/custom_sampling/schedulers"
+
+ FUNCTION = "get_sigmas"
+
+ def get_sigmas(self, model, steps):
+ sigmas = comfy.samplers.calculate_sigmas_scheduler(model.model, "sgm_uniform", steps).cpu()
+ return (sigmas, )
+
+class SamplerLCMAlternative:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required":
+ {"euler_steps": ("INT", {"default": 0, "min": -10000, "max": 10000}),
+ "ancestral": ("FLOAT", {"default": 0, "min": 0, "max": 1.0, "step": 0.01, "round": True}),
+ "noise_mult": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 2.0, "step": 0.001, "round": True}),
+ }
+ }
+ RETURN_TYPES = ("SAMPLER",)
+ CATEGORY = "sampling/custom_sampling/samplers"
+
+ FUNCTION = "get_sampler"
+
+ def get_sampler(self, euler_steps, ancestral, noise_mult):
+ sampler = comfy.samplers.KSAMPLER(sample_lcm_alt, extra_options={"euler_steps": euler_steps, "noise_mult": noise_mult, "ancestral": ancestral})
+ return (sampler, )
+
+class SamplerLCMCycle:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required":
+ {"euler_steps": ("INT", {"default": 1, "min": 1, "max": 50}),
+ "lcm_steps": ("INT", {"default": 2, "min": 1, "max": 50}),
+ "tweak_sigmas": ("BOOLEAN", {"default": False}),
+ "ancestral": ("FLOAT", {"default": 0, "min": 0, "max": 1.0, "step": 0.01, "round": False}),
+ }
+ }
+ RETURN_TYPES = ("SAMPLER",)
+ CATEGORY = "sampling/custom_sampling/samplers"
+
+ FUNCTION = "get_sampler"
+
+ def get_sampler(self, euler_steps, lcm_steps, tweak_sigmas, ancestral):
+ sampler = comfy.samplers.KSAMPLER(sample_lcm_cycle, extra_options={"euler_steps": euler_steps, "lcm_steps": lcm_steps, "tweak_sigmas": tweak_sigmas, "ancestral": ancestral})
+ return (sampler, )
+
+
+NODE_CLASS_MAPPINGS = {
+ "LCMScheduler": LCMScheduler,
+ "SamplerLCMAlternative": SamplerLCMAlternative,
+ "SamplerLCMCycle": SamplerLCMCycle,
+}
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/Animation_Nodes.md b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/Animation_Nodes.md
new file mode 100644
index 0000000000000000000000000000000000000000..aa1495fba22d6decd42ae0829ce6c609e921950a
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/Animation_Nodes.md
@@ -0,0 +1,203 @@
+# CR Animation Nodes
+A comprehensive suite of nodes to enhance your animations
+
+These nodes include some features similar to Deforum, and also some new ideas.
+
+If you would like to contribute to this project with suggestions or feedback then please DM me on the AI Revolution discord, or add issues or feature requests here on Github.
+
+## CivitAI Post
+[ComfyUI - CR Animation Nodes](https://civitai.com/models/137333/comfyui-cr-animation-nodes)
+
+## Example Videos
+These YouTube short videos were made by [AI Music Experiment](https://www.youtube.com/channel/UCypaKOXWzzTxDvr3jWlfD6g) and made use of early versions of the nodes.
+
+[Cat Morph](https://www.youtube.com/shorts/kiSO-8i4RZ4)
+
+[Wolf Girl](https://www.youtube.com/shorts/bDWL5GIbmvs)
+
+## Demo Workflows
+Demo workflows are available on CivitAI. These are designed to demonstrate how the nodes function. They are not full animation workflows. Full template workflows will be published when the project nears completion.
+
+[ComfyUI - CR Animation Nodes - Demo Workflows](https://civitai.com/models/138947/comfyui-cr-animation-nodes-demo-workflows)
+
+
+
+## Recommended Downloads
+The following node packs are recommended for building workflows using these nodes:
+
+[Comfyroll Custom Nodes](https://civitai.com/models/87609/comfyroll-custom-nodes-for-comfyui)
+
+[Fizz Nodes](https://github.com/FizzleDorf/ComfyUI_FizzNodes)
+
+- This is needed for the Prompt Scheduler
+
+[MTB](https://github.com/melMass/comfy_mtb)
+
+- This is needed for the Animation Builder and several other nodes
+
+## Compatibility
+These nodes are designed to work with both Fizz Nodes and MTB Nodes. We are also looking at testing with Loopchain.
+
+# List of Nodes
+These are the first 20 nodes. There are 35 nodes currently in development. It is planned to release these in multiple drops during September.
+
+Feedback on the new nodes is welcomed.
+
+## Scheduling Nodes
+
+CR Schedule Prompts SD1.5
+
+CR Schedule Prompts SDXL
+
+CR Simple Value Scheduler
+
+CR Simple Text Scheduler
+
+CR Simple Scheduler
+
+CR Central Scheduling Table
+
+CR Value Scheduler
+
+CR Text Scheduler
+
+CR Load Scheduled Models
+
+## Prompt Keyframe Nodes
+CR Simple Prompt List
+
+CR Simple Prompt List Keyframes
+
+CR Prompt List
+
+CR Prompt List Keyframes
+
+CR Keyframe List
+
+CR Promp Text
+
+## List Nodes
+CR Model List
+
+CR LoRA List
+
+CR Text List
+
+CR Text List Simple
+
+CR Image List
+
+CR Image List Simple
+
+## Gradient and Increment Nodes
+CR Gradient Float
+
+CR Gradient Integer
+
+CR Increment Float
+
+CR Increment Integer
+
+## Cycler Nodes
+CR Cycle Models
+
+CR Cycle LoRAs
+
+CR Cycle Text
+
+CR Cycle Text Simple
+
+CR Cycle Images
+
+CR Cycle Images Simple
+
+## Index Nodes
+CR Index Reset
+
+CR Index increment
+
+CR Index Multiply
+
+## Latent Nodes
+CR Interpolate Latents
+
+## IO Nodes
+CR Load Animation Frames
+
+CR Output Schedule To File
+
+CR Load Schedule From File
+
+CR Load Flow Frames
+
+CR Output Flow Frames
+
+## Utlity Nodes
+CR Debatch Frames
+
+CR Text List To String
+
+CR Current Frame
+
+# Overview of New Nodes
+Please see this WIP CivitAI article for a guide to the new nodes
+
+[CR Animation Nodes Guide](https://civitai.com/articles/2001/comfyui-guide-to-cr-animation-nodes)
+
+# Troubleshooting
+A troubleshooting article will be added soon
+
+# Node Images
+**Model Scheduling**
+
+
+
+
+
+
+
+**Prompt Keyframe Nodes**
+
+
+
+
+
+
+
+**Cycler Nodes**
+
+
+
+**Text Cycler Nodes**
+
+
+
+**Image Cycler Nodes**
+
+
+
+**Interpolation Nodes**
+
+
+
+**IO Nodes**
+
+
+
+
+
+# Credits
+
+WASasquatch https://github.com/WASasquatch/was-node-suite-comfyui
+
+melMass https://github.com/melMass/comfy_mtb
+
+FizzleDorf https://github.com/FizzleDorf/ComfyUI_FizzNodes
+
+SeargeDP https://github.com/SeargeDP/SeargeSDXL
+
+ltdrdata https://github.com/ltdrdata/ComfyUI-Impact-Pack
+
+LucianoCirino https://github.com/LucianoCirino/efficiency-nodes-comfyui
+
+sylym https://github.com/sylym/comfy_vid2vid
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/Patch_Notes.md b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/Patch_Notes.md
new file mode 100644
index 0000000000000000000000000000000000000000..313659bdb052d6a5e7f2e5313fb7e6b363a40672
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/Patch_Notes.md
@@ -0,0 +1,151 @@
+# 🧩 Comfyroll Custom Nodes - Patch Notes
+
+## PR59 Dec 6, 2023
+
+__Changed Nodes__
+
+ CR Aspect Ratio
+
+ - added prescale_factor
+
+## PR55 Dec 2, 2023
+
+__Other Changes__
+
+ - removed node images
+ - fixes to CR Page Layout
+
+## PR54 Dec 2, 2023
+
+__Other Changes__
+
+ - added show-help outputs on animation nodes with links to wiki
+
+## PR51, PR52 Dec 2, 2023
+
+__Added Nodes__
+
+ CR Random RGB
+
+## PR50 Dec 1, 2023
+
+__Other Changes__
+
+ - added show-help outputs with links to wiki
+
+## PR48 Nov 30, 2023
+
+__Other Changes__
+
+ - disabled CR Load Prompt Style
+ - rename classes on logic nodes
+ - increased max sizes on Aspect Ratio nodes
+
+## PR45 Nov 29, 2023
+
+__Added Nodes__
+
+ CR Random Hex Color
+
+__Changed Nodes__
+
+ CR Color Tint
+
+ - added custom color
+
+ CR Simple Text Panel
+
+ - added outline text
+
+__Other Changes__
+
+ - added demo workflows
+
+## PR44 Nov 28, 2023
+
+__Changed Nodes__
+
+ CR Select Model
+
+ - added ckpt_name output
+
+__Other Changes__
+
+ - added new Patch Notes page
+
+## PR40 Nov 27, 2023
+
+__Added Nodes__
+
+ CR Select Model
+
+ - allows selection of model from one of 5 preset models
+
+__Changed Nodes__
+
+ CR Simple Text Watermark
+
+ - added batch support
+ - added custom font hex color
+
+ CR Aspect Ratio
+
+ - changed descriptions in aspect_ratios for issue 24
+
+ CR Upscale Image
+
+ - fixed issue with batched images
+
+__Other Changes__
+
+ - changed preset RGB for brown to 160, 85, 15
+
+
+## PR39 Nov 26, 2023
+
+__Changed Nodes__
+
+ CR Halftone Filter
+
+ - changed handling for RGBA inputs
+
+
+## PR38 Nov 26, 2023
+
+ __Added Nodes__
+
+ CR Aspect Ratio
+
+ - combines aspect ratio options for both SD1.5 and SDXL
+ - includes empty_latent output
+
+__Changed Nodes__
+
+ CR Halftone Filter
+
+ - added resolution options
+ - modified antialias_scale parameters
+
+ CR SDXL Aspect Ratio
+
+ - added empty_latent output
+
+ CR SD1.5 Aspect Ratio
+
+ - added empty_latent output
+
+
+## PR37 Nov 19, 2023
+
+__Added Nodes__
+
+ CR Simple Text Watermark
+
+ - adds a text watermark to an image
+
+__Other Changes__
+
+ - merged CR Animation Nodes into Comfyroll custom Nodes
+ - added CR Animation Nodes demo workflows
+ - added reduce_opacity function in graphics_functions
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/README.md b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..81e8332acf66dde414b6d1e693d8a00054a47721
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/README.md
@@ -0,0 +1,223 @@
+# 🧩 Comfyroll Custom Nodes for SDXL and SD1.5
+
+Co-authored by Suzie1 and RockOfFire
+
+These nodes can be used in any ComfyUI workflow.
+
+# Installation
+
+1. cd custom_nodes
+2. git clone https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes.git
+3. Restart ComfyUI
+
+You can also install the nodes using the following methods:
+* install using [ComfyUI Manager](https://github.com/ltdrdata/ComfyUI-Manager)
+* download from [CivitAI](https://civitai.com/models/87609/comfyroll-custom-nodes-for-comfyui)
+
+# Patch Notes
+
+https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/blob/main/Patch_Notes.md
+
+
+# Wiki
+
+https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki
+
+# List of Custom Nodes
+
+__🔳 Aspect Ratio__
+* CR SDXL Aspect Ratio
+* CR SD1.5 Aspect Ratio
+* CR Aspect Ratio (new 27/11/2023)
+
+__🌟 SDXL__
+* CR SDXL Prompt Mix Presets
+* CR SDXL Style Text
+* CR SDXL Base Prompt Encoder
+
+__💊 LoRA__
+* CR Load LoRA
+* CR LoRA Stack
+* CR Apply LoRA Stack
+
+__🕹️ ControlNet__
+* CR Apply ControlNet
+* CR Multi-ControlNet Stack
+* CR Apply Multi-ControlNet Stack
+
+__🔂 Process__
+* CR Img2Img Process Switch
+* CR Hires Fix Process Switch
+* CR Batch Process Switch
+
+__👓 Graphics - Filter__
+* CR Color Tint
+* CR Halftone Filter
+
+__🌈 Graphics - Pattern__
+* CR Halftone Grid
+* CR Color Bars
+* CR Style Bars
+* CR Checker Pattern
+* CR Polygons
+* CR Color Gradient
+* CR Radial Gradiant
+* CR Starburst Lines
+* CR Starburst Colors
+* CR Simple Binary Pattern (new 8/12/2023)
+* CR Binary Pattern (new 8/12/2023)
+
+__🔤 Graphics - Text__
+* CR Overlay Text
+* CR Draw Text
+* CR Mask Text
+* CR Composite Text
+
+__👽 Graphics - Template__
+* CR Simple Meme Template
+* CR Simple Banner
+* CR Comic Panel Templates
+
+__🌁 Graphics - Layout__
+* CR Image Panel
+* CR Page Layout
+* CR Image Grid Panel
+* CR Image Border
+* CR Color Panel
+* CR Simple Text Panel
+* CR Overlay Transparent Image
+
+__✈️ Module__
+* CR Module Pipe Loader
+* CR Module Input
+* CR Module Output
+
+__🛩️ Pipe__
+* CR Image Pipe In
+* CR Image Pipe Edit
+* CR Image Pipe Out
+* CR Pipe Switch
+
+__⛏️ Model Merge__
+* CR Model Stack
+* CR Apply Model Merge
+
+__🔍 Upscale__
+* CR Multi Upscale Stack
+* CR Upscale Image
+* CR Apply Multi Upscale
+
+__📉 XY Grid__
+* CR XY List
+* CR XY Interpolate
+* CR XY Index
+* CR XY From Folder
+* CR XY Save Grid Image
+* CR Image Output
+
+__🔢 Index__
+* CR Index
+* CR Index Increment
+* CR Index Multiply
+* CR Index Reset
+* CR Trigger
+
+__🔧 Conversion__
+* CR String To Number
+* CR String To Combo
+* CR Float To String
+* CR Float To Integer
+* CR Integer To String
+* CR Text List To String
+* CR Seed to Int
+
+__🔀 Logic__
+* CR Image Input Switch
+* CR Image Input Switch (4 way)
+* CR Latent Input Switch
+* CR Conditioning Input Switch
+* CR Clip Input Switch
+* CR Model Input Switch
+* CR ControlNet Input Switch
+* CR VAE Input Switch
+* CR Text Input Switch
+* CR Text Input Switch (4 way)
+* CR Switch Model and CLIP
+
+__🎲 Random__
+* CR Random Hex Color
+* CR Random RGB
+* CR Random Multiline Values (new 8/12/2023)
+* CR Random RGB Gradient (new 8/12/2023)
+
+__📦 Other__
+* CR Latent Batch Size
+* CR Prompt Text
+* CR Split String
+* CR Integer Multiple
+* CR Seed
+* CR Value
+* CR Conditioning Mixer (new 27/11/2023)
+* CR Select Model (new 27/11/2023)
+
+__Deleted Nodes__
+* CR Aspect Ratio SDXL replaced by CR SDXL Aspect Ratio
+* CR SDXL Prompt Mixer replaced by CR SDXL Prompt Mix Presets
+
+# CR Animation Nodes
+
+CR Animation Nodes are now included in the Comfyroll Custom Nodes pack.
+
+[Animation Nodes](https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/blob/suzie_dev/Animation_Nodes.md)
+
+# Multi-ControlNet methodology
+
+The method used in CR Apply Multi-ControlNet is to chain the conditioning so that the output from the first Controlnet becomes the input to the second.
+
+For an example of this method see this link:
+
+https://comfyanonymous.github.io/ComfyUI_examples/controlnet/#mixing-controlnets
+
+# Multi-ControlNet compatability with Efficiency nodes
+
+
+
+CR LoRA Stack and CR Multi-ControlNet Stack are both compatible with the Efficient Loader node, in Efficiency nodes by LucianoCirino.
+
+CR Apply Multi-ControlNet Stack can accept inputs from the Control Net Stacker node in the Efficiency nodes (see diagram in Node Images below).
+
+# SDXL Prompt Mix Presets
+
+Preset mappings can be found in this CivitAI article:
+
+https://civitai.com/articles/1835
+
+# Comfyroll Workflow Templates
+
+The nodes were originally made for use in the Comfyroll Template Workflows.
+
+[Comfyroll Template Workflows](https://civitai.com/models/59806/comfyroll-template-workflows)
+
+[Comfyroll Pro Templates](https://civitai.com/models/85619/comfyroll-pro-template)
+
+[Comfyroll SDXL Workflow Templates](https://civitai.com/models/118005/comfyroll-sdxl-workflow-templates)
+
+[SDXL Workflow for ComfyUI with Multi-ControlNet](https://civitai.com/models/129858/sdxl-workflow-for-comfyui-with-multi-controlnet)
+
+[SDXL and SD1.5 Model Merge Templates for ComfyUI](https://civitai.com/models/123125/sdxl-and-sd15-model-merge-templates-for-comfyui)
+
+# Credits
+
+comfyanonymous/[ComfyUI](https://github.com/comfyanonymous/ComfyUI) - A powerful and modular stable diffusion GUI.
+
+WASasquatch/[was-node-suite-comfyui](https://github.com/WASasquatch/was-node-suite-comfyui) - A powerful custom node extensions of ComfyUI.
+
+TinyTerra/[ComfyUI_tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes) - A selection of nodes for Stable Diffusion ComfyUI
+
+hnmr293/[ComfyUI-nodes-hnmr](https://github.com/hnmr293/ComfyUI-nodes-hnmr) - ComfyUI custom nodes - merge, grid (aka xyz-plot) and others
+
+SeargeDP/[SeargeSDXL](https://github.com/SeargeDP) - ComfyUI custom nodes - Prompt nodes and Conditioning nodes
+
+LucianoCirino/[efficiency-nodes-comfyui](https://github.com/LucianoCirino/efficiency-nodes-comfyui) - A collection of ComfyUI custom nodes.
+
+SLAPaper/[ComfyUI-Image-Selector](https://github.com/SLAPaper/ComfyUI-Image-Selector) - Select one or some of images from a batch
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/__init__.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..a6fdff9cd7f696e71975c878733a97ccafea5af0
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/__init__.py
@@ -0,0 +1,410 @@
+"""
+@author: RockOfFire
+@title: Comfyroll Custom Nodes
+@nickname: Comfyroll Custom Nodes
+@description: Custom nodes for SDXL and SD1.5 including Multi-ControlNet, LoRA, Aspect Ratio, Process Switches, and many more nodes.
+"""
+
+from .nodes.nodes import *
+from .nodes.legacy_nodes import *
+from .nodes.lora import *
+from .nodes.controlnet import *
+from .nodes.pipe import *
+from .nodes.sdxl import *
+from .nodes.logic import *
+from .nodes.model_merge import *
+from .nodes.upscale import *
+from .nodes.xygrid import *
+from .nodes.index import *
+from .nodes.conversion import *
+from .nodes.matplot import *
+from .nodes.pil_text import *
+from .nodes.pil_layout import *
+from .nodes.pil_filter import *
+from .nodes.pil_template import *
+from .nodes.pil_pattern import *
+from .nodes.nodes_random import *
+
+from .animation_nodes.interpolation import *
+from .animation_nodes.io import *
+from .animation_nodes.prompt import *
+from .animation_nodes.schedulers import *
+from .animation_nodes.schedules import *
+from .animation_nodes.lists import *
+from .animation_nodes.utils import *
+from .animation_nodes.cyclers import *
+
+LIVE_NODE_CLASS_MAPPINGS = {
+ ### Misc Nodes
+ "CR Image Output": CR_ImageOutput,
+ "CR Integer Multiple": CR_IntegerMultipleOf,
+ "CR Latent Batch Size": CR_LatentBatchSize,
+ "CR Seed": CR_Seed,
+ "CR Prompt Text":CR_PromptText,
+ "CR Split String":CR_SplitString,
+ "CR Value": CR_Value,
+ "CR Conditioning Mixer":CR_ConditioningMixer,
+ "CR Select Model": CR_SelectModel,
+ ### Aspect Ratio Nodes
+ "CR SD1.5 Aspect Ratio":CR_AspectRatioSD15,
+ "CR SDXL Aspect Ratio":CR_SDXLAspectRatio,
+ "CR Aspect Ratio": CR_AspectRatio,
+ ### Legacy Nodes
+ "CR Image Size": CR_ImageSize,
+ "CR Aspect Ratio SDXL": CR_AspectRatio_SDXL,
+ ### ControlNet Nodes
+ "CR Apply ControlNet": CR_ApplyControlNet,
+ "CR Multi-ControlNet Stack": CR_ControlNetStack,
+ "CR Apply Multi-ControlNet": CR_ApplyControlNetStack,
+ ### LoRA Nodes
+ "CR Load LoRA": CR_LoraLoader,
+ "CR LoRA Stack": CR_LoRAStack,
+ "CR Apply LoRA Stack": CR_ApplyLoRAStack,
+ ### Model Merge Nodes
+ "CR Apply Model Merge": CR_ApplyModelMerge,
+ "CR Model Merge Stack": CR_ModelMergeStack,
+ ### Pipe Nodes
+ "CR Module Pipe Loader": CR_ModulePipeLoader,
+ "CR Module Input": CR_ModuleInput,
+ "CR Module Output": CR_ModuleOutput,
+ "CR Image Pipe In": CR_ImagePipeIn,
+ "CR Image Pipe Edit": CR_ImagePipeEdit,
+ "CR Image Pipe Out": CR_ImagePipeOut,
+ "CR Pipe Switch": CR_InputSwitchPipe,
+ ### SDXL Nodes
+ "CR SDXL Prompt Mix Presets": CR_PromptMixPresets,
+ "CR SDXL Style Text": CR_SDXLStyleText,
+ "CR SDXL Base Prompt Encoder": CR_SDXLBasePromptEncoder,
+ ### Upscale Nodes
+ "CR Multi Upscale Stack": CR_MultiUpscaleStack,
+ "CR Upscale Image": CR_UpscaleImage,
+ "CR Apply Multi Upscale": CR_ApplyMultiUpscale,
+ ### XY Grid Nodes
+ "CR XY List": CR_XYList,
+ "CR XY Interpolate": CR_XYInterpolate,
+ "CR XY Index": CR_XYIndex,
+ "CR XY From Folder": CR_XYFromFolder,
+ "CR XY Save Grid Image": CR_XYSaveGridImage,
+ ### Graphics Pattern
+ "CR Halftone Grid": CR_HalftoneGrid,
+ "CR Color Bars": CR_ColorBars,
+ "CR Style Bars": CR_StyleBars,
+ "CR Checker Pattern": CR_CheckerPattern,
+ "CR Polygons": CR_Polygons,
+ "CR Color Gradient": CR_ColorGradient,
+ "CR Radial Gradient": CR_RadialGradient,
+ "CR Starburst Lines": CR_StarburstLines,
+ "CR Starburst Colors": CR_StarburstColors,
+ "CR Simple Binary Pattern": CR_BinaryPatternSimple,
+ "CR Binary Pattern": CR_BinaryPattern,
+ ### Graphics Text
+ "CR Overlay Text": CR_OverlayText,
+ "CR Draw Text": CR_DrawText,
+ "CR Mask Text": CR_MaskText,
+ "CR Composite Text": CR_CompositeText,
+ #"CR Arabic Text RTL": CR_ArabicTextRTL,
+ "CR Simple Text Watermark": CR_SimpleTextWatermark,
+ #"CR System TrueType Font": CR_SystemTrueTypeFont,
+ #"CR Display Font": CR_DisplayFont,
+ ### Graphics Filter
+ "CR Halftone Filter": CR_HalftoneFilter,
+ "CR Color Tint": CR_ColorTint,
+ ### Graphics Layout
+ "CR Page Layout": CR_PageLayout,
+ "CR Image Panel": CR_ImagePanel,
+ "CR Image Grid Panel": CR_ImageGridPanel,
+ "CR Image Border": CR_ImageBorder,
+ "CR Simple Text Panel": CR_SimpleTextPanel,
+ "CR Color Panel": CR_ColorPanel,
+ "CR Overlay Transparent Image": CR_OverlayTransparentImage,
+ #"CR Simple Titles": CR_SimpleTitles,
+ ### Graphics Template
+ "CR Simple Meme Template": CR_SimpleMemeTemplate,
+ "CR Simple Banner": CR_SimpleBanner,
+ "CR Comic Panel Templates": CR_ComicPanelTemplates,
+ ### Utils Logic Nodes
+ "CR Image Input Switch": CR_ImageInputSwitch,
+ "CR Image Input Switch (4 way)": CR_ImageInputSwitch4way,
+ "CR Latent Input Switch": CR_LatentInputSwitch,
+ "CR Conditioning Input Switch": CR_ConditioningInputSwitch,
+ "CR Clip Input Switch": CR_ClipInputSwitch,
+ "CR Model Input Switch": CR_ModelInputSwitch,
+ "CR ControlNet Input Switch": CR_ControlNetInputSwitch,
+ "CR VAE Input Switch": CR_VAEInputSwitch,
+ "CR Text Input Switch": CR_TextInputSwitch,
+ "CR Text Input Switch (4 way)": CR_TextInputSwitch4way,
+ "CR Switch Model and CLIP": CR_ModelAndCLIPInputSwitch,
+ ### Utils Process Nodes
+ "CR Batch Process Switch": CR_BatchProcessSwitch,
+ "CR Img2Img Process Switch": CR_Img2ImgProcessSwitch,
+ "CR Hires Fix Process Switch": CR_HiResFixProcessSwitch,
+ ### Utils Index Nodes
+ "CR Index": CR_Index,
+ "CR Index Increment": CR_IncrementIndex,
+ "CR Index Multiply": CR_MultiplyIndex,
+ "CR Index Reset": CR_IndexReset,
+ "CR Trigger": CR_Trigger,
+ ### Utils Conversion Nodes
+ "CR String To Number": CR_StringToNumber,
+ "CR String To Combo": CR_StringToCombo,
+ "CR Float To String": CR_FloatToString,
+ "CR Float To Integer": CR_FloatToInteger,
+ "CR Integer To String": CR_IntegerToString,
+ "CR Text List To String": CR_TextListToString,
+ "CR Seed to Int": CR_SeedToInt,
+ ### Utils Random Nodes
+ "CR Random Hex Color": CR_RandomHexColor,
+ "CR Random RGB": CR_RandomRGB,
+ "CR Random Multiline Values": CR_RandomMultilineValues,
+ "CR Random RGB Gradient": CR_RandomRGBGradient,
+ #------------------------------------------------------
+ ### Animation Nodes
+ # Schedules
+ "CR Simple Schedule": CR_SimpleSchedule,
+ "CR Central Schedule": CR_CentralSchedule,
+ "CR Combine Schedules": CR_CombineSchedules,
+ "CR Output Schedule To File": CR_OutputScheduleToFile,
+ "CR Load Schedule From File": CR_LoadScheduleFromFile,
+ "CR Schedule Input Switch": Comfyroll_ScheduleInputSwitch,
+ # Schedulers
+ "CR Simple Value Scheduler": CR_SimpleValueScheduler,
+ "CR Simple Text Scheduler": CR_SimpleTextScheduler,
+ "CR Value Scheduler": CR_ValueScheduler,
+ "CR Text Scheduler": CR_TextScheduler,
+ "CR Load Scheduled Models": CR_LoadScheduledModels,
+ "CR Load Scheduled LoRAs": CR_LoadScheduledLoRAs,
+ "CR Prompt Scheduler": CR_PromptScheduler,
+ "CR Simple Prompt Scheduler": CR_SimplePromptScheduler,
+ # Prompt
+ "CR Prompt List": CR_PromptList,
+ "CR Prompt List Keyframes": CR_PromptListKeyframes,
+ "CR Simple Prompt List": CR_SimplePromptList,
+ "CR Simple Prompt List Keyframes": CR_SimplePromptListKeyframes,
+ "CR Keyframe List": CR_KeyframeList,
+ "CR Prompt Text": CR_PromptText,
+ #"CR Load Prompt Style": CR_LoadPromptStyle,
+ "CR Encode Scheduled Prompts": CR_EncodeScheduledPrompts,
+ # Interpolation
+ "CR Gradient Float": CR_GradientFloat,
+ "CR Gradient Integer": CR_GradientInteger,
+ "CR Increment Float": CR_IncrementFloat,
+ "CR Increment Integer": CR_IncrementInteger,
+ "CR Interpolate Latents": CR_InterpolateLatents,
+ # Lists
+ "CR Model List": CR_ModelList,
+ "CR LoRA List": CR_LoRAList,
+ "CR Text List": CR_TextList,
+ "CR Text List Simple": CR_TextListSimple,
+ "CR Image List": CR_ImageList,
+ "CR Image List Simple": CR_ImageListSimple,
+ # Cyclers
+ "CR Cycle Models": CR_CycleModels,
+ "CR Cycle LoRAs": CR_CycleLoRAs,
+ "CR Cycle Text": CR_CycleText,
+ "CR Cycle Text Simple": CR_CycleTextSimple,
+ "CR Cycle Images": CR_CycleImages,
+ "CR Cycle Images Simple": CR_CycleImagesSimple,
+ # Utils
+ "CR Debatch Frames": CR_DebatchFrames,
+ "CR Current Frame": CR_CurrentFrame,
+ "CR Input Text List": CR_InputTextList,
+ # IO
+ "CR Load Animation Frames": CR_LoadAnimationFrames,
+ "CR Load Flow Frames": CR_LoadFlowFrames,
+ "CR Output Flow Frames": CR_OutputFlowFrames,
+}
+
+LIVE_NODE_DISPLAY_NAME_MAPPINGS = {
+ ### Misc Nodes
+ "CR Image Output": "💾 CR Image Output",
+ "CR Integer Multiple": "⚙️ CR Integer Multiple",
+ "CR Latent Batch Size": "⚙️ CR Latent Batch Size",
+ "CR Seed": "🌱 CR Seed",
+ "CR Prompt Text": "📝 CR Prompt Text",
+ "CR Split String": "⚙️ CR Split String",
+ "CR Value": "⚙️ CR Value",
+ "CR Conditioning Mixer": "⚙️ CR Conditioning Mixer",
+ "CR Select Model": "🔮 CR Select Model",
+ ### Aspect Ratio Nodes
+ "CR SD1.5 Aspect Ratio": "🔳 CR SD1.5 Aspect Ratio",
+ "CR SDXL Aspect Ratio": "🔳 CR SDXL Aspect Ratio",
+ "CR Aspect Ratio": "🔳 CR Aspect Ratio",
+ ### Legacy Nodes
+ "CR Image Size": "CR Image Size (Legacy)",
+ "CR Aspect Ratio SDXL": "CR Aspect Ratio SDXL (Legacy)",
+ ### ControlNet Nodes
+ "CR Apply ControlNet": "🕹️ CR Apply ControlNet",
+ "CR Multi-ControlNet Stack": "🕹️ CR Multi-ControlNet Stack",
+ "CR Apply Multi-ControlNet": "🕹️ CR Apply Multi-ControlNet",
+ ### LoRA Nodes
+ "CR Load LoRA": "💊 CR Load LoRA",
+ "CR LoRA Stack": "💊 CR LoRA Stack",
+ "CR Apply LoRA Stack": "💊 CR Apply LoRA Stack",
+ ### Model Merge Nodes
+ "CR Apply Model Merge": "⛏️ CR Apply Model Merge",
+ "CR Model Merge Stack": "⛏️ CR Model Merge Stack",
+ ### Pipe Nodes
+ "CR Module Pipe Loader": "✈️ CR Module Pipe Loader",
+ "CR Module Input": "✈️ CR Module Input",
+ "CR Module Output": "✈️ CR Module Output",
+ "CR Image Pipe In": "🛩 CR Image Pipe In",
+ "CR Image Pipe Edit": "🛩️ CR Image Pipe Edit",
+ "CR Image Pipe Out": "🛩️ CR Image Pipe Out",
+ "CR Pipe Switch": "🔀️ CR Pipe Switch",
+ ### SDXL Nodes
+ "CR SDXL Prompt Mix Presets": "🌟 CR SDXL Prompt Mix Presets",
+ "CR SDXL Style Text": "🌟 CR SDXL Style Text",
+ "CR SDXL Base Prompt Encoder": "🌟 CR SDXL Base Prompt Encoder",
+ ### Upscale Nodes
+ "CR Multi Upscale Stack": "🔍 CR Multi Upscale Stack",
+ "CR Upscale Image": "🔍 CR Upscale Image",
+ "CR Apply Multi Upscale": "🔍 CR Apply Multi Upscale",
+ ### XY Grid Nodes
+ "CR XY List": "📉 CR XY List",
+ "CR XY Interpolate": "📉 CR XY Interpolate",
+ "CR XY Index": "📉 CR XY Index",
+ "CR XY From Folder": "📉 CR XY From Folder",
+ "CR XY Save Grid Image": "📉 CR XY Save Grid Image",
+ ### Graphics Pattern
+ "CR Halftone Grid" : "🟫 CR Halftone Grid",
+ "CR Color Bars" : "🟫 CR Color Bars",
+ "CR Style Bars" : "🟪 CR Style Bars",
+ "CR Checker Pattern": "🟦 CR Checker Pattern",
+ "CR Polygons": "🟩 CR Polygons",
+ "CR Color Gradient": "🟨 CR Color Gradient",
+ "CR Radial Gradient": "🟨 CR Radial Gradient",
+ "CR Starburst Lines": "🟧 CR Starburst Lines",
+ "CR Starburst Colors": "🟥 CR Starburst Colors",
+ "CR Simple Binary Pattern": "🟥 CR Simple Binary Pattern",
+ "CR Binary Pattern": "🟥 CR Binary Pattern",
+ ### Graphics Text
+ "CR Overlay Text": "🔤 CR Overlay Text",
+ "CR Draw Text": "🔤️ CR Draw Text",
+ "CR Mask Text": "🔤️ CR Mask Text",
+ "CR Composite Text": "🔤️ CR Composite Text",
+ #"CR Arabic Text RTL": "🔤️ CR Arabic Text RTL",
+ "CR Simple Text Watermark": "🔤️ CR Simple Text Watermark",
+ ### Graphics Filter
+ "CR Halftone Filter": "🎨 Halftone Filter",
+ "CR Color Tint": "🎨 CR Color Tint",
+ ### Graphics Layout
+ "CR Image Panel": "🌁 CR Image Panel",
+ "CR Image Grid Panel": "🌁 CR Image Grid Panel",
+ "CR Simple Text Panel": "🌁 CR Simple Text Panel",
+ "CR Color Panel": "🌁 CR Color Panel",
+ "CR Page Layout": "🌁 CR Page Layout",
+ "CR Image Border": "🌁 CR Image Border",
+ "CR Overlay Transparent Image": "🌁 CR Overlay Transparent Image",
+ #"CR Simple Titles": "🌁 CR Simple Titles",
+ ### Graphics Template
+ "CR Simple Meme Template": "👽 CR Simple Meme Template",
+ "CR Simple Banner": "👽 CR Simple Banner",
+ "CR Comic Panel Templates": "👽 CR Comic Panel Templates",
+ ### Utils Logic Nodes
+ "CR Image Input Switch": "🔀 CR Image Input Switch",
+ "CR Image Input Switch (4 way)": "🔀 CR Image Input Switch (4 way)",
+ "CR Latent Input Switch": "🔀 CR Latent Input Switch",
+ "CR Conditioning Input Switch": "🔀 CR Conditioning Input Switch",
+ "CR Clip Input Switch": "🔀 CR Clip Input Switch",
+ "CR Model Input Switch": "🔀 CR Model Input Switch",
+ "CR ControlNet Input Switch": "🔀 CR ControlNet Input Switch",
+ "CR VAE Input Switch": "🔀 CR VAE Input Switch",
+ "CR Text Input Switch": "🔀 CR Text Input Switch",
+ "CR Text Input Switch (4 way)": "🔀 CR Text Input Switch (4 way)",
+ "CR Switch Model and CLIP": "🔀 CR Switch Model and CLIP",
+ ### Utils Process Nodes
+ "CR Batch Process Switch": "🔂 CR Batch Process Switch",
+ "CR Img2Img Process Switch": "🔂 CR Img2Img Process Switch",
+ "CR Hires Fix Process Switch": "🔂 CR Hires Fix Process Switch",
+ ### Utils Index Nodes
+ "CR Index":"🔢 CR Index",
+ "CR Index Increment": "🔢 CR Index Increment",
+ "CR Index Multiply": "🔢 CR Index Multiply",
+ "CR Index Reset": "🔢 CR Index Reset",
+ "CR Trigger": "🔢 CR Trigger",
+ ### Utils Conversion Nodes
+ "CR String To Number": "🔧 CR String To Number",
+ "CR String To Combo": "🔧 CR String To Combo",
+ "CR Float To String": "🔧 CR Float To String",
+ "CR Float To Integer": "🔧 CR Float To Integer",
+ "CR Integer To String": "🔧 CR Integer To String",
+ "CR Text List To String": "🔧 CR Text List To String",
+ "CR Seed to Int": "🔧 CR Seed to Int",
+ ### Utils Random Nodes
+ "CR Random Hex Color": "🎲 CR Random Hex Color",
+ "CR Random RGB": "🎲 CR Random RGB",
+ "CR Random Multiline Values": "🎲 CR Random Multiline Values",
+ "CR Random RGB Gradient": "🎲 CR Random RGB Gradient",
+ #------------------------------------------------------
+ ### Animation Nodes
+ # Schedules
+ "CR Simple Schedule": "📋 CR Simple Schedule",
+ "CR Central Schedule": "📋 CR Central Schedule",
+ "CR Combine Schedules": "📋 CR Combine Schedules",
+ "CR Output Schedule To File": "📋 CR Output Schedule To File",
+ "CR Load Schedule From File": "📋 CR Load Schedule From File",
+ "CR Schedule Input Switch": "📋 CR Schedule Input Switch",
+ # Schedulers
+ "CR Simple Value Scheduler": "📑 CR Simple Value Scheduler",
+ "CR Simple Text Scheduler": "📑 CR Simple Text Scheduler",
+ "CR Value Scheduler": "📑 CR Value Scheduler",
+ "CR Text Scheduler": "📑 CR Text Scheduler",
+ "CR Load Scheduled Models": "📑 CR Load Scheduled Models",
+ "CR Load Scheduled LoRAs": "📑 CR Load Scheduled LoRAs",
+ "CR Prompt Scheduler": "📑 CR Prompt Scheduler",
+ "CR Simple Prompt Scheduler": "📑 CR Simple Prompt Scheduler",
+ # Prompt
+ "CR Prompt List": "📝 CR Prompt List",
+ "CR Prompt List Keyframes": "📝 CR Prompt List Keyframes",
+ "CR Simple Prompt List": "📝 CR Simple Prompt List",
+ "CR Simple Prompt List Keyframes": "📝 CR Simple Prompt List Keyframes",
+ "CR Keyframe List": "📝 CR Keyframe List",
+ "CR Prompt Text": "📝 CR Prompt Text",
+ #"CR Load Prompt Style": "📝 CR Load Prompt Style",
+ "CR Encode Scheduled Prompts": "📝 CR Encode Scheduled Prompts",
+ # Interpolation
+ "CR Gradient Float": "🔢 CR Gradient Float",
+ "CR Gradient Integer": "🔢 CR Gradient Integer",
+ "CR Increment Float": "🔢 CR Increment Float",
+ "CR Increment Integer": "🔢 CR Increment Integer",
+ "CR Interpolate Latents": "🔢 CR Interpolate Latents",
+ # Lists
+ "CR Model List": "📃 CR Model List",
+ "CR LoRA List": "📃 CR LoRA List",
+ "CR Text List": "📃 CR Text List",
+ "CR Text List Simple": "📃 CR Text List Simple",
+ "CR Image List": "📃 CR Image List",
+ "CR Image List Simple": "📃 CR Image List Simple",
+ "CR Input Text List": "📃 CR Input Text List",
+ # Cyclers
+ "CR Cycle Models": "♻️ CR Cycle Models",
+ "CR Cycle LoRAs": "♻️ CR Cycle LoRAs",
+ "CR Cycle Text": "♻️ CR Cycle Text",
+ "CR Cycle Text Simple": "♻️ CR Cycle Text Simple",
+ "CR Cycle Images": "♻️ CR Cycle Images",
+ "CR Cycle Images Simple": "♻️ CR Cycle Images Simple",
+ # Utils
+ "CR Debatch Frames": "🛠️ CR Debatch Frames",
+ "CR Current Frame": "🛠️ CR Current Frame",
+ # IO
+ "CR Load Animation Frames": "⌨️ CR Load Animation Frames",
+ "CR Load Flow Frames": "⌨️ CR Load Flow Frames",
+ "CR Output Flow Frames": "⌨️ CR Output Flow Frames",
+}
+
+INCLUDE_DEV_NODES = False
+
+try:
+ from .dev_node_mappings import DEV_NODE_CLASS_MAPPINGS, DEV_NODE_DISPLAY_NAME_MAPPINGS
+ if INCLUDE_DEV_NODES:
+ NODE_CLASS_MAPPINGS = {**DEV_NODE_CLASS_MAPPINGS, **LIVE_NODE_CLASS_MAPPINGS}
+ NODE_DISPLAY_NAME_MAPPINGS = {**DEV_NODE_DISPLAY_NAME_MAPPINGS, **LIVE_NODE_DISPLAY_NAME_MAPPINGS}
+ print("\033[34mComfyroll Custom Nodes: \033[92mDev Nodes Loaded\033[0m")
+ else:
+ NODE_CLASS_MAPPINGS = LIVE_NODE_CLASS_MAPPINGS
+ NODE_DISPLAY_NAME_MAPPINGS = LIVE_NODE_DISPLAY_NAME_MAPPINGS
+except ImportError:
+ NODE_CLASS_MAPPINGS = LIVE_NODE_CLASS_MAPPINGS
+ NODE_DISPLAY_NAME_MAPPINGS = LIVE_NODE_DISPLAY_NAME_MAPPINGS
+
+print("\033[34mComfyroll Custom Nodes: \033[92mLoaded\033[0m")
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/__pycache__/__init__.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a6425bb903c2bfa0a7409a4d90cfbf0531c432b4
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/__pycache__/__init__.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/__pycache__/categories.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/__pycache__/categories.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d209c8f37b3edf555b218458b87c623a3dd3980f
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/__pycache__/categories.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/__pycache__/config.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/__pycache__/config.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8a40bb884c7f5414d2b27754f83d6943ea63cb01
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/__pycache__/config.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/__pycache__/dev_node_mappings.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/__pycache__/dev_node_mappings.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8e3ac8b291d54cf72d53afdc09770367049ce7a7
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/__pycache__/dev_node_mappings.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/cyclers.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/cyclers.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..bb306a89b7b142211c4ed072c003898dc2c88681
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/cyclers.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/functions.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/functions.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..22f9624baa833b68f2d61941d2570ba9c774041e
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/functions.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/interpolation.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/interpolation.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c83cb7f649a517b80360879146aa8600623fbad9
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/interpolation.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/io.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/io.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f40778925e44820b182d5060f98c1fd2dd0970fe
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/io.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/json_functions.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/json_functions.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f0271f807454cdb8055e01ce6b67d8ceaf3ebe67
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/json_functions.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/lists.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/lists.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7afc4c31bfbe8dfb10760951ce2d1edef50a60e9
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/lists.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/prompt.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/prompt.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..72415aaf890cb740aa081085f71ab246610afc92
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/prompt.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/schedulers.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/schedulers.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2a42a5e99ad8016a5c31023d08b1804e96e8d2b0
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/schedulers.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/schedules.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/schedules.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..34c39f6f210b9ecccf01d7d42f7c49d2805b0396
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/schedules.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/utils.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/utils.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ceaecf3b5f8ad85590345efa58cdc3c16cd64b19
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/__pycache__/utils.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/cyclers.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/cyclers.py
new file mode 100644
index 0000000000000000000000000000000000000000..62e58549cc47cc5e34833d41f72e7bb0026d48d6
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/cyclers.py
@@ -0,0 +1,377 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# CR Animation Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/CR-Animation-Nodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+
+import comfy.sd
+import torch
+import os
+import sys
+import folder_paths
+import random
+from PIL import Image, ImageEnhance
+import numpy as np
+import io
+from ..categories import icons
+
+sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), "comfy"))
+#---------------------------------------------------------------------------------------------------------------------#
+# FUNCTIONS
+#---------------------------------------------------------------------------------------------------------------------#
+def pil2tensor(image):
+ return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
+#---------------------------------------------------------------------------------------------------------------------#
+# NODES
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_CycleModels:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ modes = ["Off", "Sequential"]
+
+ return {"required": {"mode": (modes,),
+ "model": ("MODEL",),
+ "clip": ("CLIP",),
+ "model_list": ("MODEL_LIST",),
+ "frame_interval": ("INT", {"default": 30, "min": 0, "max": 999, "step": 1,}),
+ "loops": ("INT", {"default": 1, "min": 1, "max": 1000}),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ },
+ }
+
+ RETURN_TYPES = ("MODEL", "CLIP", "VAE", "STRING", )
+ RETURN_NAMES = ("MODEL", "CLIP", "VAE", "show_help", )
+ FUNCTION = "cycle_models"
+ CATEGORY = icons.get("Comfyroll/Animation/Cyclers")
+
+ def cycle_models(self, mode, model, clip, model_list, frame_interval, loops, current_frame,):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Cycler-Nodes#cr-cycle-models"
+
+ # Initialize the list
+ model_params = list()
+
+ # Extend lora_params with the lora_list items
+ if model_list:
+ for _ in range(loops):
+ model_params.extend(model_list)
+ #print(f"[Debug] CR Cycle Models:{model_params}")
+
+ if mode == "Off":
+ return (model, clip, show_help, )
+
+ elif mode == "Sequential":
+ if current_frame == 0:
+ return (model, clip, show_help, )
+ else:
+ # Calculate the index of the current model based on the current_frame and frame_interval
+ current_model_index = (current_frame // frame_interval) % len(model_params)
+ #print(f"[Debug] CR Cycle Models:{current_model_index}")
+
+ # Get the parameters of the current model
+ current_model_params = model_params[current_model_index]
+ model_alias, ckpt_name = current_model_params
+ print(f"[Info] CR Cycle Models: Current model is {ckpt_name}")
+
+ # Load the current model
+ ckpt_path = folder_paths.get_full_path("checkpoints", ckpt_name)
+ out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True,
+ embedding_directory=folder_paths.get_folder_paths("embeddings"))
+ return (out, show_help, )
+ #else:
+ # return (model, clip)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_CycleLoRAs:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ modes = ["Off", "Sequential"]
+
+ return {"required": {"mode": (modes,),
+ "model": ("MODEL",),
+ "clip": ("CLIP",),
+ "lora_list": ("LORA_LIST",),
+ "frame_interval": ("INT", {"default": 30, "min": 0, "max": 999, "step": 1,}),
+ "loops": ("INT", {"default": 1, "min": 1, "max": 1000}),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ },
+ }
+
+ RETURN_TYPES = ("MODEL", "CLIP", "STRING", )
+ RETURN_NAMES = ("MODEL", "CLIP", "show_help", )
+ FUNCTION = "cycle"
+ CATEGORY = icons.get("Comfyroll/Animation/Cyclers")
+
+ def cycle(self, mode, model, clip, lora_list, frame_interval, loops, current_frame):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Cycler-Nodes#cr-cycle-loras"
+
+ # Initialize the list
+ lora_params = list()
+
+ # Extend lora_params with lora_list items
+ if lora_list:
+ for _ in range(loops):
+ lora_params.extend(lora_list)
+ #print(f"[Debug] CR Cycle LoRAs:{lora_params}")
+ else:
+ return (model, clip, show_help, )
+
+ if mode == "Sequential":
+ # Calculate the index of the current LoRA based on the current_frame and frame_interval
+ current_lora_index = (current_frame // frame_interval) % len(lora_params)
+ #print(f"[Debug] CR Cycle LoRAs:{current_lora_index}")
+
+ # Get the parameters of the current LoRA
+ current_lora_params = lora_params[current_lora_index]
+ lora_alias, lora_name, model_strength, clip_strength = current_lora_params
+
+ # Load the current LoRA
+ lora_path = folder_paths.get_full_path("loras", lora_name)
+ lora = comfy.utils.load_torch_file(lora_path, safe_load=True)
+ print(f"[Info] CR_CycleLoRAs: Current LoRA is {lora_name}")
+
+ # Apply the current LoRA to the model and clip
+ model_lora, clip_lora = comfy.sd.load_lora_for_models(
+ model, clip, lora, model_strength, clip_strength)
+ return (model_lora, clip_lora, show_help, )
+ else:
+ return (model, clip, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_CycleText:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ modes = ["Sequential"]
+
+ return {"required": {"mode": (modes,),
+ "text_list": ("TEXT_LIST",),
+ "frame_interval": ("INT", {"default": 30, "min": 0, "max": 999, "step": 1,}),
+ "loops": ("INT", {"default": 1, "min": 1, "max": 1000}),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ },
+ }
+
+ RETURN_TYPES = ("STRING", "STRING", )
+ RETURN_NAMES = ("STRING", "show_help", )
+ FUNCTION = "cycle_text"
+ CATEGORY = icons.get("Comfyroll/Animation/Cyclers")
+
+ def cycle_text(self, mode, text_list, frame_interval, loops, current_frame,):
+
+ # Initialize the list
+ text_params = list()
+
+ # Extend text_params with text_list items
+ if text_list:
+ for _ in range(loops):
+ text_params.extend(text_list)
+ #print(f"[Debug] CR Cycle Text:{text_params}")
+
+ if mode == "Sequential":
+ # Calculate the index of the current text string based on the current_frame and frame_interval
+ current_text_index = (current_frame // frame_interval) % len(text_params)
+ #print(f"[Debug] CR Cycle Text:{current_text_index}")
+
+ # Get the parameters of the current text
+ current_text_params = text_params[current_text_index]
+ print(f"[Debug] CR Cycle Text:{current_text_params}")
+ text_alias, current_text_item = current_text_params
+ #print(f"[Debug] CR Cycle Text:{current_text_item}")
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Cycler-Nodes#cr-cycle-text"
+
+ return (current_text_item, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_CycleTextSimple:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ modes = ["Sequential"]
+
+ return {"required": {"mode": (modes,),
+ "frame_interval": ("INT", {"default": 30, "min": 0, "max": 999, "step": 1,}),
+ "loops": ("INT", {"default": 1, "min": 1, "max": 1000}),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ },
+ "optional": {"text_1": ("STRING", {"multiline": False, "default": ""}),
+ "text_2": ("STRING", {"multiline": False, "default": ""}),
+ "text_3": ("STRING", {"multiline": False, "default": ""}),
+ "text_4": ("STRING", {"multiline": False, "default": ""}),
+ "text_5": ("STRING", {"multiline": False, "default": ""}),
+ "text_list_simple": ("TEXT_LIST_SIMPLE",),
+ },
+ }
+
+ RETURN_TYPES = ("STRING", "STRING", )
+ RETURN_NAMES = ("STRING", "show_help", )
+ FUNCTION = "cycle_text"
+ CATEGORY = icons.get("Comfyroll/Animation/Cyclers")
+
+ def cycle_text(self, mode, frame_interval, loops, current_frame,
+ text_1, text_2, text_3, text_4, text_5,
+ text_list_simple=None ):
+
+ # Initialize the list
+ text_params = list()
+
+ text_list = list()
+ if text_1 != "":
+ text_list.append(text_1)
+ if text_2 != "":
+ text_list.append(text_2)
+ if text_3 != "":
+ text_list.append(text_3)
+ if text_4 != "":
+ text_list.append(text_4)
+ if text_5 != "":
+ text_list.append(text_5)
+
+ # Extend text_params with text items
+ for _ in range(loops):
+ if text_list_simple:
+ text_params.extend(text_list_simple)
+ text_params.extend(text_list)
+ #print(f"[Debug] CR Cycle Text:{len(text_params)}")
+ #print(f"[Debug] CR Cycle Text:{text_params}")
+
+ if mode == "Sequential":
+ # Calculate the index of the current text string based on the current_frame and frame_interval
+ current_text_index = (current_frame // frame_interval) % len(text_params)
+ #print(f"[Debug] CR Cycle Text:{current_text_index}")
+
+ # Get the parameters of the current text
+ current_text_item = text_params[current_text_index]
+ #print(f"[Debug] CR Cycle Text
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Cycler-Nodes#cr-cycle-text-simple"
+
+ return (current_text_item, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_CycleImages:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ modes = ["Sequential"]
+
+ return {"required": {"mode": (modes,),
+ "image_list": ("IMAGE_LIST",),
+ "frame_interval": ("INT", {"default": 30, "min": 0, "max": 999, "step": 1,}),
+ "loops": ("INT", {"default": 1, "min": 1, "max": 1000}),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ },
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("IMAGE", "show_help", )
+ FUNCTION = "cycle"
+ CATEGORY = icons.get("Comfyroll/Animation/Cyclers")
+
+ def cycle(self, mode, image_list, frame_interval, loops, current_frame,):
+
+ # Initialize the list
+ image_params = list()
+
+ # Extend image_params with image_list items
+ if image_list:
+ for _ in range(loops):
+ image_params.extend(image_list)
+
+ if mode == "Sequential":
+ # Calculate the index of the current image string based on the current_frame and frame_interval
+ current_image_index = (current_frame // frame_interval) % len(image_params)
+ print(f"[Debug] CR Cycle Image:{current_image_index}")
+
+ # Get the parameters of the current image
+ current_image_params = image_params[current_image_index]
+ image_alias, current_image_item = current_image_params
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Cycler-Nodes#cr-cycle-images"
+
+ return (current_image_item, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_CycleImagesSimple:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ modes = ["Sequential"]
+
+ return {"required": {"mode": (modes,),
+ "frame_interval": ("INT", {"default": 30, "min": 0, "max": 999, "step": 1,}),
+ "loops": ("INT", {"default": 1, "min": 1, "max": 1000}),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,})
+ },
+ "optional": {"image_1": ("IMAGE",),
+ "image_2": ("IMAGE",),
+ "image_3": ("IMAGE",),
+ "image_4": ("IMAGE",),
+ "image_5": ("IMAGE",),
+ "image_list_simple": ("IMAGE_LIST_SIMPLE",)
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("IMAGE", "show_help", )
+ FUNCTION = "cycle_image"
+ CATEGORY = icons.get("Comfyroll/Animation/Cyclers")
+
+ def cycle_image(self, mode, frame_interval, loops, current_frame,
+ image_1=None, image_2=None, image_3=None, image_4=None, image_5=None,
+ image_list_simple=None ):
+
+ # Initialize the list
+ image_params = list()
+
+ image_list = list()
+ if image_1 != None:
+ image_list.append(image_1),
+ if image_2 != None:
+ image_list.append(image_2),
+ if image_3 != None:
+ image_list.append(image_3),
+ if image_4 != None:
+ image_list.append(image_4),
+ if image_5 != None:
+ image_list.append(image_5),
+
+ # Extend image_params with image items
+ for _ in range(loops):
+ if image_list_simple:
+ image_params.extend(image_list_simple)
+ image_params.extend(image_list)
+
+ if mode == "Sequential":
+ # Calculate the index of the current image string based on the current_frame and frame_interval
+ current_image_index = (current_frame // frame_interval) % len(image_params)
+ print(f"[Debug] CR Cycle Text:{current_image_index}")
+
+ # Get the parameters of the current image
+ current_image_item = image_params[current_image_index]
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Cycler-Nodes#cr-cycle-images-simple"
+ return (current_image_item, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+# 6 nodes
+'''
+NODE_CLASS_MAPPINGS = {
+ ### Cyclers
+ "CR Cycle Models":CR_CycleModels,
+ "CR Cycle LoRAs":CR_CycleLoRAs,
+ "CR Cycle Images":CR_CycleImages,
+ "CR Cycle Images":CR_CycleImagesSimple,
+ "CR Cycle Text":CR_CycleText,
+ "CR Cycle Text Simple":CR_CycleTextSimple,
+}
+'''
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/functions.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/functions.py
new file mode 100644
index 0000000000000000000000000000000000000000..890fb851fc0dcbd29e043c273dd518ade8164905
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/functions.py
@@ -0,0 +1,105 @@
+#-----------------------------------------------------------------------------------------------------------#
+# CR Animation Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/CR-Animation-Nodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#-----------------------------------------------------------------------------------------------------------#
+
+#-----------------------------------------------------------------------------------------------------------#
+# FUNCTIONS
+#-----------------------------------------------------------------------------------------------------------#
+
+def keyframe_scheduler(schedule, schedule_alias, current_frame):
+
+ # Initialise
+ schedule_lines = list()
+ previous_params = ""
+
+ # Loop through the schedule to find lines with matching schedule_alias
+ for item in schedule:
+ alias = item[0]
+ if alias == schedule_alias:
+ schedule_lines.extend([(item)])
+
+ # Loop through the filtered lines
+ for i, item in enumerate(schedule_lines):
+ # Get alias and schedule line
+ alias, line = item
+
+ # Skip empty lines
+ if not line.strip():
+ print(f"[Warning] Skipped blank line at line {i}")
+ continue
+
+ # Get parameters from the tuples
+ frame_str, params = line.split(',', 1)
+ frame = int(frame_str)
+
+ # Strip spaces at start of params
+ params = params.lstrip()
+
+ # Return the params
+ if frame < current_frame:
+ previous_params = params
+ continue
+ if frame == current_frame:
+ previous_params = params
+ else:
+ params = previous_params
+ return params
+
+ # Continue using the final params after the last schedule line has been evaluated
+ return previous_params
+
+def prompt_scheduler(schedule, schedule_alias, current_frame):
+
+ # Initialise
+ schedule_lines = list()
+ previous_prompt = ""
+ previous_keyframe = 0
+
+ #print(schedule, schedule_alias, current_frame)
+
+ # Loop through the schedule to find lines with matching schedule_alias
+ for item in schedule:
+ alias = item[0]
+ if alias == schedule_alias:
+ schedule_lines.extend([(item)])
+
+ # Loop through the filtered lines
+ for i, item in enumerate(schedule_lines):
+ # Get alias and schedule line
+ alias, line = item
+
+ # Get parameters from the tuples
+ frame_str, prompt = line.split(',', 1)
+ frame_str = frame_str.strip('\"')
+ frame = int(frame_str)
+
+ # Strip leading spaces and quotes
+ prompt = prompt.lstrip()
+ prompt = prompt.replace('"', '')
+
+ # Return the parameters
+ if frame < current_frame:
+ previous_prompt = prompt
+ previous_keyframe = frame
+ #print(f"[Debug] frame < current_frame, frame {frame}, current frame {current_frame}")
+ #print(f"[Debug] frame < current_frame, prompt {prompt}")
+ continue
+ if frame == current_frame:
+ next_prompt = prompt
+ next_keyframe = frame
+ previous_prompt = prompt
+ previous_keyframe = frame
+ #print(f"[Debug] frame = current_frame, frame {frame}, current frame {current_frame}, next keyframe {next_keyframe}")
+ #print(f"[Debug] frame = current_frame, prompt {prompt}")
+ else:
+ next_prompt = prompt
+ next_keyframe = frame
+ prompt = previous_prompt
+ #print(f"[Debug] frame > current_frame, frame {frame}, current frame {current_frame}, next keyframe {next_keyframe}")
+ #print(f"[Debug] frame > current_frame, next prompt {next_prompt}")
+
+ return prompt, next_prompt, previous_keyframe, next_keyframe
+
+ # Continue using the final params after the last schedule line has been evaluated
+ return previous_prompt, previous_prompt, previous_keyframe, previous_keyframe
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/interpolation.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/interpolation.py
new file mode 100644
index 0000000000000000000000000000000000000000..f328e4c014a18868ee3fa70ac00ab9dd4105036d
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/interpolation.py
@@ -0,0 +1,225 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# CR Animation Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/CR-Animation-Nodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+
+import torch
+from ..categories import icons
+
+#---------------------------------------------------------------------------------------------------------------------#
+# NODES
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_GradientInteger:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ gradient_profiles = ["Lerp"]
+
+ return {"required": {"start_value": ("INT", {"default": 1.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "end_value": ("INT", {"default": 1.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "start_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "frame_duration": ("INT", {"default": 1.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "gradient_profile": (gradient_profiles,)
+ },
+ }
+
+ RETURN_TYPES = ("INT", "STRING", )
+ RETURN_NAMES = ("INT", "show_help", )
+ FUNCTION = "gradient"
+ CATEGORY = icons.get("Comfyroll/Animation/Interpolate")
+
+ def gradient(self, start_value, end_value, start_frame, frame_duration, current_frame, gradient_profile):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Interpolation-Nodes#cr-gradient-integer"
+
+ if current_frame < start_frame:
+ return (start_value, show_help, )
+
+ if current_frame > start_frame + frame_duration:
+ return (end_value, show_help, )
+
+ step = (end_value - start_value) / frame_duration
+
+ current_step = current_frame - start_frame
+
+ int_out = start_value + int(current_step * step)
+
+ return (int_out, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_GradientFloat:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ gradient_profiles = ["Lerp"]
+
+ return {"required": {"start_value": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 9999.0, "step": 0.01,}),
+ "end_value": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 9999.0, "step": 0.01,}),
+ "start_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "frame_duration": ("INT", {"default": 1.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "gradient_profile": (gradient_profiles,)
+ },
+ }
+
+ RETURN_TYPES = ("FLOAT", "STRING", )
+ RETURN_NAMES = ("FLOAT", "show_help", )
+ FUNCTION = "gradient"
+ CATEGORY = icons.get("Comfyroll/Animation/Interpolate")
+
+ def gradient(self, start_value, end_value, start_frame, frame_duration, current_frame, gradient_profile):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Interpolation-Nodes#cr-gradient-float"
+
+ if current_frame < start_frame:
+ return (start_value, show_help, )
+
+ if current_frame > start_frame + frame_duration:
+ return (end_value, show_help, )
+
+ step = (end_value - start_value) / frame_duration
+
+ current_step = current_frame - start_frame
+
+ float_out = start_value + current_step * step
+
+ return (float_out, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_IncrementFloat:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ return {"required": {"start_value": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 9999.0, "step": 0.001,}),
+ "step": ("FLOAT", {"default": 0.1, "min": -9999.0, "max": 9999.0, "step": 0.001,}),
+ "start_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.00,}),
+ "frame_duration": ("INT", {"default": 1.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ },
+ }
+
+ RETURN_TYPES = ("FLOAT", "STRING", )
+ RETURN_NAMES = ("FLOAT", "show_help", )
+ OUTPUT_NODE = True
+ FUNCTION = "increment"
+ CATEGORY = icons.get("Comfyroll/Animation/Interpolate")
+
+ def increment(self, start_value, step, start_frame, frame_duration, current_frame):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Interpolation-Nodes#cr-increment-float"
+
+ #print(f"current frame {current_frame}")
+ if current_frame < start_frame:
+ return (start_value, show_help, )
+
+ current_value = start_value + (current_frame - start_frame) * step
+ if current_frame <= start_frame + frame_duration:
+ current_value += step
+ #print(f" 1:
+ print("Warning: Conditioning from contains more than 1 cond, only the first one will actually be applied to conditioning_to.")
+
+ cond_from = conditioning_from[0][0]
+ pooled_output_from = conditioning_from[0][1].get("pooled_output", None)
+
+ for i in range(len(conditioning_to)):
+ t1 = conditioning_to[i][0]
+ pooled_output_to = conditioning_to[i][1].get("pooled_output", pooled_output_from)
+ t0 = cond_from[:,:t1.shape[1]]
+ if t0.shape[1] < t1.shape[1]:
+ t0 = torch.cat([t0] + [torch.zeros((1, (t1.shape[1] - t0.shape[1]), t1.shape[2]))], dim=1)
+
+ tw = torch.mul(t1, conditioning_to_strength) + torch.mul(t0, (1.0 - conditioning_to_strength))
+ t_to = conditioning_to[i][1].copy()
+ if pooled_output_from is not None and pooled_output_to is not None:
+ t_to["pooled_output"] = torch.mul(pooled_output_to, conditioning_to_strength) + torch.mul(pooled_output_from, (1.0 - conditioning_to_strength))
+ elif pooled_output_from is not None:
+ t_to["pooled_output"] = pooled_output_from
+
+ n = [tw, t_to]
+ out.append(n)
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Prompt-Nodes#cr-encode-scheduled-prompts"
+ return (out, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+# 7 nodes
+'''
+NODE_CLASS_MAPPINGS = {
+ "CR Prompt List":CR_PromptList,
+ "CR Prompt List Keyframes":CR_PromptListKeyframes,
+ "CR Simple Prompt List":CR_SimplePromptList,
+ "CR Simple Prompt List Keyframes":CR_SimplePromptListKeyframes,
+ "CR Keyframe List":CR_KeyframeList,
+ #"CR Load Prompt Style":CR_LoadPromptStyle,
+ "CR Encode Scheduled Prompts":CR_EncodeScheduledPrompts,
+}
+'''
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/schedulers.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/schedulers.py
new file mode 100644
index 0000000000000000000000000000000000000000..fdbb03221ab2e23bb5f663962d9bb09f2ee36fb6
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/schedulers.py
@@ -0,0 +1,523 @@
+#-----------------------------------------------------------------------------------------------------------#
+# CR Animation Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/CR-Animation-Nodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#-----------------------------------------------------------------------------------------------------------#
+
+import comfy.sd
+import os
+import sys
+import folder_paths
+from nodes import LoraLoader
+from .functions import keyframe_scheduler, prompt_scheduler
+from ..categories import icons
+
+#-----------------------------------------------------------------------------------------------------------#
+# NODES
+#-----------------------------------------------------------------------------------------------------------#
+# Schedulers
+#-----------------------------------------------------------------------------------------------------------#
+class CR_ValueScheduler:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ modes = ["Default Value", "Schedule"]
+ return {"required": {"mode": (modes,),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "schedule_alias": ("STRING", {"default": "", "multiline": False}),
+ "default_value": ("FLOAT", {"default": 1.0, "min": -9999.0, "max": 9999.0, "step": 0.01,}),
+ "schedule_format": (["CR", "Deforum"],),
+ },
+ "optional": {"schedule": ("SCHEDULE",),
+ }
+ }
+
+ RETURN_TYPES = ("INT", "FLOAT", "STRING", )
+ RETURN_NAMES = ("INT", "FLOAT", "show_help", )
+ FUNCTION = "schedule"
+ CATEGORY = icons.get("Comfyroll/Animation/Schedulers")
+
+ def schedule(self, mode, current_frame, schedule_alias, default_value, schedule_format, schedule=None):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Scheduler-Nodes#cr-value-scheduler"
+
+ if mode == "Default Value":
+ print(f"[Info] CR Value Scheduler: Scheduler {schedule_alias} is disabled")
+ int_out, float_out = int(default_value), float(default_value)
+ return (int_out, float_out, show_help, )
+
+ # Get params
+ params = keyframe_scheduler(schedule, schedule_alias, current_frame)
+
+ # Handle case where there is no schedule line for frame 0
+ if params == "":
+ if current_frame == 0:
+ print(f"[Warning] CR Value Scheduler. No frame 0 found in schedule. Starting with default value at frame 0")
+ int_out, float_out = int(default_value), float(default_value)
+ else:
+ # Try the params
+ try:
+ value = float(params)
+ int_out, float_out = int(value), float(value)
+ except ValueError:
+ print(f"[Warning] CR Value Scheduler. Invalid params: {params}")
+ return()
+ return (int_out, float_out, show_help, )
+
+#-----------------------------------------------------------------------------------------------------------#
+class CR_TextScheduler:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ modes = ["Default Text", "Schedule"]
+ return {"required": {"mode": (modes,),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "schedule_alias": ("STRING", {"default": "", "multiline": False}),
+ "default_text": ("STRING", {"multiline": False, "default": "default text"}),
+ "schedule_format": (["CR", "Deforum"],),
+ },
+ "optional": {"schedule": ("SCHEDULE",),
+ }
+ }
+
+ RETURN_TYPES = ("STRING", "STRING", )
+ RETURN_NAMES = ("STRING", "show_help", )
+ FUNCTION = "schedule"
+ CATEGORY = icons.get("Comfyroll/Animation/Schedulers")
+
+ def schedule(self, mode, current_frame, schedule_alias, default_text, schedule_format, schedule=None):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Scheduler-Nodes#cr-text-scheduler"
+
+ if mode == "Default Text":
+ print(f"[Info] CR Text Scheduler: Scheduler {schedule_alias} is disabled")
+ text_out = default_text
+ return (text_out, show_help, )
+
+ # Get params
+ params = keyframe_scheduler(schedule, schedule_alias, current_frame)
+
+ # Handle case where there is no schedule line for frame 0
+ if params == "":
+ if current_frame == 0:
+ print(f"[Warning] CR Text Scheduler. No frame 0 found in schedule. Starting with default value at frame 0")
+ text_out = default_value,
+ else:
+ # Try the params
+ try:
+ text_out = params
+ except ValueError:
+ print(f"[Warning] CR Text Scheduler. Invalid params: {params}")
+ return()
+ return (text_out, show_help, )
+
+
+#-----------------------------------------------------------------------------------------------------------#
+class CR_PromptScheduler:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ modes = ["Default Prompt", "Keyframe List", "Schedule"]
+ return {"required": {"mode": (modes,),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "default_prompt": ("STRING", {"multiline": False, "default": "default prompt"}),
+ "schedule_format": (["CR", "Deforum"],),
+ #"pingpong_keyframes": (["No", "Yes"],),
+ "interpolate_prompt": (["Yes", "No"],),
+ },
+ "optional": {"schedule": ("SCHEDULE",),
+ "schedule_alias": ("STRING", {"default prompt": "", "multiline": False}),
+ "keyframe_list": ("STRING", {"multiline": True, "default": "keyframe list"}),
+ "prepend_text": ("STRING", {"multiline": True, "default": "prepend text"}),
+ "append_text": ("STRING", {"multiline": True, "default": "append text"}),
+ }
+ }
+
+ RETURN_TYPES = ("STRING", "STRING", "FLOAT", "STRING", )
+ RETURN_NAMES = ("current_prompt", "next_prompt", "weight", "show_help", )
+ FUNCTION = "schedule"
+ CATEGORY = icons.get("Comfyroll/Animation/Schedulers")
+
+ def schedule(self, mode, prepend_text, append_text, current_frame, schedule_alias, default_prompt, schedule_format, interpolate_prompt, keyframe_list="", schedule=None):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Scheduler-Nodes#cr-prompt-scheduler"
+
+ schedule_lines = list()
+
+ if mode == "Default Prompt":
+ print(f"[Info] CR Prompt Scheduler: Scheduler {schedule_alias} is disabled")
+ return (default_prompt, default_prompt, 1.0, show_help, )
+
+ if mode == "Keyframe List":
+ if keyframe_list == "":
+ print(f"[Error] CR Prompt Scheduler: No keyframe list found.")
+ return ()
+ else:
+ lines = keyframe_list.split('\n')
+ for line in lines:
+ # If deforum, convert to CR format
+ if schedule_format == "Deforum":
+ line = line.replace(":", ",")
+ line = line.rstrip(',')
+ line = line.lstrip()
+ # Strip empty lines
+ if not line.strip():
+ print(f"[Warning] CR Simple Prompt Scheduler. Skipped blank line at line {i}")
+ continue
+ schedule_lines.extend([(schedule_alias, line)])
+ schedule = schedule_lines
+
+ if mode == "Schedule":
+ if schedule is None:
+ print(f"[Error] CR Prompt Scheduler: No schedule found.")
+ return ()
+ # If deforum, convert to CR format
+ if schedule_format == "Deforum":
+ for item in schedule:
+ alias, line = item
+ line = line.replace(":", ",")
+ line = line.rstrip(',')
+ schedule_lines.extend([(schedule_alias, line)])
+ schedule = schedule_lines
+
+ current_prompt, next_prompt, current_keyframe, next_keyframe = prompt_scheduler(schedule, schedule_alias, current_frame)
+
+ if current_prompt == "":
+ print(f"[Warning] CR Simple Prompt Scheduler. No prompt found for frame. Schedules should start at frame 0.")
+ else:
+ try:
+ current_prompt_out = prepend_text + ", " + str(current_prompt) + ", " + append_text
+ next_prompt_out = prepend_text + ", " + str(next_prompt) + ", " + append_text
+ from_index = int(current_keyframe)
+ to_index = int(next_keyframe)
+ except ValueError:
+ print(f"[Warning] CR Simple Text Scheduler. Invalid keyframe at frame {current_frame}")
+
+ if from_index == to_index or interpolate_prompt == "No":
+ weight_out = 1.0
+ else:
+ weight_out = (to_index - current_frame) / (to_index - from_index)
+
+ #if pingpong_keyframes == "Yes":
+ # temp = current_prompt_out
+ # current_prompt_out = next_prompt_out
+ # next_prompt_out = temp
+ # weight_out = 1 - weight_out
+
+ return (current_prompt_out, next_prompt_out, weight_out, show_help, )
+
+#-----------------------------------------------------------------------------------------------------------#
+class CR_SimplePromptScheduler:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {"keyframe_list": ("STRING", {"multiline": True, "default": "frame_number, text"}),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "keyframe_format": (["CR", "Deforum"],),
+ },
+ }
+
+ RETURN_TYPES = ("STRING", "STRING", "FLOAT", "STRING", )
+ RETURN_NAMES = ("current_prompt", "next_prompt", "weight", "show_help", )
+ FUNCTION = "simple_schedule"
+ CATEGORY = icons.get("Comfyroll/Animation/Schedulers")
+
+ def simple_schedule(self, keyframe_list, keyframe_format, current_frame):
+
+ keyframes = list()
+
+ if keyframe_list == "":
+ print(f"[Error] CR Simple Prompt Scheduler. No lines in keyframe list")
+ return ()
+ lines = keyframe_list.split('\n')
+ for line in lines:
+ # If deforum, convert to CR format
+ if keyframe_format == "Deforum":
+ line = line.replace(":", ",")
+ line = line.rstrip(',')
+ if not line.strip():
+ print(f"[Warning] CR Simple Prompt Scheduler. Skipped blank line at line {i}")
+ continue
+ keyframes.extend([("SIMPLE", line)])
+
+ #print(f"[Debug] CR Simple Prompt Scheduler. Calling function")
+ current_prompt, next_prompt, current_keyframe, next_keyframe = prompt_scheduler(keyframes, "SIMPLE", current_frame)
+
+ if current_prompt == "":
+ print(f"[Warning] CR Simple Prompt Scheduler. No prompt found for frame. Simple schedules must start at frame 0.")
+ else:
+ try:
+ current_prompt_out = str(current_prompt)
+ next_prompt_out = str(next_prompt)
+ from_index = int(current_keyframe)
+ to_index = int(next_keyframe)
+ except ValueError:
+ print(f"[Warning] CR Simple Text Scheduler. Invalid keyframe at frame {current_frame}")
+
+ if from_index == to_index:
+ weight_out = 1.0
+ else:
+ weight_out = (to_index - current_frame) / (to_index - from_index)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Scheduler-Nodes#cr-simple-prompt-scheduler"
+
+ return(current_prompt_out, next_prompt_out, weight_out, show_help, )
+
+#-----------------------------------------------------------------------------------------------------------#
+class CR_SimpleValueScheduler:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {"schedule": ("STRING", {"multiline": True, "default": "frame_number, value"}),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ },
+ }
+
+ RETURN_TYPES = ("INT", "FLOAT", "STRING", )
+ RETURN_NAMES = ("INT", "FLOAT", "show_help", )
+ FUNCTION = "simple_schedule"
+ CATEGORY = icons.get("Comfyroll/Animation/Schedulers")
+
+ def simple_schedule(self, schedule, current_frame):
+
+ schedule_lines = list()
+
+ if schedule == "":
+ print(f"[Warning] CR Simple Value Scheduler. No lines in schedule")
+ return ()
+
+ lines = schedule.split('\n')
+ for line in lines:
+ schedule_lines.extend([("SIMPLE", line)])
+
+ params = keyframe_scheduler(schedule_lines, "SIMPLE", current_frame)
+
+ if params == "":
+ print(f"[Warning] CR Simple Value Scheduler. No schedule found for frame. Simple schedules must start at frame 0.")
+ else:
+ try:
+ int_out = int(params.split('.')[0]) #rounds down
+ float_out = float(params)
+ except ValueError:
+ print(f"[Warning] CR Simple Value Scheduler. Invalid params {params} at frame {current_frame}")
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Scheduler-Nodes#cr-simple-value-scheduler"
+
+ return (int_out, float_out, show_help, )
+
+#-----------------------------------------------------------------------------------------------------------#
+class CR_SimpleTextScheduler:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {"schedule": ("STRING", {"multiline": True, "default": "frame_number, text"}),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ },
+ }
+
+ RETURN_TYPES = ("STRING", "STRING", )
+ RETURN_NAMES = ("STRING", "show_help", )
+ FUNCTION = "simple_schedule"
+ CATEGORY = icons.get("Comfyroll/Animation/Schedulers")
+
+ def simple_schedule(self, schedule, current_frame):
+
+ schedule_lines = list()
+
+ if schedule == "":
+ print(f"[Warning] CR Simple Text Scheduler. No lines in schedule")
+ return ()
+
+ lines = schedule.split('\n')
+ for line in lines:
+ schedule_lines.extend([("SIMPLE", line)])
+
+ params = keyframe_scheduler(schedule_lines, "SIMPLE", current_frame)
+
+ if params == "":
+ print(f"[Warning] CR Simple Text Scheduler. No schedule found for frame. Simple schedules must start at frame 0.")
+ else:
+ try:
+ text_out = str(params)
+ except ValueError:
+ print(f"[Warning] CR Simple Text Scheduler. Invalid params {params} at frame {current_frame}")
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Scheduler-Nodes#cr-simple-text-scheduler"
+
+ return(text_out, show_help, )
+
+#-----------------------------------------------------------------------------------------------------------#
+class CR_LoadScheduledModels:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ modes = ["Load default Model", "Schedule"]
+
+ return {"required": {"mode": (modes,),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "schedule_alias": ("STRING", {"default": "", "multiline": False}),
+ "default_model": (folder_paths.get_filename_list("checkpoints"), ),
+ "schedule_format": (["CR", "Deforum"],)
+ },
+ "optional": {"model_list": ("MODEL_LIST",),
+ "schedule": ("SCHEDULE",)
+ },
+ }
+
+ RETURN_TYPES = ("MODEL", "CLIP", "VAE", "STRING", )
+ RETURN_NAMES = ("MODEL", "CLIP", "VAE", "show_help", )
+ FUNCTION = "schedule"
+ CATEGORY = icons.get("Comfyroll/Animation/Schedulers")
+
+ def schedule(self, mode, current_frame, schedule_alias, default_model, schedule_format, model_list=None, schedule=None):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Scheduler-Nodes#cr-load-scheduled-models"
+
+ #model_name = ""
+
+ # Load default Model mode
+ if mode == "Load default Model":
+ ckpt_path = folder_paths.get_full_path("checkpoints", default_model)
+ out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, embedding_directory=folder_paths.get_folder_paths("embeddings"))
+ print(f"[Debug] CR Load Scheduled Models. Loading default model.")
+ return (out[:3], show_help, )
+
+ # Get params
+ params = keyframe_scheduler(schedule, schedule_alias, current_frame)
+
+ # Handle case where there is no schedule line for a frame
+ if params == "":
+ print(f"[Warning] CR Load Scheduled Models. No model specified in schedule for frame {current_frame}. Using default model.")
+ ckpt_path = folder_paths.get_full_path("checkpoints", default_model)
+ out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, embedding_directory=folder_paths.get_folder_paths("embeddings"))
+ return (out[:3], show_help, )
+ else:
+ # Try the params
+ try:
+ model_alias = str(params)
+ except ValueError:
+ print(f"[Warning] CR Load Scheduled Models. Invalid params: {params}")
+ return()
+
+ # Iterate through the model list to get the model name
+ for ckpt_alias, ckpt_name in model_list:
+ if ckpt_alias == model_alias:
+ model_name = ckpt_name
+ break # Exit the loop early once a match is found, ignores any duplicate matches
+
+ # Check if a matching model has been found
+ if model_name == "":
+ print(f"[Info] CR Load Scheduled Models. No model alias match found for {model_alias}. Frame {current_frame} will produce an error.")
+ return()
+ else:
+ print(f"[Info] CR Load Scheduled Models. Model alias {model_alias} matched to {model_name}")
+
+ # Load the new model
+ ckpt_path = folder_paths.get_full_path("checkpoints", model_name)
+ out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, embedding_directory=folder_paths.get_folder_paths("embeddings"))
+ print(f"[Info] CR Load Scheduled Models. Loading new checkpoint model {model_name}")
+ return (out[:3], show_help, )
+
+#-----------------------------------------------------------------------------------------------------------#
+class CR_LoadScheduledLoRAs:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ modes = ["Off", "Load default LoRA", "Schedule"]
+
+ return {"required": {"mode": (modes,),
+ "model": ("MODEL",),
+ "clip": ("CLIP", ),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "schedule_alias": ("STRING", {"default": "", "multiline": False}),
+ "default_lora": (folder_paths.get_filename_list("loras"), ),
+ "strength_model": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
+ "strength_clip": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
+ "schedule_format": (["CR", "Deforum"],)
+ },
+ "optional": {"lora_list": ("LORA_LIST",),
+ "schedule": ("SCHEDULE",)
+ },
+ }
+
+ RETURN_TYPES = ("MODEL", "CLIP", "STRING", )
+ RETURN_NAMES = ("MODEL", "CLIP", "show_help", )
+ FUNCTION = "schedule"
+ CATEGORY = icons.get("Comfyroll/Animation/Schedulers")
+
+ def schedule(self, mode, model, clip, current_frame, schedule_alias, default_lora, strength_model, strength_clip, schedule_format, lora_list=None, schedule=None):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Scheduler-Nodes#cr-load-scheduled-loras"
+ #lora_name = ""
+
+ # Off mode
+ if mode == "Off":
+ print(f"[Info] CR Load Scheduled LoRAs. Disabled.")
+ return (model, clip, show_help, )
+
+ # Load Default LoRA mode
+ if mode == "Load default LoRA":
+ if default_lora == None:
+ return (model, clip, show_help, )
+ if strength_model == 0 and strength_clip == 0:
+ return (model, clip, show_help, )
+ model, clip = LoraLoader().load_lora(model, clip, default_lora, strength_model, strength_clip)
+ print(f"[Info] CR Load Scheduled LoRAs. Loading default LoRA {lora_name}.")
+ return (model, clip, show_help, )
+
+ # Get params
+ params = keyframe_scheduler(schedule, schedule_alias, current_frame)
+
+ # Handle case where there is no schedule line for a frame
+ if params == "":
+ print(f"[Warning] CR Load Scheduled LoRAs. No LoRA specified in schedule for frame {current_frame}. Using default lora.")
+ if default_lora != None:
+ model, clip = LoraLoader().load_lora(model, clip, default_lora, strength_model, strength_clip)
+ return (model, clip, show_help, )
+ else:
+ # Unpack the parameters
+ parts = params.split(',')
+ if len(parts) == 3:
+ s_lora_alias = parts[0].strip()
+ s_strength_model = float(parts[1].strip())
+ s_strength_clip = float(parts[1].strip())
+ else:
+ print(f"[Warning] CR Simple Value Scheduler. Skipped invalid line: {line}")
+ return()
+
+ # Iterate through the LoRA list to get the LoRA name
+ for l_lora_alias, l_lora_name, l_strength_model, l_strength_clip in lora_list:
+ print(l_lora_alias, l_lora_name, l_strength_model, l_strength_clip)
+ if l_lora_alias == s_lora_alias:
+ print(f"[Info] CR Load Scheduled LoRAs. LoRA alias match found for {s_lora_alias}")
+ lora_name = l_lora_name
+ break # Exit the loop early once a match is found, ignores any duplicate matches
+
+ # Check if a matching LoRA has been found
+ if lora_name == "":
+ print(f"[Info] CR Load Scheduled LoRAs. No LoRA alias match found for {s_lora_alias}. Frame {current_frame}.")
+ return()
+ else:
+ print(f"[Info] CR Load Scheduled LoRAs. LoRA {lora_name}")
+
+ # Load the new LoRA
+ model, clip = LoraLoader().load_lora(model, clip, lora_name, s_strength_model, s_strength_clip)
+ print(f"[Debug] CR Load Scheduled LoRAs. Loading new LoRA {lora_name}")
+ return (model, clip, show_help, )
+
+#-----------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#-----------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+# 11 nodes
+'''
+NODE_CLASS_MAPPINGS = {
+ ### Schedulers
+ "CR Simple Value Scheduler":CR_SimpleValueScheduler,
+ "CR Simple Text Scheduler":CR_SimpleTextScheduler,
+ "CR Simple Prompt Scheduler":CR_SimplePromptScheduler,
+ "CR Load Scheduled Models":CR_LoadScheduledModels,
+ "CR Load Scheduled LoRAs":CR_LoadScheduledLoRAs,
+ "CR Load Scheduled ControlNets":CR_LoadScheduledControlNets,
+ "CR Value Scheduler":CR_ValueScheduler,
+ "CR Text Scheduler":CR_TextScheduler,
+ "CR Prompt Scheduler":CR_PromptScheduler,
+}
+'''
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/schedules.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/schedules.py
new file mode 100644
index 0000000000000000000000000000000000000000..85566d288ead36d771be4e5bd803ac6b9e6378cf
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/schedules.py
@@ -0,0 +1,308 @@
+#-----------------------------------------------------------------------------------------------------------#
+# CR Animation Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/CR-Animation-Nodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#-----------------------------------------------------------------------------------------------------------#
+
+import comfy.sd
+import os
+import sys
+import folder_paths
+from nodes import LoraLoader
+from .functions import keyframe_scheduler, prompt_scheduler
+from ..categories import icons
+
+#-----------------------------------------------------------------------------------------------------------#
+# Schedules
+#-----------------------------------------------------------------------------------------------------------#
+class CR_SimpleSchedule:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ schedule_types = ["Value", "Text", "Prompt", "Prompt Weight", "Model", "LoRA", "ControlNet", "Style", "Upscale", "Camera", "Job"]
+ return {"required": {"schedule": ("STRING",
+ {"multiline": True, "default": "frame_number, item_alias, [attr_value1, attr_value2]"}
+ ),
+ "schedule_type": (schedule_types,),
+ "schedule_alias": ("STRING", {"default": "", "multiline": False}),
+ "schedule_format": (["CR", "Deforum"],),
+ },
+ }
+
+ RETURN_TYPES = ("SCHEDULE", "STRING", )
+ RETURN_NAMES = ("SCHEDULE", "show_help", )
+ FUNCTION = "send_schedule"
+ CATEGORY = icons.get("Comfyroll/Animation/Schedule")
+
+ def send_schedule(self, schedule, schedule_type, schedule_alias, schedule_format):
+
+ schedule_lines = list()
+
+ # Extend the list for each line in the schedule
+ if schedule != "" and schedule_alias != "":
+ lines = schedule.split('\n')
+ for line in lines:
+ # Skip empty lines
+ if not line.strip():
+ print(f"[Warning] CR Simple Schedule. Skipped blank line: {line}")
+ continue
+
+ schedule_lines.extend([(schedule_alias, line)])
+ #print(f"[Debug] CR Simple Schedule: {schedule_lines}")
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Schedule-Nodes#cr-simple-schedule"
+
+ return (schedule_lines, show_help, )
+
+#-----------------------------------------------------------------------------------------------------------#
+class CR_CombineSchedules:
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {"required": {
+ },
+ "optional":{
+ "schedule_1": ("SCHEDULE",),
+ "schedule_2": ("SCHEDULE",),
+ "schedule_3": ("SCHEDULE",),
+ "schedule_4": ("SCHEDULE",),
+ },
+ }
+
+ RETURN_TYPES = ("SCHEDULE", "STRING", )
+ RETURN_NAMES = ("SCHEDULE", "show_text", )
+ FUNCTION = "combine"
+ CATEGORY = icons.get("Comfyroll/Animation/Schedule")
+
+ def combine(self, schedule_1=None, schedule_2=None, schedule_3=None, schedule_4=None):
+
+ # Initialise the list
+ schedules = list()
+ schedule_text = list()
+
+ # Extend the list for each schedule in connected stacks
+ if schedule_1 is not None:
+ schedules.extend([l for l in schedule_1]),
+ schedule_text.extend(schedule_1),
+
+ if schedule_2 is not None:
+ schedules.extend([l for l in schedule_2]),
+ schedule_text.extend(schedule_2),
+
+ if schedule_3 is not None:
+ schedules.extend([l for l in schedule_3]),
+ schedule_text.extend(schedule_3),
+
+ if schedule_4 is not None:
+ schedules.extend([l for l in schedule_4]),
+ schedule_text.extend(schedule_4),
+
+ print(f"[Debug] CR Combine Schedules: {schedules}")
+
+ show_text = "".join(str(schedule_text))
+
+ return (schedules, show_text, )
+
+#-----------------------------------------------------------------------------------------------------------#
+class CR_CentralSchedule:
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ schedule_types = ["Value", "Text", "Prompt", "Prompt Weight", "Model", "LoRA", "ControlNet", "Style", "Upscale", "Camera", "Job"]
+ return {"required": {
+ "schedule_1": ("STRING", {"multiline": True, "default": "schedule"}),
+ "schedule_type1": (schedule_types,),
+ "schedule_alias1": ("STRING", {"multiline": False, "default": ""}),
+ "schedule_2": ("STRING", {"multiline": True, "default": "schedule"}),
+ "schedule_type2": (schedule_types,),
+ "schedule_alias2": ("STRING", {"multiline": False, "default": ""}),
+ "schedule_3": ("STRING", {"multiline": True, "default": "schedule"}),
+ "schedule_type3": (schedule_types,),
+ "schedule_alias3": ("STRING", {"multiline": False, "default": ""}),
+ "schedule_format": (["CR", "Deforum"],),
+ },
+ "optional": {"schedule": ("SCHEDULE",)
+ },
+ }
+
+ RETURN_TYPES = ("SCHEDULE", "STRING", )
+ RETURN_NAMES = ("SCHEDULE", "show_text", )
+ FUNCTION = "build_schedule"
+ CATEGORY = icons.get("Comfyroll/Animation/Schedule")
+
+ def build_schedule(self, schedule_1, schedule_type1, schedule_alias1, schedule_2, schedule_type2, schedule_alias2, schedule_3, schedule_type3, schedule_alias3, schedule_format, schedule=None):
+
+ # schedule_type and schedule_format are not used in the function
+
+ # Initialise the list
+ schedules = list()
+ schedule_text = list()
+
+ # Extend the list for each schedule in linked stacks
+ if schedule is not None:
+ schedules.extend([l for l in schedule])
+ schedule_text.extend([l for l in schedule]),
+
+ # Extend the list for each schedule in the stack
+ if schedule_1 != "" and schedule_alias1 != "":
+ lines = schedule_1.split('\n')
+ for line in lines:
+ schedules.extend([(schedule_alias1, line)]),
+ schedule_text.extend([(schedule_alias1 + "," + schedule_1 + "\n")]),
+
+ if schedule_2 != "" and schedule_alias2 != "":
+ lines = schedule_2.split('\n')
+ for line in lines:
+ schedules.extend([(schedule_alias2, line)]),
+ schedule_text.extend([(schedule_alias2 + "," + schedule_2 + "\n")]),
+
+ if schedule_3 != "" and schedule_alias3 != "":
+ lines = schedule_3.split('\n')
+ for line in lines:
+ schedules.extend([(schedule_alias3, line)]),
+ schedule_text.extend([(schedule_alias3 + "," + schedule_3 + "\n")]),
+
+ #print(f"[Debug] CR Schedule List: {schedules}")
+
+ show_text = "".join(schedule_text)
+
+ return (schedules, show_text, )
+
+#-----------------------------------------------------------------------------------------------------------#
+class Comfyroll_ScheduleInputSwitch:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "Input": ("INT", {"default": 1, "min": 1, "max": 2}),
+ "schedule1": ("SCHEDULE",),
+ "schedule2": ("SCHEDULE",)
+ }
+ }
+
+ RETURN_TYPES = ("SCHEDULE", "STRING", )
+ RETURN_NAMES = ("SCHEDULE", "show_help", )
+ OUTPUT_NODE = True
+ FUNCTION = "switch"
+
+ CATEGORY = icons.get("Comfyroll/Animation/Schedule")
+
+ def switch(self, Input, schedule1, schedule2):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Schedule-Nodes#cr-schedule-input-switch"
+ if Input == 1:
+ return (schedule1, show_help, )
+ else:
+ return (schedule2, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_OutputScheduleToFile:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {
+ "output_file_path": ("STRING", {"multiline": False, "default": ""}),
+ "file_name": ("STRING", {"multiline": False, "default": ""}),
+ "file_extension": (["txt", "csv"],),
+ "schedule": ("SCHEDULE",),
+ }
+ }
+
+ RETURN_TYPES = ()
+ OUTPUT_NODE = True
+ FUNCTION = "csvoutput"
+ CATEGORY = icons.get("Comfyroll/Animation/Schedule")
+
+ def csvoutput(self, output_file_path, file_name, schedule, file_extension):
+ filepath = output_file_path + "\\" + file_name + "." + file_extension
+
+ index = 2
+
+ if(output_file_path == "" or file_name == ""):
+ print(f"[Warning] CR Output Schedule To File. No file details found. No file output.")
+ return ()
+
+ while os.path.exists(filepath):
+ if os.path.exists(filepath):
+ filepath = output_file_path + "\\" + file_name + str(index) + "." + file_extension
+
+ index = index + 1
+ else:
+ break
+
+ print(f"[Info] CR_Output Schedule To File: Saving to {filepath}")
+
+ if file_extension == "csv":
+ with open(filepath, "w", newline="") as csv_file:
+ csv_writer = csv.writer(csv_file)
+ csv_writer.writerows(schedule)
+ else:
+ with open(filepath, "w", newline="") as text_writer:
+ for line in schedule:
+ str_item = f'{line[0]},"{line[1]}"\n'
+ text_writer.write(str_item)
+
+
+ return ()
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_LoadScheduleFromFile:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {
+ "input_file_path": ("STRING", {"multiline": False, "default": ""}),
+ "file_name": ("STRING", {"multiline": False, "default": ""}),
+ "file_extension": (["txt", "csv"],),
+ }
+ }
+
+ RETURN_TYPES = ("SCHEDULE", "STRING", )
+ RETURN_NAMES = ("SCHEDULE", "show_text", )
+ FUNCTION = "csvinput"
+ CATEGORY = icons.get("Comfyroll/Animation/Schedule")
+
+ def csvinput(self, input_file_path, file_name, file_extension):
+ filepath = input_file_path + "\\" + file_name + "." + file_extension
+ print(f"CR_Load Schedule From File: Loading {filepath}")
+
+ lists = []
+
+ if file_extension == "csv":
+ with open(filepath, "r") as csv_file:
+ reader = csv.reader(csv_file)
+
+ for row in reader:
+ lists.append(row)
+
+ else:
+ with open(filepath, "r") as txt_file:
+ for row in txt_file:
+ parts = row.strip().split(",", 1)
+
+ if len(parts) >= 2:
+ second_part = parts[1].strip('"')
+ lists.append([parts[0], second_part])
+
+ print(lists)
+ return(lists,str(lists),)
+
+#-----------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#-----------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+# 11 nodes
+'''
+NODE_CLASS_MAPPINGS = {
+ ### Schedules
+ "CR Simple Schedule":CR_SimpleSchedule,
+ "CR Combine Schedules":CR_CombineSchedules,
+ "CR Central Schedule":CR_CentralSchedule,
+ "CR Schedule To ScheduleList":CR_ScheduleToScheduleList,
+ "CR Schedule Input Switch": Comfyroll_ScheduleInputSwitch,
+ "CR Output Schedule To File":CR_OutputScheduleToFile,
+ "CR Load Schedule From File":CR_LoadScheduleFromFile,
+}
+'''
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/utils.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..694066b43a09f8c6c0f0c10aeddcd304cbbcc91a
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/animation_nodes/utils.py
@@ -0,0 +1,61 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# CR Animation Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/CR-Animation-Nodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+
+from ..categories import icons
+
+#---------------------------------------------------------------------------------------------------------------------#
+
+class CR_DebatchFrames:
+ # cloned from ltdrdata Image Batch To Image List node
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": { "frames": ("IMAGE",), } }
+
+ RETURN_TYPES = ("IMAGE",)
+ RETURN_NAMES = ("debatched_frames",)
+ OUTPUT_IS_LIST = (True,)
+ FUNCTION = "debatch"
+ CATEGORY = icons.get("Comfyroll/Animation/Utils")
+
+ def debatch(self, frames):
+ images = [frames[i:i + 1, ...] for i in range(frames.shape[0])]
+ return (images, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_CurrentFrame:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required":{
+ "index": ("INT", {"default": 1, "min": -10000, "max": 10000}),
+ "print_to_console": (["Yes","No"],),
+ }
+ }
+
+ RETURN_TYPES = ("INT",)
+ RETURN_NAMES = ("index",)
+ FUNCTION = "to_console"
+ CATEGORY = icons.get("Comfyroll/Animation/Utils")
+
+ def to_console(self, index, print_to_console):
+ if print_to_console == "Yes":
+ print(f"[Info] CR Current Frame:{index}")
+
+ return (index, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+# 8 nodes
+'''
+NODE_CLASS_MAPPINGS = {
+ # Utils
+ "CR Debatch Frames":CR_DebatchFrames,
+ "CR Current Frame":CR_CurrentFrame,
+}
+'''
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/categories.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/categories.py
new file mode 100644
index 0000000000000000000000000000000000000000..d2ce562570d093e122b0b6c8b4ecbcd6e4c6ebbd
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/categories.py
@@ -0,0 +1,37 @@
+icons = {
+ "Comfyroll/Upscale": "🧩 Comfyroll/🔍 Upscale",
+ "Comfyroll/Model Merge": "🧩 Comfyroll/⛏️ Model Merge",
+ "Comfyroll/Utils/Logic": "🧩 Comfyroll/🛠️ Utils/🔀 Logic",
+ "Comfyroll/Utils/Process": "🧩 Comfyroll/🛠️ Utils/🔂 Process",
+ "Comfyroll/Utils/Index": "🧩 Comfyroll/🛠️ Utils/🔢 Index",
+ "Comfyroll/Utils/Conversion": "🧩 Comfyroll/🛠️ Utils/🔧 Conversion",
+ "Comfyroll/Utils/Random": "🧩 Comfyroll/🛠️ Utils/🎲 Random",
+ "Comfyroll/LoRA": "🧩 Comfyroll/💊 LoRA",
+ "Comfyroll/ControlNet": "🧩 Comfyroll/🕹️ ControlNet",
+ "Comfyroll/XY Grid": "🧩 Comfyroll/📉 XY Grid",
+ "Comfyroll/SDXL": "🧩 Comfyroll/🌟 SDXL",
+ "Comfyroll/Aspect Ratio": "🧩 Comfyroll/🔳 Aspect Ratio",
+ "Comfyroll/Pipe/Module": "🧩 Comfyroll/🎷 Pipe/✈️ Module",
+ "Comfyroll/Pipe/Image": "🧩 Comfyroll/🎷 Pipe/🛩️ Image",
+ "Comfyroll/Pipe": "🧩 Comfyroll/🎷 Pipe",
+ "Comfyroll/Graphics/Text": "🧩 Comfyroll/👾 Graphics/🔤 Text",
+ "Comfyroll/Graphics/Layout": "🧩 Comfyroll/👾 Graphics/🌁 Layout",
+ "Comfyroll/Graphics/Template": "🧩 Comfyroll/👾 Graphics/👽 Template",
+ "Comfyroll/Graphics/Filter": "🧩 Comfyroll/👾 Graphics/🎨 Filter",
+ "Comfyroll/Graphics/Pattern": "🧩 Comfyroll/👾 Graphics/🌈 Pattern",
+ "Comfyroll/Graphics/3D": "🧩 Comfyroll/👾 Graphics/3D",
+ "Comfyroll/Graphics/Utilty": "🧩 Comfyroll/👾 Graphics/🔧 Utility",
+ "Comfyroll/Workflow": "🧩 Comfyroll/Workflow",
+ "Comfyroll/Other": "🧩 Comfyroll/📦 Other",
+ "Comfyroll/Other/Legacy": "🧩 Comfyroll/📦 Other/💀 Legacy",
+ "Comfyroll/Animation/Camera": "🧩 Comfyroll/🎥 Animation/🎦 Camera",
+ "Comfyroll/Animation/Utils": "🧩 Comfyroll/🎥 Animation/🛠️ Utils",
+ "Comfyroll/Animation/Schedule": "🧩 Comfyroll/🎥 Animation/📋 Schedule",
+ "Comfyroll/Animation/Schedulers": "🧩 Comfyroll/🎥 Animation/📑 Schedulers",
+ "Comfyroll/Animation/Prompt": "🧩 Comfyroll/🎥 Animation/📝 Prompt",
+ "Comfyroll/Animation/List": "🧩 Comfyroll/🎥 Animation/📃 List",
+ "Comfyroll/Animation/Cyclers": "🧩 Comfyroll/🎥 Animation/♻️ Cyclers",
+ "Comfyroll/Animation/Interpolate": "🧩 Comfyroll/🎥 Animation/🔢 Interpolate",
+ "Comfyroll/Animation/IO": "🧩 Comfyroll/🎥 Animation/⌨️ IO",
+ "Comfyroll/Animation/Other": "🧩 Comfyroll/🎥 Animation/📦 Other",
+}
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/config.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/config.py
new file mode 100644
index 0000000000000000000000000000000000000000..dfb45566341dc925b82e7d83d4272ac5a69eba5f
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/config.py
@@ -0,0 +1,37 @@
+color_mapping = {
+ "white": (255, 255, 255),
+ "black": (0, 0, 0),
+ "red": (255, 0, 0),
+ "green": (0, 255, 0),
+ "blue": (0, 0, 255),
+ "yellow": (255, 255, 0),
+ "cyan": (0, 255, 255),
+ "magenta": (255, 0, 255),
+ "orange": (255, 165, 0),
+ "purple": (128, 0, 128),
+ "pink": (255, 192, 203),
+ "brown": (160, 85, 15),
+ "gray": (128, 128, 128),
+ "lightgray": (211, 211, 211),
+ "darkgray": (169, 169, 169),
+ "olive": (128, 128, 0),
+ "lime": (0, 128, 0),
+ "teal": (0, 128, 128),
+ "navy": (0, 0, 128),
+ "maroon": (128, 0, 0),
+ "fuchsia": (255, 0, 128),
+ "aqua": (0, 255, 128),
+ "silver": (192, 192, 192),
+ "gold": (255, 215, 0),
+ "turquoise": (64, 224, 208),
+ "lavender": (230, 230, 250),
+ "violet": (238, 130, 238),
+ "coral": (255, 127, 80),
+ "indigo": (75, 0, 130),
+}
+
+COLORS = ["custom", "white", "black", "red", "green", "blue", "yellow",
+ "cyan", "magenta", "orange", "purple", "pink", "brown", "gray",
+ "lightgray", "darkgray", "olive", "lime", "teal", "navy", "maroon",
+ "fuchsia", "aqua", "silver", "gold", "turquoise", "lavender",
+ "violet", "coral", "indigo"]
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_node_mappings.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_node_mappings.py
new file mode 100644
index 0000000000000000000000000000000000000000..4df9289563a65db27b8511efeb036115574c5ef4
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_node_mappings.py
@@ -0,0 +1,99 @@
+from .animation_nodes.prompt import *
+
+from .dev_nodes.dev_nodes import *
+from .dev_nodes.graphics_dev_nodes import *
+from .dev_nodes.dev_pil_3D import *
+from .dev_nodes.dev_workflow import *
+from .dev_nodes.animation_dev_nodes import *
+from .dev_nodes.dev_schedulers import *
+from .dev_nodes.dev_xygrid import *
+
+DEV_NODE_CLASS_MAPPINGS = {
+ ### XY Dev Nodes
+ "CR XYZ List": CR_XYZList,
+ "CR XYZ Interpolate": CR_XYZInterpolate,
+ "CR XYZ Index": CR_XYZIndex,
+ ### Graphics Dev Nodes
+ "CR Multi-Panel Meme Template": CR_MultiPanelMemeTemplate,
+ "CR Popular Meme Templates": CR_PopularMemeTemplates,
+ "CR Draw Perspective Text": CR_DrawPerspectiveText,
+ "CR Simple Annotations": CR_SimpleAnnotations,
+ "CR Apply Annotations": CR_ApplyAnnotations,
+ "CR Add Annotation": CR_AddAnnotation,
+ "CR 3D Polygon": CR_3DPolygon,
+ "CR 3D Solids": CR_3DSolids,
+ "CR Draw OBJ": CR_DrawOBJ,
+ "CR Simple Image Watermark": CR_SimpleImageWatermark,
+ "CR Comic Panel Templates (Advanced)": CR_ComicPanelTemplatesAdvanced,
+ ### Workflow Dev Nodes
+ "CR Job List": CR_JobList,
+ "CR Job Scheduler": CR_JobScheduler,
+ "CR Check Job Complete": CR_CheckJobComplete,
+ "CR Spawn Workflow Instance": CR_SpawnWorkflowInstance,
+ "CR Job Current Frame": CR_JobCurrentFrame,
+ "CR Load Workflow": CR_LoadWorkflow,
+ ### Animation Dev Nodes
+ "CR Prompt Weight Scheduler": CR_PromptWeightScheduler,
+ "CR Load Scheduled ControlNets": CR_LoadScheduledControlNets,
+ "CR Interpolate Prompt Weights": CR_InterpolatePromptWeights,
+ "CR Text List Cross Join": CR_TextListCrossJoin,
+ "CR Schedule Camera Movements": CR_ScheduleCameraMovements,
+ "CR Schedule Styles": CR_ScheduleStyles,
+ "CR Style List": CR_StyleList,
+ "CR Cycle Styles": CR_CycleStyles,
+ "CR Image Transition": CR_ImageTransition,
+ "CR Strobe Images": CR_StrobeImages,
+ "CR Alternate Latents": CR_AlternateLatents,
+ "CR 3D Camera Drone": CR_DroneCamera3D,
+ "CR 3D Camera Static": CR_StaticCamera3D,
+ "CR Interpolate Zoom": CR_InterpolateZoom,
+ "CR Interpolate Rotation": CR_InterpolateRotation,
+ "CR Interpolate Track": CR_InterpolateTrack,
+ "CR Continuous Zoom": CR_ContinuousZoom,
+ "CR Continuous Rotation": CR_ContinuousRotation,
+ "CR Continuous Track": CR_ContinuousTrack,
+}
+
+DEV_NODE_DISPLAY_NAME_MAPPINGS = {
+ # Dev Nodes
+ "CR XYZ List": "CR XYZ List (Dev)",
+ "CR XYZ Interpolate": "CR XYZ Interpolate (Dev)",
+ "CR XYZ Index": "CR XYZ Index (Dev)",
+ "CR Multi-Panel Meme Template": "CR Multi-Panel Meme Template (Dev)",
+ "CR Popular Meme Templates": "CR Popular Meme Templates (Dev)",
+ "CR Draw Perspective Text": "CR Draw Perspective Text (Dev)",
+ "CR Simple Annotations": "CR Simple Annotations (Dev)",
+ "CR Apply Annotations": "CR Apply Annotations (Prototype)",
+ "CR Add Annotation": "CR Add Annotation (Prototype)",
+ "CR 3D Polygon": "CR 3D Polygon (Dev)",
+ "CR 3D Solids": "CR 3D Solids (Dev)",
+ "CR Draw OBJ": "CR Draw OBJ",
+ "CR Simple Image Watermark": "CR Simple Image Watermark (Dev)",
+ "CR Comic Panel Templates Advanced": "👽 Comic Panel Templates (Advanced)",
+ "CR Job List": "CR Job List (Prototype)",
+ "CR Job Scheduler": "CR Job Scheduler (Prototype)",
+ "CR Check Job Complete": "CR Check Job Complete (Prototype)",
+ "CR Spawn Workflow Instance": "CR Spawn Workflow Instance (Prototype)",
+ "CR Job Current Frame": "CR Job Current Frame (Prototype)",
+ "CR Load Workflow": "CR Load Workflow (Prototype)",
+ ### Animation Dev Nodes
+ "CR Prompt Weight Scheduler": "CR Prompt Weight Scheduler (Dev)",
+ "CR Load Scheduled ControlNets": "CR Load Scheduled ControlNets (Dev)",
+ "CR Interpolate Prompt Weights": "CR Interpolate Prompt Weights (Dev)",
+ "CR Text List Cross Join": "CR Text List Cross Join (Dev)",
+ "CR Schedule Camera Movements": "CR Schedule Camera Movements (Prototype)",
+ "CR Schedule Styles": "CR Schedule Styles (Prototype)",
+ "CR Style List": "CR Style List (Prototype)",
+ "CR Cycle Styles": "CR Cycle Styles (Prototype)",
+ "CR Image Transition": "CR Image Transition (Prototype)",
+ "CR Strobe Images": "CR Strobe Images (Prototype)",
+ "CR Alternate Latents": "CR Alternate Latents (Prototype)",
+ "CR 3D Camera Drone": "CR 3D Camera Drone (Prototype)",
+ "CR 3D Camera Static": "CR 3D Camera Static (Prototype)",
+ "CR Interpolate Zoom": "CR Interpolate Zoom (Prototype)",
+ "CR Interpolate Rotation": "CR Interpolate Rotation (Prototype)",
+ "CR Interpolate Track": "CR Interpolate Track (Prototype)",
+ "CR Continuous Zoom": "CR Continuous Zoom (Prototype)",
+ "CR Continuous Rotation": "CR Continuous Rotation (Prototype)",
+ "CR Continuous Track": "CR Continuous Track (Prototype)",
+}
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/__pycache__/dev_nodes.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/__pycache__/dev_nodes.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..317c75858a65856dc94688d829e555ab16bd1e11
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/__pycache__/dev_nodes.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/__pycache__/dev_pil_3D.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/__pycache__/dev_pil_3D.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7013d02a2b8ef9051bba08b8bf6eea185a121575
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/__pycache__/dev_pil_3D.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/__pycache__/graphics_dev_nodes.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/__pycache__/graphics_dev_nodes.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..6dfac61a950c5ce83d96e129bf8550fb0c7ce38b
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/__pycache__/graphics_dev_nodes.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/animation_dev_nodes.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/animation_dev_nodes.py
new file mode 100644
index 0000000000000000000000000000000000000000..1222feccf186b663a8546e2a96339f431365d05f
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/animation_dev_nodes.py
@@ -0,0 +1,759 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# CR Animation Nodes by RockOfFire and Akatsuzi
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+
+from PIL import Image
+import torch
+import numpy as np
+import folder_paths
+from math import sqrt, ceil
+from ..categories import icons
+
+#---------------------------------------------------------------------------------------------------------------------#
+# Interpolation Nodes
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_InterpolatePromptWeights:
+
+ @classmethod
+ def INPUT_TYPES(cls):
+
+ #interpolation_methods = ["lerp", "slerp"]
+ interpolation_methods = ["lerp"]
+
+ return {
+ "required": {
+ "weight1": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
+ "weight2": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
+ "method": (interpolation_methods,),
+ },
+ #"optional": {"schedule": ("SCHEDULE",),
+ #}
+
+ }
+
+ RETURN_TYPES = ("FLOAT",)
+ RETURN_NAMES = ("weight",)
+ FUNCTION = "interpolate"
+
+ CATEGORY = icons.get("Comfyroll/Animation/Interpolate")
+
+ def interpolate(self, weight1, weight2, method):
+
+ weight = 0.5
+ return (weight,)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ImageTransition:
+ @classmethod
+ def INPUT_TYPES(s):
+ transition_types = ["Morph", "Dissolve", "Cross-Fade", "Jump Cut",
+ "Swipe-Left", "Swipe-Right", "Fade to Black"]
+
+ return {"required": {
+ "image1": ("IMAGE",),
+ "image2": ("IMAGE",),
+ "transition_type": (transition_types,),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "start_keyframe": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "end_keyframe": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ },
+ }
+
+ RETURN_TYPES = ("IMAGE", )
+ FUNCTION = "get_image"
+ CATEGORY = icons.get("Comfyroll/Animation/Other")
+
+ def get_image(self, image1, image2, transition_type, current_frame, start_keyframe, end_keyframe):
+
+ image_out = image1
+
+ return (image_out,)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_AlternateLatents:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "latent1": ("LATENT",),
+ "latent2": ("LATENT",),
+ "frame_interval": ("INT", {"default": 1, "min": 1, "max": 999}),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ }
+ }
+
+ RETURN_TYPES = ("LATENT",)
+ FUNCTION = "InputLatents"
+ CATEGORY = icons.get("Comfyroll/Animation/Other")
+
+ def InputLatents(self, latent1, latent2, frame_interval, current_frame):
+
+ if current_frame == 0:
+ return latent1 # Start with the first latent
+
+ frame_mod = (current_frame // frame_interval) % 2
+
+ if frame_mod == 0:
+ return latent1
+ else:
+ return latent2
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_StrobeImages:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "animationframes1": ("IMAGE",),
+ "animation frames2": ("IMAGE",),
+ "frame_interval": ("INT", {"default": 1, "min": 1, "max": 999}),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "strobe"
+ CATEGORY = icons.get("Comfyroll/Animation/Other")
+
+ def strobe(self, image1, image2, frame_interval, current_frame):
+
+ if current_frame == 0:
+ return image1 # Start with the first image
+
+ frame_mod = (current_frame // frame_interval) % 2
+
+ if frame_mod == 0:
+ return image1
+ else:
+ return image2
+
+#---------------------------------------------------------------------------------------------------------------------#
+# Camera Nodes
+#---------------------------------------------------------------------------------------------------------------------#
+# add more camera types and motions, including
+# hand held, steadycam
+# crane/boom camera
+# dolly/track
+# pov
+# static camera
+#---------------------------------------------------------------------------------------------------------------------#
+
+class CR_StaticCamera3D:
+ @classmethod
+ def INPUT_TYPES(s):
+ rotation_types = ["Tilt Up", "Tilt Down", "Pan Left", "Pan Right"]
+
+ return {"required": {
+ "image": ("IMAGE",),
+ "x_position": ("FLOAT", {"default": 0.0}),
+ "y_position": ("FLOAT", {"default": 0.0}),
+ "pan_rotation": ("FLOAT", {"default": 0.0, "min": -10.0, "max": 10.0, "step": 0.1}),
+ "tilt_rotation": ("FLOAT", {"default": 0.0, "min": -10.0, "max": 10.0, "step": 0.1}),
+ "zoom_factor": ("FLOAT", {"default": 0.0, "min": -10.0, "max": 10.0, "step": 0.1}),
+ "start_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "frame_duration": ("INT", {"default": 1.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ },
+ }
+
+ RETURN_TYPES = ("IMAGE", )
+ RETURN_NAMES = ("IMAGE", )
+ FUNCTION = "static3d"
+ CATEGORY = icons.get("Comfyroll/Animation/Camera")
+
+ def static3d(self, image, x_position, y_position, pan_rotation, tilt_rotation, zoom_factor, start_frame, current_frame, frame_duration):
+
+ if current_frame < start_frame:
+ return (image,)
+
+ if current_frame >= start_frame + frame_duration:
+ return (image,)
+
+ if rotation_type == "Tilt Up":
+ rotation_angle = start_angle - (tilt_rotation * ((current_frame - start_frame) / frame_duration))
+ elif rotation_type == "Tilt Down":
+ rotation_angle = start_angle + (tilt_rotation * ((current_frame - start_frame) / frame_duration))
+ elif rotation_type == "Pan Left":
+ rotation_angle = start_angle - (pan_rotation * ((current_frame - start_frame) / frame_duration))
+ elif rotation_type == "Pan Right":
+ rotation_angle = start_angle + (pan_rotation * ((current_frame - start_frame) / frame_duration))
+ else:
+ rotation_angle = start_angle
+
+ # Apply rotation to the image using your rotation logic here
+ # You should apply the rotation angle to transform the image accordingly
+
+ # image_out = apply_rotation(image, rotation_angle) # Replace this with your actual rotation logic
+ image_out = image
+
+ return (image_out,)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_DroneCamera3D:
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {"required": {
+ "image": ("IMAGE",),
+ "x_position": ("FLOAT", {"default": 0.0}),
+ "y_position": ("FLOAT", {"default": 0.0}),
+ "yaw_angle": ("FLOAT", {"default": 0.0}),
+ "pitch_angle": ("FLOAT", {"default": 0.0}),
+ "roll_angle": ("FLOAT", {"default": 0.0}),
+ "zoom_factor": ("FLOAT", {"default": 0.0}),
+ "start_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "frame_duration": ("INT", {"default": 1.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", )
+ RETURN_NAMES = ("IMAGE", )
+ FUNCTION = "drone3d"
+ CATEGORY = icons.get("Comfyroll/Animation/Camera")
+
+ def drone3d(self, x_position, y_position, z_position, yaw_angle, pitch_angle,
+ roll_angle, zoom_factor, start_frame, current_frame, frame_duration):
+ """
+ Create an animation of aerial camera movements.
+
+ Args:
+ x_position (float): The camera's x-coordinate in 3D space.
+ y_position (float): The camera's y-coordinate in 3D space.
+ z_position (float): The camera's z-coordinate in 3D space.
+ yaw_angle (float): The camera's rotation around the vertical axis (yaw).
+ pitch_angle (float): The camera's rotation around the lateral axis (pitch).
+ roll_angle (float): The camera's rotation around the longitudinal axis (roll).
+ fov (float): The camera's field of view angle.
+ zoom_factor (float): The zoom factor affecting FOV or object size.
+ frame_rate (int): The number of frames per second for the animation.
+ animation_duration (float): The total duration of the animation in seconds.
+ image_width (int): The width of the output image in pixels.
+ image_height (int): The height of the output image in pixels.
+
+ Returns:
+ List[PIL.Image.Image]: A list of image.
+ """
+ # Your animation generation logic here
+ # Combine camera movements, FOV changes, and zoom effects
+ # Generate image based on the provided parameters
+
+ #animation_frames = [] # List to store image
+
+ # Implement your animation generation logic here
+ image_out = image
+
+ return image_out
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_InterpolateZoom:
+ @classmethod
+ def INPUT_TYPES(s):
+ zoom_types = ["Zoom In", "Zoom Out",]
+ return {"required": {
+ "image": ("IMAGE",),
+ "zoom_type": (zoom_types,),
+ "start_factor": ("INT", {"default": 1.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "step_factor": ("INT", {"default": 1.0, "min": -9999.0, "max": 9999.0, "step": 1.0,}),
+ "start_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "frame_duration": ("INT", {"default": 1.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ },
+ }
+
+ RETURN_TYPES = ("IMAGE", )
+ RETURN_NAMES = ("IMAGE", )
+ FUNCTION = "zoom"
+ CATEGORY = icons.get("Comfyroll/Animation/Camera")
+
+ def zoom(self, image, zoom_type, current_frame, zoom_factor):
+
+ if current_frame < start_frame:
+ return (image,)
+
+ if current_frame >= start_frame + frame_duration:
+ return (image,)
+
+ if zoom_type == "Zoom In":
+ zoom_factor = 1.0 + (zoom_amount - 1.0) * ((current_frame - start_frame) / frame_duration)
+ elif zoom_type == "Zoom Out":
+ zoom_factor = 1.0 - (zoom_amount - 1.0) * ((current_frame - start_frame) / frame_duration)
+ else:
+ zoom_factor = 1.0
+
+ # Apply zoom to the image using your zoom logic here
+ # You should apply the zoom factor to resize the image accordingly
+
+ # image_out = apply_zoom(image, zoom_factor) # Replace this with your actual zoom logic
+ image_out = image
+
+ return (image_out,)
+
+#---------------------------------------------------------------------------------------------------------------------------------------------------#
+class CR_InterpolateRotation:
+ @classmethod
+ def INPUT_TYPES(s):
+ rotation_types = ["Tilt Up", "Tilt Down", "Pan Left", "Pan Right"]
+
+ return {"required": {
+ "image": ("IMAGE",),
+ "rotation_type": (rotation_types,),
+ "pan_rotation": ("FLOAT", {"default": 0.0, "min": -10.0, "max": 10.0, "step": 0.1}),
+ "tilt_rotation": ("FLOAT", {"default": 0.0, "min": -10.0, "max": 10.0, "step": 0.1}),
+ "start_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "frame_duration": ("INT", {"default": 1.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ },
+ }
+
+ RETURN_TYPES = ("IMAGE", )
+ RETURN_NAMES = ("IMAGE", )
+ FUNCTION = "rotate"
+ CATEGORY = icons.get("Comfyroll/Animation/Camera")
+
+ def rotate(self, image, rotation_type, current_frame, rotation_angle):
+
+ if current_frame < start_frame:
+ return (image,)
+
+ if current_frame >= start_frame + frame_duration:
+ return (image,)
+
+ if rotation_type == "Tilt Up":
+ rotation_angle = start_angle - (tilt_rotation * ((current_frame - start_frame) / frame_duration))
+ elif rotation_type == "Tilt Down":
+ rotation_angle = start_angle + (tilt_rotation * ((current_frame - start_frame) / frame_duration))
+ elif rotation_type == "Pan Left":
+ rotation_angle = start_angle - (pan_rotation * ((current_frame - start_frame) / frame_duration))
+ elif rotation_type == "Pan Right":
+ rotation_angle = start_angle + (pan_rotation * ((current_frame - start_frame) / frame_duration))
+ else:
+ rotation_angle = start_angle
+
+ # Apply rotation to the image using your rotation logic here
+ # You should apply the rotation angle to transform the image accordingly
+
+ # image_out = apply_rotation(image, rotation_angle) # Replace this with your actual rotation logic
+ image_out = image
+
+ return (image_out,)
+
+#---------------------------------------------------------------------------------------------------------------------------------------------------#
+class CR_InterpolateTrack:
+ @classmethod
+ def INPUT_TYPES(s):
+ track_types = ["Track Left", "Track Left", "Track Up", "Track Down"]
+
+ return {"required": {
+ "image": ("IMAGE",),
+ "track_type": (track_types,),
+ "track_speed": ("INT", {"default": 1.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "start_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "frame_duration": ("INT", {"default": 1.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ },
+ }
+
+ RETURN_TYPES = ("IMAGE", )
+ RETURN_NAMES = ("IMAGE", )
+ FUNCTION = "track"
+ CATEGORY = icons.get("Comfyroll/Animation/Camera")
+
+ def track(self, image, rotation_type, current_frame, rotation_angle):
+
+ if current_frame < start_frame:
+ return (image,)
+
+ if current_frame >= start_frame + frame_duration:
+ return (image,)
+
+ if track_type == "Track Left":
+ track_distance = track_speed * ((current_frame - start_frame) / frame_duration)
+ elif track_type == "Track Right":
+ track_distance = track_speed * ((current_frame - start_frame) / frame_duration)
+ elif track_type == "Track Up":
+ track_distance = track_speed * ((current_frame - start_frame) / frame_duration)
+ elif track_type == "Track Down":
+ track_distance = track_speed * ((current_frame - start_frame) / frame_duration)
+ else:
+ track_distance = 0.0
+
+ # Apply tracking motion to the image using your tracking logic here
+ # You should apply the track_distance to translate the image accordingly
+
+ # image_out = apply_tracking(image, track_distance) # Replace this with your actual tracking logic
+ image_out = image
+
+ return (image_out,)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ContinuousZoom:
+ @classmethod
+ def INPUT_TYPES(s):
+ zoom_types = ["Zoom In", "Zoom Out",]
+
+ return {"required": {
+ "image": ("IMAGE",),
+ "zoom_type": (zoom_types,),
+ "zoom_factor": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ },
+ }
+
+ RETURN_TYPES = ("IMAGE", )
+ RETURN_NAMES = ("IMAGE", )
+ FUNCTION = "zoom"
+ CATEGORY = icons.get("Comfyroll/Animation/Camera")
+
+ def zoom(self, image, zoom_type, current_frame, zoom_factor):
+ """
+ Apply continuous zoom to the input image based on the zoom type and factor.
+
+ Args:
+ image (PIL.Image.Image): The input image.
+ zoom_type (str): Either "Zoom In" or "Zoom Out" to specify the zoom direction.
+ zoom_factor (float): The factor by which to zoom the image.
+
+ Returns:
+ PIL.Image.Image: The zoomed image.
+ """
+ width, height = image.size
+
+ if zoom_type == "Zoom In":
+ new_width = int(width / zoom_factor)
+ new_height = int(height / zoom_factor)
+ else:
+ new_width = int(width * zoom_factor)
+ new_height = int(height * zoom_factor)
+
+ # Perform image resizing using the specified resampling method (LANCZOS)
+ image_out = image.resize((new_width, new_height), resample=Image.LANCZOS)
+
+ return (image_out,)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ContinuousRotation:
+ @classmethod
+ def INPUT_TYPES(s):
+ rotation_types = ["Tilt Up", "Tilt Down", "Pan Left", "Pan Right"]
+ return {"required": {
+ "image": ("IMAGE",),
+ "rotation_type": (rotation_types,),
+ "rotation_angle": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ },
+ }
+
+ RETURN_TYPES = ("IMAGE", )
+ RETURN_NAMES = ("IMAGE", )
+ FUNCTION = "get_image"
+ CATEGORY = icons.get("Comfyroll/Animation/Camera")
+
+ def get_image(self, image, rotation_type, current_frame, rotation_angle):
+
+ """
+ Apply continuous camera rotation to the input image.
+
+ Args:
+ image (PIL.Image.Image): The input image.
+ rotation_type (str): One of "Tilt Up", "Tilt Down", "Pan Left", or "Pan Right" to specify the rotation type.
+ rotation_angle (float): The angle by which to rotate the camera.
+
+ Returns:
+ PIL.Image.Image: The rotated image.
+ """
+ if rotation_type == "Tilt Up":
+ rotated_image = image.rotate(-rotation_angle, resample=Image.LANCZOS, expand=True)
+ elif rotation_type == "Tilt Down":
+ rotated_image = image.rotate(rotation_angle, resample=Image.LANCZOS, expand=True)
+ elif rotation_type == "Pan Left":
+ rotated_image = image.rotate(rotation_angle, resample=Image.LANCZOS, expand=True)
+ elif rotation_type == "Pan Right":
+ rotated_image = image.rotate(-rotation_angle, resample=Image.LANCZOS, expand=True)
+ else:
+ rotated_image = image # No rotation
+
+ image_out = image
+
+ return (image_out,)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ContinuousTrack:
+ @classmethod
+ def INPUT_TYPES(s):
+ track_types = ["Track Left", "Track Left", "Track Up", "Track Down"]
+ return {"required": {
+ "image": ("IMAGE",),
+ "track_type": (track_types,),
+ "track_speed": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ },
+ }
+
+ RETURN_TYPES = ("IMAGE", )
+ RETURN_NAMES = ("IMAGE", )
+ FUNCTION = "get_image"
+ CATEGORY = icons.get("Comfyroll/Animation/Camera")
+
+ def get_image(self, image, rotation_type, current_frame, rotation_angle):
+ """
+ Apply continuous tracking to the input image based on the tracking type and speed.
+
+ Args:
+ image (PIL.Image.Image): The input image.
+ track_type (str): One of "Track Left", "Track Right", "Track Up", or "Track Down" to specify tracking direction.
+ track_speed (float): The speed of the tracking operation.
+
+ Returns:
+ PIL.Image.Image: The tracked image.
+ """
+ # Placeholder for tracking logic based on the provided track_type and track_speed
+ # Replace with actual tracking implementation
+
+ # In this example, we're just returning the original image
+ image_out = image
+
+ return (image_out,)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_TextListCrossJoin:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required":{
+ "text_list_simple1": ("TEXT_LIST_SIMPLE", {"default": 1, "min": 0, "max": 10000}),
+ "text_list_simple2": ("TEXT_LIST_SIMPLE", {"default": 1, "min": 0, "max": 10000}),
+ }
+ }
+
+ RETURN_TYPES = ("TEXT_LIST_SIMPLE", )
+ RETURN_NAMES = ("TEXT_LIST_SIMPLE", )
+ FUNCTION = "cross_join"
+ CATEGORY = icons.get("Comfyroll/Animation/List")
+
+ def cross_join(self, current_frame, max_frames):
+
+ lines = list()
+
+ for line1 in list1:
+ for line2 in list2:
+ concat_line = line1 + ',' + line2
+ lines.append(concat_line)
+
+ list = list1
+ return (lines, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+# Load From List
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_LoadModelFromList:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {"model_list": ("MODEL_LIST",),
+ "model_ID": ("STRING", {"default": "", "multiline": False}),
+ },
+ }
+
+ RETURN_TYPES = ("MODEL", "CLIP", "VAE")
+ RETURN_NAMES = ("MODEL", "CLIP", "VAE")
+ FUNCTION = "loadmodel"
+ CATEGORY = icons.get("Comfyroll/Animation/List")
+
+ def loadmodel(self, model_list, model_ID,):
+
+ print(model_list)
+ #get modelname from ID
+ #try GPT for this
+ model_name = "SD1_5\CounterfeitV25_25.safetensors"
+ ckpt_path = folder_paths.get_full_path("checkpoints", model_name)
+ out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True,
+ embedding_directory=folder_paths.get_folder_paths("embeddings"))
+ return out
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_LoadLoRAFromList:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {"model": ("MODEL",),
+ "clip": ("CLIP",),
+ "lora_list": ("LORA_LIST",),
+ "lora_ID": ("STRING", {"default": "", "multiline": False}),
+ },
+ }
+
+ RETURN_TYPES = ("MODEL", "CLIP", )
+ RETURN_NAMES = ("MODEL", "CLIP", )
+ FUNCTION = "loadlora"
+ CATEGORY = icons.get("Comfyroll/Animation/List")
+
+ def loadlora(self, model, clip, lora_ID, lora_list,):
+
+ print(lora_list)
+ #if lora_list == None:
+
+ #pick lora by ID
+ #load lora
+
+ return (model, clip,)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_LoadStyleFromList:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {"style_list": ("STYLE_LIST",),
+ "style_ID": ("STRING", {"default": "", "multiline": False}),
+ },
+ }
+
+ RETURN_TYPES = ("STYLE", )
+ RETURN_NAMES = ("STYLE", )
+ FUNCTION = "loadstyle"
+ CATEGORY = icons.get("Comfyroll/Animation/List")
+
+ def loadstyle(self, style_ID, style_list,):
+
+ print(style_list)
+ #if style_list == None:
+
+ #pick style by ID
+ #load style
+
+ return (style_ID,)
+
+#--------------------------------------------------------------------------------------------------------------------#
+class CR_StyleList:
+
+ @classmethod
+ def INPUT_TYPES(cls):
+
+ #current_directory = os.path.dirname(os.path.realpath(__file__))
+ #self.json_data, style_files = load_styles_from_directory(current_directory)
+
+ style_files = ["None"] + folder_paths.get_filename_list("loras")
+
+ return {"required": {
+ "style_name1": (style_files,),
+ "alias1": ("STRING", {"multiline": False, "default": ""}),
+ "style_name2": (style_files,),
+ "alias2": ("STRING", {"multiline": False, "default": ""}),
+ "style_name3": (style_files,),
+ "alias3": ("STRING", {"multiline": False, "default": ""}),
+ "style_name4": (style_files,),
+ "alias4": ("STRING", {"multiline": False, "default": ""}),
+ "style_name5": (style_files,),
+ "alias5": ("STRING", {"multiline": False, "default": ""}),
+ },
+ "optional": {"style_list": ("style_LIST",)
+ },
+ }
+
+ RETURN_TYPES = ("STYLE_LIST", "STRING", )
+ RETURN_NAMES = ("STYLE_LIST", "show_text", )
+ FUNCTION = "style_list"
+ CATEGORY = icons.get("Comfyroll/Animation/List")
+
+ def style_list(self, style_name1, alias1, style_name2, alias2, style_name3, alias3, style_name4, alias4,
+ style_name5, alias5, style_list=None):
+
+ # Initialise the list
+ styles = list()
+
+ # Extend the list for each style in the stack
+ if style_list is not None:
+ styles.extend([l for l in style_list])
+
+ if style_name1 != "None":
+ styles.extend([(alias1 + "," + style_name1 + "\n")]),
+
+ if style_name2 != "None":
+ styles.extend([(alias2 + "," + style_name2 + "\n")]),
+
+ if style_name3 != "None":
+ styles.extend([(alias3 + "," + style_name3 + "\n")]),
+
+ if style_name4 != "None":
+ styles.extend([(alias4 + "," + style_name4 + "\n")]),
+
+ if style_name5 != "None":
+ styles.extend([(alias5 + "," + style_name5 + "\n")]),
+
+ #print(f"[TEST] CR style List: {styles}")
+
+ show_text = "".join(styles)
+
+ return (styles, show_text, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_CycleStyles:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ cycle_methods = ["Sequential", "Random"]
+
+ return {"required": {"switch": ([
+ "Off",
+ "On"],),
+ "style_list": ("STYLE_LIST",),
+ "frame_interval": ("INT", {"default": 30, "min": 0, "max": 999, "step": 1,}),
+ "loops": ("INT", {"default": 1, "min": 1, "max": 1000}),
+ "cycle_method": (cycle_methods,),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ },
+ }
+
+ RETURN_TYPES = ("STYLE", )
+ FUNCTION = "cycle"
+ CATEGORY = icons.get("Comfyroll/Animation/Other")
+
+ def cycle(self, switch, style_list, frame_interval, loops, cycle_method, current_frame,):
+
+ if switch == "Off":
+ return (None, )
+
+ print(style_list)
+ #loop through style names
+
+ style_ID = 1
+
+ return (style_ID, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+'''
+ #
+ "CR Interpolate Prompt Weights":CR_InterpolatePromptWeights,
+ "CR Alternate Latents":CR_AlternateLatents,
+ "CR Image Transition":CR_ImageTransition,
+ "CR Strobe Images": CR_StrobeImages,
+ #
+ "CR 3D Camera Drone":CR_DroneCamera3D,
+ "CR 3D Camera Static":CR_StaticCamera3D,
+ "CR Interpolate Zoom":CR_InterpolateZoom,
+ "CR Interpolate Rotation":CR_InterpolateRotation,
+ "CR Interpolate Track":CR_InterpolateTrack,
+ "CR Continuous Zoom":CR_ContinuousZoom,
+ "CR Continuous Rotation":CR_ContinuousRotation,
+ "CR Continuous Track":CR_ContinuousTrack,
+ #
+ "CR Text List Cross Join":CR_TextListCrossJoin,
+ "CR Style List":CR_StyleList,
+ #
+ "CR Load Model From List":CR_LoadModelFromList,
+ "CR Load LoRA From List":CR_LoadLoRAFromList,
+ "CR Load Style From List":CR_LoadStyleFromList,
+ "CR Load Image From List":CR_LoadImageFromList,
+ "CR Load Text From List":CR_LoadTextFromList,
+ #
+ "CR Cycle Styles":CR_CycleStyles,
+}
+'''
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/dev_nodes.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/dev_nodes.py
new file mode 100644
index 0000000000000000000000000000000000000000..fc135471694e029eb2200e122c8373d4a9cee93e
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/dev_nodes.py
@@ -0,0 +1,18 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# Comfyroll Custom Nodes by RockOfFire and Akatsuzi https://github.com/RockOfFire/ComfyUI_Comfyroll_CustomNodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+
+from ..categories import icons
+
+#---------------------------------------------------------------------------------------------------------------------#
+
+#---------------------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+'''
+NODE_CLASS_MAPPINGS = {
+}
+'''
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/dev_pil_3D.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/dev_pil_3D.py
new file mode 100644
index 0000000000000000000000000000000000000000..016af2fe32a499c44d5ec0184ddd623a5ade9e3c
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/dev_pil_3D.py
@@ -0,0 +1,282 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# Comfyroll Custom Nodes by RockOfFire and Akatsuzi https://github.com/RockOfFire/ComfyUI_Comfyroll_CustomNodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+
+import numpy as np
+import math
+import torch
+import os
+from PIL import Image, ImageDraw
+from ..categories import icons
+from ..config import color_mapping, COLORS
+from pywavefront import Wavefront
+
+#---------------------------------------------------------------------------------------------------------------------#
+
+def tensor2pil(image):
+ return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
+
+def pil2tensor(image):
+ return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
+
+def align_text(align_txt, img_center_x, img_center_y, img_width, img_height, pos_x, pos_y, txt_width, txt_height, txt_padding):
+ if align_txt == "center":
+ txt_center_x = img_center_x + pos_x - txt_width / 2
+ txt_center_y = img_center_y + pos_y - txt_height / 2
+ elif align_txt == "top left":
+ txt_center_x = pos_x + txt_padding
+ txt_center_y = pos_y + txt_padding
+ if align_txt == "top right":
+ txt_center_x = img_width + pos_x - txt_width - txt_padding
+ txt_center_y = pos_y + txt_padding
+ elif align_txt == "top center":
+ txt_center_x = img_width/2 + pos_x - txt_width/2 - txt_padding
+ txt_center_y = pos_y + txt_padding
+ elif align_txt == "bottom left":
+ txt_center_x = pos_x + txt_padding
+ txt_center_y = img_height + pos_y - txt_height - txt_padding
+ elif align_txt == "bottom right":
+ txt_center_x = img_width + pos_x - txt_width - txt_padding
+ txt_center_y = img_height + pos_y - txt_height - txt_padding
+ elif align_txt == "bottom center":
+ txt_center_x = img_width/2 + pos_x - txt_width/2 - txt_padding
+ txt_center_y = img_height + pos_y - txt_height - txt_padding
+ return (txt_center_x, txt_center_y, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_3DPolygon:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ shapes = ["cube","tetrahedron"]
+
+ return {"required": {
+ "shape": (shapes,),
+ "image_width": ("INT", {"default": 512, "min": 64, "max": 2048}),
+ "image_height": ("INT", {"default": 512, "min": 64, "max": 2048}),
+ "radius": ("INT", {"default": 100, "min": 2, "max": 2048}),
+ "distance": ("INT", {"default": 200, "min": 2, "max": 2048}),
+ "rotation_angle": ("FLOAT", {"default": 0, "min": 0, "max": 3600, "step": 0.5}),
+ },
+ }
+
+ RETURN_TYPES = ("IMAGE", )
+ FUNCTION = "draw_cube"
+ CATEGORY = icons.get("Comfyroll/Graphics/3D")
+
+ def draw_cube(self, shape, image_width, image_height, radius, distance, rotation_angle=45):
+ # Create a blank canvas
+ size = (image_height, image_width)
+ image = Image.new("RGB", size)
+ draw = ImageDraw.Draw(image)
+
+ if shape == "cube":
+ vertices = [
+ (-radius, -radius, -radius),
+ (radius, -radius, -radius),
+ (radius, radius, -radius),
+ (-radius, radius, -radius),
+ (-radius, -radius, radius),
+ (radius, -radius, radius),
+ (radius, radius, radius),
+ (-radius, radius, radius)
+ ]
+ edges = [
+ (0, 1), (1, 2), (2, 3), (3, 0),
+ (4, 5), (5, 6), (6, 7), (7, 4),
+ (0, 4), (1, 5), (2, 6), (3, 7)
+ ]
+ elif shape == "tetrahedron":
+ vertices = [
+ (0, radius, 0),
+ (radius, -radius, -radius),
+ (-radius, -radius, -radius),
+ (0, -radius, radius)
+ ]
+ edges = [
+ (0, 1), (0, 2), (0, 3),
+ (1, 2), (2, 3), (3, 1)
+ ]
+
+ # Function to project 3D points to 2D
+ def project_point(point):
+ x, y, z = point
+ x_2d = x * distance / (z + distance) + size[0] / 2
+ y_2d = y * distance / (z + distance) + size[1] / 2
+ return x_2d, y_2d
+
+ # Rotate the cube
+ rotated_vertices = []
+ angle = math.radians(rotation_angle)
+ cos_a = math.cos(angle)
+ sin_a = math.sin(angle)
+ for vertex in vertices:
+ x, y, z = vertex
+ new_x = x * cos_a - z * sin_a
+ new_z = x * sin_a + z * cos_a
+ rotated_vertices.append((new_x, y, new_z))
+
+ # Project and draw the rotated cube
+ for edge in edges:
+ start_point = project_point(rotated_vertices[edge[0]])
+ end_point = project_point(rotated_vertices[edge[1]])
+ draw.line([start_point, end_point], fill=(255, 255, 255))
+
+ # Convert the PIL image to a PyTorch tensor
+ tensor_image = pil2tensor(image)
+
+ return (tensor_image,)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_3DSolids:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {
+ "image_width": ("INT", {"default": 512, "min": 64, "max": 2048}),
+ "image_height": ("INT", {"default": 512, "min": 64, "max": 2048}),
+ "radius": ("INT", {"default": 100, "min": 2, "max": 2048}),
+ "height": ("INT", {"default": 100, "min": 2, "max": 2048}),
+ "distance": ("INT", {"default": 200, "min": 2, "max": 2048}),
+ "rotation_angle": ("FLOAT", {"default": 0, "min": 0, "max": 3600, "step": 0.5}),
+ },
+ }
+
+ RETURN_TYPES = ("IMAGE", )
+ FUNCTION = "draw"
+ CATEGORY = icons.get("Comfyroll/Graphics/3D")
+
+ def draw(self, image_width, image_height, radius, height, distance, rotation_angle=45):
+
+ # Create a blank canvas
+ size = (image_height, image_width)
+ image = Image.new("RGB", size)
+ draw = ImageDraw.Draw(image)
+
+ # Define the cone's vertices
+ vertices = [
+ (0, height / 2, 0),
+ (0, -height / 2, 0)
+ ]
+
+ num_points = 20 # Number of points to approximate the circular base
+ base_points = [
+ (radius * math.cos(2 * math.pi * i / num_points), -height / 2, radius * math.sin(2 * math.pi * i / num_points))
+ for i in range(num_points)
+ ]
+ vertices = vertices + base_points
+
+ # Define the cone's edges as pairs of vertices
+ edges = []
+ for i in range(num_points):
+ edges.append((0, i + 2))
+ edges.append((1, i + 2))
+ edges.append((i + 2, (i + 3) if i < num_points - 1 else 2))
+
+ # Function to project 3D points to 2D
+ def project_point(point):
+ x, y, z = point
+ x_2d = x * distance / (z + distance) + size[0] / 2
+ y_2d = y * distance / (z + distance) + size[1] / 2
+ return x_2d, y_2d
+
+ # Rotate the cone
+ rotated_vertices = []
+ angle = math.radians(rotation_angle)
+ cos_a = math.cos(angle)
+ sin_a = math.sin(angle)
+ for vertex in vertices:
+ x, y, z = vertex
+ new_x = x * cos_a - z * sin_a
+ new_z = x * sin_a + z * cos_a
+ rotated_vertices.append((new_x, y, new_z))
+
+ # Project and draw the rotated cone's faces with different colors
+ colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255)] # Example colors
+ for i in range(num_points):
+ vertices_indices = [0, i + 2, (i + 3) if i < num_points - 1 else 2]
+ face_vertices = [project_point(rotated_vertices[idx]) for idx in vertices_indices]
+ fill_color = colors[i % 3] # Cycle through colors for each face
+ draw.polygon(face_vertices, fill=fill_color)
+
+ # Draw the edges
+ for edge in edges:
+ start_point = project_point(rotated_vertices[edge[0]])
+ end_point = project_point(rotated_vertices[edge[1]])
+ draw.line([start_point, end_point], fill=(0, 0, 0))
+
+ # Convert the PIL image to a PyTorch tensor
+ tensor_image = pil2tensor(image)
+
+ return (tensor_image,)
+
+#---------------------------------------------------------------------------------------------------------------------#
+
+class CR_DrawOBJ:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ obj_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "obj")
+ file_list = [f for f in os.listdir(obj_dir) if os.path.isfile(os.path.join(obj_dir, f)) and f.lower().endswith(".obj")]
+
+
+ return {"required": {
+ "image_width": ("INT", {"default": 512, "min": 64, "max": 2048}),
+ "image_height": ("INT", {"default": 512, "min": 64, "max": 2048}),
+ "obj_name": (file_list,),
+ "line_color": (COLORS[1:],),
+ },
+ }
+
+ RETURN_TYPES = ("IMAGE", )
+ FUNCTION = "draw_wireframe"
+ CATEGORY = icons.get("Comfyroll/Graphics/3D")
+
+ def draw_wireframe(self, obj_name, image_width=800, image_height=800, line_color="black"):
+
+ # Load the OBJ file
+ obj_file = "obj\\" + str(obj_name)
+ resolved_obj_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), obj_file)
+ scene = Wavefront(resolved_obj_path)
+
+ # Create a blank image
+ img = Image.new("RGB", (image_width, image_height), (0, 0, 0))
+ draw = ImageDraw.Draw(img)
+
+ for name, material in scene.materials.items():
+ for face in material.mesh.faces:
+ vertices = [scene.vertices[i] for i in face]
+
+ # Draw lines between vertices to create wireframe
+ for i in range(len(vertices)):
+ x1, y1, z1 = vertices[i]
+ x2, y2, z2 = vertices[(i + 1) % len(vertices)]
+
+ # Scale and translate vertices to fit the image
+ x1 = int((x1 + 1) * image_width / 2)
+ y1 = int((1 - y1) * image_height / 2)
+ x2 = int((x2 + 1) * image_width / 2)
+ y2 = int((1 - y2) * image_height / 2)
+
+ draw.line([(x1, y1), (x2, y2)], fill=line_color)
+
+ # Convert the PIL image to a PyTorch tensor
+ tensor_image = pil2tensor(img)
+
+ return (tensor_image,)
+
+#---------------------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+'''
+NODE_CLASS_MAPPINGS = {
+ "CR 3D Polygon":CR_3DPolygon,
+ "CR 3D Solids":CR_3DSolids,
+ "CR Draw OBJ":CR_DrawOBJ,
+}
+'''
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/dev_schedulers.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/dev_schedulers.py
new file mode 100644
index 0000000000000000000000000000000000000000..85d77f771f33592a33cb38c81d08f45d2fb93316
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/dev_schedulers.py
@@ -0,0 +1,223 @@
+#-----------------------------------------------------------------------------------------------------------#
+# CR Animation Nodes by RockOfFire and Akatsuzi https://github.com/RockOfFire/CR-Animation-Nodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#-----------------------------------------------------------------------------------------------------------#
+
+import comfy.sd
+import os
+import sys
+import folder_paths
+from nodes import LoraLoader
+from ..animation_nodes.functions import keyframe_scheduler, prompt_scheduler
+from ..categories import icons
+
+#-----------------------------------------------------------------------------------------------------------#
+# NODES
+#-----------------------------------------------------------------------------------------------------------#
+# Schedulers
+#-----------------------------------------------------------------------------------------------------------#
+class CR_PromptWeightScheduler:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ modes = ["Default Value", "Schedule"]
+ return {"required": {"mode": (modes,),
+ "current_prompt": ("STRING", {"multiline": False, "default": "prepend text"}),
+ "next_prompt": ("STRING", {"multiline": False, "default": "append text"}),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "schedule_alias": ("STRING", {"default": "", "multiline": False}),
+ "default_text": ("STRING", {"default": "default prompt", "multiline": False}),
+ "schedule_format": (["CR", "Deforum"],),
+ },
+ "optional": {"schedule": ("SCHEDULE",),
+ }
+ }
+
+ RETURN_TYPES = ("STRING", "STRING", "FLOAT", )
+ RETURN_NAMES = ("current_prompt", "next_prompt", "weight", )
+ FUNCTION = "schedule"
+ CATEGORY = icons.get("Comfyroll/Animation/Schedulers")
+
+ def schedule(self, mode, current_prompt, next_prompt, current_frame, schedule_alias, default_value, schedule_format, schedule=None):
+
+ if mode == "Default Value":
+ print(f"[Info] CR Prompt Weight Scheduler: Scheduler {schedule_alias} is disabled")
+ text_out = default_value
+ return (text_out,)
+
+ # Get params
+ params = keyframe_scheduler(schedule, schedule_alias, current_frame)
+
+ # Handle case where there is no schedule line for frame 0
+ if params == "":
+ if current_frame == 0:
+ print(f"[Warning] CR Prompt Weight Scheduler. No frame 0 found in schedule. Starting with default value at frame 0")
+ text_out = default_value,
+ else:
+ # Try the params
+ try:
+ weight = float(params)
+ except ValueError:
+ print(f"[Warning] CR Prompt Weight Scheduler. Invalid params: {params}")
+ return()
+
+ # Insert prepend and append text
+ current_prompt_out = default_text
+ next_prompt_out = default_text
+
+ return (current_prompt_out, next_prompt_out, weight_out, )
+
+#-----------------------------------------------------------------------------------------------------------#
+class CR_LoadScheduledControlNets:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ modes = ["Off", "Load default ControlNet", "Schedule"]
+
+ return {"required": {"mode": (modes,),
+ "conditioning": ("CONDITIONING", ),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "schedule_alias": ("STRING", {"default": "", "multiline": False}),
+ "default_controlnet": (folder_paths.get_filename_list("loras"), ),
+ "strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
+ "schedule_format": (["CR", "Deforum"],)
+ },
+ "optional": {"controlnet_list": ("CONTROLNET_LIST",),
+ "schedule": ("SCHEDULE",)
+ },
+ }
+
+ RETURN_TYPES = ("CONDITIONING", )
+ FUNCTION = "schedule"
+ CATEGORY = icons.get("Comfyroll/Animation/Schedulers")
+
+ def schedule(self, mode, conditioning, current_frame, schedule_alias, default_controlnet, strength, schedule_format, controlnet_list=None, schedule=None):
+
+ controlnet_name = ""
+
+ # Off mode
+ if mode == "Off":
+ print(f"[Info] CR Load Scheduled ControlNets. Disabled.")
+ return (conditioning,)
+
+ # Load Default ControlNet mode
+ if mode == "Load default ControlNet":
+ if default_controlnet == None:
+ return (conditioning,)
+ if strength_model == 0 and strength_clip == 0:
+ return (conditioning,)
+ model, clip = ControlNetLoader().load_controlnet(control_net_name)
+ print(f"[Info] CR Load Scheduled ControlNets. Loading default ControlNet {controlnet_name}.")
+ return (conditioning,)
+
+ # Get params
+ params = keyframe_scheduler(schedule, schedule_alias, current_frame)
+
+ # Handle case where there is no schedule line for a frame
+ if params == "":
+ print(f"[Warning] CR Load Scheduled ControlNets. No ControlNet specified in schedule for frame {current_frame}. Using default controlnet.")
+ if default_controlnet != None:
+ conditioning = LoraLoader().load_controlnet(model, clip, default_controlnet, strength_model, strength_clip)
+ return (conditioning,)
+ else:
+ # Unpack the parameters
+ parts = params.split(',')
+ if len(parts) == 3:
+ s_controlnet_alias = parts[0].strip()
+ s_strength_model = float(parts[1].strip())
+ s_strength_clip = float(parts[1].strip())
+ else:
+ print(f"[Warning] CR Load Scheduled ControlNets. Skipped invalid line: {line}")
+ return()
+
+ # Iterate through the LoRA list to get the LoRA name
+ for l_controlnet_alias, l_controlnet_name, l_strength_model, l_strength_clip in controlnet_list:
+ print(l_controlnet_alias, l_controlnet_name, l_strength_model, l_strength_clip)
+ if l_controlnet_alias == s_controlnet_alias:
+ print(f"[Info] CR Load Scheduled ControlNets. LoRA alias match found for {s_controlnet_alias}")
+ controlnet_name = l_controlnet_name
+ break # Exit the loop early once a match is found, ignores any duplicate matches
+
+ # Check if a matching LoRA has been found
+ if controlnet_name == "":
+ print(f"[Info] CR Load Scheduled ControlNets. No ControlNet alias match found for {s_controlnet_alias}. Frame {current_frame}.")
+ return()
+ else:
+ print(f"[Info] CR Load Scheduled ControlNets. controlnet_name {controlnet_name}")
+ # Load the new LoRA
+ model, clip = LoraLoader().load_controlnet(model, clip, controlnet_name, s_strength_model, s_strength_clip)
+ print(f"[Debug] CR Load Scheduled ControlNets. Loading new controlnet {controlnet_name}")
+ return (conditioning,)
+
+#-----------------------------------------------------------------------------------------------------------#
+class CR_ScheduleCameraMovements:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {"image": ("IMAGE",),
+ "current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "max_frames": ("INT", {"default": 1, "min": 1, "max": 10000}),
+ "schedule": ("SCHEDULE",),
+ "schedule_alias": ("STRING", {"default": "", "multiline": False}),
+ "schedule_format": (["CR", "Deforum"],),
+ },
+ }
+
+ RETURN_TYPES = ("IMAGE", )
+ RETURN_NAMES = ("IMAGE", )
+ FUNCTION = "get_image"
+ CATEGORY = icons.get("Comfyroll/Animation/Schedulers")
+
+ def get_image(self, image, camera_schedule, current_frame, max_frames, schedule_format, ):
+
+ image_out = image1
+
+ return (image_out,)
+
+#-----------------------------------------------------------------------------------------------------------#
+class CR_ScheduleStyles:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {"current_frame": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "max_frames": ("INT", {"default": 120, "min": 1, "max": 10000}),
+ "schedule": ("SCHEDULE",),
+ "schedule_alias": ("STRING", {"default": "", "multiline": False}),
+ "schedule_format": (["CR", "Deforum"],),
+ },
+ "optional": {"style_list": ("STYLE_LIST",),
+ }
+ }
+
+ RETURN_TYPES = ("STYLE", )
+ RETURN_NAMES = ("STYLE", )
+ FUNCTION = "schedule"
+ CATEGORY = icons.get("Comfyroll/Animation/Schedulers")
+
+ def schedule(self, current_frame, max_frames, schedule, schedule_alias, schedule_format, style_list=None):
+
+ #loop through tuple list in schedule
+ #expand tuples
+ #do something
+ #return output
+
+ return (None,)
+
+#-----------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#-----------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+# 11 nodes
+'''
+NODE_CLASS_MAPPINGS = {
+ # Schedulers
+ "CR Simple Prompt Scheduler":CR_SimplePromptScheduler,
+ "CR Load Scheduled ControlNets":CR_LoadScheduledControlNets,
+ "CR Prompt Weight Scheduler":CR_PromptWeightScheduler,
+ "CR Schedule Camera Movements":CR_ScheduleCameraMovements,
+ "CR Schedule Styles":CR_ScheduleStyles,
+ "CR Schedule ControlNets":CR_ScheduleControlNets,
+}
+'''
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/dev_workflow.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/dev_workflow.py
new file mode 100644
index 0000000000000000000000000000000000000000..d1d4e3f4e9034d4849a55c35ab63d56e63a85daa
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/dev_workflow.py
@@ -0,0 +1,173 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# CR Animation Nodes by RockOfFire and Akatsuzi https://github.com/RockOfFire/CR-Animation-Nodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+
+from ..categories import icons
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_JobList:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ job_types = ["Input", "Batch Process", "Output"]
+ return {"required":{
+ "job_desc1": ("STRING", {"default": "job description", "multiline": True}),
+ "job_type1": (job_types,),
+ "job_alias1": ("STRING", {"default": "", "multiline": False}),
+ "job_desc2": ("STRING", {"default": "job description", "multiline": True}),
+ "job_type2": (job_types,),
+ "job_alias2": ("STRING", {"default": "", "multiline": False}),
+ "job_desc3": ("STRING", {"default": "job description", "multiline": True}),
+ "job_type3": (job_types,),
+ "job_alias3": ("STRING", {"default": "", "multiline": False}),
+ },
+ "optional": {"job": ("JOB",),
+ }
+ }
+
+ RETURN_TYPES = ("JOB", )
+ RETURN_NAMES = ("JOB", )
+ FUNCTION = "increment"
+ CATEGORY = icons.get("Comfyroll/Workflow")
+
+ def increment(self, job_desc1, job_type1, job_alias1, job_desc2, job_type2, job_alias2, job_desc3, job_type3, job_alias3, job=None):
+ job = list()
+ return (job, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_JobScheduler:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ status = ["Asleep", "Awake"]
+
+ return {"required":{
+ "schedule": ("SCHEDULE", ),
+ "index": ("INT", {"default": 1, "min": -10000, "max": 10000}),
+ "schedule_alias": ("STRING", {"default": "", "multiline": False}),
+ "status": (status,),
+ }
+ }
+
+ RETURN_TYPES = ("JOB", "STRING", )
+ RETURN_NAMES = ("JOB", "log", )
+ FUNCTION = "listen"
+ CATEGORY = icons.get("Comfyroll/Workflow")
+
+ def listen(listen, index, schedule, schedule_alias, status):
+ log = ""
+ return (log, )
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_JobCurrentFrame:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ return {"required":{
+ "index": ("INT", {"default": 1, "min": -10000, "max": 10000}),
+ "max_frames": ("INT", {"default": 1, "min": 0, "max": 10000}),
+ "print_to_console": ([
+ "Yes",
+ "No"],),
+ }
+ }
+
+ RETURN_TYPES = ("INT", "INT",)
+ RETURN_NAMES = ("current_frame", "max_frames",)
+ FUNCTION = "to_console"
+ CATEGORY = icons.get("Comfyroll/Workflow")
+
+ def to_console(self, index, max_frames, print_to_console):
+ if print_to_console == "Yes":
+ print(f"[Info] CR Current Frame:{index}")
+ current_frame = index
+
+ return (current_frame, max_frames, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_CheckJobComplete:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required":{
+ "current_frame": ("INT", {"default": 1, "min": 0, "max": 10000}),
+ "max_frames": ("INT", {"default": 1, "min": 0, "max": 10000}),
+ }
+ }
+
+ RETURN_TYPES = ("BOOL", )
+ RETURN_NAMES = ("BOOL", )
+ FUNCTION = "reset"
+ CATEGORY = icons.get("Comfyroll/Workflow")
+
+ def reset(self, current_frame, max_frames):
+
+ return (BOOL)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_SpawnWorkflowInstance:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ #mode = ["API"]
+
+ return {"required":{
+ #"mode": (mode,),
+ "job": ("JOB", ),
+ #"job_alias": ("STRING", {"default": "", "multiline": False}),
+ "workflow_path": ("STRING", {"multiline": False, "default": ""}),
+ "workflow_name": ("STRING", {"multiline": False, "default": ""}),
+ }
+ }
+
+ RETURN_TYPES = ()
+ RETURN_NAMES = ()
+ OUTPUT_NODE = True
+ FUNCTION = "spawn"
+ CATEGORY = icons.get("Comfyroll/Workflow")
+
+ def spawn(self, job, workflow_path, workflow_name):
+
+ return ()
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_LoadWorkflow:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ return {"required":{
+ "workflow_path": ("STRING", {"multiline": False, "default": ""}),
+ "workflow_name": ("STRING", {"multiline": False, "default": ""}),
+ }
+ }
+
+ RETURN_TYPES = ("WORKFLOW", )
+ RETURN_NAMES = ("WORKFLOW", )
+ FUNCTION = "workflow"
+ CATEGORY = icons.get("Comfyroll/Workflow")
+
+ def spawn(self, mode, job, schedule, workflow):
+ workflow = ""
+ return (workflow, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+# 3 nodes
+'''
+NODE_CLASS_MAPPINGS = {
+ # Jobs
+ "CR Job List": CR_JobList,
+ "CR Job Scheduler": CR_JobScheduler,
+ "CR Job Current Frame": CR_JobCurrentFrame,
+ "CR Check Job Complete": CR_CheckJobComplete,
+ "CR Spawn Workflow Instance": CR_SpawnWorkflowInstance,
+ "CR Load Workflow": CR_LoadWorkflow,
+}
+'''
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/dev_xygrid.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/dev_xygrid.py
new file mode 100644
index 0000000000000000000000000000000000000000..1c611868fd51acc25c0def0c6d582f156cf53d4d
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/dev_xygrid.py
@@ -0,0 +1,398 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# Comfyroll Nodes by RockOfFire and Akatsuzi https://github.com/RockOfFire/CR-Animation-Nodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+
+import os
+import folder_paths
+from PIL import Image, ImageFont
+import torch
+import numpy as np
+import re
+from pathlib import Path
+import typing as t
+from dataclasses import dataclass
+from ..nodes.xygrid_functions import create_images_grid_by_columns, Annotation
+from ..categories import icons
+
+def tensor_to_pillow(image: t.Any) -> Image.Image:
+ return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
+
+def pillow_to_tensor(image: Image.Image) -> t.Any:
+ return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
+
+def find_highest_numeric_value(directory, filename_prefix):
+ highest_value = -1 # Initialize with a value lower than possible numeric values
+
+ # Iterate through all files in the directory
+ for filename in os.listdir(directory):
+ if filename.startswith(filename_prefix):
+ try:
+ # Extract numeric part of the filename
+ numeric_part = filename[len(filename_prefix):]
+ numeric_str = re.search(r'\d+', numeric_part).group()
+ numeric_value = int(numeric_str)
+ # Check if the current numeric value is higher than the highest found so far
+ if numeric_value > highest_value:
+ highest_value = int(numeric_value)
+ except ValueError:
+ # If the numeric part is not a valid integer, ignore the file
+ continue
+
+ return highest_value
+
+#---------------------------------------------------------------------------------------------------------------------#
+# NODES
+#---------------------------------------------------------------------------------------------------------------------#
+# These nodes are based on https://github.com/LEv145/images-grid-comfy-plugin
+#---------------------------------------------------------------------------------------------------------------------
+class CR_XYZList:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required":{
+ "index": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "list1": ("STRING", {"multiline": True, "default": "x"}),
+ "x_prepend": ("STRING", {"multiline": False, "default": ""}),
+ "x_append": ("STRING", {"multiline": False, "default": ""}),
+ "x_annotation_prepend": ("STRING", {"multiline": False, "default": ""}),
+ "list2": ("STRING", {"multiline": True, "default": "y"}),
+ "y_prepend": ("STRING", {"multiline": False, "default": ""}),
+ "y_append": ("STRING", {"multiline": False, "default": ""}),
+ "y_annotation_prepend": ("STRING", {"multiline": False, "default": ""}),
+ "list3": ("STRING", {"multiline": True, "default": "z"}),
+ "z_prepend": ("STRING", {"multiline": False, "default": ""}),
+ "z_append": ("STRING", {"multiline": False, "default": ""}),
+ "z_annotation_prepend": ("STRING", {"multiline": False, "default": ""}),
+ }
+ }
+
+ RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "BOOLEAN",)
+ RETURN_NAMES = ("X", "Y", "Z", "x_annotation", "y_annotation", "z_annotation", "trigger",)
+ FUNCTION = "cross_join"
+ CATEGORY = icons.get("Comfyroll/XY Grid")
+
+ def cross_join(self, list1, list2, list3,
+ x_prepend, x_append, x_annotation_prepend,
+ y_prepend, y_append, y_annotation_prepend,
+ z_prepend, z_append, z_annotation_prepend, index):
+
+ # Index values for all XY nodes start from 1
+ index -=1
+ trigger = False
+
+ listx = re.split(r',(?=(?:[^"]*"[^"]*")*[^"]*$)', list1)
+ listy = re.split(r',(?=(?:[^"]*"[^"]*")*[^"]*$)', list2)
+ listz = re.split(r',(?=(?:[^"]*"[^"]*")*[^"]*$)', list3)
+
+ listx = [item.strip() for item in listx]
+ listy = [item.strip() for item in listy]
+ listz = [item.strip() for item in listz]
+
+ lenx = len(listx)
+ leny = len(listy)
+ lenz = len(listz)
+
+ sheet_size = lenx * leny
+ grid_size = lenx * leny * lenz
+
+ x = index % lenx
+ y = int(index / lenx) % leny
+ z = int(index / sheet_size)
+
+ x_out = x_prepend + listx[x] + x_append
+ y_out = y_prepend + listy[y] + y_append
+ z_out = z_prepend + listz[z] + z_append
+
+ x_ann_out = ""
+ y_ann_out = ""
+ z_ann_out = ""
+
+ if index + 1 == grid_size:
+ x_ann_out = [x_annotation_prepend + item + ";" for item in listx]
+ y_ann_out = [y_annotation_prepend + item + ";" for item in listy]
+ z_ann_out = [z_annotation_prepend + item + ";" for item in listz]
+ x_ann_out = "".join([str(item) for item in x_ann_out])
+ y_ann_out = "".join([str(item) for item in y_ann_out])
+ z_ann_out = "".join([str(item) for item in z_ann_out])
+ trigger = True
+
+ return (x_out, y_out, z_out, x_ann_out, y_ann_out, z_ann_out, trigger, )
+
+#---------------------------------------------------------------------------------------------------------------------
+class CR_XYZInterpolate:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ gradient_profiles = ["Lerp"]
+
+ return {"required": {"x_columns":("INT", {"default": 5.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "x_start_value": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 0.01,}),
+ "x_step": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 9999.0, "step": 0.01,}),
+ "x_annotation_prepend": ("STRING", {"multiline": False, "default": ""}),
+ "y_rows":("INT", {"default": 5.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "y_start_value": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 0.01,}),
+ "y_step": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 9999.0, "step": 0.01,}),
+ "y_annotation_prepend": ("STRING", {"multiline": False, "default": ""}),
+ "z_sheets":("INT", {"default": 5.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "z_start_value": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 0.01,}),
+ "z_step": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 9999.0, "step": 0.01,}),
+ "z_annotation_prepend": ("STRING", {"multiline": False, "default": ""}),
+ "index": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "gradient_profile": (gradient_profiles,)
+ }
+ }
+
+ RETURN_TYPES = ("FLOAT", "FLOAT", "FLOAT", "STRING", "STRING", "STRING", "BOOLEAN", )
+ RETURN_NAMES = ("X", "Y", "Z", "x_annotation", "y_annotation", "z_annotation", "trigger", )
+ FUNCTION = "gradient"
+ CATEGORY = icons.get("Comfyroll/XY Grid")
+
+ def gradient(self, x_columns, x_start_value, x_step, x_annotation_prepend,
+ y_rows, y_start_value, y_step, y_annotation_prepend,
+ z_rows, z_start_value, z_step, z_annotation_prepend,
+ index, gradient_profile):
+
+ # Index values for all XY nodes start from 1
+ index -=1
+ trigger = False
+ sheet_size = x_columns * y_rows
+ grid_size = x_columns * y_rows * z_sheets
+
+ x = index % x_columns
+ y = int(index / x_columns) % y_rows
+ z = int(index / sheet_size)
+
+ x_float_out = round(x_start_value + x * x_step, 3)
+ y_float_out = round(y_start_value + y * y_step, 3)
+ z_float_out = round(z_start_value + z * z_step, 3)
+
+ x_ann_out = ""
+ y_ann_out = ""
+ z_ann_out = ""
+
+ if index + 1 == grid_size:
+ for i in range(0, x_columns):
+ x = index % x_columns
+ x_float_out = x_start_value + i * x_step
+ x_float_out = round(x_float_out, 3)
+ x_ann_out = x_ann_out + x_annotation_prepend + str(x_float_out) + "; "
+ for j in range(0, y_rows):
+ y = int(index / x_columns)
+ y_float_out = y_start_value + j * y_step
+ y_float_out = round(y_float_out, 3)
+ y_ann_out = y_ann_out + y_annotation_prepend + str(y_float_out) + "; "
+ for k in range(0, z_sheets):
+ z = int(index / x_columns)
+ z_float_out = z_start_value + k * z_step
+ z_float_out = round(z_float_out, 3)
+ z_ann_out = z_ann_out + z_annotation_prepend + str(z_float_out) + "; "
+
+ x_ann_out = x_ann_out[:-1]
+ y_ann_out = y_ann_out[:-1]
+ z_ann_out = z_ann_out[:-1]
+ print(x_ann_out,y_ann_out,z_ann_out)
+ trigger = True
+
+ return (x_float_out, y_float_out, z_float_out, x_ann_out, y_ann_out, z_ann_out, trigger)
+
+#---------------------------------------------------------------------------------------------------------------------
+class CR_XYZIndex:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ gradient_profiles = ["Lerp"]
+
+ return {"required": {"x_columns":("INT", {"default": 5.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "y_rows":("INT", {"default": 5.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "z_sheets":("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "index": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ }
+ }
+
+ RETURN_TYPES = ("INT", "INT", "INT",)
+ RETURN_NAMES = ("x", "y", "z",)
+ FUNCTION = "index"
+ CATEGORY = icons.get("Comfyroll/XY Grid")
+
+ def index(self, x_columns, y_rows, z_sheets, index):
+
+ # Index values for all XY nodes start from 1
+ index -=1
+ sheet_size = x_columns * y_rows
+
+ x = index % x_columns
+ y = int(index / x_columns) % y_rows
+ z = int(index / sheet_size)
+ #print (x,y,z)
+
+ return (x, y, z)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_LoadXYAnnotationFromFile:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {
+ "input_file_path": ("STRING", {"multiline": False, "default": ""}),
+ "file_name": ("STRING", {"multiline": False, "default": ""}),
+ "file_extension": (["txt", "csv"],),
+ }
+ }
+
+ RETURN_TYPES = ("GRID_ANNOTATION", "STRING", )
+ RETURN_NAMES = ("GRID_ANNOTATION", "show_text", )
+ FUNCTION = "load"
+ CATEGORY = icons.get("Comfyroll/XY Grid")
+
+ def load(self, input_file_path, file_name, file_extension):
+ filepath = input_file_path + "\\" + file_name + "." + file_extension
+ print(f"CR_Load Schedule From File: Loading {filepath}")
+
+ lists = []
+
+ if file_extension == "csv":
+ with open(filepath, "r") as csv_file:
+ reader = csv.reader(csv_file)
+
+ for row in reader:
+ lists.append(row)
+
+ else:
+ with open(filepath, "r") as txt_file:
+ for row in txt_file:
+ parts = row.strip().split(",", 1)
+
+ if len(parts) >= 2:
+ second_part = parts[1].strip('"')
+ lists.append([parts[0], second_part])
+
+ print(lists)
+ return(lists,str(lists),)
+
+#---------------------------------------------------------------------------------------------------------------------
+class CR_XYGrid:
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {"required": {
+ "images": ("IMAGE",),
+ "gap": ("INT", {"default": 0, "min": 0}),
+ "max_columns": ("INT", {"default": 1, "min": 1, "max": 10000}),
+ },
+ "optional": {
+ "annotation": ("GRID_ANNOTATION",),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "create_image"
+ CATEGORY = icons.get("Comfyroll/XY Grid")
+
+ def create_image(self, images, gap, max_columns, annotation=None):
+
+ pillow_images = [tensor_to_pillow(i) for i in images]
+ pillow_grid = create_images_grid_by_columns(
+ images=pillow_images,
+ gap=gap,
+ annotation=annotation,
+ max_columns=max_columns,
+ )
+ tensor_grid = pillow_to_tensor(pillow_grid)
+
+ return (tensor_grid,)
+
+#---------------------------------------------------------------------------------------------------------------------
+class CR_XYSaveGridImage:
+# originally based on SaveImageSequence by mtb
+
+ def __init__(self):
+ self.type = "output"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+
+ output_dir = folder_paths.output_directory
+ output_folders = [name for name in os.listdir(output_dir) if os.path.isdir(os.path.join(output_dir,name))]
+
+ return {
+ "required": {"mode": (["Save", "Preview"],),
+ "output_folder": (sorted(output_folders), ),
+ "image": ("IMAGE", ),
+ "filename_prefix": ("STRING", {"default": "CR"}),
+ "file_format": (["webp", "jpg", "png", "tif"],),
+ },
+ "optional": {"output_path": ("STRING", {"default": '', "multiline": False}),
+ "trigger": ("BOOLEAN", {"default": False},),
+ }
+ }
+
+ RETURN_TYPES = ()
+ FUNCTION = "save_image"
+ OUTPUT_NODE = True
+ CATEGORY = icons.get("Comfyroll/XY Grid")
+
+ def save_image(self, mode, output_folder, image, file_format, output_path='', filename_prefix="CR", trigger=False):
+
+ if trigger == False:
+ return ()
+
+ output_dir = folder_paths.get_output_directory()
+ out_folder = os.path.join(output_dir, output_folder)
+
+ # Set the output path
+ if output_path != '':
+ if not os.path.exists(output_path):
+ print(f"[Warning] CR Save XY Grid Image: The input_path `{output_path}` does not exist")
+ return ("",)
+ out_path = output_path
+ else:
+ out_path = os.path.join(output_dir, out_folder)
+
+ if mode == "Preview":
+ out_path = folder_paths.temp_directory
+
+ print(f"[Info] CR Save XY Grid Image: Output path is `{out_path}`")
+
+ # Set the counter
+ counter = find_highest_numeric_value(out_path, filename_prefix) + 1
+ #print(f"[Debug] counter {counter}")
+
+ # Output image
+ output_image = image[0].cpu().numpy()
+ img = Image.fromarray(np.clip(output_image * 255.0, 0, 255).astype(np.uint8))
+
+ output_filename = f"{filename_prefix}_{counter:05}"
+ img_params = {'png': {'compress_level': 4},
+ 'webp': {'method': 6, 'lossless': False, 'quality': 80},
+ 'jpg': {'format': 'JPEG'},
+ 'tif': {'format': 'TIFF'}
+ }
+ self.type = "output" if mode == "Save" else 'temp'
+
+ resolved_image_path = os.path.join(out_path, f"{output_filename}.{file_format}")
+ img.save(resolved_image_path, **img_params[file_format])
+ print(f"[Info] CR Save XY Grid Image: Saved to {output_filename}.{file_format}")
+ out_filename = f"{output_filename}.{file_format}"
+ preview = {"ui": {"images": [{"filename": out_filename,"subfolder": out_path,"type": self.type,}]}}
+
+ return preview
+
+#---------------------------------------------------------------------------------------------------------------------
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+# 0 nodes released
+'''
+NODE_CLASS_MAPPINGS = {
+ # XY Grid
+ "CR XYZ List":CR_XYZList,
+ "CR XYZ Index":CR_XYZIndex,
+ "CR XYZ Interpolate":CR_XYZInterpolate,
+ "CR Load XY Annotation From File":CR_LoadXYAnnotationFromFile,
+ "CR XY Grid":CR_XYGrid,
+}
+'''
+
+
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/graphics_dev_nodes.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/graphics_dev_nodes.py
new file mode 100644
index 0000000000000000000000000000000000000000..f5d9e04f48655d6c1979abdb3afdb97207548c5e
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/dev_nodes/graphics_dev_nodes.py
@@ -0,0 +1,675 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# Comfyroll Custom Nodes by RockOfFire and Akatsuzi https://github.com/RockOfFire/ComfyUI_Comfyroll_CustomNodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+
+import numpy as np
+import torch
+import os
+from PIL import Image, ImageDraw, ImageOps, ImageFont
+from ..categories import icons
+from ..config import color_mapping, COLORS
+from ..nodes.graphics_functions import (hex_to_rgb,
+ get_color_values,
+ text_panel,
+ combine_images,
+ apply_outline_and_border,
+ get_font_size,
+ draw_text_on_image,
+ crop_and_resize_image)
+
+font_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "fonts")
+file_list = [f for f in os.listdir(font_dir) if os.path.isfile(os.path.join(font_dir, f)) and f.lower().endswith(".ttf")]
+
+#try:
+# import Markdown
+#except ImportError:
+# import pip
+# pip.main(['install', 'Markdown'])
+
+#---------------------------------------------------------------------------------------------------------------------#
+
+ALIGN_OPTIONS = ["top", "center", "bottom"]
+ROTATE_OPTIONS = ["text center", "image center"]
+JUSTIFY_OPTIONS = ["left", "center", "right"]
+PERSPECTIVE_OPTIONS = ["top", "bottom", "left", "right"]
+
+#---------------------------------------------------------------------------------------------------------------------#
+
+def tensor2pil(image):
+ return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
+
+def pil2tensor(image):
+ return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_MultiPanelMemeTemplate:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ font_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "fonts")
+ file_list = [f for f in os.listdir(font_dir) if os.path.isfile(os.path.join(font_dir, f)) and f.lower().endswith(".ttf")]
+ templates = ["vertical - 2 image + 2 text",
+ "vertical - 3 image + 3 text",
+ "vertical - 4 image + 4 text",
+ "horizontal - 2 image + 2 text",
+ "horizontal - text bar + 2 image",
+ "text bar + 1 image with overlay text",
+ "text bar + 4 image",
+ "text bar + 4 image with overlay text"]
+ colors = COLORS[1:]
+
+ return {"required": {
+ "template": (templates,),
+ "image_1": ("IMAGE",),
+ "text_1": ("STRING", {"multiline": True, "default": "text_1"}),
+ "text_2": ("STRING", {"multiline": True, "default": "text_2"}),
+ "text_3": ("STRING", {"multiline": True, "default": "text_3"}),
+ "text_4": ("STRING", {"multiline": True, "default": "text_4"}),
+ "font_name": (file_list,),
+ "font_size": ("INT", {"default": 50, "min": 1, "max": 1024}),
+ "font_color": (colors,),
+ "bar_color": (colors,),
+ "reverse_panels": (["No", "Yes"],),
+ },
+ "optional": {
+ "image_2": ("IMAGE",),
+ "image_3": ("IMAGE",),
+ "image_4": ("IMAGE",),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("image", "show_help", )
+ FUNCTION = "draw_text"
+ CATEGORY = icons.get("Comfyroll/Graphics/Template")
+
+ def draw_text(self, template, image_1, text_1, text_2, text_3, text_4,
+ font_name, font_size, font_color, bar_color, reverse_panels, image_2 = None, image_3 = None, image_4 = None):
+
+ show_help = "example help text"
+
+ # Convert the PIL image back to a torch tensor
+ return image_1, show_help,
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_PopularMemeTemplates:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ templates = ["Expanding brain",
+ "My honest reaction",
+ "The GF I want",
+ "Who would win?",
+ "I have 4 sides",
+ "This is Fine",
+ "Is This a Pigeon?",
+ "Drake hotline bling"]
+ colors = COLORS[1:]
+
+ return {"required": {
+ "meme": (templates,),
+ "image_1": ("IMAGE",),
+
+ "text_1": ("STRING", {"multiline": True, "default": "text_1"}),
+ "text_2": ("STRING", {"multiline": True, "default": "text_2"}),
+ "text_3": ("STRING", {"multiline": True, "default": "text_3"}),
+ "text_4": ("STRING", {"multiline": True, "default": "text_4"}),
+ "font_name": (file_list,),
+ "font_size": ("INT", {"default": 50, "min": 1, "max": 1024}),
+ "font_color": (colors,),
+ },
+ "optional": {
+ "image_2": ("IMAGE",),
+ "image_3": ("IMAGE",),
+ "image_4": ("IMAGE",),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("image", "show_help", )
+ FUNCTION = "draw_text"
+ CATEGORY = icons.get("Comfyroll/Graphics/Template")
+
+ def draw_text(self, meme, image_1, text_1, text_2, text_3, text_4,
+ font_name, font_size, font_color, image_2 = None, image_3 = None, image_4 = None):
+
+ show_help = "example help text"
+
+ # Convert the PIL image back to a torch tensor
+ return image_1, show_help,
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_DrawPerspectiveText:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ return {"required": {
+ "image_width": ("INT", {"default": 512, "min": 64, "max": 2048}),
+ "image_height": ("INT", {"default": 512, "min": 64, "max": 2048}),
+ "text": ("STRING", {"multiline": True, "default": "text"}),
+ "font_name": (file_list,),
+ "font_size": ("INT", {"default": 50, "min": 1, "max": 1024}),
+ "font_color": (COLORS,),
+ "background_color": (COLORS,),
+ "align": (ALIGN_OPTIONS,),
+ "justify": (JUSTIFY_OPTIONS,),
+ "margins": ("INT", {"default": 0, "min": -1024, "max": 1024}),
+ "line_spacing": ("INT", {"default": 0, "min": -1024, "max": 1024}),
+ "position_x": ("INT", {"default": 0, "min": -4096, "max": 4096}),
+ "position_y": ("INT", {"default": 0, "min": -4096, "max": 4096}),
+ "perspective_factor": ("FLOAT", {"default": 0.00, "min": 0.00, "max": 1.00, "step": 0.01}),
+ "perspective_direction": (PERSPECTIVE_OPTIONS,),
+ },
+ "optional": {
+ "font_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ "bg_color_hex": ("STRING", {"multiline": False, "default": "#000000"})
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("image", "show_help", )
+ FUNCTION = "draw_text"
+ CATEGORY = icons.get("Comfyroll/Graphics/Text")
+
+ def draw_text(self, image_width, image_height, text,
+ font_name, font_size, font_color, background_color,
+ margins, line_spacing,
+ position_x, position_y,
+ align, justify,
+ perspective_factor, perspective_direction,
+ font_color_hex='#000000', bg_color_hex='#000000'):
+
+ # Get RGB values for the text and background colors
+ text_color = get_color_values(font_color, font_color_hex, color_mapping)
+ bg_color = get_color_values(background_color, bg_color_hex, color_mapping)
+
+ # Create PIL images for the text and background layers and text mask
+ size = (image_width, image_height)
+ text_image = Image.new('RGB', size, text_color)
+ back_image = Image.new('RGB', size, bg_color)
+ text_mask = Image.new('L', back_image.size)
+
+ # Draw the text on the text mask
+ text_mask = draw_masked_text_v2(text_mask, text, font_name, font_size,
+ margins, line_spacing,
+ position_x, position_y,
+ align, justify,
+ perspective_factor, perspective_direction)
+
+ # Composite the text image onto the background image using the rotated text mask
+ image_out = Image.composite(text_image, back_image, text_mask)
+ preview_out = text_mask
+
+ show_help = "example help text"
+
+ # Convert the PIL image back to a torch tensor
+ return pil2tensor(image_out), pil2tensor(preview_out), show_help,
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_SimpleAnnotations:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ bar_opts = ["top", "bottom", "top and bottom", "no bars"]
+
+ return {"required": {
+ "image": ("IMAGE",),
+ "text_top": ("STRING", {"multiline": True, "default": "text_top"}),
+ "text_bottom": ("STRING", {"multiline": True, "default": "text_bottom"}),
+ "font_name": (file_list,),
+ "max_font_size": ("INT", {"default": 100, "min": 50, "max": 150}),
+ "font_color": (COLORS,),
+ "bar_color": (COLORS,),
+ "bar_options": (bar_opts,),
+ "bar_scaling_factor": ("FLOAT", {"default": 0.2, "min": 0.1, "max": 2, "step": 0.1}),
+ },
+ "optional": {
+ "font_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ "bar_color_hex": ("STRING", {"multiline": False, "default": "#000000"})
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("image", "show_help", )
+ FUNCTION = "make_meme"
+ CATEGORY = icons.get("Comfyroll/Graphics/Text")
+
+ def make_meme(self, image,
+ text_top, text_bottom,
+ font_name, max_font_size,
+ font_color, bar_color, bar_options, bar_scaling_factor,
+ font_color_hex='#000000',
+ bar_color_hex='#000000'):
+
+ text_color = get_color_values(font_color, font_color_hex, color_mapping)
+ bar_color = get_color_values(bar_color, bar_color_hex, color_mapping)
+
+ # Convert tensor images
+ image_3d = image[0, :, :, :]
+
+ # Calculate the height factor
+ if bar_options == "top":
+ height_factor = 1 + bar_scaling_factor
+ elif bar_options == "bottom":
+ height_factor = 1 + bar_scaling_factor
+ elif bar_options == "top and bottom":
+ height_factor = 1 + 2 * bar_scaling_factor
+ else:
+ height_factor = 1.0
+
+ # Create PIL images for the image and text bars
+ back_image = tensor2pil(image_3d)
+ size = back_image.width, int(back_image.height * height_factor)
+ result_image = Image.new("RGB", size)
+
+ # Define font settings
+ font_file = "fonts\\" + str(font_name)
+ resolved_font_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), font_file)
+
+ # Create the drawing context
+ draw = ImageDraw.Draw(result_image)
+
+ # Create two color bars at the top and bottom
+ bar_width = back_image.width
+ bar_height = back_image.height // 5 ### add parameter for this in adv node
+ top_bar = Image.new("RGB", (bar_width, bar_height), bar_color)
+ bottom_bar = Image.new("RGB", (bar_width, bar_height), bar_color)
+
+ # Composite the result image onto the input image
+ if bar_options == "top" or bar_options == "top and bottom":
+ image_out = result_image.paste(back_image, (0, bar_height))
+ else:
+ image_out = result_image.paste(back_image, (0, 0))
+
+ # Get the font size and draw the text
+ if bar_options == "top" or bar_options == "top and bottom":
+ result_image.paste(top_bar, (0, 0))
+ font_top = get_font_size(draw, text_top, bar_width, bar_height, resolved_font_path, max_font_size)
+ draw_text_on_image(draw, 0, bar_width, bar_height, text_top, font_top, text_color, "No")
+
+ if bar_options == "bottom" or bar_options == "top and bottom":
+ result_image.paste(bottom_bar, (0, (result_image.height - bar_height)))
+ font_bottom = get_font_size(draw, text_bottom, bar_width, bar_height, resolved_font_path, max_font_size)
+ if bar_options == "bottom":
+ y_position = back_image.height
+ else:
+ y_position = bar_height + back_image.height
+ draw_text_on_image(draw, y_position, bar_width, bar_height, text_bottom, font_bottom, text_color, "No")
+
+ # Overlay text on image
+ if bar_options == "bottom" and text_top > "":
+ font_top = get_font_size(draw, text_top, bar_width, bar_height, resolved_font_path, max_font_size)
+ draw_text_on_image(draw, 0, bar_width, bar_height, text_top, font_top, text_color, "No")
+
+ if (bar_options == "top" or bar_options == "none") and text_bottom > "":
+ font_bottom = get_font_size(draw, text_bottom, bar_width, bar_height, resolved_font_path, max_font_size)
+ y_position = back_image.height
+ draw_text_on_image(draw, y_position, bar_width, bar_height, text_bottom, font_bottom, text_color, "No")
+
+ if bar_options == "none" and text_bottom > "":
+ font_bottom = get_font_size(draw, text_bottom, bar_width, bar_height, resolved_font_path, max_font_size)
+ y_position = back_image.height - bar_height
+ draw_text_on_image(draw, y_position, bar_width, bar_height, text_bottom, font_bottom, text_color, "No")
+
+ show_help = "example help text"
+
+ image_out = np.array(result_image).astype(np.float32) / 255.0
+ image_out = torch.from_numpy(image_out).unsqueeze(0)
+
+ # Convert the PIL image back to a torch tensor
+ #return (pil2tensor(image_out), show_help, )
+ return (image_out, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ApplyAnnotations:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ font_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "fonts")
+ file_list = [f for f in os.listdir(font_dir) if os.path.isfile(os.path.join(font_dir, f)) and f.lower().endswith(".ttf")]
+ bar_opts = ["no bars", "top", "bottom", "top and bottom"]
+
+ return {"required": {
+ "image": ("IMAGE", ),
+ "annotation_stack": ("ANNOTATION_STACK", ),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("image", "show_help", )
+ FUNCTION = "apply_annotations"
+ CATEGORY = icons.get("Comfyroll/Graphics/Text")
+
+ def apply_annotations(self, image, annotation_stack):
+
+ show_help = "example help text"
+
+ return (image_out, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_AddAnnotation:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ font_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "fonts")
+ file_list = [f for f in os.listdir(font_dir) if os.path.isfile(os.path.join(font_dir, f)) and f.lower().endswith(".ttf")]
+ bar_opts = ["no bars", "top", "bottom", "top and bottom"]
+
+ return {"required": {
+ "text": ("STRING", {"multiline": True, "default": "text_top"}),
+ "font_name": (file_list,),
+ "font_size": ("INT", {"default": 100, "min": 20, "max": 150}),
+ "font_color": (COLORS,),
+ "position_x": ("INT", {"default": 0, "min": 0, "max": 4096}),
+ "position_y": ("INT", {"default": 0, "min": 0, "max": 4096}),
+ "justify": (JUSTIFY_OPTIONS,),
+ },
+ "optional": {
+ "annotation_stack": ("ANNOTATION_STACK",),
+ "font_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ }
+ }
+
+ RETURN_TYPES = ("ANNOTATION_STACK", "STRING", )
+ RETURN_NAMES = ("ANNOTATION_STACK", "show_help", )
+ FUNCTION = "add_annotation"
+ CATEGORY = icons.get("Comfyroll/Graphics/Text")
+
+ def add_annotation(self, image,
+ font_name, font_size, font_color,
+ position_x, position_y, justify,
+ annotation_stack=None, font_color_hex='#000000'):
+
+ show_help = "example help text"
+
+ return (annotation_stack, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_SimpleImageWatermark:
+
+ @classmethod
+ def INPUT_TYPES(cls):
+
+ ALIGN_OPTIONS = ["center", "top left", "top center", "top right", "bottom left", "bottom center", "bottom right"]
+
+ return {"required": {
+ "image": ("IMAGE",),
+ "watermark_image": ("IMAGE",),
+ "watermark_scale": ("FLOAT", {"default": 1, "min": 0.1, "max": 5.00, "step": 0.01}),
+ "opacity": ("FLOAT", {"default": 0.30, "min": 0.00, "max": 1.00, "step": 0.01}),
+ "align": (ALIGN_OPTIONS,),
+ "x_margin": ("INT", {"default": 20, "min": -1024, "max": 1024}),
+ "y_margin": ("INT", {"default": 20, "min": -1024, "max": 1024}),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", )
+ FUNCTION = "overlay_image"
+ CATEGORY = icons.get("Comfyroll/Graphics/Layout")
+
+ def overlay_image(self, image, watermark_image, watermark_scale, opacity, align, x_margin, y_margin):
+
+ # Create PIL images for the background layer
+ image = tensor2pil(image)
+ watermark_image = tensor2pil(watermark_image)
+
+ # Open images using Pillow
+ image = image.convert("RGBA")
+ watermark = watermark_image.convert("RGBA")
+
+ # Resize watermark if needed
+ watermark = watermark.resize(image.size)
+
+ # Create a transparent layer for the watermark
+ watermark_layer = Image.new("RGBA", image.size, (0, 0, 0, 0))
+ draw = ImageDraw.Draw(watermark_layer)
+
+ # Calculate the position to place the watermark based on the alignment
+ if align == 'center':
+ watermark_pos = ((image.width - watermark.width) // 2, (image.height - watermark.height) // 2)
+ elif align == 'top left':
+ watermark_pos = (x_margin, y_margin)
+ elif align == 'top center':
+ watermark_pos = ((image.width - watermark.width) // 2, y_margin)
+ elif align == 'top right':
+ watermark_pos = (image.width - watermark.width - x_margin, y_margin)
+ elif align == 'bottom left':
+ watermark_pos = (x_margin, image.height - watermark.height - y_margin)
+ elif align == 'bottom center':
+ watermark_pos = ((image.width - watermark.width) // 2, image.height - watermark.height - y_margin)
+ elif align == 'bottom right':
+ watermark_pos = (image.width - watermark.width - x_margin, image.height - watermark.height - y_margin)
+
+ # Paste the watermark onto the transparent layer
+ #watermark_layer.paste(watermark, watermark_pos, watermark)
+
+ # Blend the images using the specified opacity
+ #image = Image.alpha_composite(image, watermark_layer)
+
+ # Adjust the opacity of the watermark layer if needed
+ if opacity != 1:
+ watermark_layer = reduce_opacity(watermark_layer, opacity)
+
+ # Composite the text layer on top of the original image
+ image_out = Image.composite(watermark_layer, image, watermark_layer)
+
+ # Convert the PIL image back to a torch tensor
+ return pil2tensor(image_out)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ComicPanelTemplatesAdvanced:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ directions = ["left to right", "right to left"]
+
+ templates = ["custom",
+ "G22", "G33",
+ "H2", "H3",
+ "H12", "H13",
+ "H21", "H23",
+ "H31", "H32",
+ "V2", "V3",
+ "V12", "V13",
+ "V21", "V23",
+ "V31", "V32"]
+
+ return {"required": {
+ "page_width": ("INT", {"default": 512, "min": 8, "max": 4096}),
+ "page_height": ("INT", {"default": 512, "min": 8, "max": 4096}),
+ "template": (templates,),
+ "reading_direction": (directions,),
+ "border_thickness": ("INT", {"default": 5, "min": 0, "max": 1024}),
+ "outline_thickness": ("INT", {"default": 2, "min": 0, "max": 1024}),
+ "outline_color": (COLORS,),
+ "panel_color": (COLORS,),
+ "background_color": (COLORS,),
+ },
+ "optional": {
+ "images1": ("IMAGE",),
+ "images2": ("IMAGE",),
+ "images3": ("IMAGE",),
+ "images4": ("IMAGE",),
+ "custom_panel_layout": ("STRING", {"multiline": False, "default": "H123"}),
+ "outline_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ "panel_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ "bg_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("image", "show_help", )
+ FUNCTION = "layout"
+ CATEGORY = icons.get("Comfyroll/Graphics/Template")
+
+ def layout(self, page_width, page_height, template, reading_direction,
+ border_thickness, outline_thickness,
+ outline_color, panel_color, background_color,
+ images1=None, images2=None, images3=None, images4=None, custom_panel_layout='G44',
+ outline_color_hex='#000000', panel_color_hex='#000000', bg_color_hex='#000000'):
+
+
+
+ panels = []
+ k = 0
+ batches = 0
+
+ # Convert tensor images to PIL
+ if images1 is not None:
+ images1 = [tensor2pil(image) for image in images1]
+ len_images1 = len(images1)
+ batches+=1
+
+ if images2 is not None:
+ images2 = [tensor2pil(image) for image in images2]
+ len_images2 = len(images2)
+ batches+=1
+
+ if images3 is not None:
+ images3 = [tensor2pil(image) for image in images3]
+ len_images3 = len(images3)
+ batches+=1
+
+ if images4 is not None:
+ images4 = [tensor2pil(image) for image in images4]
+ len_images4 = len(images4)
+ batches+=1
+
+ # Get RGB values for the text and background colors
+ outline_color = get_color_values(outline_color, outline_color_hex, color_mapping)
+ panel_color = get_color_values(panel_color, panel_color_hex, color_mapping)
+ bg_color = get_color_values(background_color, bg_color_hex, color_mapping)
+
+ # Create page and apply bg color
+ size = (page_width - (2 * border_thickness), page_height - (2 * border_thickness))
+ page = Image.new('RGB', size, bg_color)
+ draw = ImageDraw.Draw(page)
+
+ if template == "custom":
+ template = custom_panel_layout
+
+ # Calculate panel positions and add to bg image
+ first_char = template[0]
+ if first_char == "G":
+ rows = int(template[1])
+ columns = int(template[2])
+ panel_width = (page.width - (2 * columns * (border_thickness + outline_thickness))) // columns
+ panel_height = (page.height - (2 * rows * (border_thickness + outline_thickness))) // rows
+ #Batch Loop
+ #for b in range(batches):
+ # Row loop
+ for i in range(rows):
+ # Column Loop
+ for j in range(columns):
+ # Draw the panel
+ create_and_paste_panel(page, border_thickness, outline_thickness,
+ panel_width, panel_height, page.width,
+ panel_color, bg_color, outline_color,
+ images1, i, j, k, len_images1, reading_direction)
+ k += 1
+
+ elif first_char == "H":
+ rows = len(template) - 1
+ panel_height = (page.height - (2 * rows * (border_thickness + outline_thickness))) // rows
+ #Batch Loop
+ #for b in range(batches):
+ # Row loop
+ for i in range(rows):
+ columns = int(template[i+1])
+ panel_width = (page.width - (2 * columns * (border_thickness + outline_thickness))) // columns
+ # Column Loop
+ for j in range(columns):
+ # Draw the panel
+ create_and_paste_panel(page, border_thickness, outline_thickness,
+ panel_width, panel_height, page.width,
+ panel_color, bg_color, outline_color,
+ images1, i, j, k, len_images1, reading_direction)
+ k += 1
+
+ elif first_char == "V":
+ columns = len(template) - 1
+ panel_width = (page.width - (2 * columns * (border_thickness + outline_thickness))) // columns
+ #Batch Loop
+ #for b in range(batches):
+ # Column Loop
+ for j in range(columns):
+ rows = int(template[j+1])
+ panel_height = (page.height - (2 * rows * (border_thickness + outline_thickness))) // rows
+ # Row loop
+ for i in range(rows):
+ # Draw the panel
+ create_and_paste_panel(page, border_thickness, outline_thickness,
+ panel_width, panel_height, page.width,
+ panel_color, bg_color, outline_color,
+ images1, i, j, k, len_images1, reading_direction)
+ k += 1
+
+ # Add a border to the page
+ if border_thickness > 0:
+ page = ImageOps.expand(page, border_thickness, bg_color)
+
+ show_help = "example help text"
+
+ return (pil2tensor(page), show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+'''
+class CR_ASCIIPattern:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ return {"required": {
+ "image": ("IMAGE",),
+ }
+ }
+
+ RETURN_TYPES = ("STRING", "STRING", )
+ RETURN_NAMES = ("multiline_text", "show_help", )
+ FUNCTION = "draw_pattern"
+ CATEGORY = icons.get("Comfyroll/Graphics/Pattern")
+
+ def draw_pattern(self, image):
+
+ pixel_ascii_map = "`^\",:;Il!i~+_-?][}{1)(|\\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$"
+
+ im = image
+ x = list(im.getdata())
+ for pixel in iter(x):
+ x = sum(pixel) // 3 # integer division
+ x = (x * len(pixel_ascii_map)) // 255 # rescaling
+ ascii_val = pixel_ascii_map[x]
+
+ text_out = ascii_val
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Text-Nodes#cr-ascii-pattern"
+
+ # Convert the PIL image back to a torch tensor
+ return (text_out, show_help, )
+'''
+#---------------------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+'''
+NODE_CLASS_MAPPINGS = {
+ "CR Multi-Panel Meme Template": CR_MultiPanelMemeTemplate,
+ "CR Popular Meme Templates": CR_PopularMemeTemplates,
+ "CR Draw Perspective Text": CR_DrawPerspectiveText,
+ "CR Simple Annotations": CR_SimpleAnnotations,
+ "CR Apply Annotations": CR_ApplyAnnotations,
+ "CR Add Annotation": CR_AddAnnotation,
+ "CR Simple Image Watermark": CR_SimpleImageWatermark,
+ "CR Comic Panel Templates Advanced": CR_ComicPanelTemplatesAdvanced,
+ "CR ASCII Pattern": CR_ASCIIPattern,
+}
+'''
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/AlumniSansCollegiateOne-Regular.ttf b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/AlumniSansCollegiateOne-Regular.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..047451fa81fd6faaa8365ef45ddab3459b7ad655
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/AlumniSansCollegiateOne-Regular.ttf differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/NotoSansArabic-Regular.ttf b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/NotoSansArabic-Regular.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..79359c460b13e94945a8700227dabdc790091c0e
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/NotoSansArabic-Regular.ttf differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/Oswald-Bold.ttf b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/Oswald-Bold.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..b9c6e3767357f4bf5016a21dc0b6015d08be3c99
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/Oswald-Bold.ttf differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/PixelifySans-Bold.ttf b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/PixelifySans-Bold.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..4d8aeb670dc228fe48a6e90366bae9f0d6930f4c
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/PixelifySans-Bold.ttf differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/Quicksand-Bold.ttf b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/Quicksand-Bold.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..07d5127c04b17a9a62121d12aeb00b701e920500
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/Quicksand-Bold.ttf differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/Roboto-Regular.ttf b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/Roboto-Regular.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..3033308a695ab4efa60441c23f2c18aaa94c568f
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/Roboto-Regular.ttf differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/YoungSerif-Regular.ttf b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/YoungSerif-Regular.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..f454fbedd4c8b00833d17fffa1f89acb5149b02c
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/YoungSerif-Regular.ttf differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/comic.ttf b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/comic.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..2d8e9ca9ce1216331f7d665090eb5678bacdd614
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/comic.ttf differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/impact.ttf b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/impact.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..2675688cd0e23bec6295d5d218b8be276705468c
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/fonts/impact.ttf differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/images/CR Animation drop 10.JPG b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/images/CR Animation drop 10.JPG
new file mode 100644
index 0000000000000000000000000000000000000000..fd7be15ec99c2a01d0dc3efce5a7de30ec36391c
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/images/CR Animation drop 10.JPG differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/images/Gradient Nodes 2.JPG b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/images/Gradient Nodes 2.JPG
new file mode 100644
index 0000000000000000000000000000000000000000..fc0da59302128adf6020cc8a6bd841de7ae33a4e
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/images/Gradient Nodes 2.JPG differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/controlnet.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/controlnet.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ac3b373aa0c8ddd85856cb6f9aed998472729590
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/controlnet.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/conversion.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/conversion.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4023b237f2f173550216175302293c0cd318dfdd
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/conversion.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/graphics_functions.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/graphics_functions.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..6721ca7fdb98fc564b05c03e73701328a7e9bc17
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/graphics_functions.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/index.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/index.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..caa1245c2406fec9b47cec8c65edf33a17f010ca
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/index.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/legacy_nodes.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/legacy_nodes.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1b67cf015734942d8be8a751ced532b60421c10a
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/legacy_nodes.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/logic.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/logic.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b80abea3dc9ebe024dcd426ced5f36eac1a3cb1b
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/logic.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/lora.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/lora.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..69f5ac6fee3fd99aa59fb8a183d39d61f0ebcc72
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/lora.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/matplot.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/matplot.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..46d04c63e7c6c14dfb925066b2ab602cfc6a16c0
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/matplot.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/model_merge.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/model_merge.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..def69b99d3089eecf774ab39912427cd7dfe6346
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/model_merge.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/nodes.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/nodes.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..aece1cef55881e2d9d1d196e865567e7548c2970
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/nodes.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/nodes_random.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/nodes_random.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a0fa751d6fb84bc1dea853da0ef025cbe1454457
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/nodes_random.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/pil_filter.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/pil_filter.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..df86996ff5af051215192ac34f03bbf9e67ffd8f
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/pil_filter.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/pil_layout.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/pil_layout.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..766e4d348a5236c6b1110b88a641ac3ee5ea5ed9
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/pil_layout.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/pil_pattern.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/pil_pattern.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..23a142ae2452d08d3f8f78b123399e2af56e8926
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/pil_pattern.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/pil_template.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/pil_template.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ae5f6a782295b4160d2c8560c9af1aaf393a8c7f
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/pil_template.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/pil_text.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/pil_text.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..722798ee2dbeba9bb80e8b5cccce788e9b3c5af6
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/pil_text.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/pipe.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/pipe.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1b720015d1adb7f48592bc340fdae0d5e1ff162d
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/pipe.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/sdxl.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/sdxl.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ac31dfc99feee5dd693d605c639e3cdeeabd335f
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/sdxl.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/upscale.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/upscale.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..3e809b3fa817f308b464dc3d1a01edd93afc9c13
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/upscale.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/upscale_functions.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/upscale_functions.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..55bc5fcd9c1fe0b427061c6a5bfee4c5482173d1
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/upscale_functions.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/xygrid.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/xygrid.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5b35bd93d4ed2b6578b3f389eda1a913c9c9ba3d
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/xygrid.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/xygrid_functions.cpython-311.pyc b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/xygrid_functions.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..97a8868b268a422fca4bbeb548c359d549a12310
Binary files /dev/null and b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/__pycache__/xygrid_functions.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/controlnet.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/controlnet.py
new file mode 100644
index 0000000000000000000000000000000000000000..e0bf86bf39c3b931572362042805ea0268853914
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/controlnet.py
@@ -0,0 +1,174 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# Comfyroll Custom Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes #
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI #
+#---------------------------------------------------------------------------------------------------------------------#
+
+import os
+import sys
+import comfy.controlnet
+import comfy.sd
+import folder_paths
+from nodes import ControlNetApplyAdvanced
+from ..categories import icons
+
+sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), "comfy"))
+
+#---------------------------------------------------------------------------------------------------------------------#
+# This node will apply any type of ControlNet.
+class CR_ApplyControlNet:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {"conditioning": ("CONDITIONING", ),
+ "control_net": ("CONTROL_NET", ),
+ "image": ("IMAGE", ),
+ "switch": ([
+ "On",
+ "Off"],),
+ "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01})
+ }}
+ RETURN_TYPES = ("CONDITIONING", "STRING", )
+ RETURN_NAMES = ("CONDITIONING", "show_help", )
+ FUNCTION = "apply_controlnet"
+
+ CATEGORY = icons.get("Comfyroll/ControlNet")
+
+ def apply_controlnet(self, conditioning, control_net, image, switch, strength):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/ControlNet-Nodes#cr-apply-controlnet"
+ if strength == 0 or switch == "Off":
+ return (conditioning, show_help, )
+
+ c = []
+ control_hint = image.movedim(-1,1)
+ for t in conditioning:
+ n = [t[0], t[1].copy()]
+ c_net = control_net.copy().set_cond_hint(control_hint, strength)
+ if 'control' in t[1]:
+ c_net.set_previous_controlnet(t[1]['control'])
+ n[1]['control'] = c_net
+ c.append(n)
+ return (c, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+# This node is a stack of controlnets each with their own switch.
+class CR_ControlNetStack:
+
+ controlnets = ["None"] + folder_paths.get_filename_list("controlnet")
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ #controlnets = ["None"]
+ return {"required": {
+ },
+ "optional": {
+ "switch_1": (["Off","On"],),
+ "controlnet_1": (cls.controlnets,),
+ "controlnet_strength_1": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
+ "start_percent_1": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
+ "end_percent_1": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
+ #
+ "switch_2": (["Off","On"],),
+ "controlnet_2": (cls.controlnets,),
+ "controlnet_strength_2": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
+ "start_percent_2": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
+ "end_percent_2": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
+ #
+ "switch_3": (["Off","On"],),
+ "controlnet_3": (cls.controlnets,),
+ "controlnet_strength_3": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
+ "start_percent_3": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
+ "end_percent_3": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
+ "image_1": ("IMAGE",),
+ "image_2": ("IMAGE",),
+ "image_3": ("IMAGE",),
+ "controlnet_stack": ("CONTROL_NET_STACK",)
+ },
+ }
+
+ RETURN_TYPES = ("CONTROL_NET_STACK", "STRING", )
+ RETURN_NAMES = ("CONTROLNET_STACK", "show_help", )
+ FUNCTION = "controlnet_stacker"
+ CATEGORY = icons.get("Comfyroll/ControlNet")
+
+ def controlnet_stacker(self, switch_1, controlnet_1, controlnet_strength_1, start_percent_1, end_percent_1,
+ switch_2, controlnet_2, controlnet_strength_2, start_percent_2, end_percent_2,
+ switch_3, controlnet_3, controlnet_strength_3, start_percent_3, end_percent_3,
+ image_1=None, image_2=None, image_3=None, controlnet_stack=None):
+
+ # Initialise the list
+ controlnet_list= []
+
+ if controlnet_stack is not None:
+ controlnet_list.extend([l for l in controlnet_stack if l[0] != "None"])
+
+ if controlnet_1 != "None" and switch_1 == "On" and image_1 is not None:
+ controlnet_path = folder_paths.get_full_path("controlnet", controlnet_1)
+ controlnet_1 = comfy.controlnet.load_controlnet(controlnet_path)
+ controlnet_list.extend([(controlnet_1, image_1, controlnet_strength_1, start_percent_1, end_percent_1)]),
+
+ if controlnet_2 != "None" and switch_2 == "On" and image_2 is not None:
+ controlnet_path = folder_paths.get_full_path("controlnet", controlnet_2)
+ controlnet_2 = comfy.controlnet.load_controlnet(controlnet_path)
+ controlnet_list.extend([(controlnet_2, image_2, controlnet_strength_2, start_percent_2, end_percent_2)]),
+
+ if controlnet_3 != "None" and switch_3 == "On" and image_3 is not None:
+ controlnet_path = folder_paths.get_full_path("controlnet", controlnet_3)
+ controlnet_3 = comfy.controlnet.load_controlnet(controlnet_path)
+ controlnet_list.extend([(controlnet_3, image_3, controlnet_strength_3, start_percent_3, end_percent_3)]),
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/ControlNet-Nodes#cr-multi-controlnet-stack"
+
+ return (controlnet_list, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+# This applies the ControlNet stack.
+class CR_ApplyControlNetStack:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {"base_positive": ("CONDITIONING", ),
+ "base_negative": ("CONDITIONING",),
+ "switch": (["Off","On"],),
+ "controlnet_stack": ("CONTROL_NET_STACK", ),
+ }
+ }
+
+ RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "STRING", )
+ RETURN_NAMES = ("base_pos", "base_neg", "show_help", )
+ FUNCTION = "apply_controlnet_stack"
+ CATEGORY = icons.get("Comfyroll/ControlNet")
+
+ def apply_controlnet_stack(self, base_positive, base_negative, switch, controlnet_stack=None,):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/ControlNet-Nodes#cr-apply-multi-controlnet-stack"
+
+ if switch == "Off":
+ return (base_positive, base_negative, show_help, )
+
+ if controlnet_stack is not None:
+ for controlnet_tuple in controlnet_stack:
+ controlnet_name, image, strength, start_percent, end_percent = controlnet_tuple
+
+ if type(controlnet_name) == str:
+ controlnet_path = folder_paths.get_full_path("controlnet", controlnet_name)
+ controlnet = comfy.sd.load_controlnet(controlnet_path)
+ else:
+ controlnet = controlnet_name
+
+ controlnet_conditioning = ControlNetApplyAdvanced().apply_controlnet(base_positive, base_negative,
+ controlnet, image, strength,
+ start_percent, end_percent)
+
+ base_positive, base_negative = controlnet_conditioning[0], controlnet_conditioning[1]
+
+ return (base_positive, base_negative, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+'''
+NODE_CLASS_MAPPINGS = {
+ "CR Apply ControlNet": CR_ApplyControlNet,
+ "CR Multi-ControlNet Stack":CR_ControlNetStack,
+ "CR Apply Multi-ControlNet":CR_ApplyControlNetStack,
+}
+'''
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/conversion.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/conversion.py
new file mode 100644
index 0000000000000000000000000000000000000000..1a997bfb34f1cc2c1907ad70579e3755c1c12f9a
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/conversion.py
@@ -0,0 +1,183 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# Comfyroll Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/CR-Animation-Nodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+
+from ..categories import icons
+
+class AnyType(str):
+ def __ne__(self, __value: object) -> bool:
+ return False
+
+any = AnyType("*")
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_StringToNumber:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {"text": ("STRING", {"multiline": False, "default": "text"}),
+ },
+ }
+
+ RETURN_TYPES = ("INT", "FLOAT", "STRING", )
+ RETURN_NAMES = ("INT", "FLOAT", "show_help", )
+ FUNCTION = "convert"
+ CATEGORY = icons.get("Comfyroll/Utils/Conversion")
+
+ def convert(self, text):
+
+ # Check if number
+ if text.replace('.','',1).isdigit():
+ float_out = float(text)
+ int_out = int(float_out)
+ else:
+ print(f"[Error] CR String To Number. Not a number.")
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Conversion-Nodes#cr-string-to-number"
+ return (int_out, float_out, show_help,)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_TextListToString:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {
+ "text_list": ("STRING", {"forceInput": True}),
+ },
+ }
+
+ RETURN_TYPES = ("STRING", "STRING", )
+ RETURN_NAMES = ("STRING", "show_help", )
+ FUNCTION = "joinlist"
+ CATEGORY = icons.get("Comfyroll/Utils/Conversion")
+
+ def joinlist(self, text_list):
+
+ string_out = " ".join(text_list)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Conversion-Nodes#cr-text-list-to-string"
+
+ return (string_out, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+# based on Repeater node by pythongosssss
+class CR_StringToCombo:
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "text": ("STRING", {"multiline": False, "default": "", "forceInput": True}),
+ },
+ }
+
+ RETURN_TYPES = (any, "STRING", )
+ RETURN_NAMES = ("any", "show_help", )
+ FUNCTION = "convert"
+ CATEGORY = icons.get("Comfyroll/Utils/Conversion")
+
+ def convert(self, text):
+
+ text_list = list()
+
+ if text != "":
+ values = text.split(',')
+ text_list = values[0]
+ print(text_list)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Conversion-Nodes#cr-string-to-combo"
+
+ return (text_list, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------------------------------------#
+# Cloned from Mikey Nodes
+class CR_IntegerToString:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {"int_": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
+ }
+ }
+
+ RETURN_TYPES = ("STRING","STRING", )
+ RETURN_NAMES = ("STRING","show_help", )
+ FUNCTION = 'convert'
+ CATEGORY = icons.get("Comfyroll/Utils/Conversion")
+
+ def convert(self, int_):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Conversion-Nodes#cr-integer-to-string"
+ return (f'{int_}', show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------------------------------------#
+# Cloned from Mikey Nodes
+class CR_FloatToString:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {"float_": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1000000.0}),
+ }
+ }
+
+ RETURN_TYPES = ('STRING', "STRING", )
+ RETURN_NAMES = ('STRING', "show_help", )
+ FUNCTION = 'convert'
+ CATEGORY = icons.get("Comfyroll/Utils/Conversion")
+
+ def convert(self, float_):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Conversion-Nodes#cr-float-to-string"
+ return (f'{float_}', show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------
+class CR_FloatToInteger:
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {"required": {"_float": ("FLOAT", {"default": 0.0})}}
+
+ RETURN_TYPES = ("INT", "STRING", )
+ RETURN_NAMES = ("INT", "show_help", )
+ FUNCTION = "convert"
+ CATEGORY = icons.get("Comfyroll/Utils/Conversion")
+
+ def convert(self, _float):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Conversion-Nodes#cr-float-to-integer"
+ return (int(_float), show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------------------------------------#
+# This node is used to convert type Seed to type INT
+class CR_SeedToInt:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "seed": ("SEED", ),
+ }
+ }
+
+ RETURN_TYPES = ("INT", "STRING", )
+ RETURN_NAMES = ("INT", "show_help", )
+ FUNCTION = "seed_to_int"
+ CATEGORY = icons.get("Comfyroll/Utils/Conversion")
+
+ def seed_to_int(self, seed):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Conversion-Nodes#cr-seed-to-int"
+ return (seed.get('seed'), show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+# 10 nodes published
+'''
+NODE_CLASS_MAPPINGS = {
+ "CR String To Number":CR_StringToNumber,
+ "CR String To Combo":CR_StringToCombo,
+ "CR Float To String":CR_FloatToString,
+ "CR Float To Integer":CR_FloatToInteger,
+ "CR Integer To String":CR_IntegerToString,
+ "CR Text List To String":CR_TextListToString,
+ "CR Seed to Int": CR_SeedToInt,
+}
+'''
+
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/graphics_functions.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/graphics_functions.py
new file mode 100644
index 0000000000000000000000000000000000000000..bcbffd02037a2999eeab04caae087360541dc818
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/graphics_functions.py
@@ -0,0 +1,445 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# Comfyroll Custom Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+
+import os
+import random
+from PIL import Image, ImageDraw, ImageFont, ImageOps, ImageEnhance
+from ..config import color_mapping
+
+font_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "fonts")
+file_list = [f for f in os.listdir(font_dir) if os.path.isfile(os.path.join(font_dir, f)) and f.lower().endswith(".ttf")]
+
+
+def align_text(align, img_height, text_height, text_pos_y, margins):
+ if align == "center":
+ text_plot_y = img_height / 2 - text_height / 2 + text_pos_y
+ elif align == "top":
+ text_plot_y = text_pos_y + margins
+ elif align == "bottom":
+ text_plot_y = img_height - text_height + text_pos_y - margins
+ return text_plot_y
+
+
+def justify_text(justify, img_width, line_width, margins):
+ if justify == "left":
+ text_plot_x = 0 + margins
+ elif justify == "right":
+ text_plot_x = img_width - line_width - margins
+ elif justify == "center":
+ text_plot_x = img_width/2 - line_width/2
+ return text_plot_x
+
+
+def get_text_size(draw, text, font):
+ bbox = draw.textbbox((0, 0), text, font=font)
+
+ # Calculate the text width and height
+ text_width = bbox[2] - bbox[0]
+ text_height = bbox[3] - bbox[1]
+ return text_width, text_height
+
+
+def draw_masked_text(text_mask, text,
+ font_name, font_size,
+ margins, line_spacing,
+ position_x, position_y,
+ align, justify,
+ rotation_angle, rotation_options):
+
+ # Create the drawing context
+ draw = ImageDraw.Draw(text_mask)
+
+ # Define font settings
+ font_folder = "fonts"
+ font_file = os.path.join(font_folder, font_name)
+ resolved_font_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), font_file)
+ font = ImageFont.truetype(str(resolved_font_path), size=font_size)
+
+ # Split the input text into lines
+ text_lines = text.split('\n')
+
+ # Calculate the size of the text plus padding for the tallest line
+ max_text_width = 0
+ max_text_height = 0
+
+ for line in text_lines:
+ # Calculate the width and height of the current line
+ line_width, line_height = get_text_size(draw, line, font)
+
+ line_height = line_height + line_spacing
+ max_text_width = max(max_text_width, line_width)
+ max_text_height = max(max_text_height, line_height)
+
+ # Get the image width and height
+ image_width, image_height = text_mask.size
+ image_center_x = image_width / 2
+ image_center_y = image_height / 2
+
+ text_pos_y = position_y
+ sum_text_plot_y = 0
+ text_height = max_text_height * len(text_lines)
+
+ for line in text_lines:
+ # Calculate the width of the current line
+ line_width, _ = get_text_size(draw, line, font)
+
+ # Get the text x and y positions for each line
+ text_plot_x = position_x + justify_text(justify, image_width, line_width, margins)
+ text_plot_y = align_text(align, image_height, text_height, text_pos_y, margins)
+
+ # Add the current line to the text mask
+ draw.text((text_plot_x, text_plot_y), line, fill=255, font=font)
+
+ text_pos_y += max_text_height # Move down for the next line
+ sum_text_plot_y += text_plot_y # Sum the y positions
+
+ # Calculate centers for rotation
+ text_center_x = text_plot_x + max_text_width / 2
+ text_center_y = sum_text_plot_y / len(text_lines)
+
+ if rotation_options == "text center":
+ rotated_text_mask = text_mask.rotate(rotation_angle, center=(text_center_x, text_center_y))
+ elif rotation_options == "image center":
+ rotated_text_mask = text_mask.rotate(rotation_angle, center=(image_center_x, image_center_y))
+
+ return rotated_text_mask
+
+def draw_text_on_image(draw, y_position, bar_width, bar_height, text, font, text_color, font_outline):
+
+ # Calculate the width and height of the text
+ text_width, text_height = get_text_size(draw, text, font)
+
+ if font_outline == "thin":
+ outline_thickness = text_height // 40
+ elif font_outline == "thick":
+ outline_thickness = text_height // 20
+ elif font_outline == "extra thick":
+ outline_thickness = text_height // 10
+
+ outline_color = (0, 0, 0)
+
+ text_lines = text.split('\n')
+
+ if len(text_lines) == 1:
+ x = (bar_width - text_width) // 2
+ y = y_position + (bar_height - text_height) // 2 - (bar_height * 0.10)
+ if font_outline == "none":
+ draw.text((x, y), text, fill=text_color, font=font)
+ else:
+ draw.text((x, y), text, fill=text_color, font=font, stroke_width=outline_thickness, stroke_fill='black')
+ elif len(text_lines) > 1:
+ # Calculate the width and height of the text
+ text_width, text_height = get_text_size(draw, text_lines[0], font)
+
+ x = (bar_width - text_width) // 2
+ y = y_position + (bar_height - text_height * 2) // 2 - (bar_height * 0.15)
+ if font_outline == "none":
+ draw.text((x, y), text_lines[0], fill=text_color, font=font)
+ else:
+ draw.text((x, y), text_lines[0], fill=text_color, font=font, stroke_width=outline_thickness, stroke_fill='black')
+
+ # Calculate the width and height of the text
+ text_width, text_height = get_text_size(draw, text_lines[1], font)
+
+ x = (bar_width - text_width) // 2
+ y = y_position + (bar_height - text_height * 2) // 2 + text_height - (bar_height * 0.00)
+ if font_outline == "none":
+ draw.text((x, y), text_lines[1], fill=text_color, font=font)
+ else:
+ draw.text((x, y), text_lines[1], fill=text_color, font=font, stroke_width=outline_thickness, stroke_fill='black')
+
+
+def get_font_size(draw, text, max_width, max_height, font_path, max_font_size):
+
+ # Adjust the max-width to allow for start and end padding
+ max_width = max_width * 0.9
+
+ # Start with the maximum font size
+ font_size = max_font_size
+ font = ImageFont.truetype(str(font_path), size=font_size)
+
+ # Get the first two lines
+ text_lines = text.split('\n')[:2]
+
+ if len(text_lines) == 2:
+ font_size = min(max_height//2, max_font_size)
+ font = ImageFont.truetype(str(font_path), size=font_size)
+
+ # Calculate max text width and height with the current font
+ max_text_width = 0
+ longest_line = text_lines[0]
+ for line in text_lines:
+ # Calculate the width and height of the current line
+ line_width, line_height = get_text_size(draw, line, font)
+
+ if line_width > max_text_width:
+ longest_line = line
+ max_text_width = max(max_text_width, line_width)
+
+ # Calculate the width and height of the text
+ text_width, text_height = get_text_size(draw, text, font)
+
+ # Decrease the font size until it fits within the bounds
+ while max_text_width > max_width or text_height > 0.88 * max_height / len(text_lines):
+ font_size -= 1
+ font = ImageFont.truetype(str(font_path), size=font_size)
+ max_text_width, text_height = get_text_size(draw, longest_line, font)
+
+ return font
+
+
+def hex_to_rgb(hex_color):
+ hex_color = hex_color.lstrip('#') # Remove the '#' character, if present
+ r = int(hex_color[0:2], 16)
+ g = int(hex_color[2:4], 16)
+ b = int(hex_color[4:6], 16)
+ return (r, g, b)
+
+
+def text_panel(image_width, image_height, text,
+ font_name, font_size, font_color,
+ font_outline_thickness, font_outline_color,
+ background_color,
+ margins, line_spacing,
+ position_x, position_y,
+ align, justify,
+ rotation_angle, rotation_options):
+
+ """
+ Create an image with text overlaid on a background.
+
+ Returns:
+ PIL.Image.Image: Image with text overlaid on the background.
+ """
+
+ # Create PIL images for the text and background layers and text mask
+ size = (image_width, image_height)
+ panel = Image.new('RGB', size, background_color)
+
+ # Draw the text on the text mask
+ image_out = draw_text(panel, text,
+ font_name, font_size, font_color,
+ font_outline_thickness, font_outline_color,
+ background_color,
+ margins, line_spacing,
+ position_x, position_y,
+ align, justify,
+ rotation_angle, rotation_options)
+
+ return image_out
+
+
+def draw_text(panel, text,
+ font_name, font_size, font_color,
+ font_outline_thickness, font_outline_color,
+ bg_color,
+ margins, line_spacing,
+ position_x, position_y,
+ align, justify,
+ rotation_angle, rotation_options):
+
+ # Create the drawing context
+ draw = ImageDraw.Draw(panel)
+
+ # Define font settings
+ font_folder = "fonts"
+ font_file = os.path.join(font_folder, font_name)
+ resolved_font_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), font_file)
+ font = ImageFont.truetype(str(resolved_font_path), size=font_size)
+
+ # Split the input text into lines
+ text_lines = text.split('\n')
+
+ # Calculate the size of the text plus padding for the tallest line
+ max_text_width = 0
+ max_text_height = 0
+
+ for line in text_lines:
+ # Calculate the width and height of the current line
+ line_width, line_height = get_text_size(draw, line, font)
+
+ line_height = line_height + line_spacing
+ max_text_width = max(max_text_width, line_width)
+ max_text_height = max(max_text_height, line_height)
+
+ # Get the image center
+ image_center_x = panel.width / 2
+ image_center_y = panel.height / 2
+
+ text_pos_y = position_y
+ sum_text_plot_y = 0
+ text_height = max_text_height * len(text_lines)
+
+ for line in text_lines:
+ # Calculate the width and height of the current line
+ line_width, line_height = get_text_size(draw, line, font)
+
+ # Get the text x and y positions for each line
+ text_plot_x = position_x + justify_text(justify, panel.width, line_width, margins)
+ text_plot_y = align_text(align, panel.height, text_height, text_pos_y, margins)
+
+ # Add the current line to the text mask
+ draw.text((text_plot_x, text_plot_y), line, fill=font_color, font=font, stroke_width=font_outline_thickness, stroke_fill=font_outline_color)
+
+ text_pos_y += max_text_height # Move down for the next line
+ sum_text_plot_y += text_plot_y # Sum the y positions
+
+ text_center_x = text_plot_x + max_text_width / 2
+ text_center_y = sum_text_plot_y / len(text_lines)
+
+ if rotation_options == "text center":
+ rotated_panel = panel.rotate(rotation_angle, center=(text_center_x, text_center_y), resample=Image.BILINEAR)
+ elif rotation_options == "image center":
+ rotated_panel = panel.rotate(rotation_angle, center=(image_center_x, image_center_y), resample=Image.BILINEAR)
+
+ return rotated_panel
+
+
+def combine_images(images, layout_direction='horizontal'):
+ """
+ Combine a list of PIL Image objects either horizontally or vertically.
+
+ Args:
+ images (list of PIL.Image.Image): List of PIL Image objects to combine.
+ layout_direction (str): 'horizontal' for horizontal layout, 'vertical' for vertical layout.
+
+ Returns:
+ PIL.Image.Image: Combined image.
+ """
+
+ if layout_direction == 'horizontal':
+ combined_width = sum(image.width for image in images)
+ combined_height = max(image.height for image in images)
+ else:
+ combined_width = max(image.width for image in images)
+ combined_height = sum(image.height for image in images)
+
+ combined_image = Image.new('RGB', (combined_width, combined_height))
+
+ x_offset = 0
+ y_offset = 0 # Initialize y_offset for vertical layout
+ for image in images:
+ combined_image.paste(image, (x_offset, y_offset))
+ if layout_direction == 'horizontal':
+ x_offset += image.width
+ else:
+ y_offset += image.height
+
+ return combined_image
+
+
+def apply_outline_and_border(images, outline_thickness, outline_color, border_thickness, border_color):
+ for i, image in enumerate(images):
+ # Apply the outline
+ if outline_thickness > 0:
+ image = ImageOps.expand(image, outline_thickness, fill=outline_color)
+
+ # Apply the border
+ if border_thickness > 0:
+ image = ImageOps.expand(image, border_thickness, fill=border_color)
+
+ images[i] = image
+
+ return images
+
+
+def get_color_values(color, color_hex, color_mapping):
+
+ #Get RGB values for the text and background colors.
+
+ if color == "custom":
+ color_rgb = hex_to_rgb(color_hex)
+ else:
+ color_rgb = color_mapping.get(color, (0, 0, 0)) # Default to black if the color is not found
+
+ return color_rgb
+
+
+def hex_to_rgb(hex_color):
+ hex_color = hex_color.lstrip('#') # Remove the '#' character, if present
+ r = int(hex_color[0:2], 16)
+ g = int(hex_color[2:4], 16)
+ b = int(hex_color[4:6], 16)
+ return (r, g, b)
+
+
+def crop_and_resize_image(image, target_width, target_height):
+ width, height = image.size
+ aspect_ratio = width / height
+ target_aspect_ratio = target_width / target_height
+
+ if aspect_ratio > target_aspect_ratio:
+ # Crop the image's width to match the target aspect ratio
+ crop_width = int(height * target_aspect_ratio)
+ crop_height = height
+ left = (width - crop_width) // 2
+ top = 0
+ else:
+ # Crop the image's height to match the target aspect ratio
+ crop_height = int(width / target_aspect_ratio)
+ crop_width = width
+ left = 0
+ top = (height - crop_height) // 2
+
+ # Perform the center cropping
+ cropped_image = image.crop((left, top, left + crop_width, top + crop_height))
+
+ return cropped_image
+
+
+def create_and_paste_panel(page, border_thickness, outline_thickness,
+ panel_width, panel_height, page_width,
+ panel_color, bg_color, outline_color,
+ images, i, j, k, len_images, reading_direction):
+ panel = Image.new("RGB", (panel_width, panel_height), panel_color)
+ if k < len_images:
+ img = images[k]
+ image = crop_and_resize_image(img, panel_width, panel_height)
+ image.thumbnail((panel_width, panel_height), Image.Resampling.LANCZOS)
+ panel.paste(image, (0, 0))
+ panel = ImageOps.expand(panel, border=outline_thickness, fill=outline_color)
+ panel = ImageOps.expand(panel, border=border_thickness, fill=bg_color)
+ new_panel_width, new_panel_height = panel.size
+ if reading_direction == "right to left":
+ page.paste(panel, (page_width - (j + 1) * new_panel_width, i * new_panel_height))
+ else:
+ page.paste(panel, (j * new_panel_width, i * new_panel_height))
+
+
+def reduce_opacity(img, opacity):
+ """Returns an image with reduced opacity."""
+ assert opacity >= 0 and opacity <= 1
+ if img.mode != 'RGBA':
+ img = img.convert('RGBA')
+ else:
+ img = img.copy()
+ alpha = img.split()[3]
+ alpha = ImageEnhance.Brightness(alpha).enhance(opacity)
+ img.putalpha(alpha)
+ return img
+
+
+def random_hex_color():
+ # Generate three random values for RGB
+ r = random.randint(0, 255)
+ g = random.randint(0, 255)
+ b = random.randint(0, 255)
+
+ # Convert RGB to hex format
+ hex_color = "#{:02x}{:02x}{:02x}".format(r, g, b)
+
+ return hex_color
+
+
+def random_rgb():
+ # Generate three random values for RGB
+ r = random.randint(0, 255)
+ g = random.randint(0, 255)
+ b = random.randint(0, 255)
+
+ # Format RGB as a string in the format "128,128,128"
+ rgb_string = "{},{},{}".format(r, g, b)
+
+ return rgb_string
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/index.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/index.py
new file mode 100644
index 0000000000000000000000000000000000000000..f26f84684c5c4ce7c359a9a5e583b4c0dca3ad66
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/index.py
@@ -0,0 +1,129 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# Comfyroll Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/CR-Animation-Nodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+
+from ..categories import icons
+
+#---------------------------------------------------------------------------------------------------------------------
+class CR_Trigger:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {"index": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "trigger_value": ("INT", {"default": 1, "min": 0, "max": 10000}),
+ },
+ }
+
+ RETURN_TYPES = ("INT", "BOOLEAN", "STRING", )
+ RETURN_NAMES = ("index", "trigger", "show_help", )
+ FUNCTION = "trigger"
+ CATEGORY = icons.get("Comfyroll/Utils/Index")
+
+ def trigger(self, index, trigger_value):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Index-Nodes#cr-trigger"
+ return (index, index == trigger_value, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------
+class CR_Index:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {"index": ("INT", {"default": 1, "min": 0, "max": 10000}),
+ "print_to_console": (["Yes","No"],),
+ },
+ }
+
+ RETURN_TYPES = ("INT", "STRING", )
+ RETURN_NAMES = ("INT", "show_help", )
+ FUNCTION = "index"
+ CATEGORY = icons.get("Comfyroll/Utils/Index")
+
+ def index(self, index, print_to_console):
+
+ if print_to_console == "Yes":
+ print(f"[Info] CR Index:{index}")
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Index-Nodes#cr-index"
+ return (index, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------
+class CR_IncrementIndex:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required":{
+ "index": ("INT", {"default": 1, "min": -10000, "max": 10000}),
+ "interval": ("INT", {"default": 1, "min": -10000, "max": 10000}),
+ }
+ }
+
+ RETURN_TYPES = ("INT", "INT", "STRING", )
+ RETURN_NAMES = ("index", "interval", "show_help", )
+ FUNCTION = "increment"
+ CATEGORY = icons.get("Comfyroll/Utils/Index")
+
+ def increment(self, index, interval):
+ index+=interval
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Index-Nodes#cr-index-increment"
+ return (index, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_MultiplyIndex:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required":{
+ "index": ("INT", {"default": 1, "min": 0, "max": 10000}),
+ "factor": ("INT", {"default": 1, "min": 0, "max": 10000}),
+ }
+ }
+
+
+ RETURN_TYPES = ("INT", "INT", "STRING", )
+ RETURN_NAMES = ("index", "factor", "show_help", )
+ FUNCTION = "multiply"
+ CATEGORY = icons.get("Comfyroll/Utils/Index")
+
+ def multiply(self, index, factor):
+ index = index * factor
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Index-Nodes#cr-index-multiply"
+ return (index, factor, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_IndexReset:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required":{
+ "index": ("INT", {"default": 1, "min": 0, "max": 10000}),
+ "reset_to": ("INT", {"default": 1, "min": 0, "max": 10000}),
+ }
+ }
+
+
+ RETURN_TYPES = ("INT", "INT", "STRING", )
+ RETURN_NAMES = ("index", "reset_to", "show_help", )
+ FUNCTION = "reset"
+ CATEGORY = icons.get("Comfyroll/Utils/Index")
+
+ def reset(self, index, reset_to):
+ index = reset_to
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Index-Nodes#cr-index-reset"
+ return (index, reset_to, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+'''
+NODE_CLASS_MAPPINGS = {
+ # Index
+ "CR Index":CR_Index,
+ "CR Index Increment":CR_IncrementIndex,
+ "CR Index Multiply":CR_MultiplyIndex,
+ "CR Index Reset":CR_IndexReset,
+ "CR Trigger":CR_Trigger,
+}
+'''
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/legacy_nodes.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/legacy_nodes.py
new file mode 100644
index 0000000000000000000000000000000000000000..ab6fa3ff5d76bb4d22f5c390372040fcf3a46cc6
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/legacy_nodes.py
@@ -0,0 +1,93 @@
+#---------------------------------------------------------------------------------------------------------------------------------------------------#
+# Comfyroll Custom Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes #
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI #
+#---------------------------------------------------------------------------------------------------------------------------------------------------#
+
+from ..categories import icons
+
+#---------------------------------------------------------------------------------------------------------------------------------------------------#
+class CR_ImageSize:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "width": ("INT", {"default": 512, "min": 64, "max": 2048}),
+ "height": ("INT", {"default": 512, "min": 64, "max": 2048}),
+ "upscale_factor": ("FLOAT", {"default": 1, "min": 1, "max": 2000})
+ }
+ }
+ RETURN_TYPES = ("INT", "INT", "FLOAT", "STRING", )
+ RETURN_NAMES = ("Width", "Height", "upscale_factor", "show_help", )
+ FUNCTION = "ImageSize"
+ CATEGORY = icons.get("Comfyroll/Other/Legacy")
+
+ def ImageSize(self, width, height, upscale_factor):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Legacy-Nodes#cr-image-size"
+ return(width, height, upscale_factor, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------------------------------------#
+class CR_AspectRatio_SDXL:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "width": ("INT", {"default": 1024, "min": 64, "max": 2048}),
+ "height": ("INT", {"default": 1024, "min": 64, "max": 2048}),
+ "aspect_ratio": (["custom", "1:1 square 1024x1024", "3:4 portrait 896x1152", "5:8 portrait 832x1216", "9:16 portrait 768x1344", "9:21 portrait 640x1536", "4:3 landscape 1152x896", "3:2 landscape 1216x832", "16:9 landscape 1344x768", "21:9 landscape 1536x640"],),
+ "swap_dimensions": (["Off", "On"],),
+ "upscale_factor1": ("FLOAT", {"default": 1, "min": 1, "max": 2000}),
+ "upscale_factor2": ("FLOAT", {"default": 1, "min": 1, "max": 2000}),
+ "batch_size": ("INT", {"default": 1, "min": 1, "max": 64})
+ }
+ }
+ RETURN_TYPES = ("INT", "INT", "FLOAT", "FLOAT", "INT", "STRING", )
+ RETURN_NAMES = ("INT", "INT", "FLOAT", "FLOAT", "INT", "show_help", )
+ #RETURN_NAMES = ("Width", "Height")
+ FUNCTION = "Aspect_Ratio"
+
+ CATEGORY = icons.get("Comfyroll/Other/Legacy")
+
+ def Aspect_Ratio(self, width, height, aspect_ratio, swap_dimensions, upscale_factor1, upscale_factor2, batch_size):
+ if aspect_ratio == "1:1 square 1024x1024":
+ width, height = 1024, 1024
+ elif aspect_ratio == "3:4 portrait 896x1152":
+ width, height = 896, 1152
+ elif aspect_ratio == "5:8 portrait 832x1216":
+ width, height = 832, 1216
+ elif aspect_ratio == "9:16 portrait 768x1344":
+ width, height = 768, 1344
+ elif aspect_ratio == "9:21 portrait 640x1536":
+ width, height = 640, 1536
+ elif aspect_ratio == "4:3 landscape 1152x896":
+ width, height = 1152, 896
+ elif aspect_ratio == "3:2 landscape 1216x832":
+ width, height = 1216, 832
+ elif aspect_ratio == "16:9 landscape 1344x768":
+ width, height = 1344, 768
+ elif aspect_ratio == "21:9 landscape 1536x640":
+ width, height = 1536, 640
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Legacy-Nodes#cr-aspect-ratio-sdxl"
+
+ if swap_dimensions == "On":
+ return(height, width, upscale_factor1, upscale_factor2, batch_size,show_help,)
+ else:
+ return(width, height, upscale_factor1, upscale_factor2, batch_size,show_help,)
+
+#---------------------------------------------------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+'''
+NODE_CLASS_MAPPINGS = {
+ "CR Image Size": CR_ImageSize,
+ "CR Aspect Ratio SDXL": CR_AspectRatio_SDXL,
+}
+'''
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/logic.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/logic.py
new file mode 100644
index 0000000000000000000000000000000000000000..b805b5fabc684df5f1e81acf69b27c4169b059d3
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/logic.py
@@ -0,0 +1,436 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# Comfyroll Custom Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+
+from ..categories import icons
+
+#---------------------------------------------------------------------------------------------------------------------#
+# Logic Switches
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ImageInputSwitch:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "Input": ("INT", {"default": 1, "min": 1, "max": 2}),
+ "image1": ("IMAGE",),
+ "image2": ("IMAGE",)
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("IMAGE", "show_help", )
+ FUNCTION = "switch"
+ CATEGORY = icons.get("Comfyroll/Utils/Logic")
+
+ def switch(self, Input, image1, image2):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Logic-Nodes#cr-image-input-switch"
+ if Input == 1:
+ return (image1, show_help, )
+ else:
+ return (image2, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_LatentInputSwitch:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "Input": ("INT", {"default": 1, "min": 1, "max": 2}),
+ "latent1": ("LATENT",),
+ "latent2": ("LATENT",)
+ }
+ }
+
+ RETURN_TYPES = ("LATENT", "STRING", )
+ RETURN_NAMES = ("LATENT", "show_help", )
+ FUNCTION = "switch"
+ CATEGORY = icons.get("Comfyroll/Utils/Logic")
+
+ def switch(self, Input, latent1, latent2):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Logic-Nodes#cr-latent-input-switch"
+ if Input == 1:
+ return (latent1, show_help, )
+ else:
+ return (latent2, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ConditioningInputSwitch:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "Input": ("INT", {"default": 1, "min": 1, "max": 2}),
+ "conditioning1": ("CONDITIONING",),
+ "conditioning2": ("CONDITIONING",)
+ }
+ }
+
+ RETURN_TYPES = ("CONDITIONING", "STRING", )
+ RETURN_NAMES = ("CONDITIONING", "show_help", )
+ FUNCTION = "switch"
+ CATEGORY = icons.get("Comfyroll/Utils/Logic")
+
+ def switch(self, Input, conditioning1, conditioning2):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Logic-Nodes#cr-conditioning-input-switch"
+ if Input == 1:
+ return (conditioning1, show_help, )
+ else:
+ return (conditioning2, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ClipInputSwitch:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "Input": ("INT", {"default": 1, "min": 1, "max": 2}),
+ "clip1": ("CLIP",),
+ "clip2": ("CLIP",)
+ }
+ }
+
+ RETURN_TYPES = ("CLIP", "STRING", )
+ RETURN_NAMES = ("CLIP", "show_help", )
+ FUNCTION = "switch"
+ CATEGORY = icons.get("Comfyroll/Utils/Logic")
+
+ def switch(self, Input, clip1, clip2):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Logic-Nodes#cr-clip-input-switch"
+ if Input == 1:
+ return (clip1, show_help, )
+ else:
+ return (clip2, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ModelInputSwitch:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "Input": ("INT", {"default": 1, "min": 1, "max": 2}),
+ "model1": ("MODEL",),
+ "model2": ("MODEL",)
+ }
+ }
+
+ RETURN_TYPES = ("MODEL", "STRING", )
+ RETURN_NAMES = ("MODEL", "show_help", )
+ FUNCTION = "switch"
+ CATEGORY = icons.get("Comfyroll/Utils/Logic")
+
+ def switch(self, Input, model1, model2):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Logic-Nodes#cr-model-input-switch"
+ if Input == 1:
+ return (model1, show_help, )
+ else:
+ return (model2, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+#This is an input switch for controlNet. Can pick an input and that image will be the one picked for the workflow.
+class CR_ControlNetInputSwitch:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "Input": ("INT", {"default": 1, "min": 1, "max": 2}),
+ "control_net1": ("CONTROL_NET",),
+ "control_net2": ("CONTROL_NET",)
+ }
+ }
+
+ RETURN_TYPES = ("CONTROL_NET", "STRING", )
+ RETURN_NAMES = ("CONTROL_NET", "show_help", )
+ FUNCTION = "switch"
+ CATEGORY = icons.get("Comfyroll/Utils/Logic")
+
+ def switch(self, Input, control_net1, control_net2):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Logic-Nodes#cr-controlnet-input-switch"
+ if Input == 1:
+ return (control_net1, show_help, )
+ else:
+ return (control_net2, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+#This is an input switch for text. Can pick an input and that image will be the one picked for the workflow.
+class CR_TextInputSwitch:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "Input": ("INT", {"default": 1, "min": 1, "max": 2}),
+ "text1": ("STRING", {"forceInput": True}),
+ "text2": ("STRING", {"forceInput": True}),
+ }
+ }
+
+ RETURN_TYPES = ("STRING", "STRING", )
+ RETURN_NAMES = ("STRING", "show_help", )
+ FUNCTION = "switch"
+ CATEGORY = icons.get("Comfyroll/Utils/Logic")
+
+ def switch(self, Input, text1, text2,):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Logic-Nodes#cr-text-input-switch"
+ if Input == 1:
+ return (text1, show_help, )
+ else:
+ return (text2, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_VAEInputSwitch:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "Input": ("INT", {"default": 1, "min": 1, "max": 2}),
+ "VAE1": ("VAE", {"forceInput": True}),
+ "VAE2": ("VAE", {"forceInput": True}),
+ }
+ }
+
+ RETURN_TYPES = ("VAE", "STRING", )
+ RETURN_NAMES = ("VAE", "show_help", )
+ FUNCTION = "switch"
+ CATEGORY = icons.get("Comfyroll/Utils/Logic")
+
+ def switch(self, Input, VAE1, VAE2,):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Logic-Nodes#cr-vae-input-switch"
+ if Input == 1:
+ return (VAE1, show_help, )
+ else:
+ return (VAE2, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ImageInputSwitch4way:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "Input": ("INT", {"default": 1, "min": 1, "max": 4}),
+ "image1": ("IMAGE",),
+ },
+ "optional": {
+ "image2": ("IMAGE",),
+ "image3": ("IMAGE",),
+ "image4": ("IMAGE",),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("IMAGE", "show_help", )
+ FUNCTION = "switch"
+ CATEGORY = icons.get("Comfyroll/Utils/Logic")
+
+ def switch(self, Input, image1, image2=None, image3=None, image4=None):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Logic-Nodes#cr-text-input-switch-4-way"
+ if Input == 1:
+ return (image1, show_help, )
+ elif Input == 2:
+ return (image2, show_help, )
+ elif Input == 3:
+ return (image3, show_help, )
+ else:
+ return (image4, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_TextInputSwitch4way:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "Input": ("INT", {"default": 1, "min": 1, "max": 4}),
+ "text1": ("STRING", {"forceInput": True}),
+ },
+ "optional": {
+ "text2": ("STRING", {"forceInput": True}),
+ "text3": ("STRING", {"forceInput": True}),
+ "text4": ("STRING", {"forceInput": True}),
+ }
+ }
+
+ RETURN_TYPES = ("STRING", "STRING", )
+ RETURN_NAMES = ("STRING", "show_help", )
+ FUNCTION = "switch"
+ CATEGORY = icons.get("Comfyroll/Utils/Logic")
+
+ def switch(self, Input, text1, text2=None, text3=None, text4=None):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Logic-Nodes#cr-text-input-switch-4-way"
+ if Input == 1:
+ return (text1, show_help, )
+ elif Input == 2:
+ return (text2, show_help, )
+ elif Input == 3:
+ return (text3, show_help, )
+ else:
+ return (text4, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ModelAndCLIPInputSwitch:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "Input": ("INT", {"default": 1, "min": 1, "max": 2}),
+ "model1": ("MODEL",),
+ "clip1": ("CLIP",),
+ "model2": ("MODEL",),
+ "clip2": ("CLIP",)
+ }
+ }
+
+ RETURN_TYPES = ("MODEL", "CLIP", "STRING", )
+ RETURN_NAMES = ("MODEL", "CLIP", "show_help", )
+ FUNCTION = "switch"
+ CATEGORY = icons.get("Comfyroll/Utils/Logic")
+
+ def switch(self, Input, clip1, clip2, model1, model2):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Logic-Nodes#cr-switch-model-and-clip"
+ if Input == 1:
+ return (model1, clip1, show_help, )
+ else:
+ return (model2, clip2, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+# Process switches
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_Img2ImgProcessSwitch:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "Input": (["txt2img", "img2img"],),
+ "txt2img": ("LATENT",),
+ "img2img": ("LATENT",)
+ }
+ }
+
+ RETURN_TYPES = ("LATENT", "STRING", )
+ RETURN_NAMES = ("LATENT", "show_help", )
+ FUNCTION = "switch"
+ CATEGORY = icons.get("Comfyroll/Utils/Process")
+
+ def switch(self, Input, txt2img, img2img):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Process-Nodes#cr-img2img-process-switch"
+ if Input == "txt2img":
+ return (txt2img, show_help, )
+ else:
+ return (img2img, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_HiResFixProcessSwitch:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "Input": (["latent_upscale", "image_upscale"],),
+ "latent_upscale": ("LATENT",),
+ "image_upscale": ("LATENT",)
+ }
+ }
+
+ RETURN_TYPES = ("LATENT", "STRING", )
+ RETURN_NAMES = ("LATENT", "STRING", )
+ FUNCTION = "switch"
+ CATEGORY = icons.get("Comfyroll/Utils/Process")
+
+ def switch(self, Input, latent_upscale, image_upscale):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Process-Nodes#cr-hires-fix-process-switch"
+ if Input == "latent_upscale":
+ return (latent_upscale, show_help, )
+ else:
+ return (image_upscale, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_BatchProcessSwitch:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "Input": (["image", "image batch"],),
+ "image": ("IMAGE", ),
+ "image_batch": ("IMAGE", )
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("IMAGE", "show_help", )
+ FUNCTION = "switch"
+ CATEGORY = icons.get("Comfyroll/Utils/Process")
+
+ def switch(self, Input, image, image_batch):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Process-Nodes#cr-batch-process-switch"
+ if Input == "image":
+ return (image, show_help, )
+ else:
+ return (image_batch, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+'''
+NODE_CLASS_MAPPINGS = {
+ # Logic switches
+ "CR Image Input Switch": CR_ImageInputSwitch,
+ "CR Latent Input Switch": CR_LatentInputSwitch,
+ "CR Conditioning Input Switch": CR_ConditioningInputSwitch,
+ "CR Clip Input Switch": CR_ClipInputSwitch,
+ "CR Model Input Switch": CR_ModelInputSwitch,
+ "CR ControlNet Input Switch": CR_ControlNetInputSwitch,
+ "CR Text Input Switch": CR_TextInputSwitch,
+ "CR VAE Input Switch": CR_VAEInputSwitch,
+ "CR Switch Model and CLIP":CR_ModelAndCLIPInputSwitch,
+ # 4-way switches
+ "CR Image Input Switch (4 way)": CR_InputImages4way,
+ "CR Text Input Switch (4 way)": CR_TextInputSwitch4way,
+ # Process switches
+ "CR Img2Img Process Switch": CR_Img2ImgProcessSwitch:,
+ "CR Hires Fix Process Switch": CR_HiResFixProcessSwitch,
+ "CR Batch Process Switch": CR_BatchProcessSwitch,
+}
+'''
+
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/lora.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/lora.py
new file mode 100644
index 0000000000000000000000000000000000000000..d0612eafd68e44e89e5914bc7ca20c6d9c1dc1a7
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/lora.py
@@ -0,0 +1,170 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# Comfyroll Custom Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes #
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI #
+#---------------------------------------------------------------------------------------------------------------------#
+
+import os
+import sys
+import comfy.sd
+import comfy.utils
+import folder_paths
+from ..categories import icons
+
+sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), "comfy"))
+
+#---------------------------------------------------------------------------------------------------------------------#
+# LoRA Nodes
+#---------------------------------------------------------------------------------------------------------------------#
+# This is a load lora node with an added switch to turn on or off. On will add the lora and off will skip the node.
+class CR_LoraLoader:
+ def __init__(self):
+ self.loaded_lora = None
+
+ @classmethod
+ def INPUT_TYPES(s):
+ file_list = folder_paths.get_filename_list("loras")
+ file_list.insert(0, "None")
+ return {"required": { "model": ("MODEL",),
+ "clip": ("CLIP", ),
+ "switch": (["On","Off"],),
+ "lora_name": (file_list, ),
+ "strength_model": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
+ "strength_clip": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
+ }}
+ RETURN_TYPES = ("MODEL", "CLIP", "STRING", )
+ RETURN_NAMES = ("MODEL", "CLIP", "show_help", )
+ FUNCTION = "load_lora"
+ CATEGORY = icons.get("Comfyroll/LoRA")
+
+ def load_lora(self, model, clip, switch, lora_name, strength_model, strength_clip):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/LoRA-Nodes#cr-load-lora"
+ if strength_model == 0 and strength_clip == 0:
+ return (model, clip, show_help, )
+
+ if switch == "Off" or lora_name == "None":
+ return (model, clip, show_help, )
+
+ lora_path = folder_paths.get_full_path("loras", lora_name)
+ lora = None
+ if self.loaded_lora is not None:
+ if self.loaded_lora[0] == lora_path:
+ lora = self.loaded_lora[1]
+ else:
+ del self.loaded_lora
+
+ if lora is None:
+ lora = comfy.utils.load_torch_file(lora_path, safe_load=True)
+ self.loaded_lora = (lora_path, lora)
+
+ model_lora, clip_lora = comfy.sd.load_lora_for_models(model, clip, lora, strength_model, strength_clip)
+ return (model_lora, clip_lora, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+# Based on Efficiency Nodes
+# This is a lora stack where a single node has 3 different loras each with their own switch
+class CR_LoRAStack:
+
+ @classmethod
+ def INPUT_TYPES(cls):
+
+ loras = ["None"] + folder_paths.get_filename_list("loras")
+
+ return {"required": {
+ "switch_1": (["Off","On"],),
+ "lora_name_1": (loras,),
+ "model_weight_1": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
+ "clip_weight_1": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
+ "switch_2": (["Off","On"],),
+ "lora_name_2": (loras,),
+ "model_weight_2": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
+ "clip_weight_2": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
+ "switch_3": (["Off","On"],),
+ "lora_name_3": (loras,),
+ "model_weight_3": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
+ "clip_weight_3": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
+ },
+ "optional": {"lora_stack": ("LORA_STACK",)
+ },
+ }
+
+ RETURN_TYPES = ("LORA_STACK", "STRING", )
+ RETURN_NAMES = ("LORA_STACK", "show_help", )
+ FUNCTION = "lora_stacker"
+ CATEGORY = icons.get("Comfyroll/LoRA")
+
+ def lora_stacker(self, lora_name_1, model_weight_1, clip_weight_1, switch_1, lora_name_2, model_weight_2, clip_weight_2, switch_2, lora_name_3, model_weight_3, clip_weight_3, switch_3, lora_stack=None):
+
+ # Initialise the list
+ lora_list=list()
+
+ if lora_stack is not None:
+ lora_list.extend([l for l in lora_stack if l[0] != "None"])
+
+ if lora_name_1 != "None" and switch_1 == "On":
+ lora_list.extend([(lora_name_1, model_weight_1, clip_weight_1)]),
+
+ if lora_name_2 != "None" and switch_2 == "On":
+ lora_list.extend([(lora_name_2, model_weight_2, clip_weight_2)]),
+
+ if lora_name_3 != "None" and switch_3 == "On":
+ lora_list.extend([(lora_name_3, model_weight_3, clip_weight_3)]),
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/LoRA-Nodes#cr-lora-stack"
+
+ return (lora_list, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+# This applies the lora stack.
+class CR_ApplyLoRAStack:
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {"required": {"model": ("MODEL",),
+ "clip": ("CLIP", ),
+ "lora_stack": ("LORA_STACK", ),
+ }
+ }
+
+ RETURN_TYPES = ("MODEL", "CLIP", "STRING", )
+ RETURN_NAMES = ("MODEL", "CLIP", "show_help", )
+ FUNCTION = "apply_lora_stack"
+ CATEGORY = icons.get("Comfyroll/LoRA")
+
+ def apply_lora_stack(self, model, clip, lora_stack=None,):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/LoRA-Nodes#cr-apply-lora-stack"
+
+ # Initialise the list
+ lora_params = list()
+
+ # Extend lora_params with lora-stack items
+ if lora_stack:
+ lora_params.extend(lora_stack)
+ else:
+ return (model, clip, show_help,)
+
+ # Initialise the model and clip
+ model_lora = model
+ clip_lora = clip
+
+ # Loop through the list
+ for tup in lora_params:
+ lora_name, strength_model, strength_clip = tup
+
+ lora_path = folder_paths.get_full_path("loras", lora_name)
+ lora = comfy.utils.load_torch_file(lora_path, safe_load=True)
+
+ model_lora, clip_lora = comfy.sd.load_lora_for_models(model_lora, clip_lora, lora, strength_model, strength_clip)
+
+ return (model_lora, clip_lora, show_help,)
+
+#---------------------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+'''
+NODE_CLASS_MAPPINGS = {
+ "CR Load LoRA": CR_LoraLoader,
+ "CR LoRA Stack":CR_LoRAStack,
+ "CR Apply LoRA Stack":CR_ApplyLoRAStack,
+}
+'''
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/matplot.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/matplot.py
new file mode 100644
index 0000000000000000000000000000000000000000..42b793460ed184b51ef4fbb9cbc07626d8269a3d
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/matplot.py
@@ -0,0 +1,848 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# Comfyroll Custom Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+
+import torch
+import numpy as np
+import os
+import sys
+import io
+import folder_paths
+from PIL import Image
+from ..categories import icons
+
+try:
+ import matplotlib.pyplot as plt
+except ImportError:
+ import pip
+ pip.main(['install', 'matplotlib'])
+ import matplotlib.pyplot as plt
+
+from matplotlib.patches import RegularPolygon
+
+#---------------------------------------------------------------------------------------------------------------------#
+
+#icons = {
+# "Comfyroll/Graphics/Pattern": "🧩 Comfyroll/👾 Graphics/🌈 Pattern",
+#}
+
+# Dictionary to map color names to RGB values
+color_mapping = {
+ "white": (255, 255, 255),
+ "black": (0, 0, 0),
+ "red": (255, 0, 0),
+ "green": (0, 255, 0),
+ "blue": (0, 0, 255),
+ "yellow": (255, 255, 0),
+ "cyan": (0, 255, 255),
+ "magenta": (255, 0, 255),
+ "orange": (255, 165, 0),
+ "purple": (128, 0, 128),
+ "pink": (255, 192, 203),
+ "brown": (165, 42, 42),
+ "gray": (128, 128, 128),
+ "lightgray": (211, 211, 211),
+ "darkgray": (169, 169, 169),
+ "olive": (128, 128, 0),
+ "lime": (0, 128, 0),
+ "teal": (0, 128, 128),
+ "navy": (0, 0, 128),
+ "maroon": (128, 0, 0),
+ "fuchsia": (255, 0, 128),
+ "aqua": (0, 255, 128),
+ "silver": (192, 192, 192),
+ "gold": (255, 215, 0),
+ "turquoise": (64, 224, 208),
+ "lavender": (230, 230, 250),
+ "violet": (238, 130, 238),
+ "coral": (255, 127, 80),
+ "indigo": (75, 0, 130),
+}
+
+COLORS = ["custom", "white", "black", "red", "green", "blue", "yellow",
+ "cyan", "magenta", "orange", "purple", "pink", "brown", "gray",
+ "lightgray", "darkgray", "olive", "lime", "teal", "navy", "maroon",
+ "fuchsia", "aqua", "silver", "gold", "turquoise", "lavender",
+ "violet", "coral", "indigo"]
+
+STYLES = ["Accent","afmhot","autumn","binary","Blues","bone","BrBG","brg",
+ "BuGn","BuPu","bwr","cividis","CMRmap","cool","coolwarm","copper","cubehelix","Dark2","flag",
+ "gist_earth","gist_gray","gist_heat","gist_rainbow","gist_stern","gist_yarg","GnBu","gnuplot","gnuplot2","gray","Greens",
+ "Greys","hot","hsv","inferno","jet","magma","nipy_spectral","ocean","Oranges","OrRd",
+ "Paired","Pastel1","Pastel2","pink","PiYG","plasma","PRGn","prism","PuBu","PuBuGn",
+ "PuOr","PuRd","Purples","rainbow","RdBu","RdGy","RdPu","RdYlBu","RdYlGn","Reds","seismic",
+ "Set1","Set2","Set3","Spectral","spring","summer","tab10","tab20","tab20b","tab20c","terrain",
+ "turbo","twilight","twilight_shifted","viridis","winter","Wistia","YlGn","YlGnBu","YlOrBr","YlOrRd"]
+
+#---------------------------------------------------------------------------------------------------------------------#
+
+def rgb_to_hex(rgb):
+ r, g, b = rgb
+ return "#{:02X}{:02X}{:02X}".format(r, g, b)
+
+def hex_to_rgb(hex_color):
+ hex_color = hex_color.lstrip('#') # Remove the '#' character, if present
+ r = int(hex_color[0:2], 16)
+ g = int(hex_color[2:4], 16)
+ b = int(hex_color[4:6], 16)
+ return (r, g, b)
+
+def tensor2pil(image):
+ return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
+
+def pil2tensor(image):
+ return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_HalftoneGrid:
+ @classmethod
+ def INPUT_TYPES(s):
+
+ return {"required": {
+ "width": ("INT", {"default": 512, "min": 64, "max": 4096}),
+ "height": ("INT", {"default": 512, "min": 64, "max": 4096}),
+ "dot_style": (STYLES,),
+ "reverse_dot_style": (["No", "Yes"],),
+ "dot_frequency": ("INT", {"default": 50, "min": 1, "max":200, "step": 1}),
+ "background_color": (COLORS,),
+ "x_pos": ("FLOAT", {"default": 0.5, "min": 0, "max": 1, "step": .01}),
+ "y_pos": ("FLOAT", {"default": 0.5, "min": 0, "max": 1, "step": .01}),
+ },
+ "optional": {
+ "bg_color_hex": ("STRING", {"multiline": False, "default": "#000000"})
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("IMAGE", "show_help", )
+ FUNCTION = "halftone"
+ CATEGORY = icons.get("Comfyroll/Graphics/Pattern")
+
+
+ def halftone(self, width, height, dot_style, reverse_dot_style, dot_frequency, background_color, x_pos, y_pos, bg_color_hex='#000000'):
+
+ if background_color == "custom":
+ bgc = bg_color_hex
+ else:
+ bgc = background_color
+
+ reverse = ""
+
+ if reverse_dot_style == "Yes":
+ reverse = "_r"
+
+ fig, ax = plt.subplots(figsize=(width/100,height/100))
+
+ dotsx = np.linspace(0, 1, dot_frequency)
+ dotsy = np.linspace(0, 1, dot_frequency)
+
+ X, Y = np.meshgrid(dotsx, dotsy)
+
+ dist = np.sqrt((X - x_pos)**2 + (Y - y_pos)**2)
+
+ fig.patch.set_facecolor(bgc)
+ ax.scatter(X, Y, c=dist, cmap=dot_style+reverse)
+
+ plt.axis('off')
+ plt.tight_layout(pad=0, w_pad=0, h_pad=0)
+ plt.autoscale(tight=True)
+
+ img_buf = io.BytesIO()
+ plt.savefig(img_buf, format='png')
+ img = Image.open(img_buf)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Pattern-Nodes#cr-halftone-grid"
+
+ return(pil2tensor(img), show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ColorBars:
+ @classmethod
+ def INPUT_TYPES(s):
+
+ modes = ["2-color"]
+
+ return {"required": {
+ "mode": (modes,),
+ "width": ("INT", {"default": 512, "min": 64, "max": 4096}),
+ "height": ("INT", {"default": 512, "min": 64, "max": 4096}),
+ "color_1": (COLORS,),
+ "color_2": (COLORS,),
+ "orientation": (["vertical", "horizontal", "diagonal", "alt_diagonal"],), #added 135 angle for diagonals
+ "bar_frequency": ("INT", {"default": 5, "min": 1, "max":200, "step": 1}),
+ "offset": ("FLOAT", {"default": 0, "min": 0, "max":20, "step": 0.05}),
+ },
+ "optional": {
+ "color1_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ "color2_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("IMAGE", "show_help", )
+ FUNCTION = "draw"
+ CATEGORY = icons.get("Comfyroll/Graphics/Pattern")
+
+ def draw(self, mode, width, height, color_1, color_2,
+ orientation, bar_frequency, offset=0,
+ color1_hex='#000000', color2_hex='#000000'):
+
+ # Get RGB values
+ if color_1 == "custom":
+ color1_rgb = hex_to_rgb(color1_hex)
+ else:
+ color1_rgb = color_mapping.get(color_1, (255, 255, 255)) # Default to white if the color is not found
+
+ if color_2 == "custom":
+ color2_rgb = hex_to_rgb(color2_hex)
+ else:
+ color2_rgb = color_mapping.get(color_2, (0, 0, 0)) # Default to black if the color is not found
+
+ canvas = np.zeros((height, width, 3), dtype=np.uint8)
+
+ bar_width = width / bar_frequency
+ bar_height = height / bar_frequency
+ offset_pixels = int(offset * max(width, height))
+
+ if orientation == "vertical":
+ for j in range(height):
+ for i in range(width):
+ if ((i + offset_pixels) // bar_width) % 2 == 0: # Check for even index
+ canvas[j, i] = color1_rgb
+ else:
+ canvas[j, i] = color2_rgb
+ elif orientation == "horizontal":
+ for j in range(height):
+ for i in range(width):
+ if ((j + offset_pixels) // bar_height) % 2 == 0: # Check for even index
+ canvas[j, i] = color1_rgb
+ else:
+ canvas[j, i] = color2_rgb
+ elif orientation == "diagonal":
+ # Calculate the bar width based on a 45 degree angle
+ bar_width = int(bar_height / np.tan(np.pi / 4)) * 2
+ for j in range(height):
+ for i in range(width):
+ # Calculate which diagonal bar the pixel belongs to with the offset
+ bar_number = (i + j + offset_pixels) // bar_width
+ if bar_number % 2 == 0: # Check for even bar number
+ canvas[j, i] = color1_rgb
+ else:
+ canvas[j, i] = color2_rgb
+ elif orientation == "alt_diagonal":
+ bar_width = int(bar_height / np.tan(np.pi / 4)) * 2
+ for j in range(height):
+ for i in range(width):
+ # Calculate which diagonal bar the pixel belongs to with the offset
+ bar_number = (i - j + width + offset_pixels) // bar_width
+ if bar_number % 2 == 0: # Check for even bar number
+ canvas[j, i] = color1_rgb
+ else:
+ canvas[j, i] = color2_rgb
+
+ fig, ax = plt.subplots(figsize=(width/100, height/100))
+
+ ax.imshow(canvas)
+
+ plt.axis('off')
+ plt.tight_layout(pad=0, w_pad=0, h_pad=0)
+ plt.autoscale(tight=True)
+
+ img_buf = io.BytesIO()
+ plt.savefig(img_buf, format='png')
+ img = Image.open(img_buf)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Pattern-Nodes#cr-color-bars"
+
+ return (pil2tensor(img), show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_StyleBars:
+ @classmethod
+ def INPUT_TYPES(s):
+
+ modes = ["color bars", "sin wave", "gradient bars"]
+
+ return {"required": {
+ "mode": (modes,),
+ "width": ("INT", {"default": 512, "min": 64, "max": 4096}),
+ "height": ("INT", {"default": 512, "min": 64, "max": 4096}),
+ "bar_style": (STYLES,),
+ "orientation": (["vertical", "horizontal", ],),
+ "bar_frequency": ("INT", {"default": 5, "min": 1, "max":200, "step": 1}),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("IMAGE", "show_help", )
+ FUNCTION = "draw"
+ CATEGORY = icons.get("Comfyroll/Graphics/Pattern")
+
+ def draw(self, mode, width, height, bar_style, orientation, bar_frequency):
+
+ # Create a horizontal or vertical bar depending on the orientation
+ if orientation == "vertical":
+ x = np.linspace(0, 1, width)
+ y = np.zeros((height, width))
+ elif orientation == "horizontal":
+ x = np.zeros((height, width))
+ y = np.linspace(0, 1, height)
+
+ # Create a grid of colors for the bar
+ X, Y = np.meshgrid(x, y)
+
+ if mode == "color bars":
+ bar_width = 1 / bar_frequency
+ if orientation == "vertical":
+ colors = (X // bar_width) % 2
+ elif orientation == "horizontal":
+ colors = (Y // bar_width) % 2
+ elif mode == "sin wave":
+ if orientation == "vertical":
+ colors = np.sin(2 * np.pi * bar_frequency * X)
+ elif orientation == "horizontal":
+ colors = np.sin(2 * np.pi * bar_frequency * Y)
+ elif mode == "gradient bars":
+ if orientation == "vertical":
+ colors = (X * bar_frequency * 2) % 2
+ elif orientation == "horizontal":
+ colors = (Y * bar_frequency * 2) % 2
+
+ fig, ax = plt.subplots(figsize=(width/100, height/100))
+
+ ax.imshow(colors, cmap=bar_style, aspect='auto')
+
+ plt.axis('off')
+ plt.tight_layout(pad=0, w_pad=0, h_pad=0)
+ plt.autoscale(tight=True)
+
+ img_buf = io.BytesIO()
+ plt.savefig(img_buf, format='png')
+ img = Image.open(img_buf)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Pattern-Nodes#cr-style-bars"
+
+ return (pil2tensor(img), show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ColorGradient:
+ @classmethod
+ def INPUT_TYPES(s):
+
+ return {"required": {
+ "width": ("INT", {"default": 512, "min": 64, "max": 4096}),
+ "height": ("INT", {"default": 512, "min": 64, "max": 4096}),
+ "start_color": (COLORS,),
+ "end_color": (COLORS,),
+ "gradient_distance": ("FLOAT", {"default": 1, "min": 0, "max": 2, "step": 0.05}),
+ "linear_transition": ("FLOAT", {"default": 0.5, "min": 0, "max": 1, "step": 0.05}),
+ "orientation": (["vertical", "horizontal", ],),
+ },
+ "optional": {
+ "start_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ "end_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("IMAGE", "show_help", )
+ FUNCTION = "draw"
+ CATEGORY = icons.get("Comfyroll/Graphics/Pattern")
+
+ def draw(self, width, height, start_color, end_color, orientation,
+ linear_transition=0.5, gradient_distance=1,
+ start_color_hex='#000000', end_color_hex='#000000'): # Default to .5 if the value is not found
+
+ # Get RGB values
+ if start_color == "custom":
+ color1_rgb = hex_to_rgb(start_color_hex)
+ else:
+ color1_rgb = color_mapping.get(start_color, (255, 255, 255)) # Default to white if the color is not found
+
+ if end_color == "custom":
+ color2_rgb = hex_to_rgb(end_color_hex)
+ else:
+ color2_rgb = color_mapping.get(end_color, (0, 0, 0)) # Default to black if the color is not found
+
+ # Create a blank canvas
+ canvas = np.zeros((height, width, 3), dtype=np.uint8)
+ transition_pixel = int(linear_transition * (width if orientation == 'horizontal' else height)) #getting center point for gradient
+
+ def get_gradient_value(pos, length, linear_transition, gradient_distance): #getting the distance we use to apply gradient
+ # Calculate the start and end of the transition
+ transition_length = length * gradient_distance
+ transition_start = linear_transition * length - transition_length / 2
+ transition_end = linear_transition * length + transition_length / 2
+
+ # Return the gradient value based on position
+ if pos < transition_start:
+ return 0
+ elif pos > transition_end:
+ return 1
+ else:
+ return (pos - transition_start) / transition_length
+
+ if orientation == 'horizontal':
+ # Define the x-values for interpolation
+ x = [0, width * linear_transition - 0.5 * width * gradient_distance, width * linear_transition + 0.5 * width * gradient_distance, width]
+ # Define the y-values for interpolation (t-values)
+ y = [0, 0, 1, 1]
+ # Interpolate
+ t_values = np.interp(np.arange(width), x, y)
+ for i, t in enumerate(t_values):
+ interpolated_color = [int(c1 * (1 - t) + c2 * t) for c1, c2 in zip(color1_rgb, color2_rgb)]
+ canvas[:, i] = interpolated_color
+
+ elif orientation == 'vertical':
+ # Define the x-values for interpolation
+ x = [0, height * linear_transition - 0.5 * height * gradient_distance, height * linear_transition + 0.5 * height * gradient_distance, height]
+ # Define the y-values for interpolation (t-values)
+ y = [0, 0, 1, 1]
+ # Interpolate
+ t_values = np.interp(np.arange(height), x, y)
+ for j, t in enumerate(t_values):
+ interpolated_color = [int(c1 * (1 - t) + c2 * t) for c1, c2 in zip(color1_rgb, color2_rgb)]
+ canvas[j, :] = interpolated_color
+
+ fig, ax = plt.subplots(figsize=(width / 100, height / 100))
+
+ ax.imshow(canvas)
+ plt.axis('off')
+ plt.tight_layout(pad=0, w_pad=0, h_pad=0)
+ plt.autoscale(tight=True)
+
+ img_buf = io.BytesIO()
+ plt.savefig(img_buf, format='png')
+ img = Image.open(img_buf)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Pattern-Nodes#cr-color-gradient"
+
+ return (pil2tensor(img), show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_RadialGradient:
+ @classmethod
+ def INPUT_TYPES(s):
+
+ return {"required": {
+ "width": ("INT", {"default": 512, "min": 64, "max": 4096}),
+ "height": ("INT", {"default": 512, "min": 64, "max": 4096}),
+ "start_color": (COLORS,),
+ "end_color": (COLORS,),
+ "gradient_distance": ("FLOAT", {"default": 1, "min": 0, "max": 2, "step": 0.05}),
+ "radial_center_x": ("FLOAT", {"default": 0.5, "min": 0, "max": 1, "step": 0.05}),
+ "radial_center_y": ("FLOAT", {"default": 0.5, "min": 0, "max": 1, "step": 0.05}),
+ },
+ "optional": {
+ "start_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ "end_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("IMAGE", "show_Help", )
+ FUNCTION = "draw"
+ CATEGORY = icons.get("Comfyroll/Graphics/Pattern")
+
+ def draw(self, width, height, start_color, end_color,
+ radial_center_x=0.5, radial_center_y=0.5, gradient_distance=1,
+ start_color_hex='#000000', end_color_hex='#000000'): # Default to .5 if the value is not found
+
+ # Get RGB values
+ if start_color == "custom":
+ color1_rgb = hex_to_rgb(start_color_hex)
+ else:
+ color1_rgb = color_mapping.get(start_color, (255, 255, 255)) # Default to white if the color is not found
+
+ if end_color == "custom":
+ color2_rgb = hex_to_rgb(end_color_hex)
+ else:
+ color2_rgb = color_mapping.get(end_color, (0, 0, 0)) # Default to black if the color is not found
+
+ # Create a blank canvas
+ canvas = np.zeros((height, width, 3), dtype=np.uint8)
+
+ center_x = int(radial_center_x * width)
+ center_y = int(radial_center_y * height)
+ # Computation for max_distance
+ max_distance = (np.sqrt(max(center_x, width - center_x)**2 + max(center_y, height - center_y)**2))*gradient_distance
+
+ for i in range(width):
+ for j in range(height):
+ distance_to_center = np.sqrt((i - center_x) ** 2 + (j - center_y) ** 2)
+ t = distance_to_center / max_distance
+ # Ensure t is between 0 and 1
+ t = max(0, min(t, 1))
+ interpolated_color = [int(c1 * (1 - t) + c2 * t) for c1, c2 in zip(color1_rgb, color2_rgb)]
+ canvas[j, i] = interpolated_color
+
+ fig, ax = plt.subplots(figsize=(width / 100, height / 100))
+
+ ax.imshow(canvas)
+ plt.axis('off')
+ plt.tight_layout(pad=0, w_pad=0, h_pad=0)
+ plt.autoscale(tight=True)
+
+ img_buf = io.BytesIO()
+ plt.savefig(img_buf, format='png')
+ img = Image.open(img_buf)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Pattern-Nodes#cr-radial-gradiant"
+
+ return (pil2tensor(img), show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_CheckerPattern:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ modes = ["regular", "stepped"]
+
+ return {"required": {
+ "mode": (modes,),
+ "width": ("INT", {"default": 512, "min": 64, "max": 4096}),
+ "height": ("INT", {"default": 512, "min": 64, "max": 4096}),
+ "color_1": (COLORS,),
+ "color_2": (COLORS,),
+ "grid_frequency": ("INT", {"default": 8, "min": 1, "max": 200, "step": 1}),
+ "step": ("INT", {"default": 2, "min": 2, "max": 200, "step": 1}),
+ },
+ "optional": {
+ "color1_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ "color2_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("IMAGE", "show_help", )
+ FUNCTION = "draw"
+ CATEGORY = icons.get("Comfyroll/Graphics/Pattern")
+
+ def draw(self, mode, width, height, color_1, color_2,
+ grid_frequency, step,
+ color1_hex='#000000', color2_hex='#000000'):
+
+ # Get RGB values
+ if color_1 == "custom":
+ color1_rgb = hex_to_rgb(color1_hex)
+ else:
+ color1_rgb = color_mapping.get(color_1, (255, 255, 255)) # Default to white if the color is not found
+
+ if color_2 == "custom":
+ color2_rgb = hex_to_rgb(color2_hex)
+ else:
+ color2_rgb = color_mapping.get(color_2, (0, 0, 0)) # Default to black if the color is not found
+
+ # Create a blank canvas
+ canvas = np.zeros((height, width, 3), dtype=np.uint8)
+
+ grid_size = width / grid_frequency
+
+ for i in range(width):
+ for j in range(height):
+
+ if mode == "regular":
+ if (i // grid_size) % 2 == (j // grid_size) % 2:
+ canvas[j, i] = color1_rgb
+ else:
+ canvas[j, i] = color2_rgb
+ elif mode == "stepped":
+ if (i // grid_size) % step != (j // grid_size) % step:
+ canvas[j, i] = color1_rgb
+ else:
+ canvas[j, i] = color2_rgb
+
+ fig, ax = plt.subplots(figsize=(width/100, height/100))
+
+ ax.imshow(canvas)
+
+ plt.axis('off')
+ plt.tight_layout(pad=0, w_pad=0, h_pad=0)
+ plt.autoscale(tight=True)
+
+ img_buf = io.BytesIO()
+ plt.savefig(img_buf, format='png')
+ img = Image.open(img_buf)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Pattern-Nodes#cr-checker-pattern"
+
+ return (pil2tensor(img), show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_Polygons:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ modes = ["hexagons", "triangles"]
+
+ return {"required": {
+ "mode": (modes,),
+ "width": ("INT", {"default": 512, "min": 64, "max": 4096}),
+ "height": ("INT", {"default": 512, "min": 64, "max": 4096}),
+ "rows": ("INT", {"default": 5, "min": 1, "max": 512}),
+ "columns": ("INT", {"default": 5, "min": 1, "max": 512}),
+ "face_color": (COLORS,),
+ "background_color": (COLORS,),
+ "line_color": (COLORS,),
+ "line_width": ("INT", {"default": 2, "min": 0, "max": 512}),
+ },
+ "optional": {
+ "face_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ "bg_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ "line_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("IMAGE", "show_help", )
+ FUNCTION = "draw"
+ CATEGORY = icons.get("Comfyroll/Graphics/Pattern")
+
+ def draw(self, mode, width, height, rows, columns,
+ face_color, background_color, line_color, line_width,
+ face_color_hex='#000000', bg_color_hex='#000000', line_color_hex='#000000'):
+
+ # Get RGB values
+ if face_color == "custom":
+ face_color = face_color_hex
+
+ if line_color == "custom":
+ line_color = line_color_hex
+
+ if background_color == "custom":
+ background_color = bg_color_hex
+
+ fig, ax = plt.subplots(figsize=(width/100, height/100))
+ fig.set_facecolor(background_color)
+ plt.xlim(0, width/100)
+ plt.ylim(0, height/100)
+ plt.axis('off')
+ plt.tight_layout(pad=0, w_pad=0, h_pad=0)
+ plt.autoscale(False)
+
+ # Get polygon shape
+ if mode == "hexagons":
+ vertices = 6
+ elif mode == "triangles":
+ vertices = 3
+
+ # Define the height and width of a hexagon
+ cell_width = (width/100) / columns
+
+ cell_height = (width/height) * np.sqrt(3) * (height/100) / (2 * columns)
+
+ for row in range(rows + 2):
+ for col in range(columns + 2):
+ x = col * cell_width
+ y = row * cell_height
+
+ # Shift every other row
+ if row % 2 == 1:
+ x += cell_width / 2
+
+ # Create a hexagon as a polygon patch
+ hexagon = RegularPolygon((x, y), numVertices=vertices, radius=cell_width/1.732, edgecolor=line_color, linewidth=line_width, facecolor=face_color)
+ ax.add_patch(hexagon)
+
+ img_buf = io.BytesIO()
+ plt.savefig(img_buf, format='png')
+ img = Image.open(img_buf)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Pattern-Nodes#cr-polygons"
+
+ return (pil2tensor(img), show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_StarburstLines:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ return {"required": {
+ "width": ("INT", {"default": 512, "min": 64, "max": 4096}),
+ "height": ("INT", {"default": 512, "min": 64, "max": 4096}),
+ "num_lines": ("INT", {"default": 6, "min": 1, "max": 500}),
+ "line_length": ("FLOAT", {"default": 5, "min": 0, "max": 100, "step": 0.1}),
+ "line_width": ("INT", {"default": 5, "min": 1, "max": 512}),
+ "line_color": (COLORS,),
+ "background_color": (COLORS,),
+ "center_x": ("INT", {"default": 0, "min": 0, "max": 1024}),
+ "center_y": ("INT", {"default": 0, "min": 0, "max": 1024}),
+ "rotation": ("FLOAT", {"default": 0, "min": 0, "max": 720}),
+ },
+ "optional": {
+ "line_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ "bg_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("IMAGE", "show_help", )
+ FUNCTION = "draw"
+ CATEGORY = icons.get("Comfyroll/Graphics/Pattern")
+
+ def draw(self, width, height, num_lines, line_length, line_width, line_color, background_color,
+ center_x, center_y, rotation=0,
+ line_color_hex='#000000', bg_color_hex='#000000'):
+
+ if line_color == "custom":
+ line_color = line_color_hex
+ else:
+ line_color = line_color
+
+ if background_color == "custom":
+ bgc = bg_color_hex
+ else:
+ bgc = background_color
+
+ # Define the angle for the spokes in the starburst
+ angle = 360 / num_lines
+
+ # Set up the plot
+ fig, ax = plt.subplots(figsize=(width/100,height/100))
+ plt.xlim(-width/100, width/100)
+ plt.ylim(-height/100, height/100)
+ plt.axis('off')
+ plt.tight_layout(pad=0, w_pad=0, h_pad=0)
+ plt.autoscale(False)
+
+ # Coordinates of the central point
+ center_x = center_x/100
+ center_y = center_y/100
+
+ # Draw the starburst lines
+ for i in range(num_lines):
+ # Calculate the endpoint of each line
+ x_unrotated = center_x + line_length * np.cos(np.radians(i * angle))
+ y_unrotated = center_y + line_length * np.sin(np.radians(i * angle))
+
+ # Apply rotation transformation
+ x = center_x + x_unrotated * np.cos(np.radians(rotation)) - y_unrotated * np.sin(np.radians(rotation))
+ y = center_y + x_unrotated * np.sin(np.radians(rotation)) + y_unrotated * np.cos(np.radians(rotation))
+
+ # Plot the line
+ fig.patch.set_facecolor(bgc)
+ ax.plot([center_x, x], [center_y, y], color=line_color, linewidth=line_width)
+
+ img_buf = io.BytesIO()
+ plt.savefig(img_buf, format='png')
+ img = Image.open(img_buf)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Pattern-Nodes#cr-starburst-lines"
+
+ return (pil2tensor(img), show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_StarburstColors:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ return {"required": {
+ "width": ("INT", {"default": 512, "min": 64, "max": 4096}),
+ "height": ("INT", {"default": 512, "min": 64, "max": 4096}),
+ "num_triangles": ("INT", {"default": 6, "min": 1, "max": 512}),
+ "color_1": (COLORS,),
+ "color_2": (COLORS,),
+ "center_x": ("INT", {"default": 0, "min": 0, "max": 512}),
+ "center_y": ("INT", {"default": 0, "min": 0, "max": 512}),
+ "rotation": ("FLOAT", {"default": 0, "min": 0, "max": 720}),
+ "bbox_factor": ("FLOAT", {"default": 2, "min": 0, "max": 2, "step": .01}),
+ },
+ "optional": {
+ "color1_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ "color2_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("IMAGE", "show_help", )
+ FUNCTION = "draw"
+ CATEGORY = icons.get("Comfyroll/Graphics/Pattern")
+
+ def draw(self, width, height, num_triangles, color_1, color_2,
+ center_x, center_y, bbox_factor, rotation=0,
+ color1_hex='#000000', color2_hex='#000000'):
+
+ # Get RGB values for the text color
+ if color_1 == "custom":
+ color_1 = color1_hex
+ else:
+ color_1 = color_1
+
+ if color_2 == "custom":
+ color_2 = color2_hex
+ else:
+ color_2 = color_2
+
+ # Set up the plot
+ fig, ax = plt.subplots()
+
+ x = width/100
+ y = height/100
+
+ fig, ax = plt.subplots(figsize=(x,y))
+ plt.xlim(-x/2, x/2)
+ plt.ylim(-y/2, y/2)
+
+ plt.axis('off')
+ plt.tight_layout(pad=0, w_pad=0, h_pad=0)
+ plt.autoscale(False)
+
+ # Set the size of the starburst bounding box in x and y dimensions
+ box_width = bbox_factor * x
+ box_height = bbox_factor * y
+
+ # Initialize a color list for alternating colors
+ colors = [color_1, color_2]
+
+ tri = num_triangles
+
+ # Draw the starburst triangles with alternating colors and square pattern
+ for i in range(tri):
+ # Calculate the endpoints of the triangle with varying length
+ x1 = center_x/100
+ y1 = center_y/100
+ x2_unrotated = (box_width / 2) * np.cos(np.radians(i * 360 / tri))
+ y2_unrotated = (box_height / 2) * np.sin(np.radians(i * 360 / tri))
+ x3_unrotated = (box_width / 2) * np.cos(np.radians((i + 1) * 360 / tri))
+ y3_unrotated = (box_height / 2) * np.sin(np.radians((i + 1) * 360 / tri))
+
+ #apply rotation transform
+ x2 = x2_unrotated * np.cos(np.radians(rotation)) - y2_unrotated * np.sin(np.radians(rotation))
+ y2 = x2_unrotated * np.sin(np.radians(rotation)) + y2_unrotated * np.cos(np.radians(rotation))
+ x3 = x3_unrotated * np.cos(np.radians(rotation)) - y3_unrotated * np.sin(np.radians(rotation))
+ y3 = x3_unrotated * np.sin(np.radians(rotation)) + y3_unrotated * np.cos(np.radians(rotation))
+
+ # Plot the triangle with alternating colors
+ ax.fill([x1, x2, x3, x1], [y1, y2, y3, y1], color=colors[i % 2])
+
+ img_buf = io.BytesIO()
+ plt.savefig(img_buf, format='png')
+ img = Image.open(img_buf)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Pattern-Nodes#cr-starburst-colors"
+
+ return (pil2tensor(img), show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+'''
+NODE_CLASS_MAPPINGS = {
+ "CR Color Bars": CR_ColorBars,
+ "CR Style Bars": CR_StyleBars,
+ "CR Checker Pattern": CR_CheckerPattern,
+ "CR Polygons":CR_Polygons,
+ "CR Halftone Grid": CR_HalftoneGrid,
+ "CR Color Gradient": CR_ColorGradient,
+ "CR Radial Gradient": CR_RadialGradient,
+ "CR Overlay Text": CR_OverlayText,
+ "CR Starburst Lines": CR_StarburstLines,
+ "CR Starburst Colors": CR_StarburstColors,
+}
+'''
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/model_merge.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/model_merge.py
new file mode 100644
index 0000000000000000000000000000000000000000..585bdbbdff43754dfe45f82c39fcd34729f98059
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/model_merge.py
@@ -0,0 +1,176 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# Comfyroll Custom Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+
+import comfy.sd
+import comfy.model_management
+import folder_paths
+from ..categories import icons
+
+#---------------------------------------------------------------------------------------------------------------------#
+# Model Merge Nodes
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ModelMergeStack:
+
+ @classmethod
+ def INPUT_TYPES(cls):
+
+ checkpoint_files = ["None"] + folder_paths.get_filename_list("checkpoints")
+
+ return {"required": {"switch_1": (["Off","On"],),
+ "ckpt_name1": (checkpoint_files,),
+ "model_ratio1": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01}),
+ "clip_ratio1": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01}),
+ #
+ "switch_2": (["Off","On"],),
+ "ckpt_name2": (checkpoint_files,),
+ "model_ratio2": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01}),
+ "clip_ratio2": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01}),
+ #
+ "switch_3": (["Off","On"],),
+ "ckpt_name3": (checkpoint_files,),
+ "model_ratio3": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01}),
+ "clip_ratio3": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01}),
+ },
+ "optional":{
+ "model_stack": ("MODEL_STACK",),
+ },
+ }
+
+ RETURN_TYPES = ("MODEL_STACK", "STRING", )
+ RETURN_NAMES = ("MODEL_STACK", "show_help", )
+ FUNCTION = "list_checkpoints"
+ CATEGORY = icons.get("Comfyroll/Model Merge")
+
+ def list_checkpoints(self, switch_1, ckpt_name1, model_ratio1, clip_ratio1, switch_2, ckpt_name2, model_ratio2, clip_ratio2, switch_3, ckpt_name3, model_ratio3, clip_ratio3, model_stack=None):
+
+ # Initialise the list
+ model_list = list()
+
+ if model_stack is not None:
+ model_list.extend([l for l in model_stack if l[0] != "None"])
+
+ if ckpt_name1 != "None" and switch_1 == "On":
+ model_list.extend([(ckpt_name1, model_ratio1, clip_ratio1)]),
+
+ if ckpt_name2 != "None" and switch_2 == "On":
+ model_list.extend([(ckpt_name2, model_ratio2, clip_ratio2)]),
+
+ if ckpt_name3 != "None" and switch_3 == "On":
+ model_list.extend([(ckpt_name3, model_ratio3, clip_ratio3)]),
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Model-Merge-Nodes#cr-model-stack"
+
+ return (model_list, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ApplyModelMerge:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ merge_methods = ["Recursive", "Weighted"]
+
+ return {"required": {"model_stack": ("MODEL_STACK",),
+ "merge_method": (merge_methods,),
+ "normalise_ratios": (["Yes","No"],),
+ "weight_factor":("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
+ }
+ }
+
+ RETURN_TYPES = ("MODEL", "CLIP", "STRING", "STRING", )
+ RETURN_NAMES = ("MODEL", "CLIP", "model_mix_info", "show_help", )
+ FUNCTION = "merge"
+ CATEGORY = icons.get("Comfyroll/Model Merge")
+
+ def merge(self, model_stack, merge_method, normalise_ratios, weight_factor):
+
+ # Initialise
+ sum_clip_ratio = 0
+ sum_model_ratio = 0
+ model_mix_info = str("Merge Info:\n")
+
+ # If no models
+ if len(model_stack) == 0:
+ print(f"[Warning] Apply Model Merge: No active models selected in the model merge stack")
+ return()
+
+ # If only one model
+ if len(model_stack) == 1:
+ print(f"[Warning] Apply Model Merge: Only one active model found in the model merge stack. At least 2 models are normally needed for merging. The active model will be output.")
+ model_name, model_ratio, clip_ratio = model_stack[0]
+ ckpt_path = folder_paths.get_full_path("checkpoints", model_name)
+ return comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, embedding_directory=folder_paths.get_folder_paths("embeddings"))
+
+ # Calculate ratio sums for normalisation
+ for i, model_tuple in enumerate(model_stack):
+ model_name, model_ratio, clip_ratio = model_tuple
+ sum_model_ratio += model_ratio
+ sum_clip_ratio += clip_ratio
+
+ # Do recursive merge loops
+ model_mix_info = model_mix_info + "Ratios are applied using the Recursive method\n\n"
+
+ # Loop through the models and compile the merged model
+ for i, model_tuple in enumerate(model_stack):
+ model_name, model_ratio, clip_ratio = model_tuple
+ ckpt_path = folder_paths.get_full_path("checkpoints", model_name)
+ merge_model = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, embedding_directory=folder_paths.get_folder_paths("embeddings"))
+ print(f"Apply Model Merge: Model Name {model_name}, Model Ratio {model_ratio}, CLIP Ratio {clip_ratio}")
+
+ if sum_model_ratio != 1 and normalise_ratios == "Yes":
+ print(f"[Warning] Apply Model Merge: Sum of model ratios != 1. Ratios will be normalised")
+ # Normalise the ratios
+ model_ratio = round(model_ratio / sum_model_ratio, 2)
+ clip_ratio = round(clip_ratio / sum_clip_ratio, 2)
+
+ # Weighted merge method
+ if merge_method == "Weighted":
+ if i == 1:
+ # Reassign extra weight to the second model
+ model_ratio = 1 - weight_factor + (weight_factor * model_ratio)
+ clip_ratio = 1 - weight_factor + (weight_factor * clip_ratio)
+
+ #Clone the first model
+ if i == 0:
+ model1 = merge_model[0].clone()
+ clip1 = merge_model[1].clone()
+
+ model_mix_info = model_mix_info + "Base Model Name: " + model_name
+ else:
+ # Merge next model
+ # Comfy merge logic is flipped for stacked nodes. This is because the first model is effectively model1 and all subsequent models are model2.
+ model2 = merge_model[0].clone()
+ kp = model2.get_key_patches("diffusion_model.")
+ for k in kp:
+ #model1.add_patches({k: kp[k]}, 1.0 - model_ratio, model_ratio) #original logic
+ model1.add_patches({k: kp[k]}, model_ratio, 1.0 - model_ratio) #flipped logic
+ # Merge next clip
+ clip2 = merge_model[1].clone()
+ kp = clip2.get_key_patches()
+ for k in kp:
+ if k.endswith(".position_ids") or k.endswith(".logit_scale"):
+ continue
+ #clip1.add_patches({k: kp[k]}, 1.0 - clip_ratio, clip_ratio) #original logic
+ clip1.add_patches({k: kp[k]}, clip_ratio, 1.0 - clip_ratio) #flipped logic
+
+ # Update model info
+ model_mix_info = model_mix_info + "\nModel Name: " + model_name + "\nModel Ratio: " + str(model_ratio) + "\nCLIP Ratio: " + str(clip_ratio) + "\n"
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Model-Merge-Nodes#cr-apply-model-merge"
+
+ return (model1, clip1, model_mix_info, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+'''
+NODE_CLASS_MAPPINGS = {
+ "CR Apply Model Merge": CR_ApplyModelMerge,
+ "CR Model Merge Stack": CR_ModelMergeStack,
+}
+'''
+
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/nodes.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/nodes.py
new file mode 100644
index 0000000000000000000000000000000000000000..b47e5b838fdc6efa8b0ed610309257944f4dc0bd
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/nodes.py
@@ -0,0 +1,623 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# Comfyroll Custom Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+
+import torch
+import numpy as np
+import os
+import sys
+import io
+import comfy.sd
+from PIL import Image
+from PIL.PngImagePlugin import PngInfo
+import json
+import folder_paths
+import typing as tg
+import random
+from .graphics_functions import random_hex_color, random_rgb
+from ..categories import icons
+
+sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), "comfy"))
+
+#---------------------------------------------------------------------------------------------------------------------#
+# Aspect Ratio Nodes
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_AspectRatioSD15:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ aspect_ratios = ["custom",
+ "1:1 square 512x512",
+ "1:1 square 1024x1024",
+ "2:3 portrait 512x768",
+ "3:4 portrait 512x682",
+ "3:2 landscape 768x512",
+ "4:3 landscape 682x512",
+ "16:9 cinema 910x512",
+ "1.85:1 cinema 952x512",
+ "2:1 cinema 1024x512",
+ "2.39:1 anamorphic 1224x512"]
+
+ return {
+ "required": {
+ "width": ("INT", {"default": 512, "min": 64, "max": 8192}),
+ "height": ("INT", {"default": 512, "min": 64, "max": 8192}),
+ "aspect_ratio": (aspect_ratios,),
+ "swap_dimensions": (["Off", "On"],),
+ "upscale_factor": ("FLOAT", {"default": 1.0, "min": 0.1, "max": 100.0, "step":0.1}),
+ "batch_size": ("INT", {"default": 1, "min": 1, "max": 64})
+ }
+ }
+ RETURN_TYPES = ("INT", "INT", "FLOAT", "INT", "LATENT", "STRING", )
+ RETURN_NAMES = ("width", "height", "upscale_factor", "batch_size", "empty_latent", "show_help", )
+ FUNCTION = "Aspect_Ratio"
+ CATEGORY = icons.get("Comfyroll/Aspect Ratio")
+
+ def Aspect_Ratio(self, width, height, aspect_ratio, swap_dimensions, upscale_factor, batch_size):
+ if aspect_ratio == "2:3 portrait 512x768":
+ width, height = 512, 768
+ elif aspect_ratio == "3:2 landscape 768x512":
+ width, height = 768, 512
+ elif aspect_ratio == "1:1 square 512x512":
+ width, height = 512, 512
+ elif aspect_ratio == "1:1 square 1024x1024":
+ width, height = 1024, 1024
+ elif aspect_ratio == "16:9 cinema 910x512":
+ width, height = 910, 512
+ elif aspect_ratio == "3:4 portrait 512x682":
+ width, height = 512, 682
+ elif aspect_ratio == "4:3 landscape 682x512":
+ width, height = 682, 512
+ elif aspect_ratio == "1.85:1 cinema 952x512":
+ width, height = 952, 512
+ elif aspect_ratio == "2:1 cinema 1024x512":
+ width, height = 1024, 512
+ elif aspect_ratio == "2.39:1 anamorphic 1224x512":
+ width, height = 1224, 512
+
+ if swap_dimensions == "On":
+ width, height = height, width
+
+ latent = torch.zeros([batch_size, 4, height // 8, width // 8])
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Aspect-Ratio-Nodes#cr-sd15-aspect-ratio"
+
+ return(width, height, upscale_factor, batch_size, {"samples":latent}, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_SDXLAspectRatio:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ aspect_ratios = ["custom",
+ "1:1 square 1024x1024",
+ "3:4 portrait 896x1152",
+ "5:8 portrait 832x1216",
+ "9:16 portrait 768x1344",
+ "9:21 portrait 640x1536",
+ "4:3 landscape 1152x896",
+ "3:2 landscape 1216x832",
+ "16:9 landscape 1344x768",
+ "21:9 landscape 1536x640"]
+
+ return {
+ "required": {
+ "width": ("INT", {"default": 1024, "min": 64, "max": 8192}),
+ "height": ("INT", {"default": 1024, "min": 64, "max": 8192}),
+ "aspect_ratio": (aspect_ratios,),
+ "swap_dimensions": (["Off", "On"],),
+ "upscale_factor": ("FLOAT", {"default": 1.0, "min": 0.1, "max": 100.0, "step":0.1}),
+ "batch_size": ("INT", {"default": 1, "min": 1, "max": 64})
+ }
+ }
+ RETURN_TYPES = ("INT", "INT", "FLOAT", "INT", "LATENT", "STRING", )
+ RETURN_NAMES = ("width", "height", "upscale_factor", "batch_size", "empty_latent", "show_help", )
+ FUNCTION = "Aspect_Ratio"
+ CATEGORY = icons.get("Comfyroll/Aspect Ratio")
+
+ def Aspect_Ratio(self, width, height, aspect_ratio, swap_dimensions, upscale_factor, batch_size):
+ if aspect_ratio == "1:1 square 1024x1024":
+ width, height = 1024, 1024
+ elif aspect_ratio == "3:4 portrait 896x1152":
+ width, height = 896, 1152
+ elif aspect_ratio == "5:8 portrait 832x1216":
+ width, height = 832, 1216
+ elif aspect_ratio == "9:16 portrait 768x1344":
+ width, height = 768, 1344
+ elif aspect_ratio == "9:21 portrait 640x1536":
+ width, height = 640, 1536
+ elif aspect_ratio == "4:3 landscape 1152x896":
+ width, height = 1152, 896
+ elif aspect_ratio == "3:2 landscape 1216x832":
+ width, height = 1216, 832
+ elif aspect_ratio == "16:9 landscape 1344x768":
+ width, height = 1344, 768
+ elif aspect_ratio == "21:9 landscape 1536x640":
+ width, height = 1536, 640
+
+ if swap_dimensions == "On":
+ width, height = height, width
+
+ latent = torch.zeros([batch_size, 4, height // 8, width // 8])
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Aspect-Ratio-Nodes#cr-sdxl-aspect-ratio"
+
+ return(width, height, upscale_factor, batch_size, {"samples":latent}, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_AspectRatio:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ aspect_ratios = ["custom",
+ "SD1.5 - 1:1 square 512x512",
+ "SD1.5 - 2:3 portrait 512x768",
+ "SD1.5 - 3:4 portrait 512x682",
+ "SD1.5 - 3:2 landscape 768x512",
+ "SD1.5 - 4:3 landscape 682x512",
+ "SD1.5 - 16:9 cinema 910x512",
+ "SD1.5 - 1.85:1 cinema 952x512",
+ "SD1.5 - 2:1 cinema 1024x512",
+ "SDXL - 1:1 square 1024x1024",
+ "SDXL - 3:4 portrait 896x1152",
+ "SDXL - 5:8 portrait 832x1216",
+ "SDXL - 9:16 portrait 768x1344",
+ "SDXL - 9:21 portrait 640x1536",
+ "SDXL - 4:3 landscape 1152x896",
+ "SDXL - 3:2 landscape 1216x832",
+ "SDXL - 16:9 landscape 1344x768",
+ "SDXL - 21:9 landscape 1536x640"]
+
+ return {
+ "required": {
+ "width": ("INT", {"default": 1024, "min": 64, "max": 8192}),
+ "height": ("INT", {"default": 1024, "min": 64, "max": 8192}),
+ "aspect_ratio": (aspect_ratios,),
+ "swap_dimensions": (["Off", "On"],),
+ "upscale_factor": ("FLOAT", {"default": 1.0, "min": 0.1, "max": 100.0, "step":0.1}),
+ "prescale_factor": ("FLOAT", {"default": 1.0, "min": 0.1, "max": 100.0, "step":0.1}),
+ "batch_size": ("INT", {"default": 1, "min": 1, "max": 64})
+ }
+ }
+ RETURN_TYPES = ("INT", "INT", "FLOAT", "FLOAT", "INT", "LATENT", "STRING", )
+ RETURN_NAMES = ("width", "height", "upscale_factor", "prescale_factor", "batch_size", "empty_latent", "show_help", )
+ FUNCTION = "Aspect_Ratio"
+ CATEGORY = icons.get("Comfyroll/Aspect Ratio")
+
+ def Aspect_Ratio(self, width, height, aspect_ratio, swap_dimensions, upscale_factor, prescale_factor, batch_size):
+
+ # SD1.5
+ if aspect_ratio == "SD1.5 - 1:1 square 512x512":
+ width, height = 512, 512
+ elif aspect_ratio == "SD1.5 - 2:3 portrait 512x768":
+ width, height = 512, 768
+ elif aspect_ratio == "SD1.5 - 16:9 cinema 910x512":
+ width, height = 910, 512
+ elif aspect_ratio == "SD1.5 - 3:4 portrait 512x682":
+ width, height = 512, 682
+ elif aspect_ratio == "SD1.5 - 3:2 landscape 768x512":
+ width, height = 768, 512
+ elif aspect_ratio == "SD1.5 - 4:3 landscape 682x512":
+ width, height = 682, 512
+ elif aspect_ratio == "SD1.5 - 1.85:1 cinema 952x512":
+ width, height = 952, 512
+ elif aspect_ratio == "SD1.5 - 2:1 cinema 1024x512":
+ width, height = 1024, 512
+ elif aspect_ratio == "SD1.5 - 2.39:1 anamorphic 1224x512":
+ width, height = 1224, 512
+ # SDXL
+ if aspect_ratio == "SDXL - 1:1 square 1024x1024":
+ width, height = 1024, 1024
+ elif aspect_ratio == "SDXL - 3:4 portrait 896x1152":
+ width, height = 896, 1152
+ elif aspect_ratio == "SDXL - 5:8 portrait 832x1216":
+ width, height = 832, 1216
+ elif aspect_ratio == "SDXL - 9:16 portrait 768x1344":
+ width, height = 768, 1344
+ elif aspect_ratio == "SDXL - 9:21 portrait 640x1536":
+ width, height = 640, 1536
+ elif aspect_ratio == "SDXL - 4:3 landscape 1152x896":
+ width, height = 1152, 896
+ elif aspect_ratio == "SDXL - 3:2 landscape 1216x832":
+ width, height = 1216, 832
+ elif aspect_ratio == "SDXL - 16:9 landscape 1344x768":
+ width, height = 1344, 768
+ elif aspect_ratio == "SDXL - 21:9 landscape 1536x640":
+ width, height = 1536, 640
+
+ if swap_dimensions == "On":
+ width, height = height, width
+
+ width = int(width*prescale_factor)
+ height = int(height*prescale_factor)
+
+ latent = torch.zeros([batch_size, 4, height // 8, width // 8])
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Aspect-Ratio-Nodes#cr-aspect-ratio"
+
+ return(width, height, upscale_factor, prescale_factor, batch_size, {"samples":latent}, show_help, )
+#---------------------------------------------------------------------------------------------------------------------#
+# Other Nodes
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ImageOutput:
+ def __init__(self):
+ self.output_dir = folder_paths.get_output_directory()
+ self.type = "output"
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required":
+ {"images": ("IMAGE", ),
+ "output_type": (["Preview", "Save"],),
+ "filename_prefix": ("STRING", {"default": "ComfyUI"})},
+ "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
+ "optional": {
+ "trigger": ("BOOLEAN", {"default": False},),}
+ }
+
+ RETURN_TYPES = ("BOOLEAN", )
+ RETURN_NAMES = ("trigger", )
+ FUNCTION = "save_images"
+ OUTPUT_NODE = True
+ CATEGORY = icons.get("Comfyroll/Other")
+
+ def save_images(self, images, filename_prefix="ComfyUI", trigger = False, output_type = "Preview", prompt=None, extra_pnginfo=None):
+ def map_filename(filename):
+ prefix_len = len(os.path.basename(filename_prefix))
+ prefix = filename[:prefix_len + 1]
+ try:
+ digits = int(filename[prefix_len + 1:].split('_')[0])
+ except:
+ digits = 0
+ return (digits, prefix)
+
+ def compute_vars(input):
+ input = input.replace("%width%", str(images[0].shape[1]))
+ input = input.replace("%height%", str(images[0].shape[0]))
+ return input
+
+ if output_type == "Save":
+ self.output_dir = folder_paths.get_output_directory()
+ self.type = "output"
+ elif output_type == "Preview":
+ self.output_dir = folder_paths.get_temp_directory()
+ self.type = "temp"
+
+ filename_prefix = compute_vars(filename_prefix)
+
+ subfolder = os.path.dirname(os.path.normpath(filename_prefix))
+ filename = os.path.basename(os.path.normpath(filename_prefix))
+
+ full_output_folder = os.path.join(self.output_dir, subfolder)
+
+ if os.path.commonpath((self.output_dir, os.path.abspath(full_output_folder))) != self.output_dir:
+ return {}
+
+ try:
+ counter = max(filter(lambda a: a[1][:-1] == filename and a[1][-1] == "_", map(map_filename, os.listdir(full_output_folder))))[0] + 1
+ except ValueError:
+ counter = 1
+ except FileNotFoundError:
+ os.makedirs(full_output_folder, exist_ok=True)
+ counter = 1
+
+ results = list()
+ for image in images:
+ i = 255. * image.cpu().numpy()
+ img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
+ 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]))
+
+ file = f"{filename}_{counter:05}_.png"
+ img.save(os.path.join(full_output_folder, file), pnginfo=metadata, compress_level=4)
+ results.append({
+ "filename": file,
+ "subfolder": subfolder,
+ "type": self.type
+ })
+ counter += 1
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Other-Nodes#cr-image-output"
+
+ return { "ui": { "images": results }, "result": (trigger,) }
+
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_IntegerMultipleOf:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "integer": ("INT", {"default": 1, "min": -18446744073709551615, "max": 18446744073709551615}),
+ "multiple": ("FLOAT", {"default": 8, "min": 1, "max": 18446744073709551615}),
+ }
+ }
+
+ RETURN_TYPES =("INT", "STRING", )
+ RETURN_NAMES =("INT", "show_help", )
+ FUNCTION = "int_multiple_of"
+ CATEGORY = icons.get("Comfyroll/Other")
+
+ def int_multiple_of(self, integer, multiple=8):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Other-Nodes#cr-integer-multiple"
+ if multiple == 0:
+ return (int(integer), show_help, )
+ integer = integer * multiple
+ return (int(integer), show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_Seed:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff})}}
+
+ RETURN_TYPES = ("INT", "STRING", )
+ RETURN_NAMES = ("seed", "show_help", )
+ FUNCTION = "seedint"
+ OUTPUT_NODE = True
+ CATEGORY = icons.get("Comfyroll/Other")
+
+ @staticmethod
+ def seedint(seed):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Other-Nodes#cr-seed"
+ return (seed, show_help,)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_LatentBatchSize:
+
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {"latent": ("LATENT", ),
+ "batch_size": ("INT", {"default": 2, "min": 1, "max": 16, "step": 1}),
+ }
+ }
+
+ RETURN_TYPES = ("LATENT", )
+ FUNCTION = "batchsize"
+ CATEGORY = icons.get("Comfyroll/Other")
+
+ def batchsize(self, latent: tg.Sequence[tg.Mapping[tg.Text, torch.Tensor]], batch_size: int):
+ samples = latent['samples']
+ shape = samples.shape
+
+ sample_list = [samples] + [
+ torch.clone(samples) for _ in range(batch_size - 1)
+ ]
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Other-Nodes#cr-latent-batch-size"
+
+ return ({
+ 'samples': torch.cat(sample_list),
+ }, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_PromptText:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {"prompt": ("STRING", {"default": "prompt", "multiline": True})}}
+
+ RETURN_TYPES = ("STRING", "STRING", )
+ RETURN_NAMES = ("prompt", "show_help", )
+ FUNCTION = "get_value"
+ CATEGORY = icons.get("Comfyroll/Other")
+
+ def get_value(self, prompt):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Other-Nodes#cr-prompt-text"
+ return (prompt, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_SplitString:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ return {"required": {"text": ("STRING", {"multiline": False, "default": "text"}),
+ "delimiter": ("STRING", {"multiline": False, "default": ","}),
+ }
+ }
+
+ RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", )
+ RETURN_NAMES = ("string_1", "string_2", "string_3", "string_4", "show_help", )
+ FUNCTION = "split"
+ CATEGORY = icons.get("Comfyroll/Other")
+
+ def split(self, text, delimiter):
+
+ # Split the text string
+ parts = text.split(delimiter)
+ strings = [part.strip() for part in parts[:4]]
+ string_1, string_2, string_3, string_4 = strings + [""] * (4 - len(strings))
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Other-Nodes#cr-split-string"
+
+ return (string_1, string_2, string_3, string_4, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_Value:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {"value": ("FLOAT", {"default": 1.0,},)}}
+
+ RETURN_TYPES = ("FLOAT", "INT", "STRING", )
+ RETURN_NAMES = ("FLOAT", "INT", "show_help", )
+ CATEGORY = icons.get("Comfyroll/Other")
+ FUNCTION = "get_value"
+
+ def get_value(self, value):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Other-Nodes#cr-value"
+ return (float(value), int(value), show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ConditioningMixer:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ mix_methods = ["Combine", "Average", "Concatenate"]
+
+ return {"required":
+ {"conditioning_1": ("CONDITIONING", ),
+ "conditioning_2": ("CONDITIONING", ),
+ "mix_method": (mix_methods, ),
+ "average_strength": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
+ }
+ }
+
+ RETURN_TYPES = ("CONDITIONING", "STRING", )
+ RETURN_NAMES = ("CONDITIONING", "show_help", )
+ FUNCTION = "conditioning"
+ CATEGORY = icons.get("Comfyroll/Other")
+
+ def conditioning(self, mix_method, conditioning_1, conditioning_2, average_strength):
+
+ conditioning_from = conditioning_1
+ conditioning_to = conditioning_2
+ conditioning_to_strength = average_strength
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Other-Nodes#cr-conditioning-mixer"
+
+ if mix_method == "Combine":
+ return (conditioning_1 + conditioning_2, show_help, )
+
+ if mix_method == "Average":
+
+ out = []
+
+ if len(conditioning_from) > 1:
+ print("Warning: ConditioningAverage conditioning_from contains more than 1 cond, only the first one will actually be applied to conditioning_to.")
+
+ cond_from = conditioning_from[0][0]
+ pooled_output_from = conditioning_from[0][1].get("pooled_output", None)
+
+ for i in range(len(conditioning_to)):
+ t1 = conditioning_to[i][0]
+ pooled_output_to = conditioning_to[i][1].get("pooled_output", pooled_output_from)
+ t0 = cond_from[:,:t1.shape[1]]
+ if t0.shape[1] < t1.shape[1]:
+ t0 = torch.cat([t0] + [torch.zeros((1, (t1.shape[1] - t0.shape[1]), t1.shape[2]))], dim=1)
+
+ tw = torch.mul(t1, conditioning_to_strength) + torch.mul(t0, (1.0 - conditioning_to_strength))
+ t_to = conditioning_to[i][1].copy()
+ if pooled_output_from is not None and pooled_output_to is not None:
+ t_to["pooled_output"] = torch.mul(pooled_output_to, conditioning_to_strength) + torch.mul(pooled_output_from, (1.0 - conditioning_to_strength))
+ elif pooled_output_from is not None:
+ t_to["pooled_output"] = pooled_output_from
+
+ n = [tw, t_to]
+ out.append(n)
+ return (out, show_help, )
+
+ if mix_method == "Concatenate":
+
+ out = []
+
+ if len(conditioning_from) > 1:
+ print("Warning: ConditioningConcat conditioning_from contains more than 1 cond, only the first one will actually be applied to conditioning_to.")
+
+ cond_from = conditioning_from[0][0]
+
+ for i in range(len(conditioning_to)):
+ t1 = conditioning_to[i][0]
+ tw = torch.cat((t1, cond_from),1)
+ n = [tw, conditioning_to[i][1].copy()]
+ out.append(n)
+ return (out, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_SelectModel:
+
+ @classmethod
+ def INPUT_TYPES(cls):
+
+ checkpoint_files = ["None"] + folder_paths.get_filename_list("checkpoints")
+
+ return {"required": {"ckpt_name1": (checkpoint_files,),
+ "ckpt_name2": (checkpoint_files,),
+ "ckpt_name3": (checkpoint_files,),
+ "ckpt_name4": (checkpoint_files,),
+ "ckpt_name5": (checkpoint_files,),
+ "select_model": ("INT", {"default": 1, "min": 1, "max": 5}),
+ }
+ }
+
+
+ RETURN_TYPES = ("MODEL", "CLIP", "VAE", "STRING", "STRING", )
+ RETURN_NAMES = ("MODEL", "CLIP", "VAE", "ckpt_name", "show_help", )
+ FUNCTION = "select_model"
+ CATEGORY = icons.get("Comfyroll/Other")
+
+ def select_model(self, ckpt_name1, ckpt_name2, ckpt_name3, ckpt_name4, ckpt_name5, select_model):
+
+ # Initialise the list
+ model_list = list()
+
+ if select_model == 1:
+ model_name = ckpt_name1
+ elif select_model == 2:
+ model_name = ckpt_name2
+ elif select_model == 3:
+ model_name = ckpt_name3
+ elif select_model == 4:
+ model_name = ckpt_name4
+ elif select_model == 5:
+ model_name = ckpt_name5
+
+ if model_name == "None":
+ print(f"CR Select Model: No model selected")
+ return()
+
+ ckpt_path = folder_paths.get_full_path("checkpoints", model_name)
+ model, clip, vae, clipvision = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True,
+ embedding_directory=folder_paths.get_folder_paths("embeddings"))
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Other-Nodes#cr-select-model"
+
+ return (model, clip, vae, model_name, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+'''
+NODE_CLASS_MAPPINGS = {
+ ### Aspect ratio
+ "CR SD1.5 Aspect Ratio": CR_AspectRatioSD15,
+ "CR SDXL Aspect Ratio":CR_SDXLAspectRatio,
+ "CR Aspect Ratio": CR_AspectRatio,
+ ### Other
+ "CR Image Output": CR_ImageOutput,
+ "CR Integer Multiple": CR_IntegerMultipleOf,
+ "CR Latent Batch Size":CR_LatentBatchSize
+ "CR Seed":CR_Seed,
+ "CR Prompt Text":CR_PromptText,
+ "CR Split String":CR_SplitString,
+ "CR Value": CR_Value,
+ "CR Conditioning Mixer":CR_ConditioningMixer,
+ "CR Select Model": CR_SelectModel,
+}
+'''
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/nodes_random.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/nodes_random.py
new file mode 100644
index 0000000000000000000000000000000000000000..630818ab7be129b69f36a203ebec700e8f3ff65c
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/nodes_random.py
@@ -0,0 +1,160 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# Comfyroll Custom Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+
+import random
+import string
+from .graphics_functions import random_hex_color, random_rgb
+from ..categories import icons
+
+#---------------------------------------------------------------------------------------------------------------------#
+# Random values
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_RandomHexColor:
+
+ @classmethod
+ def INPUT_TYPES(cls):
+
+ return {"required": {"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),}}
+
+ RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", )
+ RETURN_NAMES = ("hex_color1", "hex_color2", "hex_color3", "hex_color4", "show_help", )
+ FUNCTION = "get_colors"
+ CATEGORY = icons.get("Comfyroll/Utils/Random")
+
+ def get_colors(self, seed):
+
+ # Set the seed
+ random.seed(seed)
+
+ hex_color1 = random_hex_color()
+ hex_color2 = random_hex_color()
+ hex_color3 = random_hex_color()
+ hex_color4 = random_hex_color()
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Other-Nodes#cr-random-hex-color"
+
+ return (hex_color1, hex_color2, hex_color3, hex_color4, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_RandomRGB:
+
+ @classmethod
+ def INPUT_TYPES(cls):
+
+ return {"required": {"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),}}
+
+ RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", )
+ RETURN_NAMES = ("rgb_1", "rgb_2", "rgb_3", "rgb_4", "show_help", )
+ FUNCTION = "get_colors"
+ CATEGORY = icons.get("Comfyroll/Utils/Random")
+
+ def get_colors(self, seed):
+
+ # Set the seed
+ random.seed(seed)
+
+ rgb_1 = random_rgb()
+ rgb_2 = random_rgb()
+ rgb_3 = random_rgb()
+ rgb_4 = random_rgb()
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Other-Nodes#cr-random-rgb"
+
+ return (rgb_1, rgb_2, rgb_3, rgb_4, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_RandomMultilineValues:
+
+ @classmethod
+ def INPUT_TYPES(cls):
+
+ types = ["binary", "decimal", "hexadecimal", "alphabetic", "alphanumeric"]
+
+ return {"required": {"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
+ "value_type": (types,),
+ "rows": ("INT", {"default": 5, "min": 1, "max": 2048}),
+ "string_length": ("INT", {"default": 5, "min": 1, "max": 2048}),
+ }
+ }
+
+ RETURN_TYPES = ("STRING", "STRING", )
+ RETURN_NAMES = ("multiline_text", "show_help", )
+ FUNCTION = "generate"
+ CATEGORY = icons.get("Comfyroll/Utils/Random")
+
+ def generate(self, value_type, rows, string_length, seed):
+
+ # Set the seed
+ random.seed(seed)
+
+ if value_type == "binary":
+ choice_str = '01'
+ elif value_type == "decimal":
+ choice_str = '0123456789'
+ elif value_type == "hexadecimal":
+ choice_str = '0123456789abcdef'
+ elif value_type == "alphabetic":
+ choice_str = string.ascii_letters
+ elif value_type == "alphanumeric":
+ choice_str = string.ascii_letters + string.digits
+
+ multiline_text = '\n'.join([''.join(random.choice(choice_str) for _ in range(string_length)) for _ in range(rows)])
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Other-Nodes#cr-random-multiline-values"
+
+ return (multiline_text, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+
+class CR_RandomRGBGradient:
+
+ @classmethod
+ def INPUT_TYPES(cls):
+
+ return {"required": {"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
+ "rows": ("INT", {"default": 5, "min": 1, "max": 2048}),
+ }
+ }
+
+ RETURN_TYPES = ("STRING", "STRING", )
+ RETURN_NAMES = ("multiline_text", "show_help", )
+ FUNCTION = "generate"
+ CATEGORY = icons.get("Comfyroll/Utils/Random")
+
+ def generate(self, rows, seed):
+
+ # Set the seed
+ random.seed(seed)
+
+ temp = 0
+ multiline_text = ""
+
+ for i in range(1, rows + 1):
+ print(temp)
+ if temp <= 99 - rows + i:
+ upper_bound = min(99, temp + (99 - temp) // (rows - i + 1))
+ current_value = random.randint(temp, upper_bound)
+ multiline_text += f'{current_value}:{random.randint(0, 255)},{random.randint(0, 255)},{random.randint(0, 255)}\n'
+ print(multiline_text)
+ temp = current_value + 1
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Other-Nodes#cr-random-RGB-gradient"
+
+ return (multiline_text, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+'''
+NODE_CLASS_MAPPINGS = {
+ # Random
+ "CR Random Hex Color": CR_RandomHexColor,
+ "CR Random RGB": CR_RandomRGB,
+ "CR Random Multiline Values": CR_RandomMultilineValues,
+ "CR Random RGB Gradient": CR_RandomRGBGradient,
+}
+'''
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/pil_filter.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/pil_filter.py
new file mode 100644
index 0000000000000000000000000000000000000000..b428640e8c818537dd3e64307943180c7ab5fd86
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/pil_filter.py
@@ -0,0 +1,299 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# Comfyroll Custom Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+import torch
+import numpy as np
+from PIL import Image, ImageDraw, ImageStat, ImageFilter
+from .graphics_functions import get_color_values
+from ..config import color_mapping, COLORS
+from ..categories import icons
+
+def tensor2pil(image):
+ return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
+
+def pil2tensor(image):
+ return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
+
+#---------------------------------------------------------------------------------------------------------------------#
+# Based on Color Tint node by hnmr293
+class CR_ColorTint:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ #tints = COLORS.append('sepia')
+ '''
+ tints = ["custom", "white", "black", "sepia", "red", "green", "blue",
+ "cyan", "magenta", "yellow", "purple", "orange", "warm",
+ "cool", "lime", "navy", "vintage", "rose", "teal",
+ "maroon", "peach", "lavender", "olive"]
+ '''
+
+ return {
+ "required": {"image": ("IMAGE",),
+ "strength": ("FLOAT", {"default": 1.0,"min": 0.1,"max": 1.0,"step": 0.1}),
+ "mode": (COLORS,),
+ },
+ "optional": {"tint_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),}
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("IMAGE", "show_help", )
+ FUNCTION = "color_tint"
+ CATEGORY = icons.get("Comfyroll/Graphics/Filter")
+
+ def color_tint(self, image: torch.Tensor, strength, mode: str="sepia", tint_color_hex='#000000'):
+
+ if strength == 0:
+ return (image,)
+
+ # Get RGB values for the tint color
+ tint_color = get_color_values(mode, tint_color_hex, color_mapping)
+ color_rgb = tuple([value / 255 for value in tint_color])
+
+ sepia_weights = torch.tensor([0.2989, 0.5870, 0.1140]).view(1, 1, 1, 3).to(image.device)
+
+ mode_filters = {
+ "custom": torch.tensor([color_rgb[0], color_rgb[1], color_rgb[2]]),
+ "white": torch.tensor([1, 1, 1]),
+ "black": torch.tensor([0, 0, 0]),
+ "sepia": torch.tensor([1.0, 0.8, 0.6]),
+ "red": torch.tensor([1.0, 0.6, 0.6]),
+ "green": torch.tensor([0.6, 1.0, 0.6]),
+ "blue": torch.tensor([0.6, 0.8, 1.0]),
+ "cyan": torch.tensor([0.6, 1.0, 1.0]),
+ "magenta": torch.tensor([1.0, 0.6, 1.0]),
+ "yellow": torch.tensor([1.0, 1.0, 0.6]),
+ "purple": torch.tensor([0.8, 0.6, 1.0]),
+ "orange": torch.tensor([1.0, 0.7, 0.3]),
+ "warm": torch.tensor([1.0, 0.9, 0.7]),
+ "cool": torch.tensor([0.7, 0.9, 1.0]),
+ "lime": torch.tensor([0.7, 1.0, 0.3]),
+ "navy": torch.tensor([0.3, 0.4, 0.7]),
+ "vintage": torch.tensor([0.9, 0.85, 0.7]),
+ "rose": torch.tensor([1.0, 0.8, 0.9]),
+ "teal": torch.tensor([0.3, 0.8, 0.8]),
+ "maroon": torch.tensor([0.7, 0.3, 0.5]),
+ "peach": torch.tensor([1.0, 0.8, 0.6]),
+ "lavender": torch.tensor([0.8, 0.6, 1.0]),
+ "olive": torch.tensor([0.6, 0.7, 0.4]),
+ }
+
+ scale_filter = mode_filters[mode].view(1, 1, 1, 3).to(image.device)
+
+ grayscale = torch.sum(image * sepia_weights, dim=-1, keepdim=True)
+ tinted = grayscale * scale_filter
+
+ result = tinted * strength + image * (1 - strength)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Filter-Nodes#cr-color-tint"
+
+ return (result, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_HalftoneFilter:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(cls):
+
+ shapes = ["ellipse", "rectangle"]
+ rez = ["normal", "hi-res (2x output size)"]
+
+ return {
+ "required": {
+ "image": ("IMAGE",),
+ "dot_size": ("INT", {"default": 5, "min": 1, "max": 30, "step": 1}),
+ "dot_shape": (shapes, {"default": "ellipse"}),
+ #"scale": ("INT", {"default": 1, "min": 1, "max": 8, "step": 1}),
+ "resolution": (rez, {"default": "normal"}),
+ "angle_c": ("INT", {"default": 75, "min": 0, "max": 360, "step": 1}),
+ "angle_m": ("INT", {"default": 45, "min": 0, "max": 360, "step": 1}),
+ "angle_y": ("INT", {"default": 15, "min": 0, "max": 360, "step": 1}),
+ "angle_k": ("INT", {"default": 0, "min": 0, "max": 360, "step": 1}),
+ "greyscale": ("BOOLEAN", {"default": True}),
+ "antialias": ("BOOLEAN", {"default": True}),
+ "antialias_scale": ("INT", {"default": 2, "min": 1, "max": 4, "step": 1}),
+ "border_blending": ("BOOLEAN", {"default": False}),
+ },
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("IMAGE", "show_help", )
+ FUNCTION = "halftone_effect"
+ CATEGORY = icons.get("Comfyroll/Graphics/Filter")
+
+ def tensor_to_pil(self, tensor):
+ if tensor.ndim == 4 and tensor.shape[0] == 1: # Check for batch dimension
+ tensor = tensor.squeeze(0) # Remove batch dimension
+ if tensor.dtype == torch.float32: # Check for float tensors
+ tensor = tensor.mul(255).byte() # Convert to range [0, 255] and change to byte type
+ elif tensor.dtype != torch.uint8: # If not float and not uint8, conversion is needed
+ tensor = tensor.byte() # Convert to byte type
+
+ numpy_image = tensor.cpu().numpy()
+
+ # Determine the correct mode based on the number of channels
+ if tensor.ndim == 3:
+ if tensor.shape[2] == 1:
+ mode = 'L' # Grayscale
+ elif tensor.shape[2] == 3:
+ mode = 'RGB' # RGB
+ elif tensor.shape[2] == 4:
+ mode = 'RGBA' # RGBA
+ else:
+ raise ValueError(f"Unsupported channel number: {tensor.shape[2]}")
+ else:
+ raise ValueError(f"Unexpected tensor shape: {tensor.shape}")
+
+ pil_image = Image.fromarray(numpy_image, mode)
+ return pil_image
+
+ def pil_to_tensor(self, pil_image):
+ numpy_image = np.array(pil_image)
+ tensor = torch.from_numpy(numpy_image).float().div(255) # Convert to range [0, 1]
+ tensor = tensor.unsqueeze(0) # Add batch dimension
+ return tensor
+
+ def halftone_effect(self, image, dot_size, dot_shape, resolution, angle_c, angle_m, angle_y, angle_k, greyscale, antialias, border_blending, antialias_scale):
+
+ sample = dot_size
+ shape = dot_shape
+
+ # Map resolution to scale
+ resolution_to_scale = {
+ "normal": 1,
+ "hi-res (2x output size)": 2,
+ }
+ scale = resolution_to_scale.get(resolution, 1) # Default to 1 if resolution is not recognized
+
+ # If the input is a PyTorch tensor, convert to PIL Image
+ if isinstance(image, torch.Tensor):
+ image = self.tensor_to_pil(image)
+
+ # Ensure the image is a PIL Image
+ if not isinstance(image, Image.Image):
+ raise TypeError("The provided image is neither a PIL Image nor a PyTorch tensor.")
+
+ pil_image = image # Now we are sure pil_image is defined
+
+ # Convert to greyscale or CMYK
+ if greyscale:
+ pil_image = pil_image.convert("L")
+ channel_images = [pil_image]
+ angles = [angle_k]
+ else:
+ pil_image = pil_image.convert("CMYK")
+ channel_images = list(pil_image.split())
+ angles = [angle_c, angle_m, angle_y, angle_k]
+
+ # Apply the halftone effect using PIL
+ halftone_images = self._halftone_pil(pil_image, channel_images, sample, scale, angles, antialias, border_blending, antialias_scale, shape)
+
+ # Merge channels and convert to RGB
+ if greyscale:
+ new_image = halftone_images[0].convert("RGB") # Convert the greyscale image to RGB
+ else:
+ new_image = Image.merge("CMYK", halftone_images).convert("RGB")
+
+ result_tensor = self.pil_to_tensor(new_image)
+
+ # Debug print to check the final tensor shape
+ print("Final tensor shape:", result_tensor.shape)
+
+ return (result_tensor, show_help, )
+
+ def _halftone_pil(self, im, cmyk, sample, scale, angles, antialias, border_blending, antialias_scale, shape):
+ # If we're antialiasing, we'll multiply the size of the image by this
+ # scale while drawing, and then scale it back down again afterwards.
+ antialias_res = antialias_scale if antialias else 1
+ scale = scale * antialias_res
+
+ dots = []
+
+ for channel_index, (channel, angle) in enumerate(zip(cmyk, angles)):
+ channel = channel.rotate(angle, expand=1)
+ size = channel.size[0] * scale, channel.size[1] * scale
+ half_tone = Image.new("L", size)
+ draw = ImageDraw.Draw(half_tone)
+
+ # Cycle through one sample point at a time, drawing a circle for
+ # each one:
+ for x in range(0, channel.size[0], sample):
+ for y in range(0, channel.size[1], sample):
+
+ # Adjust the sampling near the borders for non-square angles
+ if border_blending and angle % 90 != 0 and (x < sample or y < sample or x > channel.size[0] - sample or y > channel.size[1] - sample):
+ # Get a weighted average of the neighboring pixels
+ neighboring_pixels = channel.crop((max(x - 1, 0), max(y - 1, 0), min(x + 2, channel.size[0]), min(y + 2, channel.size[1])))
+ pixels = list(neighboring_pixels.getdata())
+ weights = [0.5 if i in [0, len(pixels)-1] else 1 for i in range(len(pixels))]
+ weighted_mean = sum(p * w for p, w in zip(pixels, weights)) / sum(weights)
+ mean = weighted_mean
+ else:
+ # Area we sample to get the level:
+ box = channel.crop((x, y, x + sample, y + sample))
+ # The average level for that box (0-255):
+ mean = ImageStat.Stat(box).mean[0]
+
+ # The diameter or side length of the shape to draw based on the mean (0-1):
+ size = (mean / 255) ** 0.5
+
+ # Size of the box we'll draw the circle in:
+ box_size = sample * scale
+
+ # Diameter or side length of shape we'll draw:
+ draw_size = size * box_size
+
+ # Position of top-left of box we'll draw the circle in:
+ box_x, box_y = (x * scale), (y * scale)
+
+ # Positioned of top-left and bottom-right of circle:
+ x1 = box_x + ((box_size - draw_size) / 2)
+ y1 = box_y + ((box_size - draw_size) / 2)
+ x2 = x1 + draw_size
+ y2 = y1 + draw_size
+
+ # Draw the shape based on the variable passed
+ draw_method = getattr(draw, shape, None)
+ if draw_method:
+ draw_method([(x1, y1), (x2, y2)], fill=255)
+
+ half_tone = half_tone.rotate(-angle, expand=1)
+ width_half, height_half = half_tone.size
+
+ # Top-left and bottom-right of the image to crop to:
+ xx1 = (width_half - im.size[0] * scale) / 2
+ yy1 = (height_half - im.size[1] * scale) / 2
+ xx2 = xx1 + im.size[0] * scale
+ yy2 = yy1 + im.size[1] * scale
+
+ half_tone = half_tone.crop((xx1, yy1, xx2, yy2))
+
+ if antialias:
+ # Scale it back down to antialias the image.
+ w = int((xx2 - xx1) / antialias_scale)
+ h = int((yy2 - yy1) / antialias_scale)
+ half_tone = half_tone.resize((w, h), resample=Image.LANCZOS)
+
+ dots.append(half_tone)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Filter-Nodes#cr-halftone-filter"
+
+ return (dots, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+'''
+NODE_CLASS_MAPPINGS = {
+ "CR Halftone Filter": "CR HalftoneFilter",
+ "CR Color Tint": CR_ColorTint,
+}
+'''
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/pil_layout.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/pil_layout.py
new file mode 100644
index 0000000000000000000000000000000000000000..7bb86564b43b10d43fa914eb8887f74d343201b6
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/pil_layout.py
@@ -0,0 +1,606 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# Comfyroll Custom Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+
+import numpy as np
+import torch
+import os
+from PIL import Image, ImageDraw, ImageOps, ImageFont
+from ..categories import icons
+from ..config import color_mapping, COLORS
+from .graphics_functions import (hex_to_rgb,
+ get_color_values,
+ text_panel,
+ combine_images,
+ apply_outline_and_border,
+ get_font_size,
+ draw_text_on_image)
+
+#try:
+# import Markdown
+#except ImportError:
+# import pip
+# pip.main(['install', 'Markdown'])
+
+#---------------------------------------------------------------------------------------------------------------------#
+
+ALIGN_OPTIONS = ["top", "center", "bottom"]
+ROTATE_OPTIONS = ["text center", "image center"]
+JUSTIFY_OPTIONS = ["left", "center", "right"]
+PERSPECTIVE_OPTIONS = ["top", "bottom", "left", "right"]
+
+#---------------------------------------------------------------------------------------------------------------------#
+
+def tensor2pil(image):
+ return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
+
+def pil2tensor(image):
+ return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_PageLayout:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ font_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "fonts")
+ file_list = [f for f in os.listdir(font_dir) if os.path.isfile(os.path.join(font_dir, f)) and f.lower().endswith(".ttf")]
+
+ layout_options = ["header", "footer", "header and footer", "no header or footer"]
+
+ return {"required": {
+ "layout_options": (layout_options,),
+ "image_panel": ("IMAGE",),
+ "header_height": ("INT", {"default": 0, "min": 0, "max": 1024}),
+ "header_text": ("STRING", {"multiline": True, "default": "text"}),
+ "header_align": (JUSTIFY_OPTIONS, ),
+ "footer_height": ("INT", {"default": 0, "min": 0, "max": 1024}),
+ "footer_text": ("STRING", {"multiline": True, "default": "text"}),
+ "footer_align": (JUSTIFY_OPTIONS, ),
+ "font_name": (file_list,),
+ "font_color": (COLORS,),
+ "header_font_size": ("INT", {"default": 150, "min": 0, "max": 1024}),
+ "footer_font_size": ("INT", {"default": 50, "min": 0, "max": 1024}),
+ "border_thickness": ("INT", {"default": 0, "min": 0, "max": 1024}),
+ "border_color": (COLORS,),
+ "background_color": (COLORS,),
+ },
+ "optional": {
+ "font_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ "border_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ "bg_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("image", "show_help", )
+ FUNCTION = "layout"
+ CATEGORY = icons.get("Comfyroll/Graphics/Layout")
+
+ def layout(self, layout_options, image_panel,
+ border_thickness, border_color, background_color,
+ header_height, header_text, header_align,
+ footer_height, footer_text, footer_align,
+ font_name, font_color,
+ header_font_size, footer_font_size,
+ font_color_hex='#000000', border_color_hex='#000000', bg_color_hex='#000000'):
+
+ # Get RGB values for the text and background colors
+ font_color = get_color_values(font_color, font_color_hex, color_mapping)
+ border_color = get_color_values(border_color, border_color_hex, color_mapping)
+ bg_color = get_color_values(background_color, bg_color_hex, color_mapping)
+
+ main_panel = tensor2pil(image_panel)
+
+ # Get image width and height
+ image_width = main_panel.width
+ image_height = main_panel.height
+
+ # Set defaults
+ margins = 50
+ line_spacing = 0
+ position_x = 0
+ position_y = 0
+ align = "center"
+ rotation_angle = 0
+ rotation_options = "image center"
+ font_outline_thickness = 0
+ font_outline_color = "black"
+
+ images = []
+
+ ### Create text panels and add to images array
+ if layout_options == "header" or layout_options == "header and footer":
+ header_panel = text_panel(image_width, header_height, header_text,
+ font_name, header_font_size, font_color,
+ font_outline_thickness, font_outline_color,
+ bg_color,
+ margins, line_spacing,
+ position_x, position_y,
+ align, header_align,
+ rotation_angle, rotation_options)
+ images.append(header_panel)
+
+ images.append(main_panel)
+
+ if layout_options == "footer" or layout_options == "header and footer":
+ footer_panel = text_panel(image_width, footer_height, footer_text,
+ font_name, footer_font_size, font_color,
+ font_outline_thickness, font_outline_color,
+ bg_color,
+ margins, line_spacing,
+ position_x, position_y,
+ align, footer_align,
+ rotation_angle, rotation_options)
+ images.append(footer_panel)
+
+ combined_image = combine_images(images, 'vertical')
+
+ # Add a border to the combined image
+ if border_thickness > 0:
+ combined_image = ImageOps.expand(combined_image, border_thickness, border_color)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Layout-Nodes#cr-page-layout"
+
+ return (pil2tensor(combined_image), show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_SimpleTitles:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ font_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "fonts")
+ file_list = [f for f in os.listdir(font_dir) if os.path.isfile(os.path.join(font_dir, f)) and f.lower().endswith(".ttf")]
+
+ layout_options = ["header", "footer", "header and footer", "no header or footer"]
+
+ return {"required": {
+ "image": ("IMAGE",),
+ "header_text": ("STRING", {"multiline": True, "default": "text"}),
+ "header_height": ("INT", {"default": 0, "min": 0, "max": 1024}),
+ "header_font_size": ("INT", {"default": 150, "min": 0, "max": 1024}),
+ "header_align": (JUSTIFY_OPTIONS, ),
+ "footer_text": ("STRING", {"multiline": True, "default": "text"}),
+ "footer_height": ("INT", {"default": 0, "min": 0, "max": 1024}),
+ "footer_font_size": ("INT", {"default": 50, "min": 0, "max": 1024}),
+ "footer_align": (JUSTIFY_OPTIONS, ),
+ "font_name": (file_list,),
+ "font_color": (COLORS,),
+ "background_color": (COLORS,),
+ },
+ "optional": {
+ "font_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ "bg_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("image", "show_help", )
+ FUNCTION = "layout"
+ CATEGORY = icons.get("Comfyroll/Graphics/Layout")
+
+ def layout(self, image,
+ header_height, header_text, header_align, header_font_size,
+ footer_height, footer_text, footer_align, footer_font_size,
+ font_name, font_color, background_color,
+ font_color_hex='#000000', bg_color_hex='#000000',):
+
+ # Get RGB values for the text and background colors
+ font_color = get_color_values(font_color, font_color_hex, color_mapping)
+ bg_color = get_color_values(background_color, bg_color_hex, color_mapping)
+
+ main_panel = tensor2pil(image)
+
+ # Get image width and height
+ image_width = main_panel.width
+ image_height = main_panel.height
+
+ # Set defaults
+ margins = 50
+ line_spacing = 0
+ position_x = 0
+ position_y = 0
+ align = "center"
+ rotation_angle = 0
+ rotation_options = "image center"
+ font_outline_thickness = 0
+ font_outline_color = "black"
+
+ images = []
+
+ ### Create text panels and add to images array
+ if header_height >0:
+ header_panel = text_panel(image_width, header_height, header_text,
+ font_name, header_font_size, font_color,
+ font_outline_thickness, font_outline_color,
+ bg_color,
+ margins, line_spacing,
+ position_x, position_y,
+ align, header_align,
+ rotation_angle, rotation_options)
+ images.append(header_panel)
+
+ images.append(main_panel)
+
+ if footer_height >0:
+ footer_panel = text_panel(image_width, footer_height, footer_text,
+ font_name, footer_font_size, font_color,
+ font_outline_thickness, font_outline_color,
+ bg_color,
+ margins, line_spacing,
+ position_x, position_y,
+ align, footer_align,
+ rotation_angle, rotation_options)
+ images.append(footer_panel)
+
+ combined_image = combine_images(images, 'vertical')
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Layout-Nodes#cr-simple_titles"
+
+ return (pil2tensor(combined_image), show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ImagePanel:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ directions = ["horizontal", "vertical"]
+
+ return {"required": {
+ "image_1": ("IMAGE",),
+ "border_thickness": ("INT", {"default": 0, "min": 0, "max": 1024}),
+ "border_color": (COLORS,),
+ "outline_thickness": ("INT", {"default": 0, "min": 0, "max": 1024}),
+ "outline_color": (COLORS[1:],),
+ "layout_direction": (directions,),
+ },
+ "optional": {
+ "image_2": ("IMAGE",),
+ "image_3": ("IMAGE",),
+ "image_4": ("IMAGE",),
+ "border_color_hex": ("STRING", {"multiline": False, "default": "#000000"})
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("image", "show_help", )
+ FUNCTION = "make_panel"
+ CATEGORY = icons.get("Comfyroll/Graphics/Layout")
+
+ def make_panel(self, image_1,
+ border_thickness, border_color,
+ outline_thickness, outline_color,
+ layout_direction, image_2=None, image_3=None, image_4=None,
+ border_color_hex='#000000'):
+
+ border_color = get_color_values(border_color, border_color_hex, color_mapping)
+
+ # Convert PIL images to NumPy arrays
+ images = []
+ #image_1 = image_1[0, :, :, :]
+ images.append(tensor2pil(image_1))
+ if image_2 is not None:
+ #image_2 = image_2[0, :, :, :]
+ images.append(tensor2pil(image_2))
+ if image_3 is not None:
+ #image_3 = image_3[0, :, :, :]
+ images.append(tensor2pil(image_3))
+ if image_4 is not None:
+ #image_4 = image_4[0, :, :, :]
+ images.append(tensor2pil(image_4))
+
+ # Apply borders and outlines to each image
+ images = apply_outline_and_border(images, outline_thickness, outline_color, border_thickness, border_color)
+
+ combined_image = combine_images(images, layout_direction)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Layout-Nodes#cr-image-panel"
+
+ return (pil2tensor(combined_image), show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ImageGridPanel:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ directions = ["horizontal", "vertical"]
+
+ return {"required": {
+ "images": ("IMAGE",),
+ "border_thickness": ("INT", {"default": 0, "min": 0, "max": 1024}),
+ "border_color": (COLORS,),
+ "outline_thickness": ("INT", {"default": 0, "min": 0, "max": 1024}),
+ "outline_color": (COLORS[1:],),
+ "max_columns": ("INT", {"default": 5, "min": 0, "max": 256}),
+ },
+ "optional": {
+ "border_color_hex": ("STRING", {"multiline": False, "default": "#000000"})
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("image", "show_help", )
+ FUNCTION = "make_panel"
+ CATEGORY = icons.get("Comfyroll/Graphics/Layout")
+
+ def make_panel(self, images,
+ border_thickness, border_color,
+ outline_thickness, outline_color,
+ max_columns, border_color_hex='#000000'):
+
+ border_color = get_color_values(border_color, border_color_hex, color_mapping)
+
+ # Convert PIL images to NumPy arrays
+ images = [tensor2pil(image) for image in images]
+
+ # Apply borders and outlines to each image
+ images = apply_outline_and_border(images, outline_thickness, outline_color, border_thickness, border_color)
+
+ # Calculate dimensions for the grid
+ num_images = len(images)
+ num_rows = (num_images - 1) // max_columns + 1
+ combined_width = max(image.width for image in images) * min(max_columns, num_images)
+ combined_height = max(image.height for image in images) * num_rows
+
+ combined_image = Image.new('RGB', (combined_width, combined_height))
+
+ x_offset, y_offset = 0, 0 # Initialize offsets
+ for image in images:
+ combined_image.paste(image, (x_offset, y_offset))
+ x_offset += image.width
+ if x_offset >= max_columns * image.width:
+ x_offset = 0
+ y_offset += image.height
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Layout-Nodes#cr-image-grid-panel"
+
+ return (pil2tensor(combined_image), show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ImageBorder:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ return {"required": {
+ "image": ("IMAGE",),
+ "top_thickness": ("INT", {"default": 0, "min": 0, "max": 4096}),
+ "bottom_thickness": ("INT", {"default": 0, "min": 0, "max": 4096}),
+ "left_thickness": ("INT", {"default": 0, "min": 0, "max": 4096}),
+ "right_thickness": ("INT", {"default": 0, "min": 0, "max": 4096}),
+ "border_color": (COLORS,),
+ "outline_thickness": ("INT", {"default": 0, "min": 0, "max": 1024}),
+ "outline_color": (COLORS[1:],),
+ },
+ "optional": {
+ "border_color_hex": ("STRING", {"multiline": False, "default": "#000000"})
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("image", "show_help", )
+ FUNCTION = "make_panel"
+ CATEGORY = icons.get("Comfyroll/Graphics/Layout")
+
+ def make_panel(self, image,
+ top_thickness, bottom_thickness,
+ left_thickness, right_thickness, border_color,
+ outline_thickness, outline_color,
+ border_color_hex='#000000'):
+
+ images = []
+
+ border_color = get_color_values(border_color, border_color_hex, color_mapping)
+
+ for img in image:
+ img = tensor2pil(img)
+
+ # Apply the outline
+ if outline_thickness > 0:
+ img = ImageOps.expand(img, outline_thickness, fill=outline_color)
+
+ # Apply the borders
+ if left_thickness > 0 or right_thickness > 0 or top_thickness > 0 or bottom_thickness > 0:
+ img = ImageOps.expand(img, (left_thickness, top_thickness, right_thickness, bottom_thickness), fill=border_color)
+
+ images.append(pil2tensor(img))
+
+ images = torch.cat(images, dim=0)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Layout-Nodes#cr-image-border"
+
+ return (images, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ColorPanel:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ return {"required": {
+ "panel_width": ("INT", {"default": 512, "min": 8, "max": 4096}),
+ "panel_height": ("INT", {"default": 512, "min": 8, "max": 4096}),
+ "fill_color": (COLORS,),
+ },
+ "optional": {
+ "fill_color_hex": ("STRING", {"multiline": False, "default": "#000000"})
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("image", "show_help", )
+ FUNCTION = "make_panel"
+ CATEGORY = icons.get("Comfyroll/Graphics/Layout")
+
+ def make_panel(self, panel_width, panel_height,
+ fill_color, fill_color_hex='#000000'):
+
+ fill_color = get_color_values(fill_color, fill_color_hex, color_mapping)
+
+ size = (panel_width, panel_height)
+ panel = Image.new('RGB', size, fill_color)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Layout-Nodes#cr-color-panel"
+
+ return (pil2tensor(panel), show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_SimpleTextPanel:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ font_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "fonts")
+ file_list = [f for f in os.listdir(font_dir) if os.path.isfile(os.path.join(font_dir, f)) and f.lower().endswith(".ttf")]
+
+ return {"required": {
+ "panel_width": ("INT", {"default": 512, "min": 8, "max": 4096}),
+ "panel_height": ("INT", {"default": 512, "min": 8, "max": 4096}),
+ "text": ("STRING", {"multiline": True, "default": "text"}),
+ "font_name": (file_list,),
+ "font_color": (COLORS,),
+ "font_size": ("INT", {"default": 100, "min": 0, "max": 1024}),
+ "font_outline_thickness": ("INT", {"default": 0, "min": 0, "max": 50}),
+ "font_outline_color": (COLORS,),
+ "background_color": (COLORS,),
+ "align": (ALIGN_OPTIONS, ),
+ "justify": (JUSTIFY_OPTIONS, ),
+ },
+ "optional": {
+ "font_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ "bg_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("image", "show_help", )
+ FUNCTION = "layout"
+ CATEGORY = icons.get("Comfyroll/Graphics/Layout")
+
+ def layout(self, panel_width, panel_height,
+ text, align, justify,
+ font_name, font_color, font_size,
+ font_outline_thickness, font_outline_color,
+ background_color,
+ font_color_hex='#000000', font_outline_color_hex='#000000', bg_color_hex='#000000'):
+
+ # Get RGB values for the text and background colors
+ font_color = get_color_values(font_color, font_color_hex, color_mapping)
+ outline_color = get_color_values(font_outline_color, font_outline_color_hex, color_mapping)
+ bg_color = get_color_values(background_color, bg_color_hex, color_mapping)
+
+ # Set defaults
+ margins = 50
+ line_spacing = 0
+ position_x = 0
+ position_y = 0
+ rotation_angle = 0
+ rotation_options = "image center"
+
+ ### Create text panels
+
+ panel = text_panel(panel_width, panel_height, text,
+ font_name, font_size, font_color,
+ font_outline_thickness, outline_color,
+ bg_color,
+ margins, line_spacing,
+ position_x, position_y,
+ align, justify,
+ rotation_angle, rotation_options)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Layout-Nodes#cr-simple-text-panel"
+
+ return (pil2tensor(panel), show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_OverlayTransparentImage:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ return {"required": {
+ "back_image": ("IMAGE",),
+ "overlay_image": ("IMAGE",),
+ "transparency": ("FLOAT", {"default": 0, "min": 0, "max": 1, "step": 0.1}),
+ "offset_x": ("INT", {"default": 0, "min": -4096, "max": 4096}),
+ "offset_y": ("INT", {"default": 0, "min": -4096, "max": 4096}),
+ "rotation_angle": ("FLOAT", {"default": 0.0, "min": -360.0, "max": 360.0, "step": 0.1}),
+ "overlay_scale_factor": ("FLOAT", {"default": 1.0, "min": -0.1, "max": 100.0, "step": 0.1}),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", )
+ FUNCTION = "overlay_image"
+ CATEGORY = icons.get("Comfyroll/Graphics/Layout")
+
+ def overlay_image(self, back_image, overlay_image,
+ transparency, offset_x, offset_y, rotation_angle, overlay_scale_factor=1.0):
+
+ """
+ Overlay an image onto another image with transparency, rotation, and scaling.
+
+ Args:
+ back_image (torch.Tensor): Background image tensor.
+ overlay_image (torch.Tensor): Overlay image tensor.
+ transparency (float): Transparency level for the overlay image (0.0 to 1.0).
+ offset_x (int): X-coordinate relative to the center of the back image.
+ offset_y (int): Y-coordinate relative to the center of the back image.
+ rotation_angle (float): Rotation angle in degrees.
+ scale_factor (float): Scaling factor for the overlay image.
+
+ Returns:
+ torch.Tensor: Resulting image tensor.
+ """
+
+ # Convert tensor images
+ #back_image = back_image[0, :, :, :]
+ #overlay_image = overlay_image[0, :, :, :]
+
+ # Create PIL images for the text and background layers and text mask
+ back_image = tensor2pil(back_image)
+ overlay_image = tensor2pil(overlay_image)
+
+ # Apply transparency to overlay image
+ overlay_image.putalpha(int(255 * (1 - transparency)))
+
+ # Rotate overlay image
+ overlay_image = overlay_image.rotate(rotation_angle, expand=True)
+
+ # Scale overlay image
+ overlay_width, overlay_height = overlay_image.size
+ new_size = (int(overlay_width * overlay_scale_factor), int(overlay_height * overlay_scale_factor))
+ overlay_image = overlay_image.resize(new_size, Image.ANTIALIAS)
+
+ # Calculate centered position relative to the center of the background image
+ center_x = back_image.width // 2
+ center_y = back_image.height // 2
+ position_x = center_x - overlay_image.width // 2 + offset_x
+ position_y = center_y - overlay_image.height // 2 + offset_y
+
+ # Paste the rotated overlay image onto the new back image at the specified position
+ back_image.paste(overlay_image, (position_x, position_y), overlay_image)
+
+ # Convert the PIL image back to a torch tensor
+ return pil2tensor(back_image),
+
+#---------------------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+'''
+NODE_CLASS_MAPPINGS = {
+ "CR Page Layout": CR_PageLayout,
+ "CR Image Grid Panel": CR_ImageGridPanel,
+ "CR Image XY Panel": CR_ImageXYPanel,
+ "CR Image Border": CR_ImageBorder,
+ "CR Color Panel": CR_ColorPanel,
+ "CR Simple Text Panel": CR_SimpleTextPanel,
+ "CR Overlay Transparent Image": CR_OverlayTransparentImage,
+ "CR Simple Titles": CR_SimpleTitles,
+}
+'''
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/pil_pattern.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/pil_pattern.py
new file mode 100644
index 0000000000000000000000000000000000000000..b7d7c64ea5095cc31ec5eadb5ac438a5e939d662
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/pil_pattern.py
@@ -0,0 +1,158 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# Comfyroll Custom Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+
+import numpy as np
+import torch
+import random
+import os
+from PIL import Image, ImageDraw
+from .graphics_functions import get_color_values
+from ..categories import icons
+from ..config import color_mapping, COLORS
+
+def pil2tensor(image):
+ return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_BinaryPatternSimple:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ return {"required": {
+ "binary_pattern": ("STRING", {"multiline": True, "default": "10101"}),
+ "width": ("INT", {"default": 512, "min": 64, "max": 4096}),
+ "height": ("INT", {"default": 512, "min": 64, "max": 4096}),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("IMAGE", "show_help", )
+ FUNCTION = "draw_pattern"
+ CATEGORY = icons.get("Comfyroll/Graphics/Pattern")
+
+ def draw_pattern(self, binary_pattern, width, height):
+ # Convert multiline binary pattern to a 2D list
+ rows = binary_pattern.strip().split('\n')
+ grid = [[int(bit) for bit in row.strip()] for row in rows]
+
+ # Calculate the size of each square
+ square_width = width // len(rows[0])
+ square_height = height // len(rows)
+
+ # Create a new image
+ image = Image.new("RGB", (width, height), color='black')
+ draw = ImageDraw.Draw(image)
+
+ # Draw grid based on the binary pattern
+ for row_index, row in enumerate(grid):
+ for col_index, bit in enumerate(row):
+ x1 = col_index * square_width
+ y1 = row_index * square_height
+ x2 = x1 + square_width
+ y2 = y1 + square_height
+
+ # Draw black square if bit is 1, else draw white square
+ color = 'black' if bit == 1 else 'white'
+ draw.rectangle([x1, y1, x2, y2], fill=color, outline="black")
+
+ image_out = pil2tensor(image)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Text-Nodes#cr-simple-binary-pattern"
+
+ # Convert the PIL image back to a torch tensor
+ return (image_out, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_BinaryPattern:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ return {"required": {
+ "binary_pattern": ("STRING", {"multiline": True, "default": "10101"}),
+ "width": ("INT", {"default": 512, "min": 64, "max": 4096}),
+ "height": ("INT", {"default": 512, "min": 64, "max": 4096}),
+ "background_color": (COLORS,),
+ "color_0": (COLORS,),
+ "color_1": (COLORS,),
+ "outline_thickness": ("INT", {"default": 0, "min": 0, "max": 1024}),
+ "outline_color": (COLORS,),
+ "jitter_distance": ("INT", {"default": 0, "min": 0, "max": 1024}),
+ },
+ "optional": {
+ "bg_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ "color0_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ "color1_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ "outline_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("IMAGE", "show_help", )
+ FUNCTION = "draw_pattern"
+ CATEGORY = icons.get("Comfyroll/Graphics/Pattern")
+
+ def draw_pattern(self, binary_pattern, width, height,
+ background_color, outline_color,
+ color_0="white", color_1="black", outline_thickness=0,
+ color0_hex='#000000', color1_hex='#000000',
+ bg_color_hex='#000000', outline_color_hex='#000000',
+ jitter_distance = 0):
+
+ # Get RGB values
+ color0 = get_color_values(color_0, color0_hex, color_mapping)
+ color1 = get_color_values(color_1, color1_hex, color_mapping)
+ bg_color = get_color_values(background_color, bg_color_hex, color_mapping)
+ outline_color = get_color_values(outline_color, outline_color_hex, color_mapping)
+
+ # Convert multiline binary pattern to a 2D list
+ rows = binary_pattern.strip().split('\n')
+ grid = [[int(bit) for bit in row.strip()] for row in rows]
+
+ # Calculate the size of each square
+ square_width = width // len(rows[0])
+ square_height = height // len(rows)
+
+ # Create a new image
+ image = Image.new("RGB", (width, height), color=bg_color)
+ draw = ImageDraw.Draw(image)
+
+ x_jitter = 0
+ y_jitter = 0
+
+ # Draw grid based on the binary pattern
+ for row_index, row in enumerate(grid):
+ for col_index, bit in enumerate(row):
+ if jitter_distance != 0:
+ x_jitter = random.uniform(0, jitter_distance)
+ y_jitter = random.uniform(0, jitter_distance)
+ x1 = col_index * square_width + x_jitter
+ y1 = row_index * square_height + y_jitter
+ x2 = x1 + square_width + x_jitter
+ y2 = y1 + square_height + y_jitter
+
+ # Draw black square if bit is 1, else draw white square
+ color = color1 if bit == 1 else color0
+ draw.rectangle([x1, y1, x2, y2], fill=color, outline=outline_color, width=outline_thickness)
+
+ image_out = pil2tensor(image)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Text-Nodes#cr-binary-pattern"
+
+ # Convert the PIL image back to a torch tensor
+ return (image_out, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+'''
+NODE_CLASS_MAPPINGS = {
+ "CR Simple Binary Pattern Simple": CR Binary Pattern Simple,
+ "CR Binary Pattern": CR_BinaryPattern,
+}
+'''
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/pil_template.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/pil_template.py
new file mode 100644
index 0000000000000000000000000000000000000000..c668622ee845673b3382d72a80bd91cb29e146e6
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/pil_template.py
@@ -0,0 +1,412 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# Comfyroll Custom Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+
+import numpy as np
+import torch
+import os
+from PIL import Image, ImageDraw, ImageOps, ImageFont
+from ..categories import icons
+from ..config import color_mapping, COLORS
+from .graphics_functions import (hex_to_rgb,
+ get_color_values,
+ get_font_size,
+ draw_text_on_image,
+ crop_and_resize_image,
+ create_and_paste_panel)
+
+font_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "fonts")
+file_list = [f for f in os.listdir(font_dir) if os.path.isfile(os.path.join(font_dir, f)) and f.lower().endswith(".ttf")]
+
+#---------------------------------------------------------------------------------------------------------------------#
+
+ALIGN_OPTIONS = ["top", "center", "bottom"]
+ROTATE_OPTIONS = ["text center", "image center"]
+JUSTIFY_OPTIONS = ["left", "center", "right"]
+PERSPECTIVE_OPTIONS = ["top", "bottom", "left", "right"]
+
+#---------------------------------------------------------------------------------------------------------------------#
+
+def tensor2pil(image):
+ return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
+
+def pil2tensor(image):
+ return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_SimpleMemeTemplate:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ font_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "fonts")
+ file_list = [f for f in os.listdir(font_dir) if os.path.isfile(os.path.join(font_dir, f)) and f.lower().endswith(".ttf")]
+ bar_opts = ["no bars", "top", "bottom", "top and bottom"]
+ simple_meme_presets = ["custom",
+ "One Does Not Simply ... MEME IN COMFY",
+ "This is fine.",
+ "Good Morning ... No Such Thing!"]
+
+ return {"required": {
+ "image": ("IMAGE",),
+ "preset": (simple_meme_presets,),
+ "text_top": ("STRING", {"multiline": True, "default": "text_top"}),
+ "text_bottom": ("STRING", {"multiline": True, "default": "text_bottom"}),
+ "font_name": (file_list,),
+ "max_font_size": ("INT", {"default": 150, "min": 20, "max": 2048}),
+ "font_color": (COLORS,),
+ "font_outline": (["none", "thin", "thick", "extra thick"],),
+ "bar_color": (COLORS,),
+ "bar_options": (bar_opts,),
+ },
+ "optional": {
+ "font_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ "bar_color_hex": ("STRING", {"multiline": False, "default": "#000000"})
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("image", "show_help", )
+ FUNCTION = "make_meme"
+ CATEGORY = icons.get("Comfyroll/Graphics/Template")
+
+ def make_meme(self, image, preset,
+ text_top, text_bottom,
+ font_name, max_font_size, font_color, font_outline,
+ bar_color, bar_options,
+ font_color_hex='#000000', bar_color_hex='#000000'):
+
+ # Get RGB values for the text and bar colors
+ text_color = get_color_values(font_color, font_color_hex, color_mapping)
+ bar_color = get_color_values(bar_color, bar_color_hex, color_mapping)
+
+ total_images = []
+
+ for img in image:
+
+ # Calculate the height factor
+ if bar_options == "top":
+ height_factor = 1.2
+ elif bar_options == "bottom":
+ height_factor = 1.2
+ elif bar_options == "top and bottom":
+ height_factor = 1.4
+ else:
+ height_factor = 1.0
+
+ if preset == "One Does Not Simply ... MEME IN COMFY":
+ text_top = "One Does Not Simply"
+ text_bottom = "MEME IN COMFY"
+ if preset == "This is fine.":
+ text_top = "This is fine."
+ text_bottom = ""
+ if preset == "Good Morning ... No Such Thing!":
+ text_top = "Good Morning"
+ text_bottom = "\"No Such Thing!\""
+
+ # Create PIL images for the image and text bars
+ back_image = tensor2pil(img)
+ size = back_image.width, int(back_image.height * height_factor)
+ result_image = Image.new("RGB", size)
+
+ # Define font settings
+ #font_file = "fonts\\" + str(font_name)
+ font_file = os.path.join("fonts", font_name)
+ resolved_font_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), font_file)
+
+ # Create the drawing context
+ draw = ImageDraw.Draw(result_image)
+
+ # Create two color bars at the top and bottom
+ bar_width = back_image.width
+ bar_height = back_image.height // 5 ### add parameter for this in adv node
+ top_bar = Image.new("RGB", (bar_width, bar_height), bar_color)
+ bottom_bar = Image.new("RGB", (bar_width, bar_height), bar_color)
+
+ # Composite the result image onto the input image
+ if bar_options == "top" or bar_options == "top and bottom":
+ image_out = result_image.paste(back_image, (0, bar_height))
+ else:
+ image_out = result_image.paste(back_image, (0, 0))
+
+ # Get the font size and draw the text
+ if bar_options == "top" or bar_options == "top and bottom":
+ result_image.paste(top_bar, (0, 0))
+ font_top = get_font_size(draw, text_top, bar_width, bar_height, resolved_font_path, max_font_size)
+ draw_text_on_image(draw, 0, bar_width, bar_height, text_top, font_top, text_color, font_outline)
+
+ if bar_options == "bottom" or bar_options == "top and bottom":
+ result_image.paste(bottom_bar, (0, (result_image.height - bar_height)))
+ font_bottom = get_font_size(draw, text_bottom, bar_width, bar_height, resolved_font_path, max_font_size)
+ if bar_options == "bottom":
+ y_position = back_image.height
+ else:
+ y_position = bar_height + back_image.height
+ draw_text_on_image(draw, y_position, bar_width, bar_height, text_bottom, font_bottom, text_color, font_outline)
+
+ # Overlay text on image
+ if bar_options == "bottom" and text_top > "":
+ font_top = get_font_size(draw, text_top, bar_width, bar_height, resolved_font_path, max_font_size)
+ draw_text_on_image(draw, 0, bar_width, bar_height, text_top, font_top, text_color, font_outline)
+
+ if (bar_options == "top" or bar_options == "none") and text_bottom > "":
+ font_bottom = get_font_size(draw, text_bottom, bar_width, bar_height, resolved_font_path, max_font_size)
+ y_position = back_image.height
+ draw_text_on_image(draw, y_position, bar_width, bar_height, text_bottom, font_bottom, text_color, font_outline)
+
+ if bar_options == "no bars" and text_bottom > "":
+ font_bottom = get_font_size(draw, text_bottom, bar_width, bar_height, resolved_font_path, max_font_size)
+ y_position = back_image.height - bar_height
+ draw_text_on_image(draw, y_position, bar_width, bar_height, text_bottom, font_bottom, text_color, font_outline)
+
+ if bar_options == "no bars" and text_top > "":
+ font_top = get_font_size(draw, text_top, bar_width, bar_height, resolved_font_path, max_font_size)
+ draw_text_on_image(draw, 0, bar_width, bar_height, text_top, font_top, text_color, font_outline)
+
+ #image_out = np.array(result_image).astype(np.float32) / 255.0
+ #image_out = torch.from_numpy(image_out).unsqueeze(0)
+
+ # Convert the PIL image back to a torch tensor
+ #return (pil2tensor(image_out), show_help, )
+ #return (image_out, show_help, )
+
+ # Convert to tensor
+ out_image = np.array(result_image.convert("RGB")).astype(np.float32) / 255.0
+ out_image = torch.from_numpy(out_image).unsqueeze(0)
+ total_images.append(out_image)
+
+ # Batch the images
+ images_out = torch.cat(total_images, 0)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Template-Nodes#cr-simple-meme-template"
+
+ return (images_out, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_SimpleBanner:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ font_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "fonts")
+ file_list = [f for f in os.listdir(font_dir) if os.path.isfile(os.path.join(font_dir, f)) and f.lower().endswith(".ttf")]
+
+ return {"required": {
+ "image": ("IMAGE",),
+ #"image_opacity": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.1}),
+ "banner_text": ("STRING", {"multiline": True, "default": "text"}),
+ "font_name": (file_list,),
+ "max_font_size": ("INT", {"default": 150, "min": 20, "max": 2048}),
+ "font_color": (COLORS,),
+ "outline_thickness": ("INT", {"default": 0, "min": 0, "max": 500}),
+ "outline_color": (COLORS,),
+ #"text_opacity": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.1}),
+ #"drop_shadow_angle": ("INT", {"default": 0, "min": 0, "max": 500}),
+ #"drop_shadow_offset": ("INT", {"default": 0, "min": 0, "max": 500}),
+ #"drop_shadow_color": (COLORS,),
+ #"drop_shadow_opacity": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.1}),
+ #"wrap_text": (["true", "false"],),
+ "margin_size": ("INT", {"default": 0, "min": 0, "max": 500}),
+ },
+ "optional": {
+ "font_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ "outline_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("image", "show_help", )
+ FUNCTION = "make_banner"
+ CATEGORY = icons.get("Comfyroll/Graphics/Template")
+
+ def make_banner(self, image, banner_text,
+ font_name, max_font_size, font_color,
+ outline_thickness, outline_color, margin_size,
+ font_color_hex='#000000', outline_color_hex='#000000'):
+
+ # Get RGB values for the text and bar colors
+ text_color = get_color_values(font_color, font_color_hex, color_mapping)
+ outline_color = get_color_values(outline_color, outline_color_hex, color_mapping)
+
+ total_images = []
+
+ for img in image:
+
+ # Create PIL images for the image and text bars
+ back_image = tensor2pil(img).convert("RGBA")
+ size = back_image.width, back_image.height
+ #result_image = Image.new("RGB", size)
+
+ # Define font settings
+ font_file = os.path.join("fonts", font_name)
+ resolved_font_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), font_file)
+
+ # Create the drawing context
+ draw = ImageDraw.Draw(back_image)
+
+ area_width = back_image.width - (margin_size * 2)
+ area_height = back_image.width - (margin_size * 2)
+
+ # Get the font size and draw the text
+ font = get_font_size(draw, banner_text, area_width, area_height, resolved_font_path, max_font_size)
+
+ x = back_image.width // 2
+ y = back_image.height // 2
+
+ if outline_thickness > 0:
+ draw.text((x, y), banner_text, fill=text_color, font=font, anchor='mm', stroke_width=outline_thickness, stroke_fill=outline_color)
+ else:
+ draw.text((x, y), banner_text, fill=text_color, font=font, anchor='mm')
+
+ # Convert to tensor
+ out_image = np.array(back_image.convert("RGB")).astype(np.float32) / 255.0
+ out_image = torch.from_numpy(out_image).unsqueeze(0)
+ total_images.append(out_image)
+
+ # Batch the images
+ images_out = torch.cat(total_images, 0)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Template-Nodes#cr-simple-banner"
+
+ return (images_out, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ComicPanelTemplates:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ directions = ["left to right", "right to left"]
+
+ templates = ["custom",
+ "G22", "G33",
+ "H2", "H3",
+ "H12", "H13",
+ "H21", "H23",
+ "H31", "H32",
+ "V2", "V3",
+ "V12", "V13",
+ "V21", "V23",
+ "V31", "V32"]
+
+ return {"required": {
+ "page_width": ("INT", {"default": 512, "min": 8, "max": 4096}),
+ "page_height": ("INT", {"default": 512, "min": 8, "max": 4096}),
+ "template": (templates,),
+ "reading_direction": (directions,),
+ "border_thickness": ("INT", {"default": 5, "min": 0, "max": 1024}),
+ "outline_thickness": ("INT", {"default": 2, "min": 0, "max": 1024}),
+ "outline_color": (COLORS,),
+ "panel_color": (COLORS,),
+ "background_color": (COLORS,),
+ },
+ "optional": {
+ "images": ("IMAGE",),
+ "custom_panel_layout": ("STRING", {"multiline": False, "default": "H123"}),
+ "outline_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ "panel_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ "bg_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("image", "show_help", )
+ FUNCTION = "layout"
+ CATEGORY = icons.get("Comfyroll/Graphics/Template")
+
+ def layout(self, page_width, page_height, template, reading_direction,
+ border_thickness, outline_thickness,
+ outline_color, panel_color, background_color,
+ images=None, custom_panel_layout='G44',
+ outline_color_hex='#000000', panel_color_hex='#000000', bg_color_hex='#000000'):
+
+ panels = []
+ k = 0
+ len_images = 0
+
+ # Convert tensor images to PIL
+ if images is not None:
+ images = [tensor2pil(image) for image in images]
+ len_images = len(images)
+
+ # Get RGB values for the text and background colors
+ outline_color = get_color_values(outline_color, outline_color_hex, color_mapping)
+ panel_color = get_color_values(panel_color, panel_color_hex, color_mapping)
+ bg_color = get_color_values(background_color, bg_color_hex, color_mapping)
+
+ # Create page and apply bg color
+ size = (page_width - (2 * border_thickness), page_height - (2 * border_thickness))
+ page = Image.new('RGB', size, bg_color)
+ draw = ImageDraw.Draw(page)
+
+ if template == "custom":
+ template = custom_panel_layout
+
+ # Calculate panel positions and add to bg image
+ first_char = template[0]
+ if first_char == "G":
+ rows = int(template[1])
+ columns = int(template[2])
+ panel_width = (page.width - (2 * columns * (border_thickness + outline_thickness))) // columns
+ panel_height = (page.height - (2 * rows * (border_thickness + outline_thickness))) // rows
+ # Row loop
+ for i in range(rows):
+ # Column Loop
+ for j in range(columns):
+ # Draw the panel
+ create_and_paste_panel(page, border_thickness, outline_thickness,
+ panel_width, panel_height, page.width,
+ panel_color, bg_color, outline_color,
+ images, i, j, k, len_images, reading_direction)
+ k += 1
+
+ elif first_char == "H":
+ rows = len(template) - 1
+ panel_height = (page.height - (2 * rows * (border_thickness + outline_thickness))) // rows
+ for i in range(rows):
+ columns = int(template[i+1])
+ panel_width = (page.width - (2 * columns * (border_thickness + outline_thickness))) // columns
+ for j in range(columns):
+ # Draw the panel
+ create_and_paste_panel(page, border_thickness, outline_thickness,
+ panel_width, panel_height, page.width,
+ panel_color, bg_color, outline_color,
+ images, i, j, k, len_images, reading_direction)
+ k += 1
+
+ elif first_char == "V":
+ columns = len(template) - 1
+ panel_width = (page.width - (2 * columns * (border_thickness + outline_thickness))) // columns
+ for j in range(columns):
+ rows = int(template[j+1])
+ panel_height = (page.height - (2 * rows * (border_thickness + outline_thickness))) // rows
+ for i in range(rows):
+ # Draw the panel
+ create_and_paste_panel(page, border_thickness, outline_thickness,
+ panel_width, panel_height, page.width,
+ panel_color, bg_color, outline_color,
+ images, i, j, k, len_images, reading_direction)
+ k += 1
+
+ # Add a border to the page
+ if border_thickness > 0:
+ page = ImageOps.expand(page, border_thickness, bg_color)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Template-Nodes#cr-comic-panel-templates"
+
+ return (pil2tensor(page), show_help, )
+
+
+#---------------------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+'''
+NODE_CLASS_MAPPINGS = {
+ "CR Simple Meme Template": CR_SimpleMemeTemplate,
+ "CR Simple Banner": CR_SimpleBanner,
+ "CR Comic Panel Templates": CR_ComicPanelTemplates,
+}
+'''
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/pil_text.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/pil_text.py
new file mode 100644
index 0000000000000000000000000000000000000000..ef2fab495b5df9ccbad09c0ef687a774474b1bef
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/pil_text.py
@@ -0,0 +1,556 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# Comfyroll Custom Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+
+import numpy as np
+import torch
+import os
+from PIL import Image, ImageDraw, ImageOps, ImageFont
+from ..categories import icons
+from ..config import color_mapping, COLORS
+from .graphics_functions import (draw_masked_text,
+ hex_to_rgb,
+ draw_text_on_image,
+ get_font_size,
+ get_text_size,
+ get_color_values,
+ reduce_opacity)
+
+'''
+try:
+ from bidi.algorithm import get_display
+except ImportError:
+ import subprocess
+ subprocess.check_call(['python', '-m', 'pip', 'install', 'python_bidi'])
+
+try:
+ import arabic_reshaper
+except ImportError:
+ import subprocess
+ subprocess.check_call(['python', '-m', 'pip', 'install', 'arabic_reshaper'])
+'''
+
+def get_offset_for_true_mm(text, draw, font):
+ anchor_bbox = draw.textbbox((0, 0), text, font=font, anchor='lt')
+ anchor_center = (anchor_bbox[0] + anchor_bbox[2]) // 2, (anchor_bbox[1] + anchor_bbox[3]) // 2
+ mask_bbox = font.getmask(text).getbbox()
+ mask_center = (mask_bbox[0] + mask_bbox[2]) // 2, (mask_bbox[1] + mask_bbox[3]) // 2
+ return anchor_center[0] - mask_center[0], anchor_center[1] - mask_center[1]
+
+#---------------------------------------------------------------------------------------------------------------------#
+
+ALIGN_OPTIONS = ["center", "top", "bottom"]
+ROTATE_OPTIONS = ["text center", "image center"]
+JUSTIFY_OPTIONS = ["center", "left", "right"]
+PERSPECTIVE_OPTIONS = ["top", "bottom", "left", "right"]
+
+#---------------------------------------------------------------------------------------------------------------------#
+
+def tensor2pil(image):
+ return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
+
+def pil2tensor(image):
+ return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_OverlayText:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ font_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "fonts")
+ file_list = [f for f in os.listdir(font_dir) if os.path.isfile(os.path.join(font_dir, f)) and f.lower().endswith(".ttf")]
+
+ return {"required": {
+ "image": ("IMAGE",),
+ "text": ("STRING", {"multiline": True, "default": "text"}),
+ "font_name": (file_list,),
+ "font_size": ("INT", {"default": 50, "min": 1, "max": 1024}),
+ "font_color": (COLORS,),
+ "align": (ALIGN_OPTIONS,),
+ "justify": (JUSTIFY_OPTIONS,),
+ "margins": ("INT", {"default": 0, "min": -1024, "max": 1024}),
+ "line_spacing": ("INT", {"default": 0, "min": -1024, "max": 1024}),
+ "position_x": ("INT", {"default": 0, "min": -4096, "max": 4096}),
+ "position_y": ("INT", {"default": 0, "min": -4096, "max": 4096}),
+ "rotation_angle": ("FLOAT", {"default": 0.0, "min": -360.0, "max": 360.0, "step": 0.1}),
+ "rotation_options": (ROTATE_OPTIONS,),
+ },
+ "optional": {"font_color_hex": ("STRING", {"multiline": False, "default": "#000000"})
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING",)
+ RETURN_NAMES = ("IMAGE", "show_help",)
+ FUNCTION = "overlay_text"
+ CATEGORY = icons.get("Comfyroll/Graphics/Text")
+
+ def overlay_text(self, image, text, font_name, font_size, font_color,
+ margins, line_spacing,
+ position_x, position_y,
+ align, justify,
+ rotation_angle, rotation_options,
+ font_color_hex='#000000'):
+
+ # Get RGB values for the text color
+ text_color = get_color_values(font_color, font_color_hex, color_mapping)
+
+ # Convert tensor images
+ image_3d = image[0, :, :, :]
+
+ # Create PIL images for the text and background layers and text mask
+ back_image = tensor2pil(image_3d)
+ text_image = Image.new('RGB', back_image.size, text_color)
+ text_mask = Image.new('L', back_image.size)
+
+ # Draw the text on the text mask
+ rotated_text_mask = draw_masked_text(text_mask, text, font_name, font_size,
+ margins, line_spacing,
+ position_x, position_y,
+ align, justify,
+ rotation_angle, rotation_options)
+
+ # Composite the text image onto the background image using the rotated text mask
+ image_out = Image.composite(text_image, back_image, rotated_text_mask)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Text-Nodes#cr-overlay-text"
+
+ # Convert the PIL image back to a torch tensor
+ return (pil2tensor(image_out), show_help,)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_DrawText:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ font_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "fonts")
+ file_list = [f for f in os.listdir(font_dir) if os.path.isfile(os.path.join(font_dir, f)) and f.lower().endswith(".ttf")]
+
+ return {"required": {
+ "image_width": ("INT", {"default": 512, "min": 64, "max": 2048}),
+ "image_height": ("INT", {"default": 512, "min": 64, "max": 2048}),
+ "text": ("STRING", {"multiline": True, "default": "text"}),
+ "font_name": (file_list,),
+ "font_size": ("INT", {"default": 50, "min": 1, "max": 1024}),
+ "font_color": (COLORS,),
+ "background_color": (COLORS,),
+ "align": (ALIGN_OPTIONS,),
+ "justify": (JUSTIFY_OPTIONS,),
+ "margins": ("INT", {"default": 0, "min": -1024, "max": 1024}),
+ "line_spacing": ("INT", {"default": 0, "min": -1024, "max": 1024}),
+ "position_x": ("INT", {"default": 0, "min": -4096, "max": 4096}),
+ "position_y": ("INT", {"default": 0, "min": -4096, "max": 4096}),
+ "rotation_angle": ("FLOAT", {"default": 0.0, "min": -360.0, "max": 360.0, "step": 0.1}),
+ "rotation_options": (ROTATE_OPTIONS,),
+ },
+ "optional": {
+ "font_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ "bg_color_hex": ("STRING", {"multiline": False, "default": "#000000"})
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING",)
+ RETURN_NAMES = ("IMAGE", "show_help",)
+ FUNCTION = "draw_text"
+ CATEGORY = icons.get("Comfyroll/Graphics/Text")
+
+ def draw_text(self, image_width, image_height, text,
+ font_name, font_size, font_color,
+ background_color,
+ margins, line_spacing,
+ position_x, position_y,
+ align, justify,
+ rotation_angle, rotation_options,
+ font_color_hex='#000000', bg_color_hex='#000000'):
+
+ # Get RGB values for the text and background colors
+ text_color = get_color_values(font_color, font_color_hex, color_mapping)
+ bg_color = get_color_values(background_color, bg_color_hex, color_mapping)
+
+ # Create PIL images for the text and background layers and text mask
+ size = (image_width, image_height)
+ text_image = Image.new('RGB', size, text_color)
+ back_image = Image.new('RGB', size, bg_color)
+ text_mask = Image.new('L', back_image.size)
+
+ # Draw the text on the text mask
+ rotated_text_mask = draw_masked_text(text_mask, text, font_name, font_size,
+ margins, line_spacing,
+ position_x, position_y,
+ align, justify,
+ rotation_angle, rotation_options)
+
+ # Composite the text image onto the background image using the rotated text mask
+ image_out = Image.composite(text_image, back_image, rotated_text_mask)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Text-Nodes#cr-draw-text"
+
+ # Convert the PIL image back to a torch tensor
+ return (pil2tensor(image_out), show_help,)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_MaskText:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ font_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "fonts")
+ file_list = [f for f in os.listdir(font_dir) if os.path.isfile(os.path.join(font_dir, f)) and f.lower().endswith(".ttf")]
+
+ return {"required": {
+ "image": ("IMAGE",),
+ "text": ("STRING", {"multiline": True, "default": "text"}),
+ "font_name": (file_list,),
+ "font_size": ("INT", {"default": 50, "min": 1, "max": 1024}),
+ "background_color": (COLORS,),
+ "align": (ALIGN_OPTIONS,),
+ "justify": (JUSTIFY_OPTIONS,),
+ "margins": ("INT", {"default": 0, "min": -1024, "max": 1024}),
+ "line_spacing": ("INT", {"default": 0, "min": -1024, "max": 1024}),
+ "position_x": ("INT", {"default": 0, "min": -4096, "max": 4096}),
+ "position_y": ("INT", {"default": 0, "min": -4096, "max": 4096}),
+ "rotation_angle": ("FLOAT", {"default": 0.0, "min": -360.0, "max": 360.0, "step": 0.1}),
+ "rotation_options": (ROTATE_OPTIONS,),
+ },
+ "optional": {
+ "bg_color_hex": ("STRING", {"multiline": False, "default": "#000000"})
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING",)
+ RETURN_NAMES = ("IMAGE", "show_help",)
+ FUNCTION = "mask_text"
+ CATEGORY = icons.get("Comfyroll/Graphics/Text")
+
+ def mask_text(self, image, text, font_name, font_size,
+ margins, line_spacing,
+ position_x, position_y, background_color,
+ align, justify,
+ rotation_angle, rotation_options,
+ bg_color_hex='#000000'):
+
+ # Get RGB values for the background color
+ bg_color = get_color_values(background_color, bg_color_hex, color_mapping)
+
+ # Convert tensor images
+ image_3d = image[0, :, :, :]
+
+ # Create PIL images for the text and background layers and text mask
+ text_image = tensor2pil(image_3d)
+ text_mask = Image.new('L', text_image.size)
+ background_image = Image.new('RGB', text_mask.size, bg_color)
+
+ # Draw the text on the text mask
+ rotated_text_mask = draw_masked_text(text_mask, text, font_name, font_size,
+ margins, line_spacing,
+ position_x, position_y,
+ align, justify,
+ rotation_angle, rotation_options)
+
+ # Invert the text mask (so the text is white and the background is black)
+ text_mask = ImageOps.invert(rotated_text_mask)
+
+ # Composite the text image onto the background image using the inverted text mask
+ image_out = Image.composite(background_image, text_image, text_mask)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Text-Nodes#cr-mask-text"
+
+ # Convert the PIL image back to a torch tensor
+ return (pil2tensor(image_out), show_help,)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_CompositeText:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ font_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "fonts")
+ file_list = [f for f in os.listdir(font_dir) if os.path.isfile(os.path.join(font_dir, f)) and f.lower().endswith(".ttf")]
+
+ return {"required": {
+ "image_text": ("IMAGE",),
+ "image_background": ("IMAGE",),
+ "text": ("STRING", {"multiline": True, "default": "text"}),
+ "font_name": (file_list,),
+ "font_size": ("INT", {"default": 50, "min": 1, "max": 1024}),
+ "align": (ALIGN_OPTIONS,),
+ "justify": (JUSTIFY_OPTIONS,),
+ "margins": ("INT", {"default": 0, "min": -1024, "max": 1024}),
+ "line_spacing": ("INT", {"default": 0, "min": -1024, "max": 1024}),
+ "position_x": ("INT", {"default": 0, "min": -4096, "max": 4096}),
+ "position_y": ("INT", {"default": 0, "min": -4096, "max": 4096}),
+ "rotation_angle": ("FLOAT", {"default": 0.0, "min": -360.0, "max": 360.0, "step": 0.1}),
+ "rotation_options": (ROTATE_OPTIONS,),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING",)
+ RETURN_NAMES = ("IMAGE", "show_help",)
+ FUNCTION = "composite_text"
+ CATEGORY = icons.get("Comfyroll/Graphics/Text")
+
+ def composite_text(self, image_text, image_background, text,
+ font_name, font_size,
+ margins, line_spacing,
+ position_x, position_y,
+ align, justify,
+ rotation_angle, rotation_options):
+
+ # Convert tensor images
+ image_text_3d = image_text[0, :, :, :]
+ image_back_3d = image_background[0, :, :, :]
+
+ # Create PIL images for the text and background layers and text mask
+ text_image = tensor2pil(image_text_3d)
+ back_image = tensor2pil(image_back_3d)
+ text_mask = Image.new('L', back_image.size)
+
+ # Draw the text on the text mask
+ rotated_text_mask = draw_masked_text(text_mask, text, font_name, font_size,
+ margins, line_spacing,
+ position_x, position_y,
+ align, justify,
+ rotation_angle, rotation_options)
+
+ # Composite the text image onto the background image using the rotated text mask
+ image_out = Image.composite(text_image, back_image, rotated_text_mask)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Text-Nodes#cr-composite-text"
+
+ # Convert the PIL image back to a torch tensor
+ return (pil2tensor(image_out), show_help,)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ArabicTextRTL:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ return {"required": {
+ "arabic_text": ("STRING", {"multiline": True, "default": "شمس"}),
+ }
+ }
+
+ RETURN_TYPES = ("STRING", "STRING", )
+ RETURN_NAMES = ("arabic_text_rtl", "show help", )
+ FUNCTION = "adjust_arabic_to_rtl"
+ CATEGORY = icons.get("Comfyroll/Graphics/Text")
+
+ def adjust_arabic_to_rtl(self, arabic_text):
+ """
+ Adjust Arabic text to read from right to left (RTL).
+
+ Args:
+ arabic_text (str): The Arabic text to be adjusted.
+
+ Returns:
+ str: The adjusted Arabic text in RTL format.
+ """
+
+ arabic_text_reshaped = arabic_reshaper.reshape(arabic_text)
+ rtl_text = get_display(arabic_text_reshaped)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Text-Nodes#cr-arabic-text-rtl"
+
+ return (rtl_text, show_help,)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_SimpleTextWatermark:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ font_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "fonts")
+ file_list = [f for f in os.listdir(font_dir) if os.path.isfile(os.path.join(font_dir, f)) and f.lower().endswith(".ttf")]
+
+ ALIGN_OPTIONS = ["center", "top left", "top center", "top right", "bottom left", "bottom center", "bottom right"]
+
+ return {"required": {
+ "image": ("IMAGE",),
+ "text": ("STRING", {"multiline": False, "default": "@ your name"}),
+ "align": (ALIGN_OPTIONS,),
+ "opacity": ("FLOAT", {"default": 0.30, "min": 0.00, "max": 1.00, "step": 0.01}),
+ "font_name": (file_list,),
+ "font_size": ("INT", {"default": 50, "min": 1, "max": 1024}),
+ "font_color": (COLORS,),
+ "x_margin": ("INT", {"default": 20, "min": -1024, "max": 1024}),
+ "y_margin": ("INT", {"default": 20, "min": -1024, "max": 1024}),
+ },
+ "optional": {
+ "font_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("IMAGE", "show_help", )
+ FUNCTION = "overlay_text"
+ CATEGORY = icons.get("Comfyroll/Graphics/Text")
+
+ def overlay_text(self, image, text, align,
+ font_name, font_size, font_color,
+ opacity, x_margin, y_margin, font_color_hex='#000000'):
+
+ # Get RGB values for the text color
+ text_color = get_color_values(font_color, font_color_hex, color_mapping)
+
+ total_images = []
+
+ for img in image:
+
+ # Create PIL images for the background layer
+ img = tensor2pil(img)
+
+ textlayer = Image.new("RGBA", img.size)
+ draw = ImageDraw.Draw(textlayer)
+
+ # Load the font
+ font_file = "fonts\\" + str(font_name)
+ resolved_font_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), font_file)
+ font = ImageFont.truetype(str(resolved_font_path), size=font_size)
+
+ # Get the size of the text
+ textsize = get_text_size(draw, text, font)
+
+ # Calculate the position to place the text based on the alignment
+ if align == 'center':
+ textpos = [(img.size[0] - textsize[0]) // 2, (img.size[1] - textsize[1]) // 2]
+ elif align == 'top left':
+ textpos = [x_margin, y_margin]
+ elif align == 'top center':
+ textpos = [(img.size[0] - textsize[0]) // 2, y_margin]
+ elif align == 'top right':
+ textpos = [img.size[0] - textsize[0] - x_margin, y_margin]
+ elif align == 'bottom left':
+ textpos = [x_margin, img.size[1] - textsize[1] - y_margin]
+ elif align == 'bottom center':
+ textpos = [(img.size[0] - textsize[0]) // 2, img.size[1] - textsize[1] - y_margin]
+ elif align == 'bottom right':
+ textpos = [img.size[0] - textsize[0] - x_margin, img.size[1] - textsize[1] - y_margin]
+
+ # Draw the text on the text layer
+ draw.text(textpos, text, font=font, fill=text_color)
+
+ # Adjust the opacity of the text layer if needed
+ if opacity != 1:
+ textlayer = reduce_opacity(textlayer, opacity)
+
+ # Composite the text layer on top of the original image
+ out_image = Image.composite(textlayer, img, textlayer)
+
+ # convert to tensor
+ out_image = np.array(out_image.convert("RGB")).astype(np.float32) / 255.0
+ out_image = torch.from_numpy(out_image).unsqueeze(0)
+ total_images.append(out_image)
+
+ images_out = torch.cat(total_images, 0)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Text-Nodes#cr-simple-text-watermark"
+
+ # Convert the PIL image back to a torch tensor
+ return (images_out, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_SystemTrueTypeFont:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(cls):
+
+ system_root = os.environ.get('SystemRoot')
+ font_dir = os.path.join(system_root, 'Fonts')
+ file_list = [f for f in os.listdir(font_dir) if os.path.isfile(os.path.join(font_dir, f)) and f.lower().endswith(".ttf")]
+
+ return {"required": {
+ "font_name": (file_list,),
+ "font_size": ("INT", {"default": 50, "min": 1, "max": 1024}),
+ }
+ }
+
+ RETURN_TYPES = ("FONT", "IMAGE", "STRING",)
+ RETURN_NAMES = ("FONT", "preview", "show_help",)
+ FUNCTION = "truetype_font"
+ CATEGORY = icons.get("Comfyroll/Graphics/Text")
+
+ def truetype_font(self, font_name, font_size):
+
+ # Construct the path to the Fonts directory
+ system_root = os.environ.get('SystemRoot')
+ fonts_directory = os.path.join(system_root, 'Fonts')
+ resolved_font_path = os.path.join(fonts_directory, font_name)
+ font_out = ImageFont.truetype(str(resolved_font_path), size=font_size)
+
+ # Create a blank image with a white background
+ image = Image.new('RGB', (300, 100), 'white')
+ draw = ImageDraw.Draw(image)
+
+ x = image.width // 2
+ y = image.height // 2
+
+ text = "abcdefghij"
+
+ # Draw the text on the image
+ draw.text((x, y), text, font=font_out, fill='black', anchor='mm')
+
+ preview = pil2tensor(image)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Text-Nodes#cr-system-truetype-font"
+
+ return (font_out, preview, show_help,)
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_DisplayFont:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ return {"required": {
+ "font": ("FONT",),
+ "text": ("STRING", {"multiline": False, "default": "abcdefghij"}),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "show_help", )
+ OUTPUT_NODE = True
+ FUNCTION = "draw_font"
+ CATEGORY = icons.get("Comfyroll/Graphics/Text")
+
+ def draw_font(self, font, text):
+ # Create a blank image with a white background
+ image = Image.new('RGB', (300, 100), 'white')
+ draw = ImageDraw.Draw(image)
+
+ # Calculate the position to center the text
+ x = image.width // 2
+ y = image.height // 2
+
+ # Draw the text on the image
+ draw.text((x, y), text, font=font, fill='black', anchor='mm')
+
+ # Convert the PIL image back to a torch tensor
+ image_out = pil2tensor(image)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Text-Nodes#cr-display-font"
+
+ return (image_out, show_help,)
+
+#---------------------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+'''
+NODE_CLASS_MAPPINGS = {
+ "CR Overlay Text":CR_OverlayText,
+ "CR Draw Text":CR_DrawText,
+ "CR Mask Text":CR_MaskText,
+ "CR Composite Text":CR_CompositeText,
+ "CR Draw Perspective Text":CR_DrawPerspectiveText,
+ "CR Arabic Text RTL": CR_ArabicTextRTL,
+ "CR Simple Text Watermark": CR_SimpleTextWatermark,
+ "CR System TrueType Font": CR_SystemTrueTypeFont,
+ "CR Display Font": CR_DisplayFont,
+}
+'''
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/pipe.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/pipe.py
new file mode 100644
index 0000000000000000000000000000000000000000..0636d9152c0a8f03d2c9077967f08651b861fe52
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/pipe.py
@@ -0,0 +1,266 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# Comfyroll Pipe Nodes by Akatsuzi https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+# based on Tiny Terra nodes
+#---------------------------------------------------------------------------------------------------------------------#
+
+from ..categories import icons
+
+#---------------------------------------------------------------------------------------------------------------------#
+# MODULE NODES
+#---------------------------------------------------------------------------------------------------------------------#
+
+class CR_ModulePipeLoader:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ #"model": ("MODEL",),
+ },
+ "optional": {
+ "model": ("MODEL",),
+ "pos": ("CONDITIONING",),
+ "neg": ("CONDITIONING",),
+ "latent": ("LATENT",),
+ "vae": ("VAE",),
+ "clip": ("CLIP",),
+ "controlnet": ("CONTROL_NET",),
+ "image": ("IMAGE",),
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff})
+ },
+ }
+
+ RETURN_TYPES = ("PIPE_LINE", "STRING", )
+ RETURN_NAMES = ("pipe", "show_help", )
+ FUNCTION = "flush"
+ CATEGORY = icons.get("Comfyroll/Pipe/Module")
+
+ def flush(self, model=0, pos=0, neg=0, latent=0, vae=0, clip=0, controlnet=0, image=0, seed=0):
+ pipe_line = (model, pos, neg, latent, vae, clip, controlnet, image, seed)
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Pipe-Nodes#cr-module-pipe-loader"
+ return (pipe_line, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ModuleInput:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {"pipe": ("PIPE_LINE",)},
+ }
+
+ RETURN_TYPES = ("PIPE_LINE", "MODEL", "CONDITIONING", "CONDITIONING", "LATENT", "VAE", "CLIP", "CONTROL_NET", "IMAGE", "INT", "STRING", )
+ RETURN_NAMES = ("pipe", "model", "pos", "neg", "latent", "vae", "clip", "controlnet", "image", "seed", "show_help", )
+ FUNCTION = "flush"
+
+ CATEGORY = icons.get("Comfyroll/Pipe/Module")
+
+ def flush(self, pipe):
+ model, pos, neg, latent, vae, clip, controlnet, image, seed = pipe
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Pipe-Nodes#cr-module-input"
+ return (pipe, model, pos, neg, latent, vae, clip, controlnet, image, seed, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ModuleOutput:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {"pipe": ("PIPE_LINE",)},
+ "optional": {
+ "model": ("MODEL",),
+ "pos": ("CONDITIONING",),
+ "neg": ("CONDITIONING",),
+ "latent": ("LATENT",),
+ "vae": ("VAE",),
+ "clip": ("CLIP",),
+ "controlnet": ("CONTROL_NET",),
+ "image": ("IMAGE",),
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff})
+ },
+ }
+
+ RETURN_TYPES = ("PIPE_LINE", "STRING", )
+ RETURN_NAMES = ("pipe", "show_help", )
+ FUNCTION = "flush"
+ CATEGORY = icons.get("Comfyroll/Pipe/Module")
+
+ def flush(self, pipe, model=None, pos=None, neg=None, latent=None, vae=None, clip=None, controlnet=None, image=None, seed=None):
+ new_model, new_pos, new_neg, new_latent, new_vae, new_clip, new_controlnet, new_image, new_seed = pipe
+
+ if model is not None:
+ new_model = model
+
+ if pos is not None:
+ new_pos = pos
+
+ if neg is not None:
+ new_neg = neg
+
+ if latent is not None:
+ new_latent = latent
+
+ if vae is not None:
+ new_vae = vae
+
+ if clip is not None:
+ new_clip = clip
+
+ if controlnet is not None:
+ new_controlnet = controlnet
+
+ if image is not None:
+ new_image = image
+
+ if seed is not None:
+ new_seed = seed
+
+ pipe = new_model, new_pos, new_neg, new_latent, new_vae, new_clip, new_controlnet, new_image, new_seed
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Pipe-Nodes#cr-module-output"
+ return (pipe, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+# PIPE NODES
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ImagePipeIn:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ #"model": ("MODEL",),
+ },
+ "optional": {
+ "image": ("IMAGE",),
+ "width": ("INT", {"default": 512, "min": 64, "max": 2048}),
+ "height": ("INT", {"default": 512, "min": 64, "max": 2048}),
+ "upscale_factor": ("FLOAT", {"default": 1, "min": 1, "max": 2000})
+ },
+ }
+
+ RETURN_TYPES = ("PIPE_LINE", "STRING", )
+ RETURN_NAMES = ("pipe", "show_help", )
+ FUNCTION = "flush"
+ CATEGORY = icons.get("Comfyroll/Pipe/Image")
+
+ def flush(self, image=0, width=0, height=0, upscale_factor=0):
+ pipe_line = (image, width, height, upscale_factor)
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Pipe-Nodes#cr-image-pipe-in"
+ return (pipe_line, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ImagePipeEdit:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {"pipe": ("PIPE_LINE",)},
+ "optional": {
+ "image": ("IMAGE",),
+ "width": ("INT", {"default": 512, "min": 64, "max": 2048}),
+ "height": ("INT", {"default": 512, "min": 64, "max": 2048}),
+ "upscale_factor": ("FLOAT", {"default": 1, "min": 1, "max": 2000})
+ },
+ }
+
+ RETURN_TYPES = ("PIPE_LINE", "STRING", )
+ RETURN_NAMES = ("pipe", "show_help", )
+ FUNCTION = "flush"
+ CATEGORY = icons.get("Comfyroll/Pipe/Image")
+
+ def flush(self, pipe, image=None, width=None, height=None, upscale_factor=None):
+ new_image, new_width, new_height, new_upscale_factor = pipe
+
+ if image is not None:
+ new_image = image
+
+ if width is not None:
+ new_width = width
+
+ if height is not None:
+ new_height = height
+
+ if upscale_factor is not None:
+ new_upscale_factor = upscale_factor
+
+ pipe = new_image, new_width, new_height, new_upscale_factor
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Pipe-Nodes#cr-image-pipe-edit"
+ return (pipe, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_ImagePipeOut:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {"pipe": ("PIPE_LINE",)},
+ }
+
+ RETURN_TYPES = ("PIPE_LINE", "IMAGE", "INT", "INT", "FLOAT", "STRING", )
+ RETURN_NAMES = ("pipe", "image", "width", "height", "upscale_factor", "show_help", )
+ FUNCTION = "flush"
+ CATEGORY = icons.get("Comfyroll/Pipe/Image")
+
+ def flush(self, pipe):
+ #if switch == "Off":
+ #return (pipe, )
+ #else:
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Pipe-Nodes#cr-image-pipe-out"
+ image, width, height, upscale_factor = pipe
+ return (pipe, image, width, height, upscale_factor, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_InputSwitchPipe:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "Input": ("INT", {"default": 1, "min": 1, "max": 2}),
+ "pipe1": ("PIPE_LINE",),
+ "pipe2": ("PIPE_LINE",)
+ }
+ }
+
+ RETURN_TYPES = ("PIPE_LINE", "STRING", )
+ RETURN_NAMES = ("PIPE_LINE", "show_help", )
+ OUTPUT_NODE = True
+ FUNCTION = "InputSwitchPipe"
+ CATEGORY = icons.get("Comfyroll/Pipe")
+
+ def InputSwitchPipe(self, Input, pipe1, pipe2):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Pipe-Nodes#cr-pipe-switch"
+ if Input == 1:
+ return (pipe1, show_help, )
+ else:
+ return (pipe2, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+'''
+NODE_CLASS_MAPPINGS_2 = {
+ "CR Module Pipe Loader":CR_ModulePipeLoader,
+ "CR Module Input":CR_ModuleInput,
+ "CR Module Output":CR_ModuleOutput,
+ "CR Image Pipe In":CR_ImagePipeIn,
+ "CR Image Pipe Edit":CR_ImagePipeEdit,
+ "CR Image Pipe Out":CR_ImagePipeOut,
+ "CR Pipe Switch":CR_InputSwitchPipe,
+}
+'''
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/sdxl.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/sdxl.py
new file mode 100644
index 0000000000000000000000000000000000000000..7db12a08061f74069e897abe08abe0230b5fe6f5
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/sdxl.py
@@ -0,0 +1,206 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# Comfyroll Custom Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+
+import torch
+import numpy as np
+from PIL import Image, ImageEnhance
+import os
+import sys
+import folder_paths
+from nodes import MAX_RESOLUTION, ControlNetApply
+from ..categories import icons
+
+sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), "comfy"))
+
+#---------------------------------------------------------------------------------------------------------------------#
+# SDXL Nodes
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_PromptMixPresets:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required":{
+ },
+ "optional":{
+ "prompt_positive": ("STRING", {"multiline": True, "default": "prompt_pos"}),
+ "prompt_negative": ("STRING", {"multiline": True, "default": "prompt_neg"}),
+ "style_positive": ("STRING", {"multiline": True, "default": "style_pos"}),
+ "style_negative": ("STRING", {"multiline": True, "default": "style_neg"}),
+ "preset": (["default with no style text", "default with style text",
+ "style boost 1", "style boost 2", "style text to refiner"],),
+ },
+ }
+
+ RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", )
+ RETURN_NAMES = ("pos_g", "pos_l", "pos_r", "neg_g", "neg_l", "neg_r", "show_help", )
+ FUNCTION = "mixer"
+ CATEGORY = icons.get("Comfyroll/SDXL")
+
+ def mixer(self, prompt_positive, prompt_negative, style_positive, style_negative, preset):
+ if preset == "default with no style text":
+ pos_g = prompt_positive
+ pos_l = prompt_positive
+ pos_r = prompt_positive
+ neg_g = prompt_negative
+ neg_l = prompt_negative
+ neg_r = prompt_negative
+ elif preset == "default with style text":
+ pos_g = prompt_positive + style_positive
+ pos_l = prompt_positive + style_positive
+ pos_r = prompt_positive + style_positive
+ neg_g = prompt_negative + style_negative
+ neg_l = prompt_negative + style_negative
+ neg_r = prompt_negative + style_negative
+ elif preset == "style boost 1":
+ pos_g = prompt_positive
+ pos_l = style_positive
+ pos_r = prompt_positive
+ neg_g = prompt_negative
+ neg_l = style_negative
+ neg_r = prompt_negative
+ elif preset == "style boost 2":
+ pos_g = style_positive
+ pos_l = prompt_positive
+ pos_r = style_positive
+ neg_g = style_negative
+ neg_l = prompt_negative
+ neg_r = style_negative
+ elif preset == "style text to refiner":
+ pos_g = prompt_positive
+ pos_l = prompt_positive
+ pos_r = style_positive
+ neg_g = prompt_negative
+ neg_l = prompt_negative
+ neg_r = style_negative
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/SDXL-Nodes#cr-sdxl-prompt-mix-presets"
+ return (pos_g, pos_l, pos_r, neg_g, neg_l, neg_r, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_SDXLStyleText:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {
+ "positive_style": ("STRING", {"default": "POS_STYLE", "multiline": True}),
+ "negative_style": ("STRING", {"default": "NEG_STYLE", "multiline": True}),
+ },
+ }
+
+ RETURN_TYPES = ("STRING", "STRING", "STRING", )
+ RETURN_NAMES = ("positive_prompt_text_l", "negative_prompt_text_l" , "show_help", )
+ FUNCTION = "get_value"
+ CATEGORY = icons.get("Comfyroll/SDXL")
+
+ def get_value(self, positive_style, negative_style):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/SDXL-Nodes#cr-sdxl-style-text"
+ return (positive_style, negative_style, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_SDXLBasePromptEncoder:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {
+ "base_clip": ("CLIP", ),
+ "pos_g": ("STRING", {"multiline": True, "default": "POS_G"}),
+ "pos_l": ("STRING", {"multiline": True, "default": "POS_L"}),
+ "neg_g": ("STRING", {"multiline": True, "default": "NEG_G"}),
+ "neg_l": ("STRING", {"multiline": True, "default": "NEG_L"}),
+ "preset": (["preset A", "preset B", "preset C"],),
+ "base_width": ("INT", {"default": 4096.0, "min": 0, "max": MAX_RESOLUTION, "step": 64}),
+ "base_height": ("INT", {"default": 4096.0, "min": 0, "max": MAX_RESOLUTION, "step": 64}),
+ "crop_w": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 64}),
+ "crop_h": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 64}),
+ "target_width": ("INT", {"default": 4096.0, "min": 0, "max": MAX_RESOLUTION, "step": 64}),
+ "target_height": ("INT", {"default": 4096.0, "min": 0, "max": MAX_RESOLUTION, "step": 64}),
+ },
+ }
+
+ RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "STRING", )
+ RETURN_NAMES = ("base_positive", "base_negative", "show_help", )
+ FUNCTION = "encode"
+ CATEGORY = icons.get("Comfyroll/SDXL")
+
+ def encode(self, base_clip, pos_g, pos_l, neg_g, neg_l, base_width, base_height, crop_w, crop_h, target_width, target_height, preset,):
+ empty = base_clip.tokenize("")
+
+ # positive prompt
+ tokens1 = base_clip.tokenize(pos_g)
+ tokens1["l"] = base_clip.tokenize(pos_l)["l"]
+
+ if len(tokens1["l"]) != len(tokens1["g"]):
+ while len(tokens1["l"]) < len(tokens1["g"]):
+ tokens1["l"] += empty["l"]
+ while len(tokens1["l"]) > len(tokens1["g"]):
+ tokens1["g"] += empty["g"]
+
+ cond1, pooled1 = base_clip.encode_from_tokens(tokens1, return_pooled=True)
+ res1 = [[cond1, {"pooled_output": pooled1, "width": base_width, "height": base_height, "crop_w": crop_w, "crop_h": crop_h, "target_width": target_width, "target_height": target_height}]]
+
+ # negative prompt
+ tokens2 = base_clip.tokenize(neg_g)
+ tokens2["l"] = base_clip.tokenize(neg_l)["l"]
+
+ if len(tokens2["l"]) != len(tokens2["g"]):
+ while len(tokens2["l"]) < len(tokens2["g"]):
+ tokens2["l"] += empty["l"]
+ while len(tokens2["l"]) > len(tokens2["g"]):
+ tokens2["g"] += empty["g"]
+
+ cond2, pooled2 = base_clip.encode_from_tokens(tokens2, return_pooled=True)
+ res2 = [[cond2, {"pooled_output": pooled2, "width": base_width, "height": base_height, "crop_w": crop_w, "crop_h": crop_h, "target_width": target_width, "target_height": target_height}]]
+
+ # positive style
+ tokens2 = base_clip.tokenize(pos_l)
+ tokens2["l"] = base_clip.tokenize(neg_l)["l"]
+
+ if len(tokens2["l"]) != len(tokens2["g"]):
+ while len(tokens2["l"]) < len(tokens2["g"]):
+ tokens2["l"] += empty["l"]
+ while len(tokens2["l"]) > len(tokens2["g"]):
+ tokens2["g"] += empty["g"]
+
+ cond2, pooled2 = base_clip.encode_from_tokens(tokens2, return_pooled=True)
+ res3 = [[cond2, {"pooled_output": pooled2, "width": base_width, "height": base_height, "crop_w": crop_w, "crop_h": crop_h, "target_width": target_width, "target_height": target_height}]]
+
+ # negative style
+ tokens2 = base_clip.tokenize(neg_l)
+ tokens2["l"] = base_clip.tokenize(neg_l)["l"]
+
+ if len(tokens2["l"]) != len(tokens2["g"]):
+ while len(tokens2["l"]) < len(tokens2["g"]):
+ tokens2["l"] += empty["l"]
+ while len(tokens2["l"]) > len(tokens2["g"]):
+ tokens2["g"] += empty["g"]
+
+ cond2, pooled2 = base_clip.encode_from_tokens(tokens2, return_pooled=True)
+ res4 = [[cond2, {"pooled_output": pooled2, "width": base_width, "height": base_height, "crop_w": crop_w, "crop_h": crop_h, "target_width": target_width, "target_height": target_height}]]
+
+ if preset == "preset A":
+ base_positive = res1
+ base_negative = res2
+ elif preset == "preset B":
+ base_positive = res3
+ base_negative = res4
+ elif preset == "preset C":
+ base_positive = res1 + res3
+ base_negative = res2 + res4
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/SDXL-Nodes#cr-sdxl-base-prompt-encoder"
+ return (base_positive, base_negative, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+'''
+NODE_CLASS_MAPPINGS = {
+ "CR SDXL Style Text":CR_SDXLStyleText,
+ "CR SDXL Base Prompt Encoder":CR_SDXLBasePromptEncoder,
+ "CR SDXL Prompt Mix Presets":CR_PromptMixPresets,
+}
+'''
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/upscale.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/upscale.py
new file mode 100644
index 0000000000000000000000000000000000000000..4ea2dcec87ef2076979ae26623e1246d7c62b344
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/upscale.py
@@ -0,0 +1,211 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# Comfyroll Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/CR-Animation-Nodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+
+import torch
+import numpy as np
+import folder_paths
+from PIL import Image
+from ..categories import icons
+from .upscale_functions import load_model, upscale_with_model, apply_resize_image
+
+#MAX_RESOLUTION=8192
+
+# PIL to Tensor
+def pil2tensor(image):
+ return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
+
+# Tensor to PIL
+def tensor2pil(image):
+ return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
+
+#---------------------------------------------------------------------------------------------------------------------
+# NODES
+#---------------------------------------------------------------------------------------------------------------------
+# These nodes are based on WAS nodes Image Resize and the Comfy Extras upscale with model nodes
+#---------------------------------------------------------------------------------------------------------------------
+class CR_UpscaleImage:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ resampling_methods = ["lanczos", "nearest", "bilinear", "bicubic"]
+
+ return {"required":
+ {"image": ("IMAGE",),
+ "upscale_model": (folder_paths.get_filename_list("upscale_models"), ),
+ "mode": (["rescale", "resize"],),
+ "rescale_factor": ("FLOAT", {"default": 2, "min": 0.01, "max": 16.0, "step": 0.01}),
+ "resize_width": ("INT", {"default": 1024, "min": 1, "max": 48000, "step": 1}),
+ "resampling_method": (resampling_methods,),
+ "supersample": (["true", "false"],),
+ "rounding_modulus": ("INT", {"default": 8, "min": 8, "max": 1024, "step": 8}),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("IMAGE", "show_help", )
+ FUNCTION = "upscale"
+ CATEGORY = icons.get("Comfyroll/Upscale")
+
+ def upscale(self, image, upscale_model, rounding_modulus=8, loops=1, mode="rescale", supersample='true', resampling_method="lanczos", rescale_factor=2, resize_width=1024):
+
+ # Load upscale model
+ up_model = load_model(upscale_model)
+
+ # Upscale with model
+ up_image = upscale_with_model(up_model, image)
+
+ for img in image:
+ pil_img = tensor2pil(img)
+ original_width, original_height = pil_img.size
+
+ for img in up_image:
+ # Get new size
+ pil_img = tensor2pil(img)
+ upscaled_width, upscaled_height = pil_img.size
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Upscale-Nodes#cr-upscale-image"
+
+ # Return if no rescale needed
+ if upscaled_width == original_width and rescale_factor == 1:
+ return (up_image, show_help)
+
+ # Image resize
+ scaled_images = []
+
+ for img in up_image:
+ scaled_images.append(pil2tensor(apply_resize_image(tensor2pil(img), original_width, original_height, rounding_modulus, mode, supersample, rescale_factor, resize_width, resampling_method)))
+ images_out = torch.cat(scaled_images, dim=0)
+
+ return (images_out, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------
+class CR_MultiUpscaleStack:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ mix_methods = ["Combine", "Average", "Concatenate"]
+ up_models = ["None"] + folder_paths.get_filename_list("upscale_models")
+
+ return {"required":
+ {
+ "switch_1": (["On","Off"],),
+ "upscale_model_1": (up_models, ),
+ "rescale_factor_1": ("FLOAT", {"default": 2, "min": 0.01, "max": 16.0, "step": 0.01}),
+ "switch_2": (["On","Off"],),
+ "upscale_model_2": (up_models, ),
+ "rescale_factor_2": ("FLOAT", {"default": 2, "min": 0.01, "max": 16.0, "step": 0.01}),
+ "switch_3": (["On","Off"],),
+ "upscale_model_3": (up_models, ),
+ "rescale_factor_3": ("FLOAT", {"default": 2, "min": 0.01, "max": 16.0, "step": 0.01}),
+ },
+ "optional": {"upscale_stack": ("UPSCALE_STACK",),
+ }
+ }
+
+ RETURN_TYPES = ("UPSCALE_STACK", "STRING", )
+ RETURN_NAMES = ("UPSCALE_STACK", "show_help", )
+ FUNCTION = "stack"
+ CATEGORY = icons.get("Comfyroll/Upscale")
+
+ def stack(self, switch_1, upscale_model_1, rescale_factor_1, switch_2, upscale_model_2, rescale_factor_2, switch_3, upscale_model_3, rescale_factor_3, upscale_stack=None):
+
+ # Initialise the list
+ upscale_list=list()
+
+ if upscale_stack is not None:
+ upscale_list.extend([l for l in upscale_stack if l[0] != "None"])
+
+ if upscale_model_1 != "None" and switch_1 == "On":
+ upscale_list.extend([(upscale_model_1, rescale_factor_1)]),
+
+ if upscale_model_2 != "None" and switch_2 == "On":
+ upscale_list.extend([(upscale_model_2, rescale_factor_2)]),
+
+ if upscale_model_3 != "None" and switch_3 == "On":
+ upscale_list.extend([(upscale_model_3, rescale_factor_3)]),
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Upscale-Nodes#cr-multi-upscale-stack"
+ return (upscale_list, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------
+class CR_ApplyMultiUpscale:
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ resampling_methods = ["lanczos", "nearest", "bilinear", "bicubic"]
+
+ return {"required": {"image": ("IMAGE",),
+ "resampling_method": (resampling_methods,),
+ "supersample": (["true", "false"],),
+ "rounding_modulus": ("INT", {"default": 8, "min": 8, "max": 1024, "step": 8}),
+ "upscale_stack": ("UPSCALE_STACK",),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "STRING", )
+ RETURN_NAMES = ("IMAGE", "show_help", )
+ FUNCTION = "apply"
+ CATEGORY = icons.get("Comfyroll/Upscale")
+
+ def apply(self, image, resampling_method, supersample, rounding_modulus, upscale_stack):
+
+ # Get original size
+ pil_img = tensor2pil(image)
+ original_width, original_height = pil_img.size
+
+ # Extend params with upscale-stack items
+ params = list()
+ params.extend(upscale_stack)
+
+ # Loop through the list
+ for tup in params:
+ upscale_model, rescale_factor = tup
+ print(f"[Info] CR Apply Multi Upscale: Applying {upscale_model} and rescaling by factor {rescale_factor}")
+ # Load upscale model
+ up_model = load_model(upscale_model)
+
+ # Upscale with model
+ up_image = upscale_with_model(up_model, image)
+
+ # Get new size
+ pil_img = tensor2pil(up_image)
+ upscaled_width, upscaled_height = pil_img.size
+
+ # Return if no rescale needed
+ if upscaled_width == original_width and rescale_factor == 1:
+ image = up_image
+ else:
+ # Image resize
+ scaled_images = []
+ mode = "rescale"
+ resize_width = 1024
+
+ for img in up_image:
+ scaled_images.append(pil2tensor(apply_resize_image(tensor2pil(img), original_width, original_height, rounding_modulus, mode, supersample, rescale_factor, resize_width, resampling_method)))
+ image = torch.cat(scaled_images, dim=0)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Upscale-Nodes#cr-apply-multi-upscale"
+
+ return (image, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+# 0 nodes released
+'''
+NODE_CLASS_MAPPINGS = {
+ # Conditioning
+ "CR Multi Upscale Stack":CR_MultiUpscaleStack,
+ "CR Upscale Image":CR_UpscaleImage,
+ "CR Apply Multi Upscale":CR_ApplyMultiUpscale,
+}
+'''
+
+
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/upscale_functions.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/upscale_functions.py
new file mode 100644
index 0000000000000000000000000000000000000000..644dd76f024bbaca74d779648e1ea24ecf9074a6
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/upscale_functions.py
@@ -0,0 +1,87 @@
+#-----------------------------------------------------------------------------------------------------------#
+# Comfyroll Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/CR-Animation-Nodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#-----------------------------------------------------------------------------------------------------------#
+
+#-----------------------------------------------------------------------------------------------------------#
+# UPSCALE FUNCTIONS
+#-----------------------------------------------------------------------------------------------------------#
+# These functions are based on WAS nodes Image Resize and the Comfy Extras upscale with model nodes
+#-----------------------------------------------------------------------------------------------------------#
+
+import torch
+#import os
+from comfy_extras.chainner_models import model_loading
+from comfy import model_management
+import numpy as np
+import comfy.utils
+import folder_paths
+from PIL import Image
+
+# PIL to Tensor
+def pil2tensor(image):
+ return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
+
+# Tensor to PIL
+def tensor2pil(image):
+ return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
+
+def load_model(model_name):
+ model_path = folder_paths.get_full_path("upscale_models", model_name)
+ sd = comfy.utils.load_torch_file(model_path, safe_load=True)
+ if "module.layers.0.residual_group.blocks.0.norm1.weight" in sd:
+ sd = comfy.utils.state_dict_prefix_replace(sd, {"module.":""})
+ out = model_loading.load_state_dict(sd).eval()
+ return out
+
+def upscale_with_model(upscale_model, image):
+ device = model_management.get_torch_device()
+ upscale_model.to(device)
+ in_img = image.movedim(-1,-3).to(device)
+ free_memory = model_management.get_free_memory(device)
+
+ tile = 512
+ overlap = 32
+
+ oom = True
+ while oom:
+ try:
+ steps = in_img.shape[0] * comfy.utils.get_tiled_scale_steps(in_img.shape[3], in_img.shape[2], tile_x=tile, tile_y=tile, overlap=overlap)
+ pbar = comfy.utils.ProgressBar(steps)
+ s = comfy.utils.tiled_scale(in_img, lambda a: upscale_model(a), tile_x=tile, tile_y=tile, overlap=overlap, upscale_amount=upscale_model.scale, pbar=pbar)
+ oom = False
+ except model_management.OOM_EXCEPTION as e:
+ tile //= 2
+ if tile < 128:
+ raise e
+
+ upscale_model.cpu()
+ s = torch.clamp(s.movedim(-3,-1), min=0, max=1.0)
+ return s
+
+def apply_resize_image(image: Image.Image, original_width, original_height, rounding_modulus, mode='scale', supersample='true', factor: int = 2, width: int = 1024, height: int = 1024, resample='bicubic'):
+
+ # Calculate the new width and height based on the given mode and parameters
+ if mode == 'rescale':
+ new_width, new_height = int(original_width * factor), int(original_height * factor)
+ else:
+ m = rounding_modulus
+ original_ratio = original_height / original_width
+ height = int(width * original_ratio)
+
+ new_width = width if width % m == 0 else width + (m - width % m)
+ new_height = height if height % m == 0 else height + (m - height % m)
+
+ # Define a dictionary of resampling filters
+ resample_filters = {'nearest': 0, 'bilinear': 2, 'bicubic': 3, 'lanczos': 1}
+
+ # Apply supersample
+ if supersample == 'true':
+ image = image.resize((new_width * 8, new_height * 8), resample=Image.Resampling(resample_filters[resample]))
+
+ # Resize the image using the given resampling filter
+ resized_image = image.resize((new_width, new_height), resample=Image.Resampling(resample_filters[resample]))
+
+ return resized_image
+
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/xygrid.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/xygrid.py
new file mode 100644
index 0000000000000000000000000000000000000000..2abdf94e4c5ad7549f4bb22743e358f00fa381c6
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/xygrid.py
@@ -0,0 +1,376 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# Comfyroll Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/CR-Animation-Nodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+# based on https://github.com/LEv145/images-grid-comfy-plugin
+#---------------------------------------------------------------------------------------------------------------------#
+
+import os
+import folder_paths
+from PIL import Image, ImageFont
+import torch
+import numpy as np
+import re
+from pathlib import Path
+import typing as t
+from dataclasses import dataclass
+from .xygrid_functions import create_images_grid_by_columns, Annotation
+from ..categories import icons
+
+def tensor_to_pillow(image: t.Any) -> Image.Image:
+ return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
+
+def pillow_to_tensor(image: Image.Image) -> t.Any:
+ return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
+
+def find_highest_numeric_value(directory, filename_prefix):
+ highest_value = -1 # Initialize with a value lower than possible numeric values
+
+ # Iterate through all files in the directory
+ for filename in os.listdir(directory):
+ if filename.startswith(filename_prefix):
+ try:
+ # Extract numeric part of the filename
+ numeric_part = filename[len(filename_prefix):]
+ numeric_str = re.search(r'\d+', numeric_part).group()
+ numeric_value = int(numeric_str)
+ # Check if the current numeric value is higher than the highest found so far
+ if numeric_value > highest_value:
+ highest_value = int(numeric_value)
+ except ValueError:
+ # If the numeric part is not a valid integer, ignore the file
+ continue
+
+ return highest_value
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_XYList:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required":{
+ "index": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "list1": ("STRING", {"multiline": True, "default": "x"}), #"forceInput": True}),
+ "x_prepend": ("STRING", {"multiline": False, "default": ""}),
+ "x_append": ("STRING", {"multiline": False, "default": ""}),
+ "x_annotation_prepend": ("STRING", {"multiline": False, "default": ""}),
+ "list2": ("STRING", {"multiline": True, "default": "y"}),
+ "y_prepend": ("STRING", {"multiline": False, "default": ""}),
+ "y_append": ("STRING", {"multiline": False, "default": ""}),
+ "y_annotation_prepend": ("STRING", {"multiline": False, "default": ""}),
+ }
+ }
+
+ RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "BOOLEAN", "STRING", )
+ RETURN_NAMES = ("X", "Y", "x_annotation", "y_annotation", "trigger", "show_help", )
+ FUNCTION = "cross_join"
+ CATEGORY = icons.get("Comfyroll/XY Grid")
+
+ def cross_join(self, list1, list2, x_prepend, x_append, x_annotation_prepend,
+ y_prepend, y_append, y_annotation_prepend, index):
+
+ # Index values for all XY nodes start from 1
+ index -=1
+
+ trigger = False
+
+ #listx = list1.split(",")
+ #listy = list2.split(",")
+ listx = re.split(r',(?=(?:[^"]*"[^"]*")*[^"]*$)', list1)
+ listy = re.split(r',(?=(?:[^"]*"[^"]*")*[^"]*$)', list2)
+
+ listx = [item.strip() for item in listx]
+ listy = [item.strip() for item in listy]
+
+ lenx = len(listx)
+ leny = len(listy)
+
+ grid_size = lenx * leny
+
+ x = index % lenx
+ y = int(index / lenx)
+
+ x_out = x_prepend + listx[x] + x_append
+ y_out = y_prepend + listy[y] + y_append
+
+ x_ann_out = ""
+ y_ann_out = ""
+
+ if index + 1 == grid_size:
+ x_ann_out = [x_annotation_prepend + item + ";" for item in listx]
+ y_ann_out = [y_annotation_prepend + item + ";" for item in listy]
+ x_ann_out = "".join([str(item) for item in x_ann_out])
+ y_ann_out = "".join([str(item) for item in y_ann_out])
+ trigger = True
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/XY-Grid-Nodes#cr-xy-list"
+
+ return (x_out, y_out, x_ann_out, y_ann_out, trigger, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_XYInterpolate:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ gradient_profiles = ["Lerp"]
+
+ return {"required": {"x_columns":("INT", {"default": 5.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "x_start_value": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 0.01,}),
+ "x_step": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 9999.0, "step": 0.01,}),
+ "x_annotation_prepend": ("STRING", {"multiline": False, "default": ""}),
+ "y_rows":("INT", {"default": 5.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "y_start_value": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 0.01,}),
+ "y_step": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 9999.0, "step": 0.01,}),
+ "y_annotation_prepend": ("STRING", {"multiline": False, "default": ""}),
+ "index": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "gradient_profile": (gradient_profiles,)
+ }
+ }
+
+ RETURN_TYPES = ("FLOAT", "FLOAT", "STRING", "STRING", "BOOLEAN", "STRING", )
+ RETURN_NAMES = ("X", "Y", "x_annotation", "y_annotation", "trigger", "show_help", )
+ FUNCTION = "gradient"
+ CATEGORY = icons.get("Comfyroll/XY Grid")
+
+ def gradient(self, x_columns, x_start_value, x_step, x_annotation_prepend,
+ y_rows, y_start_value, y_step, y_annotation_prepend,
+ index, gradient_profile):
+
+ # Index values for all XY nodes start from 1
+ index -=1
+ trigger = False
+ grid_size = x_columns * y_rows
+
+ x = index % x_columns
+ y = int(index / x_columns)
+
+ x_float_out = round(x_start_value + x * x_step, 3)
+ y_float_out = round(y_start_value + y * y_step, 3)
+
+ x_ann_out = ""
+ y_ann_out = ""
+
+ if index + 1 == grid_size:
+ for i in range(0, x_columns):
+ x = index % x_columns
+ x_float_out = x_start_value + i * x_step
+ x_float_out = round(x_float_out, 3)
+ x_ann_out = x_ann_out + x_annotation_prepend + str(x_float_out) + "; "
+ for j in range(0, y_rows):
+ y = int(index / x_columns)
+ y_float_out = y_start_value + j * y_step
+ y_float_out = round(y_float_out, 3)
+ y_ann_out = y_ann_out + y_annotation_prepend + str(y_float_out) + "; "
+
+ x_ann_out = x_ann_out[:-1]
+ y_ann_out = y_ann_out[:-1]
+ print(x_ann_out,y_ann_out)
+ trigger = True
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/XY-Grid-Nodes#cr-xy-interpolate"
+
+ return (x_float_out, y_float_out, x_ann_out, y_ann_out, trigger, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_XYIndex:
+
+ @classmethod
+ def INPUT_TYPES(s):
+ gradient_profiles = ["Lerp"]
+
+ return {"required": {"x_columns":("INT", {"default": 5.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "y_rows":("INT", {"default": 5.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ "index": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
+ }
+ }
+
+ RETURN_TYPES = ("INT", "INT", "STRING", )
+ RETURN_NAMES = ("x", "y", "show_help", )
+ FUNCTION = "index"
+ CATEGORY = icons.get("Comfyroll/XY Grid")
+
+ def index(self, x_columns, y_rows, index):
+
+ # Index values for all XY nodes start from 1
+ index -=1
+
+ x = index % x_columns
+ y = int(index / x_columns)
+
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/XY-Grid-Nodes#cr-xy-index"
+
+ return (x, y, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_XYFromFolder:
+
+ @classmethod
+ def INPUT_TYPES(cls) -> dict[str, t.Any]:
+
+ input_dir = folder_paths.output_directory
+ image_folder = [name for name in os.listdir(input_dir) if os.path.isdir(os.path.join(input_dir,name))]
+
+ return {"required":
+ {"image_folder": (sorted(image_folder), ),
+ "start_index": ("INT", {"default": 1, "min": 0, "max": 10000}),
+ "end_index": ("INT", {"default": 1, "min": 1, "max": 10000}),
+ "max_columns": ("INT", {"default": 1, "min": 1, "max": 10000}),
+ "x_annotation": ("STRING", {"multiline": True}),
+ "y_annotation": ("STRING", {"multiline": True}),
+ "font_size": ("INT", {"default": 50, "min": 1}),
+ "gap": ("INT", {"default": 0, "min": 0}),
+ },
+ "optional": {
+ "trigger": ("BOOLEAN", {"default": False},),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "BOOLEAN", "STRING", )
+ RETURN_NAMES = ("IMAGE", "trigger", "show_help", )
+ FUNCTION = "load_images"
+ CATEGORY = icons.get("Comfyroll/XY Grid")
+
+ def load_images(self, image_folder, start_index, end_index, max_columns, x_annotation, y_annotation, font_size, gap, trigger=False):
+ show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/XY-Grid-Nodes#cr-xy-from-folder"
+
+ if trigger == False:
+ return((), False, show_help, )
+
+ input_dir = folder_paths.output_directory
+ image_path = os.path.join(input_dir, image_folder)
+ file_list = sorted(os.listdir(image_path), key=lambda s: sum(((s, int(n)) for s, n in re.findall(r'(\D+)(\d+)', 'a%s0' % s)), ()))
+
+ sample_frames = []
+ pillow_images = []
+
+ if len(file_list) < end_index:
+ end_index = len(file_list)
+
+ for num in range(start_index, end_index + 1):
+ i = Image.open(os.path.join(image_path, file_list[num - 1]))
+ image = i.convert("RGB")
+ image = np.array(image).astype(np.float32) / 255.0
+ image = torch.from_numpy(image)[None,]
+ image = image.squeeze()
+ sample_frames.append(image)
+
+ resolved_font_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "fonts\Roboto-Regular.ttf")
+ font = ImageFont.truetype(str(resolved_font_path), size=font_size)
+
+ start_x_ann = (start_index % max_columns) - 1
+ start_y_ann = int(start_index / max_columns)
+
+ column_list = x_annotation.split(";")[start_x_ann:]
+ row_list = y_annotation.split(";")[start_y_ann:]
+
+ column_list = [item.strip() for item in column_list]
+ row_list = [item.strip() for item in row_list]
+
+ annotation = Annotation(column_texts=column_list, row_texts=row_list, font=font)
+ images = torch.stack(sample_frames)
+
+ pillow_images = [tensor_to_pillow(i) for i in images]
+ pillow_grid = create_images_grid_by_columns(
+ images=pillow_images,
+ gap=gap,
+ annotation=annotation,
+ max_columns=max_columns,
+ )
+ tensor_grid = pillow_to_tensor(pillow_grid)
+
+ return (tensor_grid, trigger, show_help, )
+
+#---------------------------------------------------------------------------------------------------------------------#
+class CR_XYSaveGridImage:
+# originally based on SaveImageSequence by mtb
+
+ def __init__(self):
+ self.type = "output"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+
+ output_dir = folder_paths.output_directory
+ output_folders = [name for name in os.listdir(output_dir) if os.path.isdir(os.path.join(output_dir,name))]
+
+ return {
+ "required": {"mode": (["Save", "Preview"],),
+ "output_folder": (sorted(output_folders), ),
+ "image": ("IMAGE", ),
+ "filename_prefix": ("STRING", {"default": "CR"}),
+ "file_format": (["webp", "jpg", "png", "tif"],),
+ },
+ "optional": {"output_path": ("STRING", {"default": '', "multiline": False}),
+ "trigger": ("BOOLEAN", {"default": False},),
+ }
+ }
+
+ RETURN_TYPES = ()
+ FUNCTION = "save_image"
+ OUTPUT_NODE = True
+ CATEGORY = icons.get("Comfyroll/XY Grid")
+
+ def save_image(self, mode, output_folder, image, file_format, output_path='', filename_prefix="CR", trigger=False):
+
+ if trigger == False:
+ return ()
+
+ output_dir = folder_paths.get_output_directory()
+ out_folder = os.path.join(output_dir, output_folder)
+
+ # Set the output path
+ if output_path != '':
+ if not os.path.exists(output_path):
+ print(f"[Warning] CR Save XY Grid Image: The input_path `{output_path}` does not exist")
+ return ("",)
+ out_path = output_path
+ else:
+ out_path = os.path.join(output_dir, out_folder)
+
+ if mode == "Preview":
+ out_path = folder_paths.temp_directory
+
+ print(f"[Info] CR Save XY Grid Image: Output path is `{out_path}`")
+
+ # Set the counter
+ counter = find_highest_numeric_value(out_path, filename_prefix) + 1
+ #print(f"[Debug] counter {counter}")
+
+ # Output image
+ output_image = image[0].cpu().numpy()
+ img = Image.fromarray(np.clip(output_image * 255.0, 0, 255).astype(np.uint8))
+
+ output_filename = f"{filename_prefix}_{counter:05}"
+ img_params = {'png': {'compress_level': 4},
+ 'webp': {'method': 6, 'lossless': False, 'quality': 80},
+ 'jpg': {'format': 'JPEG'},
+ 'tif': {'format': 'TIFF'}
+ }
+ self.type = "output" if mode == "Save" else 'temp'
+
+ resolved_image_path = os.path.join(out_path, f"{output_filename}.{file_format}")
+ img.save(resolved_image_path, **img_params[file_format])
+ print(f"[Info] CR Save XY Grid Image: Saved to {output_filename}.{file_format}")
+ out_filename = f"{output_filename}.{file_format}"
+ preview = {"ui": {"images": [{"filename": out_filename,"subfolder": out_path,"type": self.type,}]}}
+
+ return preview
+
+#---------------------------------------------------------------------------------------------------------------------#
+# MAPPINGS
+#---------------------------------------------------------------------------------------------------------------------#
+# For reference only, actual mappings are in __init__.py
+# 0 nodes released
+'''
+NODE_CLASS_MAPPINGS = {
+ # XY Grid
+ "CR XY List":CR_XYList,
+ "CR XY Index":CR_XYIndex,
+ "CR XY Interpolate":CR_XYInterpolate,
+ "CR XY From Folder":CR_XYFromFolder,
+ "CR XY Save Grid Image":CR_XYSaveGridImage,
+}
+'''
+
+
+
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/xygrid_functions.py b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/xygrid_functions.py
new file mode 100644
index 0000000000000000000000000000000000000000..0510a99bcfa332efc7845e2dfd99b13b224e7742
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/nodes/xygrid_functions.py
@@ -0,0 +1,218 @@
+#---------------------------------------------------------------------------------------------------------------------#
+# Comfyroll Custom Nodes by RockOfFire and Akatsuzi https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes
+# for ComfyUI https://github.com/comfyanonymous/ComfyUI
+#---------------------------------------------------------------------------------------------------------------------#
+
+# these functions are a straight clone from
+# https://github.com/LEv145/images-grid-comfy-plugin
+
+import typing as t
+from dataclasses import dataclass
+from contextlib import suppress
+
+from PIL import Image, ImageDraw, ImageFont
+
+
+WIDEST_LETTER = "W"
+
+
+@dataclass
+class Annotation():
+ column_texts: list[str]
+ row_texts: list[str]
+ font: ImageFont.FreeTypeFont
+
+
+def create_images_grid_by_columns(
+ images: list[Image.Image],
+ gap: int,
+ max_columns: int,
+ annotation: t.Optional[Annotation] = None,
+) -> Image.Image:
+ max_rows = (len(images) + max_columns - 1) // max_columns
+ return _create_images_grid(images, gap, max_columns, max_rows, annotation)
+
+
+def create_images_grid_by_rows(
+ images: list[Image.Image],
+ gap: int,
+ max_rows: int,
+ annotation: t.Optional[Annotation] = None,
+) -> Image.Image:
+ max_columns = (len(images) + max_rows - 1) // max_rows
+ return _create_images_grid(images, gap, max_columns, max_rows, annotation)
+
+
+@dataclass
+class _GridInfo():
+ image: Image.Image
+ gap: int
+ one_image_size: tuple[int, int]
+
+
+def _create_images_grid(
+ images: list[Image.Image],
+ gap: int,
+ max_columns: int,
+ max_rows: int,
+ annotation: t.Optional[Annotation],
+) -> Image.Image:
+ size = images[0].size
+ grid_width = size[0] * max_columns + (max_columns - 1) * gap
+ grid_height = size[1] * max_rows + (max_rows - 1) * gap
+
+ grid_image = Image.new("RGB", (grid_width, grid_height), color="white")
+
+ _arrange_images_on_grid(grid_image, images=images, size=size, max_columns=max_columns, gap=gap)
+
+ if annotation is None:
+ return grid_image
+ return _create_grid_annotation(
+ grid_info=_GridInfo(
+ image=grid_image,
+ gap=gap,
+ one_image_size=size,
+ ),
+ column_texts=annotation.column_texts,
+ row_texts=annotation.row_texts,
+ font=annotation.font,
+ )
+
+
+def _arrange_images_on_grid(
+ grid_image: Image.Image,
+ /,
+ images: list[Image.Image],
+ size: tuple[int, int],
+ max_columns: int,
+ gap: int,
+):
+ for i, image in enumerate(images):
+ x = (i % max_columns) * (size[0] + gap)
+ y = (i // max_columns) * (size[1] + gap)
+
+ grid_image.paste(image, (x, y))
+
+
+def _create_grid_annotation(
+ grid_info: _GridInfo,
+ column_texts: list[str],
+ row_texts: list[str],
+ font: ImageFont.FreeTypeFont,
+) -> Image.Image:
+ if not column_texts and not row_texts:
+ raise ValueError("Column text and row text is empty")
+
+ grid = grid_info.image
+ left_padding = 0
+ top_padding = 0
+
+ if row_texts:
+ left_padding = int(
+ max(
+ font.getlength(splitted_text)
+ for raw_text in row_texts
+ for splitted_text in raw_text.split("\n")
+ )
+ + font.getlength(WIDEST_LETTER)*2
+ )
+ if column_texts:
+ top_padding = int(font.size * 2)
+
+ image = Image.new(
+ "RGB",
+ (grid.size[0] + left_padding, grid.size[1] + top_padding),
+ color="white",
+ )
+ draw = ImageDraw.Draw(image)
+ # https://github.com/python-pillow/Pillow/blob/9.5.x/docs/reference/ImageDraw.rst
+ draw.font = font # type: ignore
+
+ _paste_image_to_lower_left_corner(image, grid)
+ if column_texts:
+ _draw_column_text(
+ draw=draw,
+ texts=column_texts,
+ grid_info=grid_info,
+ left_padding=left_padding,
+ top_padding=top_padding,
+ )
+ if row_texts:
+ _draw_row_text(
+ draw=draw,
+ texts=row_texts,
+ grid_info=grid_info,
+ left_padding=left_padding,
+ top_padding=top_padding,
+ )
+
+ return image
+
+
+def _draw_column_text(
+ draw: ImageDraw.ImageDraw,
+ texts: list[str],
+ grid_info: _GridInfo,
+ left_padding: int,
+ top_padding: int,
+) -> None:
+ i = 0
+ x0 = left_padding
+ y0 = 0
+ x1 = left_padding + grid_info.one_image_size[0]
+ y1 = top_padding
+ while x0 != grid_info.image.size[0] + left_padding + grid_info.gap:
+ i = _draw_text_by_xy((x0, y0, x1, y1), i, draw=draw, texts=texts)
+ x0 += grid_info.one_image_size[0] + grid_info.gap
+ x1 += grid_info.one_image_size[0] + grid_info.gap
+
+
+def _draw_row_text(
+ draw: ImageDraw.ImageDraw,
+ texts: list[str],
+ grid_info: _GridInfo,
+ left_padding: int,
+ top_padding: int,
+) -> None:
+ i = 0
+ x0 = 0
+ y0 = top_padding
+ x1 = left_padding
+ y1 = top_padding + grid_info.one_image_size[1]
+ while y0 != grid_info.image.size[1] + top_padding + grid_info.gap:
+ i = _draw_text_by_xy((x0, y0, x1, y1), i, draw=draw, texts=texts)
+ y0 += grid_info.one_image_size[1] + grid_info.gap
+ y1 += grid_info.one_image_size[1] + grid_info.gap
+
+
+def _draw_text_by_xy(
+ xy: tuple[int, int, int, int],
+ index: int,
+ \
+ draw: ImageDraw.ImageDraw,
+ texts: list[str],
+) -> int:
+ with suppress(IndexError):
+ _draw_center_text(draw, xy, texts[index])
+ return index + 1
+
+
+def _draw_center_text(
+ draw: ImageDraw.ImageDraw,
+ xy: tuple[int, int, int, int],
+ text: str,
+ fill: t.Any = "black",
+) -> None:
+ _, _, *text_size = draw.textbbox((0, 0), text)
+ draw.multiline_text(
+ (
+ (xy[2] - text_size[0] + xy[0]) / 2,
+ (xy[3] - text_size[1] + xy[1]) / 2,
+ ),
+ text,
+ fill=fill,
+ )
+
+
+def _paste_image_to_lower_left_corner(base: Image.Image, image: Image.Image) -> None:
+ base.paste(image, (base.size[0] - image.size[0], base.size[1] - image.size[1]))
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/obj/tetrahedron.obj b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/obj/tetrahedron.obj
new file mode 100644
index 0000000000000000000000000000000000000000..e15c62dfc0d7b86be17b560e996d054ba51c655b
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/obj/tetrahedron.obj
@@ -0,0 +1,14 @@
+# tetrahedron.obj created by hand.
+#
+
+g tetrahedron
+
+v 1.00 1.00 1.00
+v 2.00 1.00 1.00
+v 1.00 2.00 1.00
+v 1.00 1.00 2.00
+
+f 1 3 2
+f 1 4 3
+f 1 2 4
+f 2 3 4
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/styles/place style json files here.txt b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/styles/place style json files here.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_A1_PromptKeyframeScheduling_Demo_v01.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_A1_PromptKeyframeScheduling_Demo_v01.json
new file mode 100644
index 0000000000000000000000000000000000000000..304bf5c0035d19c2c303390cbff3c5081fa38e8a
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_A1_PromptKeyframeScheduling_Demo_v01.json
@@ -0,0 +1,1759 @@
+{
+ "last_node_id": 619,
+ "last_link_id": 1123,
+ "nodes": [
+ {
+ "id": 249,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ 1463.9153190458471,
+ -676.6040770005651
+ ],
+ "size": {
+ "0": 315,
+ "1": 98
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 902
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 1091
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null,
+ "shape": 3,
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "SD1_5\\ComfyrollAnime_v1_fp16_pruned.safetensors"
+ ]
+ },
+ {
+ "id": 509,
+ "type": "Note",
+ "pos": [
+ 1076.5295277914042,
+ -267.27814460234464
+ ],
+ "size": {
+ "0": 210,
+ "1": 120
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The primitive node increments the current_frame on each batch\n\nReset the value to 0 before each run\n"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 591,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 2190.728895078124,
+ -138.77898584960937
+ ],
+ "size": {
+ "0": 220,
+ "1": 80
+ },
+ "flags": {},
+ "order": 15,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 1045
+ },
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 1044,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "multiline": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 1087
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ ""
+ ]
+ },
+ {
+ "id": 250,
+ "type": "VAELoader",
+ "pos": [
+ 1463.9153190458471,
+ -526.6040770005652
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 837
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "vae-ft-mse-840000-ema-pruned.safetensors"
+ ]
+ },
+ {
+ "id": 528,
+ "type": "Reroute",
+ "pos": [
+ 1900,
+ -500
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 1091
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 985,
+ 1045
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 500,
+ "type": "Reroute",
+ "pos": [
+ 1900,
+ -560
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 902
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 899
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 602,
+ "type": "PreviewImage",
+ "pos": [
+ 3500,
+ -280
+ ],
+ "size": {
+ "0": 510,
+ "1": 530
+ },
+ "flags": {},
+ "order": 30,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 1083
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 491,
+ "type": "Reroute",
+ "pos": [
+ 1900,
+ -450
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 837
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 1101
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 607,
+ "type": "Reroute",
+ "pos": [
+ 2510,
+ -450
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 1101
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 1100
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 609,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 2310.721570859376,
+ 254.34497335156243
+ ],
+ "size": {
+ "0": 320,
+ "1": 80
+ },
+ "flags": {},
+ "order": 23,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 1119,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "forceInput": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "anime line-art, 1girl, long black hair, 2D, illustration"
+ ]
+ },
+ {
+ "id": 608,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 2310.721570859376,
+ 124.34497335156253
+ ],
+ "size": {
+ "0": 320,
+ "1": 80
+ },
+ "flags": {},
+ "order": 22,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 1118,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "forceInput": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "anime line-art, 1girl, long black hair, 2D, illustration"
+ ]
+ },
+ {
+ "id": 387,
+ "type": "Reroute",
+ "pos": [
+ 2510,
+ -560
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 899
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 614
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 600,
+ "type": "VAEDecode",
+ "pos": [
+ 3310,
+ -250
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 29,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 1080
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 1082
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 1083
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 601,
+ "type": "Reroute",
+ "pos": [
+ 3170,
+ -450
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 20,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 1100
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 1082
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 563,
+ "type": "CR Encode Scheduled Prompts",
+ "pos": [
+ 2190.728895078124,
+ -298.7789858496094
+ ],
+ "size": {
+ "0": 290,
+ "1": 94
+ },
+ "flags": {},
+ "order": 24,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 985
+ },
+ {
+ "name": "current_prompt",
+ "type": "STRING",
+ "link": 1115,
+ "widget": {
+ "name": "current_prompt",
+ "config": [
+ "STRING",
+ {
+ "multiline": true
+ }
+ ]
+ }
+ },
+ {
+ "name": "next_prompt",
+ "type": "STRING",
+ "link": 1116,
+ "widget": {
+ "name": "next_prompt",
+ "config": [
+ "STRING",
+ {
+ "multiline": true
+ }
+ ]
+ }
+ },
+ {
+ "name": "weight",
+ "type": "FLOAT",
+ "link": 1117,
+ "widget": {
+ "name": "weight",
+ "config": [
+ "FLOAT",
+ {
+ "default": 0,
+ "min": -9999,
+ "max": 9999,
+ "step": 0.1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 1106
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Encode Scheduled Prompts"
+ },
+ "widgets_values": [
+ "",
+ "",
+ 0
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 610,
+ "type": "CR Apply ControlNet",
+ "pos": [
+ 2570,
+ -300
+ ],
+ "size": {
+ "0": 250,
+ "1": 122
+ },
+ "flags": {},
+ "order": 26,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "conditioning",
+ "type": "CONDITIONING",
+ "link": 1106
+ },
+ {
+ "name": "control_net",
+ "type": "CONTROL_NET",
+ "link": 1112
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 1110
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 1107
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Apply ControlNet"
+ },
+ "widgets_values": [
+ "On",
+ 0.7000000000000001
+ ]
+ },
+ {
+ "id": 605,
+ "type": "LoadImage",
+ "pos": [
+ 2470,
+ -1070
+ ],
+ "size": {
+ "0": 320,
+ "1": 310
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 1110
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "depth_leres-0070.png",
+ "image"
+ ]
+ },
+ {
+ "id": 613,
+ "type": "ControlNetLoader",
+ "pos": [
+ 2470,
+ -700
+ ],
+ "size": {
+ "0": 320,
+ "1": 60
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "CONTROL_NET",
+ "type": "CONTROL_NET",
+ "links": [
+ 1112
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ControlNetLoader"
+ },
+ "widgets_values": [
+ "t2iadapter_zoedepth_sd15v1.pth"
+ ]
+ },
+ {
+ "id": 612,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 2570,
+ -130
+ ],
+ "size": {
+ "0": 250,
+ "1": 120
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 1111
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 512,
+ 768,
+ 1
+ ]
+ },
+ {
+ "id": 537,
+ "type": "CR Prompt Text",
+ "pos": [
+ 1690,
+ 60
+ ],
+ "size": {
+ "0": 310,
+ "1": 90
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "prompt",
+ "type": "STRING",
+ "links": [
+ 1044
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Prompt Text"
+ },
+ "widgets_values": [
+ "embedding:EasyNegative, "
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 490,
+ "type": "CR Current Frame",
+ "pos": [
+ 1390,
+ -230
+ ],
+ "size": {
+ "0": 320,
+ "1": 80
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "link": 836,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "links": [
+ 1089,
+ 1114
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Current Frame"
+ },
+ "widgets_values": [
+ 0,
+ "Yes"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 252,
+ "type": "KSampler",
+ "pos": [
+ 2918.4079541276733,
+ -278.0029971578494
+ ],
+ "size": {
+ "0": 290,
+ "1": 550
+ },
+ "flags": {},
+ "order": 28,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 614
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 1107
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 1087
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 1111
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 1080
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 722889772155925,
+ "fixed",
+ 20,
+ 10,
+ "dpmpp_2m",
+ "karras",
+ 1
+ ]
+ },
+ {
+ "id": 617,
+ "type": "CR Simple Prompt List",
+ "pos": [
+ 601.8719232562495,
+ 37.05121388925782
+ ],
+ "size": {
+ "0": 468.5999755859375,
+ "1": 276
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "simple_prompt_list",
+ "type": "SIMPLE_PROMPT_LIST",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "SIMPLE_PROMPT_LIST",
+ "type": "SIMPLE_PROMPT_LIST",
+ "links": [
+ 1121
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Prompt List"
+ },
+ "widgets_values": [
+ "1girl, long grey hair",
+ "1girl, long blue hair",
+ "1girl, long red hair",
+ "1girl, long black hair",
+ "1girl, long pink hair"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 614,
+ "type": "CR Prompt Scheduler",
+ "pos": [
+ 1670,
+ -280
+ ],
+ "size": {
+ "0": 350,
+ "1": 286
+ },
+ "flags": {},
+ "order": 19,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": null
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 1114,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ },
+ {
+ "name": "keyframe_list",
+ "type": "STRING",
+ "link": 1122,
+ "widget": {
+ "name": "keyframe_list",
+ "config": [
+ "STRING",
+ {
+ "multiline": true,
+ "default": "keyframe list"
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "current_prompt",
+ "type": "STRING",
+ "links": [
+ 1115,
+ 1118
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "next_prompt",
+ "type": "STRING",
+ "links": [
+ 1116,
+ 1119
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "weight",
+ "type": "FLOAT",
+ "links": [
+ 1117,
+ 1120
+ ],
+ "shape": 3,
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Prompt Scheduler"
+ },
+ "widgets_values": [
+ "Keyframe List",
+ 0,
+ "default text",
+ "Deforum",
+ "Yes",
+ "P1",
+ "anime lineart",
+ "anime line-art",
+ "2D, illustration"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 618,
+ "type": "CR Simple Prompt List Keyframes",
+ "pos": [
+ 1131.8719232562507,
+ 37.05121388925782
+ ],
+ "size": {
+ "0": 405.5999755859375,
+ "1": 178
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "simple_prompt_list",
+ "type": "SIMPLE_PROMPT_LIST",
+ "link": 1121
+ }
+ ],
+ "outputs": [
+ {
+ "name": "keyframe_list",
+ "type": "STRING",
+ "links": [
+ 1122,
+ 1123
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Prompt List Keyframes"
+ },
+ "widgets_values": [
+ 3,
+ 1,
+ "Default",
+ "Default",
+ "Default",
+ "Deforum"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 619,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 1201.8719232562507,
+ 287.051213889258
+ ],
+ "size": [
+ 332.17811035156274,
+ 133.2184790039064
+ ],
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 1123,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "forceInput": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "\"0\": \"1girl, long grey hair\",\n \"3\": \"1girl, long blue hair\",\n \"6\": \"1girl, long red hair\",\n \"9\": \"1girl, long black hair\",\n \"12\": \"1girl, long pink hair\""
+ ]
+ },
+ {
+ "id": 581,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 2420,
+ 390
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 21,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 1006,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "forceInput": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "9"
+ ]
+ },
+ {
+ "id": 582,
+ "type": "CR Integer To String",
+ "pos": [
+ 2210,
+ 420
+ ],
+ "size": {
+ "0": 320,
+ "1": 60
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 18,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "int_",
+ "type": "INT",
+ "link": 1089,
+ "widget": {
+ "name": "int_",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 18446744073709552000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 1006
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Integer To String"
+ },
+ "widgets_values": [
+ 0
+ ]
+ },
+ {
+ "id": 585,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 2430,
+ 530
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 27,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 1010,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "forceInput": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "1.0"
+ ]
+ },
+ {
+ "id": 587,
+ "type": "CR Float To String",
+ "pos": [
+ 2210,
+ 560
+ ],
+ "size": {
+ "0": 320,
+ "1": 60
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 25,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "float_",
+ "type": "FLOAT",
+ "link": 1120,
+ "widget": {
+ "name": "float_",
+ "config": [
+ "FLOAT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 1000000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 1010
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Float To String"
+ },
+ "widgets_values": [
+ 0
+ ]
+ },
+ {
+ "id": 508,
+ "type": "PrimitiveNode",
+ "pos": [
+ 1076.5295277914042,
+ -417.2781446023464
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 836
+ ],
+ "slot_index": 0,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ 0,
+ "increment"
+ ]
+ }
+ ],
+ "links": [
+ [
+ 614,
+ 387,
+ 0,
+ 252,
+ 0,
+ "MODEL"
+ ],
+ [
+ 836,
+ 508,
+ 0,
+ 490,
+ 0,
+ "INT"
+ ],
+ [
+ 837,
+ 250,
+ 0,
+ 491,
+ 0,
+ "*"
+ ],
+ [
+ 899,
+ 500,
+ 0,
+ 387,
+ 0,
+ "*"
+ ],
+ [
+ 902,
+ 249,
+ 0,
+ 500,
+ 0,
+ "*"
+ ],
+ [
+ 985,
+ 528,
+ 0,
+ 563,
+ 0,
+ "CLIP"
+ ],
+ [
+ 1006,
+ 582,
+ 0,
+ 581,
+ 0,
+ "STRING"
+ ],
+ [
+ 1010,
+ 587,
+ 0,
+ 585,
+ 0,
+ "STRING"
+ ],
+ [
+ 1044,
+ 537,
+ 0,
+ 591,
+ 1,
+ "STRING"
+ ],
+ [
+ 1045,
+ 528,
+ 0,
+ 591,
+ 0,
+ "CLIP"
+ ],
+ [
+ 1080,
+ 252,
+ 0,
+ 600,
+ 0,
+ "LATENT"
+ ],
+ [
+ 1082,
+ 601,
+ 0,
+ 600,
+ 1,
+ "VAE"
+ ],
+ [
+ 1083,
+ 600,
+ 0,
+ 602,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 1087,
+ 591,
+ 0,
+ 252,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 1089,
+ 490,
+ 0,
+ 582,
+ 0,
+ "INT"
+ ],
+ [
+ 1091,
+ 249,
+ 1,
+ 528,
+ 0,
+ "*"
+ ],
+ [
+ 1100,
+ 607,
+ 0,
+ 601,
+ 0,
+ "*"
+ ],
+ [
+ 1101,
+ 491,
+ 0,
+ 607,
+ 0,
+ "*"
+ ],
+ [
+ 1106,
+ 563,
+ 0,
+ 610,
+ 0,
+ "CONDITIONING"
+ ],
+ [
+ 1107,
+ 610,
+ 0,
+ 252,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 1110,
+ 605,
+ 0,
+ 610,
+ 2,
+ "IMAGE"
+ ],
+ [
+ 1111,
+ 612,
+ 0,
+ 252,
+ 3,
+ "LATENT"
+ ],
+ [
+ 1112,
+ 613,
+ 0,
+ 610,
+ 1,
+ "CONTROL_NET"
+ ],
+ [
+ 1114,
+ 490,
+ 0,
+ 614,
+ 1,
+ "INT"
+ ],
+ [
+ 1115,
+ 614,
+ 0,
+ 563,
+ 1,
+ "STRING"
+ ],
+ [
+ 1116,
+ 614,
+ 1,
+ 563,
+ 2,
+ "STRING"
+ ],
+ [
+ 1117,
+ 614,
+ 2,
+ 563,
+ 3,
+ "FLOAT"
+ ],
+ [
+ 1118,
+ 614,
+ 0,
+ 608,
+ 0,
+ "STRING"
+ ],
+ [
+ 1119,
+ 614,
+ 1,
+ 609,
+ 0,
+ "STRING"
+ ],
+ [
+ 1120,
+ 614,
+ 2,
+ 587,
+ 0,
+ "FLOAT"
+ ],
+ [
+ 1121,
+ 617,
+ 0,
+ 618,
+ 0,
+ "SIMPLE_PROMPT_LIST"
+ ],
+ [
+ 1122,
+ 618,
+ 0,
+ 614,
+ 2,
+ "STRING"
+ ],
+ [
+ 1123,
+ 618,
+ 0,
+ 619,
+ 0,
+ "STRING"
+ ]
+ ],
+ "groups": [
+ {
+ "title": "Model",
+ "bounding": [
+ 1437,
+ -777,
+ 383,
+ 344
+ ],
+ "color": "#3f789e",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Sampling",
+ "bounding": [
+ 2876,
+ -390,
+ 369,
+ 693
+ ],
+ "color": "#8A8",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Load Frames",
+ "bounding": [
+ 1043,
+ -523,
+ 284,
+ 416
+ ],
+ "color": "#8A8",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Conditioning",
+ "bounding": [
+ 2162,
+ -387,
+ 354,
+ 365
+ ],
+ "color": "#8A8",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Prompt",
+ "bounding": [
+ 1617,
+ -388,
+ 434,
+ 582
+ ],
+ "color": "#a1309b",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Show Values",
+ "bounding": [
+ 2159,
+ 26,
+ 515,
+ 605
+ ],
+ "color": "#3f789e",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Prompt Keyframes",
+ "bounding": [
+ 562,
+ -57,
+ 1013,
+ 506
+ ],
+ "color": "#3f789e",
+ "font_size": 24,
+ "locked": false
+ }
+ ],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_A2_PromptKeyframeNodes_Demo_v02b.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_A2_PromptKeyframeNodes_Demo_v02b.json
new file mode 100644
index 0000000000000000000000000000000000000000..2e8996247076f5987ed79e0cda8a248c2701e7cc
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_A2_PromptKeyframeNodes_Demo_v02b.json
@@ -0,0 +1,1704 @@
+{
+ "last_node_id": 87,
+ "last_link_id": 99,
+ "nodes": [
+ {
+ "id": 15,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 1480,
+ 410
+ ],
+ "size": {
+ "0": 320,
+ "1": 110
+ },
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "link": 11,
+ "widget": {
+ "name": "width",
+ "config": [
+ "INT",
+ {
+ "default": 512,
+ "min": 64,
+ "max": 8192,
+ "step": 8
+ }
+ ]
+ }
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "link": 12,
+ "widget": {
+ "name": "height",
+ "config": [
+ "INT",
+ {
+ "default": 512,
+ "min": 64,
+ "max": 8192,
+ "step": 8
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 13
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ 1
+ ]
+ },
+ {
+ "id": 14,
+ "type": "CR SD1.5 Aspect Ratio",
+ "pos": [
+ 1480,
+ 90
+ ],
+ "size": {
+ "0": 315,
+ "1": 238
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "links": [
+ 11
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "links": [
+ 12
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "upscale_factor",
+ "type": "FLOAT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "batch_size",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR SD1.5 Aspect Ratio"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ "2:3 portrait 512x768",
+ "Off",
+ 1,
+ 1
+ ]
+ },
+ {
+ "id": 16,
+ "type": "VAEDecode",
+ "pos": [
+ 1910,
+ 580
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 29,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 14
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 15
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 25
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 17,
+ "type": "VAELoader",
+ "pos": [
+ 1870,
+ 730
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 15
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "vae-ft-mse-840000-ema-pruned.safetensors"
+ ]
+ },
+ {
+ "id": 53,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 1060,
+ 760
+ ],
+ "size": {
+ "0": 260,
+ "1": 54
+ },
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 71
+ },
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 57,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "multiline": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 66
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "embedding:EasyNegative.pt"
+ ]
+ },
+ {
+ "id": 52,
+ "type": "CR Prompt Text",
+ "pos": [
+ 460,
+ 600
+ ],
+ "size": {
+ "0": 370,
+ "1": 110
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "prompt",
+ "type": "STRING",
+ "links": [
+ 55
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "Pre Text",
+ "properties": {
+ "Node name for S&R": "CR Prompt Text"
+ },
+ "widgets_values": [
+ "anime, illustration, line-art, 2.5D,"
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 54,
+ "type": "CR Prompt Text",
+ "pos": [
+ 460,
+ 770
+ ],
+ "size": {
+ "0": 370,
+ "1": 110
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "prompt",
+ "type": "STRING",
+ "links": [
+ 57
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "Negative Prompt",
+ "properties": {
+ "Node name for S&R": "CR Prompt Text"
+ },
+ "widgets_values": [
+ "embedding:EasyNegative.pt,\nnsfw"
+ ],
+ "color": "#571a1a",
+ "bgcolor": "#6b2e2e"
+ },
+ {
+ "id": 26,
+ "type": "Save Image Sequence (mtb)",
+ "pos": [
+ 2250,
+ 350
+ ],
+ "size": {
+ "0": 380,
+ "1": 290
+ },
+ "flags": {},
+ "order": 30,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 25,
+ "slot_index": 0
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 74,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999999
+ }
+ ]
+ },
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Save Image Sequence (mtb)"
+ },
+ "widgets_values": [
+ "F:\\ComfyUI\\ComfyUI_windows_portable\\ComfyUI\\output\\Test\\",
+ 5
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 49,
+ "type": "PromptSchedule",
+ "pos": [
+ 950,
+ 410
+ ],
+ "size": {
+ "0": 400,
+ "1": 280
+ },
+ "flags": {},
+ "order": 27,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 67
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 69,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ },
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 54,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "multiline": true
+ }
+ ]
+ }
+ },
+ {
+ "name": "pre_text",
+ "type": "STRING",
+ "link": 55,
+ "widget": {
+ "name": "pre_text",
+ "config": [
+ "STRING",
+ {
+ "multiline": false
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 65
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "Prompt Scheduler",
+ "properties": {
+ "Node name for S&R": "PromptSchedule"
+ },
+ "widgets_values": [
+ "\"0\": \"1girl, solo, long grey hair, grey eyes, black sweater, (smiling:`(0.5+0.5*sin(t/12))`)\",\n\"24\": \"1girl, solo, long grey hair, grey eyes, black sweater, (smiling:`(0.5+0.5*sin(t/max_f))`)\"",
+ 12,
+ 0,
+ "",
+ "",
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 65,
+ "type": "Note",
+ "pos": [
+ 1120,
+ 240
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The prompt scheduler assembles the prompt based on the current frame"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 63,
+ "type": "Note",
+ "pos": [
+ -520,
+ 370
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "To run this workflow, first press Reset in the Animation Builder and then press the Queue button, Do not use queue prompt in the ComfyUI menu."
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 66,
+ "type": "Note",
+ "pos": [
+ 50,
+ 210
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Frames are processed in sequence. The CR Current Frame node prints the current frame index to console so that you can see which frame is currently being processed"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 47,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ 460,
+ 240
+ ],
+ "size": {
+ "0": 315,
+ "1": 98
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 68
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 67,
+ 71
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "SD1_5\\ayonimix_V4VAEBaked.safetensors"
+ ]
+ },
+ {
+ "id": 67,
+ "type": "Note",
+ "pos": [
+ 50,
+ 80
+ ],
+ "size": {
+ "0": 210,
+ "1": 70
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Frames are processed in sequence starting from frame index 0"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 13,
+ "type": "KSampler",
+ "pos": [
+ 1480,
+ 580
+ ],
+ "size": {
+ "0": 320,
+ "1": 470
+ },
+ "flags": {},
+ "order": 28,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 68
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 65
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 66
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 13
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 14
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 0,
+ "fixed",
+ 20,
+ 8,
+ "euler",
+ "normal",
+ 1
+ ]
+ },
+ {
+ "id": 64,
+ "type": "Note",
+ "pos": [
+ 548.9214027773999,
+ 1557.2026116672523
+ ],
+ "size": {
+ "0": 220,
+ "1": 140
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "There are three methods of creating prompt keyframes ranging from simple to advanced.\n\n"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 77,
+ "type": "Note",
+ "pos": [
+ -460,
+ 2600
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "This method allows detailed prompt control at the keyframe level"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 24,
+ "type": "Animation Builder (mtb)",
+ "pos": [
+ -270,
+ 360
+ ],
+ "size": {
+ "0": 210,
+ "1": 320
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "frame",
+ "type": "INT",
+ "links": [
+ 23
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "0-1 (scaled)",
+ "type": "FLOAT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "count",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "loop_ended",
+ "type": "BOOLEAN",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Animation Builder (mtb)"
+ },
+ "widgets_values": [
+ 12,
+ 1,
+ 1,
+ 12,
+ 1,
+ "frame: 0 / 11",
+ "Done 😎!",
+ "reset",
+ "queue"
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 78,
+ "type": "CR Keyframe List",
+ "pos": [
+ -181.07859722259994,
+ 2597.2026116672523
+ ],
+ "size": [
+ 570,
+ 210
+ ],
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "keyframe_list",
+ "type": "STRING",
+ "links": [
+ 89
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Keyframe List"
+ },
+ "widgets_values": [
+ "\"0\": \"1girl, solo, long grey hair, (smiling:`(0.5+0.5*sin(t/12))`)\",\n\"72\": \"1girl, solo, short red hair, (smiling:`(0.5+0.5*sin(t/max_f))`)\"",
+ "Deforum"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 56,
+ "type": "CR Text Input Switch (4 way)",
+ "pos": [
+ 548.9214027773999,
+ 1377.2026116672523
+ ],
+ "size": {
+ "0": 240,
+ "1": 120
+ },
+ "flags": {},
+ "order": 26,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text1",
+ "type": "STRING",
+ "link": 98
+ },
+ {
+ "name": "text2",
+ "type": "STRING",
+ "link": 97
+ },
+ {
+ "name": "text3",
+ "type": "STRING",
+ "link": 96
+ },
+ {
+ "name": "text4",
+ "type": "STRING",
+ "link": 89,
+ "slot_index": 3
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 54
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Text Input Switch (4 way)"
+ },
+ "widgets_values": [
+ 2,
+ "",
+ "",
+ "",
+ ""
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 79,
+ "type": "CR Cycle Text Simple",
+ "pos": [
+ -914.7703639000006,
+ 1433.9683054179686
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 21,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text_list_simple",
+ "type": "TEXT_LIST_SIMPLE",
+ "link": null
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 99,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 93
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Cycle Text Simple"
+ },
+ "widgets_values": [
+ "Sequential",
+ 3,
+ 2,
+ 0,
+ "1girl, solo, long grey hair",
+ "1girl, solo, long blue hair",
+ "1girl, solo, long red hair",
+ "1girl, solo, long black hair",
+ "1girl, solo, long pink hair"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 81,
+ "type": "CR Prompt List",
+ "pos": [
+ -914.7703639000006,
+ 1793.9683054179686
+ ],
+ "size": {
+ "0": 400,
+ "1": 684.0000610351562
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "prompt_list",
+ "type": "PROMPT_LIST",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "PROMPT_LIST",
+ "type": "PROMPT_LIST",
+ "links": [
+ 91
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Prompt List"
+ },
+ "widgets_values": [
+ 3,
+ 2,
+ "1girl, solo, long grey hair",
+ "Default",
+ "Default",
+ "Default",
+ "1girl, solo, long blue hair",
+ "Default",
+ "Default",
+ "Default",
+ "1girl, solo, long red hair",
+ "Default",
+ "Default",
+ "Default",
+ "1girl, solo, long black hair",
+ "Default",
+ "Default",
+ "Default",
+ "1girl, solo, long pink hair",
+ "Default",
+ "Default",
+ "Default"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 82,
+ "type": "CR Prompt List",
+ "pos": [
+ -464.7703638999999,
+ 1793.9683054179686
+ ],
+ "size": {
+ "0": 400,
+ "1": 684.0000610351562
+ },
+ "flags": {},
+ "order": 19,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "prompt_list",
+ "type": "PROMPT_LIST",
+ "link": 91
+ }
+ ],
+ "outputs": [
+ {
+ "name": "PROMPT_LIST",
+ "type": "PROMPT_LIST",
+ "links": [
+ 90
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Prompt List"
+ },
+ "widgets_values": [
+ 3,
+ 1,
+ "",
+ "Default",
+ "Default",
+ "Default",
+ "",
+ "Default",
+ "Default",
+ "Default",
+ "",
+ "Default",
+ "Default",
+ "Default",
+ "",
+ "Default",
+ "Default",
+ "Default",
+ "",
+ "Default",
+ "Default",
+ "Default"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 83,
+ "type": "CR Simple Prompt List",
+ "pos": [
+ -454.77036389999995,
+ 1033.9683054179686
+ ],
+ "size": {
+ "0": 400,
+ "1": 276.00006103515625
+ },
+ "flags": {},
+ "order": 20,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "simple_prompt_list",
+ "type": "SIMPLE_PROMPT_LIST",
+ "link": 92
+ }
+ ],
+ "outputs": [
+ {
+ "name": "SIMPLE_PROMPT_LIST",
+ "type": "SIMPLE_PROMPT_LIST",
+ "links": [
+ 94
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Prompt List"
+ },
+ "widgets_values": [
+ "",
+ "",
+ "",
+ "",
+ ""
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 85,
+ "type": "CR Simple Prompt List",
+ "pos": [
+ -464.7703638999999,
+ 1413.9683054179686
+ ],
+ "size": {
+ "0": 400,
+ "1": 280
+ },
+ "flags": {},
+ "order": 24,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "simple_prompt_list",
+ "type": "SIMPLE_PROMPT_LIST",
+ "link": null
+ },
+ {
+ "name": "prompt_1",
+ "type": "STRING",
+ "link": 93,
+ "widget": {
+ "name": "prompt_1",
+ "config": [
+ "STRING",
+ {
+ "multiline": true,
+ "default": "prompt"
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "SIMPLE_PROMPT_LIST",
+ "type": "SIMPLE_PROMPT_LIST",
+ "links": [
+ 95
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Prompt List"
+ },
+ "widgets_values": [
+ "prompt",
+ "",
+ "",
+ "",
+ ""
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 84,
+ "type": "CR Simple Prompt List",
+ "pos": [
+ -910,
+ 1030
+ ],
+ "size": {
+ "0": 400,
+ "1": 276.00006103515625
+ },
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "simple_prompt_list",
+ "type": "SIMPLE_PROMPT_LIST",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "SIMPLE_PROMPT_LIST",
+ "type": "SIMPLE_PROMPT_LIST",
+ "links": [
+ 92
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Prompt List"
+ },
+ "widgets_values": [
+ "1girl, solo, long grey hair",
+ "1girl, solo, long blue hair",
+ "1girl, solo, long red hair",
+ "1girl, solo, long black hair",
+ "1girl, solo, long pink hair"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 80,
+ "type": "CR Prompt List Keyframes",
+ "pos": [
+ -4.770363900000342,
+ 1793.9683054179686
+ ],
+ "size": {
+ "0": 330,
+ "1": 60
+ },
+ "flags": {},
+ "order": 22,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "prompt_list",
+ "type": "PROMPT_LIST",
+ "link": 90
+ }
+ ],
+ "outputs": [
+ {
+ "name": "keyframe_list",
+ "type": "STRING",
+ "links": [
+ 96
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Prompt List Keyframes"
+ },
+ "widgets_values": [
+ "Deforum"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 87,
+ "type": "CR Simple Prompt List Keyframes",
+ "pos": [
+ -4.770363900000342,
+ 1413.9683054179686
+ ],
+ "size": {
+ "0": 405.5999755859375,
+ "1": 178
+ },
+ "flags": {},
+ "order": 25,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "simple_prompt_list",
+ "type": "SIMPLE_PROMPT_LIST",
+ "link": 95
+ }
+ ],
+ "outputs": [
+ {
+ "name": "keyframe_list",
+ "type": "STRING",
+ "links": [
+ 97
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Prompt List Keyframes"
+ },
+ "widgets_values": [
+ 3,
+ 1,
+ "Default",
+ "Default",
+ "Default",
+ "Deforum"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 86,
+ "type": "CR Simple Prompt List Keyframes",
+ "pos": [
+ -4.770363900000342,
+ 1033.9683054179686
+ ],
+ "size": {
+ "0": 405.5999755859375,
+ "1": 178
+ },
+ "flags": {},
+ "order": 23,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "simple_prompt_list",
+ "type": "SIMPLE_PROMPT_LIST",
+ "link": 94
+ }
+ ],
+ "outputs": [
+ {
+ "name": "keyframe_list",
+ "type": "STRING",
+ "links": [
+ 98
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Prompt List Keyframes"
+ },
+ "widgets_values": [
+ 3,
+ 1,
+ "Default",
+ "Default",
+ "Default",
+ "Deforum"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 76,
+ "type": "Note",
+ "pos": [
+ -1240,
+ 1440
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 15,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Cycling text adds another level of prompt control"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 25,
+ "type": "CR Current Frame",
+ "pos": [
+ 50,
+ 360
+ ],
+ "size": {
+ "0": 320,
+ "1": 60
+ },
+ "flags": {
+ "collapsed": false
+ },
+ "order": 18,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "link": 23,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "links": [
+ 69,
+ 74,
+ 99
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Current Frame"
+ },
+ "widgets_values": [
+ 1,
+ "Yes"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ }
+ ],
+ "links": [
+ [
+ 11,
+ 14,
+ 0,
+ 15,
+ 0,
+ "INT"
+ ],
+ [
+ 12,
+ 14,
+ 1,
+ 15,
+ 1,
+ "INT"
+ ],
+ [
+ 13,
+ 15,
+ 0,
+ 13,
+ 3,
+ "LATENT"
+ ],
+ [
+ 14,
+ 13,
+ 0,
+ 16,
+ 0,
+ "LATENT"
+ ],
+ [
+ 15,
+ 17,
+ 0,
+ 16,
+ 1,
+ "VAE"
+ ],
+ [
+ 23,
+ 24,
+ 0,
+ 25,
+ 0,
+ "INT"
+ ],
+ [
+ 25,
+ 16,
+ 0,
+ 26,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 54,
+ 56,
+ 0,
+ 49,
+ 2,
+ "STRING"
+ ],
+ [
+ 55,
+ 52,
+ 0,
+ 49,
+ 3,
+ "STRING"
+ ],
+ [
+ 57,
+ 54,
+ 0,
+ 53,
+ 1,
+ "STRING"
+ ],
+ [
+ 65,
+ 49,
+ 0,
+ 13,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 66,
+ 53,
+ 0,
+ 13,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 67,
+ 47,
+ 1,
+ 49,
+ 0,
+ "CLIP"
+ ],
+ [
+ 68,
+ 47,
+ 0,
+ 13,
+ 0,
+ "MODEL"
+ ],
+ [
+ 69,
+ 25,
+ 0,
+ 49,
+ 1,
+ "INT"
+ ],
+ [
+ 71,
+ 47,
+ 1,
+ 53,
+ 0,
+ "CLIP"
+ ],
+ [
+ 74,
+ 25,
+ 0,
+ 26,
+ 1,
+ "INT"
+ ],
+ [
+ 89,
+ 78,
+ 0,
+ 56,
+ 3,
+ "STRING"
+ ],
+ [
+ 90,
+ 82,
+ 0,
+ 80,
+ 0,
+ "PROMPT_LIST"
+ ],
+ [
+ 91,
+ 81,
+ 0,
+ 82,
+ 0,
+ "PROMPT_LIST"
+ ],
+ [
+ 92,
+ 84,
+ 0,
+ 83,
+ 0,
+ "SIMPLE_PROMPT_LIST"
+ ],
+ [
+ 93,
+ 79,
+ 0,
+ 85,
+ 1,
+ "STRING"
+ ],
+ [
+ 94,
+ 83,
+ 0,
+ 86,
+ 0,
+ "SIMPLE_PROMPT_LIST"
+ ],
+ [
+ 95,
+ 85,
+ 0,
+ 87,
+ 0,
+ "SIMPLE_PROMPT_LIST"
+ ],
+ [
+ 96,
+ 80,
+ 0,
+ 56,
+ 2,
+ "STRING"
+ ],
+ [
+ 97,
+ 87,
+ 0,
+ 56,
+ 1,
+ "STRING"
+ ],
+ [
+ 98,
+ 86,
+ 0,
+ 56,
+ 0,
+ "STRING"
+ ],
+ [
+ 99,
+ 25,
+ 0,
+ 79,
+ 1,
+ "INT"
+ ]
+ ],
+ "groups": [
+ {
+ "title": "Prompt Keyframes",
+ "bounding": [
+ -972,
+ 924,
+ 1799,
+ 1930
+ ],
+ "color": "#3f789e",
+ "locked": false
+ },
+ {
+ "title": "1 - Simple",
+ "bounding": [
+ -1183,
+ 1001,
+ 186,
+ 80
+ ],
+ "color": "#b58b2a",
+ "locked": false
+ },
+ {
+ "title": "2 - Intermediate",
+ "bounding": [
+ -1208,
+ 1768,
+ 188,
+ 80
+ ],
+ "color": "#b58b2a",
+ "locked": false
+ },
+ {
+ "title": "3 - Advanced",
+ "bounding": [
+ -1189,
+ 2550,
+ 189,
+ 80
+ ],
+ "color": "#b58b2a",
+ "locked": false
+ }
+ ],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_A3_SimplePromptScheduling_Demo_v01b.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_A3_SimplePromptScheduling_Demo_v01b.json
new file mode 100644
index 0000000000000000000000000000000000000000..0f0e8642224cf6d4396cadc200a1495bcf1da662
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_A3_SimplePromptScheduling_Demo_v01b.json
@@ -0,0 +1,1560 @@
+{
+ "last_node_id": 613,
+ "last_link_id": 1112,
+ "nodes": [
+ {
+ "id": 249,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ 1463.9153190458471,
+ -676.6040770005651
+ ],
+ "size": {
+ "0": 315,
+ "1": 98
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 902
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 1091
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null,
+ "shape": 3,
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "SD1_5\\ComfyrollAnime_v1_fp16_pruned.safetensors"
+ ]
+ },
+ {
+ "id": 509,
+ "type": "Note",
+ "pos": [
+ 1106.950216410155,
+ -129.61091906250033
+ ],
+ "size": {
+ "0": 210,
+ "1": 120
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The primitive node increments the current_frame on each batch\n\nReset the value to 0 before each run\n"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 591,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 2190.728895078124,
+ -138.77898584960937
+ ],
+ "size": {
+ "0": 220,
+ "1": 80
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 1045
+ },
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 1044,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "multiline": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 1087
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ ""
+ ]
+ },
+ {
+ "id": 250,
+ "type": "VAELoader",
+ "pos": [
+ 1463.9153190458471,
+ -526.6040770005652
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 837
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "vae-ft-mse-840000-ema-pruned.safetensors"
+ ]
+ },
+ {
+ "id": 528,
+ "type": "Reroute",
+ "pos": [
+ 1900,
+ -500
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 1091
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 985,
+ 1045
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 500,
+ "type": "Reroute",
+ "pos": [
+ 1900,
+ -560
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 902
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 899
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 585,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 2420.721570859376,
+ 524.3449733515627
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 24,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 1010,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "forceInput": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "0.4"
+ ]
+ },
+ {
+ "id": 587,
+ "type": "CR Float To String",
+ "pos": [
+ 2210.721570859376,
+ 554.3449733515627
+ ],
+ "size": {
+ "0": 320,
+ "1": 60
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 22,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "float_",
+ "type": "FLOAT",
+ "link": 1097,
+ "widget": {
+ "name": "float_",
+ "config": [
+ "FLOAT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 1000000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 1010
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Float To String"
+ },
+ "widgets_values": [
+ 0
+ ]
+ },
+ {
+ "id": 602,
+ "type": "PreviewImage",
+ "pos": [
+ 3500,
+ -280
+ ],
+ "size": {
+ "0": 510,
+ "1": 530
+ },
+ "flags": {},
+ "order": 27,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 1083
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 491,
+ "type": "Reroute",
+ "pos": [
+ 1900,
+ -450
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 837
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 1101
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 607,
+ "type": "Reroute",
+ "pos": [
+ 2510,
+ -450
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 1101
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 1100
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 490,
+ "type": "CR Current Frame",
+ "pos": [
+ 1400,
+ -250
+ ],
+ "size": {
+ "0": 320,
+ "1": 80
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "link": 836,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "links": [
+ 1089,
+ 1096
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Current Frame"
+ },
+ "widgets_values": [
+ 0,
+ "Yes"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 581,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 2420.721570859376,
+ 384.34497335156266
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 18,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 1006,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "forceInput": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "13"
+ ]
+ },
+ {
+ "id": 604,
+ "type": "CR Simple Prompt Scheduler",
+ "pos": [
+ 1660,
+ -280
+ ],
+ "size": {
+ "0": 340,
+ "1": 190
+ },
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 1096,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "current_prompt",
+ "type": "STRING",
+ "links": [
+ 1093,
+ 1103
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "next_prompt",
+ "type": "STRING",
+ "links": [
+ 1094,
+ 1104
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "weight",
+ "type": "FLOAT",
+ "links": [
+ 1095,
+ 1097
+ ],
+ "shape": 3,
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Prompt Scheduler"
+ },
+ "widgets_values": [
+ "\"0\": \"1girl, long grey hair\",\n\"5\": \"1girl, long blue hair\",\n\"10\": \"1girl, long red hair\",\n\"15\": \"1girl, long black hair\"",
+ 0,
+ "Deforum"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 609,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 2310.721570859376,
+ 254.34497335156243
+ ],
+ "size": {
+ "0": 320,
+ "1": 80
+ },
+ "flags": {},
+ "order": 20,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 1104,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "forceInput": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "1girl, long black hair"
+ ]
+ },
+ {
+ "id": 608,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 2310.721570859376,
+ 124.34497335156253
+ ],
+ "size": {
+ "0": 320,
+ "1": 80
+ },
+ "flags": {},
+ "order": 19,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 1103,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "forceInput": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "1girl, long red hair"
+ ]
+ },
+ {
+ "id": 387,
+ "type": "Reroute",
+ "pos": [
+ 2510,
+ -560
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 899
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 614
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 600,
+ "type": "VAEDecode",
+ "pos": [
+ 3310,
+ -250
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 26,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 1080
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 1082
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 1083
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 601,
+ "type": "Reroute",
+ "pos": [
+ 3170,
+ -450
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 1100
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 1082
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 563,
+ "type": "CR Encode Scheduled Prompts",
+ "pos": [
+ 2190.728895078124,
+ -298.7789858496094
+ ],
+ "size": {
+ "0": 290,
+ "1": 94
+ },
+ "flags": {},
+ "order": 21,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 985
+ },
+ {
+ "name": "current_prompt",
+ "type": "STRING",
+ "link": 1093,
+ "widget": {
+ "name": "current_prompt",
+ "config": [
+ "STRING",
+ {
+ "multiline": true
+ }
+ ]
+ }
+ },
+ {
+ "name": "next_prompt",
+ "type": "STRING",
+ "link": 1094,
+ "widget": {
+ "name": "next_prompt",
+ "config": [
+ "STRING",
+ {
+ "multiline": true
+ }
+ ]
+ }
+ },
+ {
+ "name": "weight",
+ "type": "FLOAT",
+ "link": 1095,
+ "widget": {
+ "name": "weight",
+ "config": [
+ "FLOAT",
+ {
+ "default": 0,
+ "min": -9999,
+ "max": 9999,
+ "step": 0.1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 1106
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Encode Scheduled Prompts"
+ },
+ "widgets_values": [
+ "",
+ "",
+ 0
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 610,
+ "type": "CR Apply ControlNet",
+ "pos": [
+ 2570,
+ -300
+ ],
+ "size": {
+ "0": 250,
+ "1": 122
+ },
+ "flags": {},
+ "order": 23,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "conditioning",
+ "type": "CONDITIONING",
+ "link": 1106
+ },
+ {
+ "name": "control_net",
+ "type": "CONTROL_NET",
+ "link": 1112
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 1110
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 1107
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Apply ControlNet"
+ },
+ "widgets_values": [
+ "On",
+ 0.7000000000000001
+ ]
+ },
+ {
+ "id": 605,
+ "type": "LoadImage",
+ "pos": [
+ 2470,
+ -1070
+ ],
+ "size": {
+ "0": 320,
+ "1": 310
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 1110
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "depth_leres-0070.png",
+ "image"
+ ]
+ },
+ {
+ "id": 613,
+ "type": "ControlNetLoader",
+ "pos": [
+ 2470,
+ -700
+ ],
+ "size": {
+ "0": 320,
+ "1": 60
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "CONTROL_NET",
+ "type": "CONTROL_NET",
+ "links": [
+ 1112
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ControlNetLoader"
+ },
+ "widgets_values": [
+ "t2iadapter_zoedepth_sd15v1.pth"
+ ]
+ },
+ {
+ "id": 537,
+ "type": "CR Prompt Text",
+ "pos": [
+ 1690,
+ -20
+ ],
+ "size": {
+ "0": 310,
+ "1": 90
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "prompt",
+ "type": "STRING",
+ "links": [
+ 1044
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Prompt Text"
+ },
+ "widgets_values": [
+ "embedding:EasyNegative, "
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 612,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 2570,
+ -130
+ ],
+ "size": {
+ "0": 250,
+ "1": 120
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 1111
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 512,
+ 768,
+ 1
+ ]
+ },
+ {
+ "id": 582,
+ "type": "CR Integer To String",
+ "pos": [
+ 2210,
+ 410
+ ],
+ "size": {
+ "0": 320,
+ "1": 60
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 15,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "int_",
+ "type": "INT",
+ "link": 1089,
+ "widget": {
+ "name": "int_",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 18446744073709552000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 1006
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Integer To String"
+ },
+ "widgets_values": [
+ 0
+ ]
+ },
+ {
+ "id": 252,
+ "type": "KSampler",
+ "pos": [
+ 2918.4079541276733,
+ -278.0029971578494
+ ],
+ "size": {
+ "0": 290,
+ "1": 550
+ },
+ "flags": {},
+ "order": 25,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 614
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 1107
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 1087
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 1111
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 1080
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 722889772155925,
+ "fixed",
+ 20,
+ 10,
+ "dpmpp_2m",
+ "karras",
+ 1
+ ]
+ },
+ {
+ "id": 508,
+ "type": "PrimitiveNode",
+ "pos": [
+ 1106.950216410155,
+ -279.6109190625014
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 836
+ ],
+ "slot_index": 0,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ 0,
+ "increment"
+ ]
+ }
+ ],
+ "links": [
+ [
+ 614,
+ 387,
+ 0,
+ 252,
+ 0,
+ "MODEL"
+ ],
+ [
+ 836,
+ 508,
+ 0,
+ 490,
+ 0,
+ "INT"
+ ],
+ [
+ 837,
+ 250,
+ 0,
+ 491,
+ 0,
+ "*"
+ ],
+ [
+ 899,
+ 500,
+ 0,
+ 387,
+ 0,
+ "*"
+ ],
+ [
+ 902,
+ 249,
+ 0,
+ 500,
+ 0,
+ "*"
+ ],
+ [
+ 985,
+ 528,
+ 0,
+ 563,
+ 0,
+ "CLIP"
+ ],
+ [
+ 1006,
+ 582,
+ 0,
+ 581,
+ 0,
+ "STRING"
+ ],
+ [
+ 1010,
+ 587,
+ 0,
+ 585,
+ 0,
+ "STRING"
+ ],
+ [
+ 1044,
+ 537,
+ 0,
+ 591,
+ 1,
+ "STRING"
+ ],
+ [
+ 1045,
+ 528,
+ 0,
+ 591,
+ 0,
+ "CLIP"
+ ],
+ [
+ 1080,
+ 252,
+ 0,
+ 600,
+ 0,
+ "LATENT"
+ ],
+ [
+ 1082,
+ 601,
+ 0,
+ 600,
+ 1,
+ "VAE"
+ ],
+ [
+ 1083,
+ 600,
+ 0,
+ 602,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 1087,
+ 591,
+ 0,
+ 252,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 1089,
+ 490,
+ 0,
+ 582,
+ 0,
+ "INT"
+ ],
+ [
+ 1091,
+ 249,
+ 1,
+ 528,
+ 0,
+ "*"
+ ],
+ [
+ 1093,
+ 604,
+ 0,
+ 563,
+ 1,
+ "STRING"
+ ],
+ [
+ 1094,
+ 604,
+ 1,
+ 563,
+ 2,
+ "STRING"
+ ],
+ [
+ 1095,
+ 604,
+ 2,
+ 563,
+ 3,
+ "FLOAT"
+ ],
+ [
+ 1096,
+ 490,
+ 0,
+ 604,
+ 0,
+ "INT"
+ ],
+ [
+ 1097,
+ 604,
+ 2,
+ 587,
+ 0,
+ "FLOAT"
+ ],
+ [
+ 1100,
+ 607,
+ 0,
+ 601,
+ 0,
+ "*"
+ ],
+ [
+ 1101,
+ 491,
+ 0,
+ 607,
+ 0,
+ "*"
+ ],
+ [
+ 1103,
+ 604,
+ 0,
+ 608,
+ 0,
+ "STRING"
+ ],
+ [
+ 1104,
+ 604,
+ 1,
+ 609,
+ 0,
+ "STRING"
+ ],
+ [
+ 1106,
+ 563,
+ 0,
+ 610,
+ 0,
+ "CONDITIONING"
+ ],
+ [
+ 1107,
+ 610,
+ 0,
+ 252,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 1110,
+ 605,
+ 0,
+ 610,
+ 2,
+ "IMAGE"
+ ],
+ [
+ 1111,
+ 612,
+ 0,
+ 252,
+ 3,
+ "LATENT"
+ ],
+ [
+ 1112,
+ 613,
+ 0,
+ 610,
+ 1,
+ "CONTROL_NET"
+ ]
+ ],
+ "groups": [
+ {
+ "title": "Model",
+ "bounding": [
+ 1437,
+ -777,
+ 383,
+ 344
+ ],
+ "color": "#3f789e",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Sampling",
+ "bounding": [
+ 2876,
+ -390,
+ 369,
+ 693
+ ],
+ "color": "#8A8",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Load Frames",
+ "bounding": [
+ 1074,
+ -385,
+ 284,
+ 416
+ ],
+ "color": "#8A8",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Conditioning",
+ "bounding": [
+ 2162,
+ -387,
+ 354,
+ 365
+ ],
+ "color": "#8A8",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Prompt",
+ "bounding": [
+ 1617,
+ -388,
+ 425,
+ 501
+ ],
+ "color": "#a1309b",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Show Values",
+ "bounding": [
+ 2159,
+ 26,
+ 515,
+ 605
+ ],
+ "color": "#3f789e",
+ "font_size": 24,
+ "locked": false
+ }
+ ],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_A4_PromptScheduling_Demo_v01a.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_A4_PromptScheduling_Demo_v01a.json
new file mode 100644
index 0000000000000000000000000000000000000000..7a114ab8d2470b4c7438f157c3109549ca717289
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_A4_PromptScheduling_Demo_v01a.json
@@ -0,0 +1,1571 @@
+{
+ "last_node_id": 616,
+ "last_link_id": 1120,
+ "nodes": [
+ {
+ "id": 249,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ 1463.9153190458471,
+ -676.6040770005651
+ ],
+ "size": {
+ "0": 315,
+ "1": 98
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 902
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 1091
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null,
+ "shape": 3,
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "SD1_5\\ComfyrollAnime_v1_fp16_pruned.safetensors"
+ ]
+ },
+ {
+ "id": 509,
+ "type": "Note",
+ "pos": [
+ 1076.5295277914042,
+ -267.27814460234464
+ ],
+ "size": {
+ "0": 210,
+ "1": 120
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The primitive node increments the current_frame on each batch\n\nReset the value to 0 before each run\n"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 591,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 2190.728895078124,
+ -138.77898584960937
+ ],
+ "size": {
+ "0": 220,
+ "1": 80
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 1045
+ },
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 1044,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "multiline": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 1087
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ ""
+ ]
+ },
+ {
+ "id": 250,
+ "type": "VAELoader",
+ "pos": [
+ 1463.9153190458471,
+ -526.6040770005652
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 837
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "vae-ft-mse-840000-ema-pruned.safetensors"
+ ]
+ },
+ {
+ "id": 528,
+ "type": "Reroute",
+ "pos": [
+ 1900,
+ -500
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 1091
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 985,
+ 1045
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 500,
+ "type": "Reroute",
+ "pos": [
+ 1900,
+ -560
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 902
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 899
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 585,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 2420.721570859376,
+ 524.3449733515627
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 24,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 1010,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "forceInput": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "1.0"
+ ]
+ },
+ {
+ "id": 602,
+ "type": "PreviewImage",
+ "pos": [
+ 3500,
+ -280
+ ],
+ "size": {
+ "0": 510,
+ "1": 530
+ },
+ "flags": {},
+ "order": 27,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 1083
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 491,
+ "type": "Reroute",
+ "pos": [
+ 1900,
+ -450
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 837
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 1101
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 607,
+ "type": "Reroute",
+ "pos": [
+ 2510,
+ -450
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 1101
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 1100
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 581,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 2420.721570859376,
+ 384.34497335156266
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 18,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 1006,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "forceInput": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "10"
+ ]
+ },
+ {
+ "id": 609,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 2310.721570859376,
+ 254.34497335156243
+ ],
+ "size": {
+ "0": 320,
+ "1": 80
+ },
+ "flags": {},
+ "order": 20,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 1119,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "forceInput": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "anime lineart, 1girl, solo, short red hair, 2D, illustration"
+ ]
+ },
+ {
+ "id": 608,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 2310.721570859376,
+ 124.34497335156253
+ ],
+ "size": {
+ "0": 320,
+ "1": 80
+ },
+ "flags": {},
+ "order": 19,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 1118,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "forceInput": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "anime lineart, 1girl, solo, short red hair, 2D, illustration"
+ ]
+ },
+ {
+ "id": 387,
+ "type": "Reroute",
+ "pos": [
+ 2510,
+ -560
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 899
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 614
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 600,
+ "type": "VAEDecode",
+ "pos": [
+ 3310,
+ -250
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 26,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 1080
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 1082
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 1083
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 601,
+ "type": "Reroute",
+ "pos": [
+ 3170,
+ -450
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 1100
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 1082
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 563,
+ "type": "CR Encode Scheduled Prompts",
+ "pos": [
+ 2190.728895078124,
+ -298.7789858496094
+ ],
+ "size": {
+ "0": 290,
+ "1": 94
+ },
+ "flags": {},
+ "order": 21,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 985
+ },
+ {
+ "name": "current_prompt",
+ "type": "STRING",
+ "link": 1115,
+ "widget": {
+ "name": "current_prompt",
+ "config": [
+ "STRING",
+ {
+ "multiline": true
+ }
+ ]
+ }
+ },
+ {
+ "name": "next_prompt",
+ "type": "STRING",
+ "link": 1116,
+ "widget": {
+ "name": "next_prompt",
+ "config": [
+ "STRING",
+ {
+ "multiline": true
+ }
+ ]
+ }
+ },
+ {
+ "name": "weight",
+ "type": "FLOAT",
+ "link": 1117,
+ "widget": {
+ "name": "weight",
+ "config": [
+ "FLOAT",
+ {
+ "default": 0,
+ "min": -9999,
+ "max": 9999,
+ "step": 0.1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 1106
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Encode Scheduled Prompts"
+ },
+ "widgets_values": [
+ "",
+ "",
+ 0
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 610,
+ "type": "CR Apply ControlNet",
+ "pos": [
+ 2570,
+ -300
+ ],
+ "size": {
+ "0": 250,
+ "1": 122
+ },
+ "flags": {},
+ "order": 23,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "conditioning",
+ "type": "CONDITIONING",
+ "link": 1106
+ },
+ {
+ "name": "control_net",
+ "type": "CONTROL_NET",
+ "link": 1112
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 1110
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 1107
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Apply ControlNet"
+ },
+ "widgets_values": [
+ "On",
+ 0.7000000000000001
+ ]
+ },
+ {
+ "id": 605,
+ "type": "LoadImage",
+ "pos": [
+ 2470,
+ -1070
+ ],
+ "size": {
+ "0": 320,
+ "1": 310
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 1110
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "depth_leres-0070.png",
+ "image"
+ ]
+ },
+ {
+ "id": 613,
+ "type": "ControlNetLoader",
+ "pos": [
+ 2470,
+ -700
+ ],
+ "size": {
+ "0": 320,
+ "1": 60
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "CONTROL_NET",
+ "type": "CONTROL_NET",
+ "links": [
+ 1112
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ControlNetLoader"
+ },
+ "widgets_values": [
+ "t2iadapter_zoedepth_sd15v1.pth"
+ ]
+ },
+ {
+ "id": 612,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 2570,
+ -130
+ ],
+ "size": {
+ "0": 250,
+ "1": 120
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 1111
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 512,
+ 768,
+ 1
+ ]
+ },
+ {
+ "id": 582,
+ "type": "CR Integer To String",
+ "pos": [
+ 2210,
+ 410
+ ],
+ "size": {
+ "0": 320,
+ "1": 60
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 15,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "int_",
+ "type": "INT",
+ "link": 1089,
+ "widget": {
+ "name": "int_",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 18446744073709552000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 1006
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Integer To String"
+ },
+ "widgets_values": [
+ 0
+ ]
+ },
+ {
+ "id": 490,
+ "type": "CR Current Frame",
+ "pos": [
+ 1390,
+ -230
+ ],
+ "size": {
+ "0": 320,
+ "1": 80
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "link": 836,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "links": [
+ 1089,
+ 1114
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Current Frame"
+ },
+ "widgets_values": [
+ 0,
+ "Yes"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 537,
+ "type": "CR Prompt Text",
+ "pos": [
+ 1700,
+ 170
+ ],
+ "size": {
+ "0": 310,
+ "1": 90
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "prompt",
+ "type": "STRING",
+ "links": [
+ 1044
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Prompt Text"
+ },
+ "widgets_values": [
+ "embedding:EasyNegative, "
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 252,
+ "type": "KSampler",
+ "pos": [
+ 2918.4079541276733,
+ -278.0029971578494
+ ],
+ "size": {
+ "0": 290,
+ "1": 550
+ },
+ "flags": {},
+ "order": 25,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 614
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 1107
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 1087
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 1111
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 1080
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 722889772155925,
+ "fixed",
+ 20,
+ 10,
+ "dpmpp_2m",
+ "karras",
+ 1
+ ]
+ },
+ {
+ "id": 587,
+ "type": "CR Float To String",
+ "pos": [
+ 2210,
+ 550
+ ],
+ "size": {
+ "0": 320,
+ "1": 60
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 22,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "float_",
+ "type": "FLOAT",
+ "link": 1120,
+ "widget": {
+ "name": "float_",
+ "config": [
+ "FLOAT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 1000000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 1010
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Float To String"
+ },
+ "widgets_values": [
+ 0
+ ]
+ },
+ {
+ "id": 614,
+ "type": "CR Prompt Scheduler",
+ "pos": [
+ 1670,
+ -280
+ ],
+ "size": {
+ "0": 350,
+ "1": 390
+ },
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": null
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 1114,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "current_prompt",
+ "type": "STRING",
+ "links": [
+ 1115,
+ 1118
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "next_prompt",
+ "type": "STRING",
+ "links": [
+ 1116,
+ 1119
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "weight",
+ "type": "FLOAT",
+ "links": [
+ 1117,
+ 1120
+ ],
+ "shape": 3,
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Prompt Scheduler"
+ },
+ "widgets_values": [
+ "Keyframe List",
+ 0,
+ "default text",
+ "Deforum",
+ "Yes",
+ "P1",
+ "\"0\": \"1girl, solo, long grey hair\",\n\"5\": \"1girl, solo, long blue hair\",\n\"10\": \"1girl, solo, short red hair\",\n\"15\": \"1girl, solo, short black hair\"",
+ "anime lineart",
+ "2D, illustration"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 508,
+ "type": "PrimitiveNode",
+ "pos": [
+ 1076.5295277914042,
+ -417.2781446023464
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 836
+ ],
+ "slot_index": 0,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ 0,
+ "increment"
+ ]
+ }
+ ],
+ "links": [
+ [
+ 614,
+ 387,
+ 0,
+ 252,
+ 0,
+ "MODEL"
+ ],
+ [
+ 836,
+ 508,
+ 0,
+ 490,
+ 0,
+ "INT"
+ ],
+ [
+ 837,
+ 250,
+ 0,
+ 491,
+ 0,
+ "*"
+ ],
+ [
+ 899,
+ 500,
+ 0,
+ 387,
+ 0,
+ "*"
+ ],
+ [
+ 902,
+ 249,
+ 0,
+ 500,
+ 0,
+ "*"
+ ],
+ [
+ 985,
+ 528,
+ 0,
+ 563,
+ 0,
+ "CLIP"
+ ],
+ [
+ 1006,
+ 582,
+ 0,
+ 581,
+ 0,
+ "STRING"
+ ],
+ [
+ 1010,
+ 587,
+ 0,
+ 585,
+ 0,
+ "STRING"
+ ],
+ [
+ 1044,
+ 537,
+ 0,
+ 591,
+ 1,
+ "STRING"
+ ],
+ [
+ 1045,
+ 528,
+ 0,
+ 591,
+ 0,
+ "CLIP"
+ ],
+ [
+ 1080,
+ 252,
+ 0,
+ 600,
+ 0,
+ "LATENT"
+ ],
+ [
+ 1082,
+ 601,
+ 0,
+ 600,
+ 1,
+ "VAE"
+ ],
+ [
+ 1083,
+ 600,
+ 0,
+ 602,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 1087,
+ 591,
+ 0,
+ 252,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 1089,
+ 490,
+ 0,
+ 582,
+ 0,
+ "INT"
+ ],
+ [
+ 1091,
+ 249,
+ 1,
+ 528,
+ 0,
+ "*"
+ ],
+ [
+ 1100,
+ 607,
+ 0,
+ 601,
+ 0,
+ "*"
+ ],
+ [
+ 1101,
+ 491,
+ 0,
+ 607,
+ 0,
+ "*"
+ ],
+ [
+ 1106,
+ 563,
+ 0,
+ 610,
+ 0,
+ "CONDITIONING"
+ ],
+ [
+ 1107,
+ 610,
+ 0,
+ 252,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 1110,
+ 605,
+ 0,
+ 610,
+ 2,
+ "IMAGE"
+ ],
+ [
+ 1111,
+ 612,
+ 0,
+ 252,
+ 3,
+ "LATENT"
+ ],
+ [
+ 1112,
+ 613,
+ 0,
+ 610,
+ 1,
+ "CONTROL_NET"
+ ],
+ [
+ 1114,
+ 490,
+ 0,
+ 614,
+ 1,
+ "INT"
+ ],
+ [
+ 1115,
+ 614,
+ 0,
+ 563,
+ 1,
+ "STRING"
+ ],
+ [
+ 1116,
+ 614,
+ 1,
+ 563,
+ 2,
+ "STRING"
+ ],
+ [
+ 1117,
+ 614,
+ 2,
+ 563,
+ 3,
+ "FLOAT"
+ ],
+ [
+ 1118,
+ 614,
+ 0,
+ 608,
+ 0,
+ "STRING"
+ ],
+ [
+ 1119,
+ 614,
+ 1,
+ 609,
+ 0,
+ "STRING"
+ ],
+ [
+ 1120,
+ 614,
+ 2,
+ 587,
+ 0,
+ "FLOAT"
+ ]
+ ],
+ "groups": [
+ {
+ "title": "Model",
+ "bounding": [
+ 1437,
+ -777,
+ 383,
+ 344
+ ],
+ "color": "#3f789e",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Sampling",
+ "bounding": [
+ 2876,
+ -390,
+ 369,
+ 693
+ ],
+ "color": "#8A8",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Load Frames",
+ "bounding": [
+ 1043,
+ -523,
+ 284,
+ 416
+ ],
+ "color": "#8A8",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Conditioning",
+ "bounding": [
+ 2162,
+ -387,
+ 354,
+ 365
+ ],
+ "color": "#8A8",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Prompt",
+ "bounding": [
+ 1617,
+ -388,
+ 434,
+ 697
+ ],
+ "color": "#a1309b",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Show Values",
+ "bounding": [
+ 2159,
+ 26,
+ 515,
+ 605
+ ],
+ "color": "#3f789e",
+ "font_size": 24,
+ "locked": false
+ }
+ ],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_A5_CentralPromptScheduling_Demo_v01a.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_A5_CentralPromptScheduling_Demo_v01a.json
new file mode 100644
index 0000000000000000000000000000000000000000..453e3e843a3c0e35980d85a213d7a712a7bb499e
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_A5_CentralPromptScheduling_Demo_v01a.json
@@ -0,0 +1,1650 @@
+{
+ "last_node_id": 616,
+ "last_link_id": 1120,
+ "nodes": [
+ {
+ "id": 249,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ 1463.9153190458471,
+ -676.6040770005651
+ ],
+ "size": {
+ "0": 315,
+ "1": 98
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 902
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 1091
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null,
+ "shape": 3,
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "SD1_5\\ComfyrollAnime_v1_fp16_pruned.safetensors"
+ ]
+ },
+ {
+ "id": 509,
+ "type": "Note",
+ "pos": [
+ 1076.5295277914042,
+ -267.27814460234464
+ ],
+ "size": {
+ "0": 210,
+ "1": 120
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The primitive node increments the current_frame on each batch\n\nReset the value to 0 before each run\n"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 591,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 2190.728895078124,
+ -138.77898584960937
+ ],
+ "size": {
+ "0": 220,
+ "1": 80
+ },
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 1045
+ },
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 1044,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "multiline": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 1087
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ ""
+ ]
+ },
+ {
+ "id": 250,
+ "type": "VAELoader",
+ "pos": [
+ 1463.9153190458471,
+ -526.6040770005652
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 837
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "vae-ft-mse-840000-ema-pruned.safetensors"
+ ]
+ },
+ {
+ "id": 528,
+ "type": "Reroute",
+ "pos": [
+ 1900,
+ -500
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 1091
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 985,
+ 1045
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 500,
+ "type": "Reroute",
+ "pos": [
+ 1900,
+ -560
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 902
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 899
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 585,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 2420.721570859376,
+ 524.3449733515627
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 25,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 1010,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "forceInput": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "0.4"
+ ]
+ },
+ {
+ "id": 602,
+ "type": "PreviewImage",
+ "pos": [
+ 3500,
+ -280
+ ],
+ "size": {
+ "0": 510,
+ "1": 530
+ },
+ "flags": {},
+ "order": 28,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 1083
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 491,
+ "type": "Reroute",
+ "pos": [
+ 1900,
+ -450
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 837
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 1101
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 607,
+ "type": "Reroute",
+ "pos": [
+ 2510,
+ -450
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 15,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 1101
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 1100
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 581,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 2420.721570859376,
+ 384.34497335156266
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 19,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 1006,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "forceInput": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "5"
+ ]
+ },
+ {
+ "id": 609,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 2310.721570859376,
+ 254.34497335156243
+ ],
+ "size": {
+ "0": 320,
+ "1": 80
+ },
+ "flags": {},
+ "order": 21,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 1119,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "forceInput": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "1girl, long black hair"
+ ]
+ },
+ {
+ "id": 608,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 2310.721570859376,
+ 124.34497335156253
+ ],
+ "size": {
+ "0": 320,
+ "1": 80
+ },
+ "flags": {},
+ "order": 20,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 1118,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "forceInput": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "1girl, long red hair"
+ ]
+ },
+ {
+ "id": 387,
+ "type": "Reroute",
+ "pos": [
+ 2510,
+ -560
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 899
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 614
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 600,
+ "type": "VAEDecode",
+ "pos": [
+ 3310,
+ -250
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 27,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 1080
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 1082
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 1083
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 601,
+ "type": "Reroute",
+ "pos": [
+ 3170,
+ -450
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 18,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 1100
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 1082
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 563,
+ "type": "CR Encode Scheduled Prompts",
+ "pos": [
+ 2190.728895078124,
+ -298.7789858496094
+ ],
+ "size": {
+ "0": 290,
+ "1": 94
+ },
+ "flags": {},
+ "order": 22,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 985
+ },
+ {
+ "name": "current_prompt",
+ "type": "STRING",
+ "link": 1115,
+ "widget": {
+ "name": "current_prompt",
+ "config": [
+ "STRING",
+ {
+ "multiline": true
+ }
+ ]
+ }
+ },
+ {
+ "name": "next_prompt",
+ "type": "STRING",
+ "link": 1116,
+ "widget": {
+ "name": "next_prompt",
+ "config": [
+ "STRING",
+ {
+ "multiline": true
+ }
+ ]
+ }
+ },
+ {
+ "name": "weight",
+ "type": "FLOAT",
+ "link": 1117,
+ "widget": {
+ "name": "weight",
+ "config": [
+ "FLOAT",
+ {
+ "default": 0,
+ "min": -9999,
+ "max": 9999,
+ "step": 0.1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 1106
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Encode Scheduled Prompts"
+ },
+ "widgets_values": [
+ "",
+ "",
+ 0
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 610,
+ "type": "CR Apply ControlNet",
+ "pos": [
+ 2570,
+ -300
+ ],
+ "size": {
+ "0": 250,
+ "1": 122
+ },
+ "flags": {},
+ "order": 24,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "conditioning",
+ "type": "CONDITIONING",
+ "link": 1106
+ },
+ {
+ "name": "control_net",
+ "type": "CONTROL_NET",
+ "link": 1112
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 1110
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 1107
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Apply ControlNet"
+ },
+ "widgets_values": [
+ "On",
+ 0.7000000000000001
+ ]
+ },
+ {
+ "id": 605,
+ "type": "LoadImage",
+ "pos": [
+ 2470,
+ -1070
+ ],
+ "size": {
+ "0": 320,
+ "1": 310
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 1110
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "depth_leres-0070.png",
+ "image"
+ ]
+ },
+ {
+ "id": 613,
+ "type": "ControlNetLoader",
+ "pos": [
+ 2470,
+ -700
+ ],
+ "size": {
+ "0": 320,
+ "1": 60
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "CONTROL_NET",
+ "type": "CONTROL_NET",
+ "links": [
+ 1112
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ControlNetLoader"
+ },
+ "widgets_values": [
+ "t2iadapter_zoedepth_sd15v1.pth"
+ ]
+ },
+ {
+ "id": 612,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 2570,
+ -130
+ ],
+ "size": {
+ "0": 250,
+ "1": 120
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 1111
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 512,
+ 768,
+ 1
+ ]
+ },
+ {
+ "id": 582,
+ "type": "CR Integer To String",
+ "pos": [
+ 2210,
+ 410
+ ],
+ "size": {
+ "0": 320,
+ "1": 60
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 16,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "int_",
+ "type": "INT",
+ "link": 1089,
+ "widget": {
+ "name": "int_",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 18446744073709552000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 1006
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Integer To String"
+ },
+ "widgets_values": [
+ 0
+ ]
+ },
+ {
+ "id": 537,
+ "type": "CR Prompt Text",
+ "pos": [
+ 1690,
+ 60
+ ],
+ "size": {
+ "0": 310,
+ "1": 90
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "prompt",
+ "type": "STRING",
+ "links": [
+ 1044
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Prompt Text"
+ },
+ "widgets_values": [
+ "embedding:EasyNegative, "
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 490,
+ "type": "CR Current Frame",
+ "pos": [
+ 1390,
+ -230
+ ],
+ "size": {
+ "0": 320,
+ "1": 80
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "link": 836,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "links": [
+ 1089,
+ 1114
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Current Frame"
+ },
+ "widgets_values": [
+ 0,
+ "Yes"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 615,
+ "type": "CR Central Schedule",
+ "pos": [
+ 1040,
+ -30
+ ],
+ "size": {
+ "0": 410,
+ "1": 550
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": [
+ 1113
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Central Schedule"
+ },
+ "widgets_values": [
+ "\"0\": \"1girl, long grey hair\",\n\"5\": \"1girl, long blue hair\",\n\"10\": \"1girl, long red hair\",\n\"15\": \"1girl, long black hair\"",
+ "Prompt",
+ "P1",
+ "schedule",
+ "Value",
+ "",
+ "schedule",
+ "Value",
+ "",
+ "Deforum"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 252,
+ "type": "KSampler",
+ "pos": [
+ 2918.4079541276733,
+ -278.0029971578494
+ ],
+ "size": {
+ "0": 290,
+ "1": 550
+ },
+ "flags": {},
+ "order": 26,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 614
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 1107
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 1087
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 1111
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 1080
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 722889772155925,
+ "fixed",
+ 20,
+ 10,
+ "dpmpp_2m",
+ "karras",
+ 1
+ ]
+ },
+ {
+ "id": 614,
+ "type": "CR Prompt Scheduler",
+ "pos": [
+ 1670,
+ -280
+ ],
+ "size": {
+ "0": 350,
+ "1": 286
+ },
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": 1113
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 1114,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ },
+ {
+ "name": "keyframe_list",
+ "type": "STRING",
+ "link": null,
+ "widget": {
+ "name": "keyframe_list",
+ "config": [
+ "STRING",
+ {
+ "multiline": true,
+ "default": "keyframe list"
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "current_prompt",
+ "type": "STRING",
+ "links": [
+ 1115,
+ 1118
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "next_prompt",
+ "type": "STRING",
+ "links": [
+ 1116,
+ 1119
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "weight",
+ "type": "FLOAT",
+ "links": [
+ 1117,
+ 1120
+ ],
+ "shape": 3,
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Prompt Scheduler"
+ },
+ "widgets_values": [
+ "Schedule",
+ 0,
+ "default text",
+ "Deforum",
+ "Yes",
+ "P1",
+ "anime lineart",
+ "anime line-art",
+ "2D, illustration"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 587,
+ "type": "CR Float To String",
+ "pos": [
+ 2210,
+ 550
+ ],
+ "size": {
+ "0": 320,
+ "1": 60
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 23,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "float_",
+ "type": "FLOAT",
+ "link": 1120,
+ "widget": {
+ "name": "float_",
+ "config": [
+ "FLOAT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 1000000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 1010
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Float To String"
+ },
+ "widgets_values": [
+ 0
+ ]
+ },
+ {
+ "id": 508,
+ "type": "PrimitiveNode",
+ "pos": [
+ 1076.5295277914042,
+ -417.2781446023464
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 836
+ ],
+ "slot_index": 0,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ 0,
+ "increment"
+ ]
+ }
+ ],
+ "links": [
+ [
+ 614,
+ 387,
+ 0,
+ 252,
+ 0,
+ "MODEL"
+ ],
+ [
+ 836,
+ 508,
+ 0,
+ 490,
+ 0,
+ "INT"
+ ],
+ [
+ 837,
+ 250,
+ 0,
+ 491,
+ 0,
+ "*"
+ ],
+ [
+ 899,
+ 500,
+ 0,
+ 387,
+ 0,
+ "*"
+ ],
+ [
+ 902,
+ 249,
+ 0,
+ 500,
+ 0,
+ "*"
+ ],
+ [
+ 985,
+ 528,
+ 0,
+ 563,
+ 0,
+ "CLIP"
+ ],
+ [
+ 1006,
+ 582,
+ 0,
+ 581,
+ 0,
+ "STRING"
+ ],
+ [
+ 1010,
+ 587,
+ 0,
+ 585,
+ 0,
+ "STRING"
+ ],
+ [
+ 1044,
+ 537,
+ 0,
+ 591,
+ 1,
+ "STRING"
+ ],
+ [
+ 1045,
+ 528,
+ 0,
+ 591,
+ 0,
+ "CLIP"
+ ],
+ [
+ 1080,
+ 252,
+ 0,
+ 600,
+ 0,
+ "LATENT"
+ ],
+ [
+ 1082,
+ 601,
+ 0,
+ 600,
+ 1,
+ "VAE"
+ ],
+ [
+ 1083,
+ 600,
+ 0,
+ 602,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 1087,
+ 591,
+ 0,
+ 252,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 1089,
+ 490,
+ 0,
+ 582,
+ 0,
+ "INT"
+ ],
+ [
+ 1091,
+ 249,
+ 1,
+ 528,
+ 0,
+ "*"
+ ],
+ [
+ 1100,
+ 607,
+ 0,
+ 601,
+ 0,
+ "*"
+ ],
+ [
+ 1101,
+ 491,
+ 0,
+ 607,
+ 0,
+ "*"
+ ],
+ [
+ 1106,
+ 563,
+ 0,
+ 610,
+ 0,
+ "CONDITIONING"
+ ],
+ [
+ 1107,
+ 610,
+ 0,
+ 252,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 1110,
+ 605,
+ 0,
+ 610,
+ 2,
+ "IMAGE"
+ ],
+ [
+ 1111,
+ 612,
+ 0,
+ 252,
+ 3,
+ "LATENT"
+ ],
+ [
+ 1112,
+ 613,
+ 0,
+ 610,
+ 1,
+ "CONTROL_NET"
+ ],
+ [
+ 1113,
+ 615,
+ 0,
+ 614,
+ 0,
+ "SCHEDULE"
+ ],
+ [
+ 1114,
+ 490,
+ 0,
+ 614,
+ 1,
+ "INT"
+ ],
+ [
+ 1115,
+ 614,
+ 0,
+ 563,
+ 1,
+ "STRING"
+ ],
+ [
+ 1116,
+ 614,
+ 1,
+ 563,
+ 2,
+ "STRING"
+ ],
+ [
+ 1117,
+ 614,
+ 2,
+ 563,
+ 3,
+ "FLOAT"
+ ],
+ [
+ 1118,
+ 614,
+ 0,
+ 608,
+ 0,
+ "STRING"
+ ],
+ [
+ 1119,
+ 614,
+ 1,
+ 609,
+ 0,
+ "STRING"
+ ],
+ [
+ 1120,
+ 614,
+ 2,
+ 587,
+ 0,
+ "FLOAT"
+ ]
+ ],
+ "groups": [
+ {
+ "title": "Model",
+ "bounding": [
+ 1437,
+ -777,
+ 383,
+ 344
+ ],
+ "color": "#3f789e",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Sampling",
+ "bounding": [
+ 2876,
+ -390,
+ 369,
+ 693
+ ],
+ "color": "#8A8",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Load Frames",
+ "bounding": [
+ 1043,
+ -523,
+ 284,
+ 416
+ ],
+ "color": "#8A8",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Conditioning",
+ "bounding": [
+ 2162,
+ -387,
+ 354,
+ 365
+ ],
+ "color": "#8A8",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Prompt",
+ "bounding": [
+ 1617,
+ -388,
+ 434,
+ 582
+ ],
+ "color": "#a1309b",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Show Values",
+ "bounding": [
+ 2159,
+ 26,
+ 515,
+ 605
+ ],
+ "color": "#3f789e",
+ "font_size": 24,
+ "locked": false
+ }
+ ],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_B1_CentralSchedule_Demo_v01b.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_B1_CentralSchedule_Demo_v01b.json
new file mode 100644
index 0000000000000000000000000000000000000000..2b69311422dfbb1de01743d85fb7b10fd3fbdb31
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_B1_CentralSchedule_Demo_v01b.json
@@ -0,0 +1,879 @@
+{
+ "last_node_id": 128,
+ "last_link_id": 146,
+ "nodes": [
+ {
+ "id": 59,
+ "type": "Note",
+ "pos": [
+ 928.237579825001,
+ 372.36553706933677
+ ],
+ "size": {
+ "0": 210,
+ "1": 130
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The CR Current Frame node prints the current frame index to console so that you can see which frame is currently being processed"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 78,
+ "type": "CR Integer To String",
+ "pos": [
+ 928.237579825001,
+ 682.3655370693361
+ ],
+ "size": {
+ "0": 210,
+ "1": 34
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "int_",
+ "type": "INT",
+ "link": 91,
+ "widget": {
+ "name": "int_",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 18446744073709552000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 90
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Integer To String"
+ },
+ "widgets_values": [
+ 0
+ ]
+ },
+ {
+ "id": 77,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 928.237579825001,
+ 772.3655370693361
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 90
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "15"
+ ]
+ },
+ {
+ "id": 114,
+ "type": "Note",
+ "pos": [
+ 368.2375798250001,
+ 572.3655370693361
+ ],
+ "size": {
+ "0": 210,
+ "1": 170
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The primitive node increments the current_frame on each batch\n\nReset the value to 0 before each run\n\nIn normal workflows the Primitive node can replace the Animation Builder"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 79,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 1648.2375798250027,
+ 622.3655370693361
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 144
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "Airbrushing"
+ ]
+ },
+ {
+ "id": 84,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 1648.2375798250027,
+ 482.3655370693361
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 99
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "1024"
+ ]
+ },
+ {
+ "id": 83,
+ "type": "CR Integer To String",
+ "pos": [
+ 1648.2375798250027,
+ 392.36553706933677
+ ],
+ "size": {
+ "0": 210,
+ "1": 34
+ },
+ "flags": {},
+ "order": 15,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "int_",
+ "type": "INT",
+ "link": 145,
+ "widget": {
+ "name": "int_",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 18446744073709552000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 99
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Integer To String"
+ },
+ "widgets_values": [
+ 0
+ ]
+ },
+ {
+ "id": 68,
+ "type": "Note",
+ "pos": [
+ 928.237579825001,
+ 242.3655370693365
+ ],
+ "size": {
+ "0": 210,
+ "1": 70
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Frames are processed in sequence starting from frame index 0"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 86,
+ "type": "Note",
+ "pos": [
+ 650,
+ -10
+ ],
+ "size": {
+ "0": 210,
+ "1": 70
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "As a general rule schedules should always have a line for frame 0"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 120,
+ "type": "Note",
+ "pos": [
+ 1260,
+ 240
+ ],
+ "size": {
+ "0": 210,
+ "1": 70
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Always convert the current_frame widget to input"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 91,
+ "type": "Reroute",
+ "pos": [
+ 990,
+ 20
+ ],
+ "size": [
+ 107.2,
+ 26
+ ],
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 138
+ }
+ ],
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": [
+ 139,
+ 140
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 25,
+ "type": "CR Current Frame",
+ "pos": [
+ 928.237579825001,
+ 572.3655370693361
+ ],
+ "size": {
+ "0": 210,
+ "1": 58
+ },
+ "flags": {
+ "collapsed": false
+ },
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "link": 134,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "links": [
+ 91,
+ 141,
+ 142
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Current Frame"
+ },
+ "widgets_values": [
+ 0,
+ "Yes"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 122,
+ "type": "CR Central Schedule",
+ "pos": [
+ -160,
+ -460
+ ],
+ "size": {
+ "0": 360,
+ "1": 510
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": [
+ 137
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Central Schedule"
+ },
+ "widgets_values": [
+ "schedule",
+ "LoRA",
+ "",
+ "schedule",
+ "Upscale",
+ "",
+ "schedule",
+ "Model",
+ "",
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 124,
+ "type": "CR Text Scheduler",
+ "pos": [
+ 1258.2375798250025,
+ 622.3655370693361
+ ],
+ "size": {
+ "0": 320,
+ "1": 150
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": 140
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 142,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 144
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Text Scheduler"
+ },
+ "widgets_values": [
+ "Schedule",
+ 0,
+ "T1",
+ "default text",
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 123,
+ "type": "CR Value Scheduler",
+ "pos": [
+ 1258.2375798250025,
+ 392.36553706933677
+ ],
+ "size": {
+ "0": 320,
+ "1": 150
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": 139
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 141,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 145
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Value Scheduler"
+ },
+ "widgets_values": [
+ "Schedule",
+ 0,
+ "V1",
+ 1024,
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 113,
+ "type": "PrimitiveNode",
+ "pos": [
+ 618.2375798249993,
+ 572.3655370693361
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 134
+ ],
+ "slot_index": 0,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ 0,
+ "increment"
+ ]
+ },
+ {
+ "id": 121,
+ "type": "CR Central Schedule",
+ "pos": [
+ 240,
+ -460
+ ],
+ "size": {
+ "0": 360,
+ "1": 510
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": 137
+ }
+ ],
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": [
+ 138,
+ 146
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Central Schedule"
+ },
+ "widgets_values": [
+ "0, 512\n2, 640\n3, 768\n4, 896\n8, 1024",
+ "Value",
+ "V1",
+ "0, Art Nouveau\n2, Antarctica\n4, 2D Game Art\n5, Animation\n8, Airbrushing",
+ "Text",
+ "T1",
+ "schedule",
+ "Camera",
+ "",
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 128,
+ "type": "CR Output Schedule To File",
+ "pos": [
+ 780,
+ -460
+ ],
+ "size": {
+ "0": 315,
+ "1": 82
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": 146
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Output Schedule To File"
+ },
+ "widgets_values": [
+ "",
+ ""
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ }
+ ],
+ "links": [
+ [
+ 90,
+ 78,
+ 0,
+ 77,
+ 0,
+ "STRING"
+ ],
+ [
+ 91,
+ 25,
+ 0,
+ 78,
+ 0,
+ "INT"
+ ],
+ [
+ 99,
+ 83,
+ 0,
+ 84,
+ 0,
+ "STRING"
+ ],
+ [
+ 123,
+ 98,
+ 0,
+ 91,
+ 0,
+ "*"
+ ],
+ [
+ 134,
+ 113,
+ 0,
+ 25,
+ 0,
+ "INT"
+ ],
+ [
+ 137,
+ 122,
+ 0,
+ 121,
+ 0,
+ "SCHEDULE"
+ ],
+ [
+ 138,
+ 121,
+ 0,
+ 91,
+ 0,
+ "*"
+ ],
+ [
+ 139,
+ 91,
+ 0,
+ 123,
+ 0,
+ "SCHEDULE"
+ ],
+ [
+ 140,
+ 91,
+ 0,
+ 124,
+ 0,
+ "SCHEDULE"
+ ],
+ [
+ 141,
+ 25,
+ 0,
+ 123,
+ 1,
+ "INT"
+ ],
+ [
+ 142,
+ 25,
+ 0,
+ 124,
+ 1,
+ "INT"
+ ],
+ [
+ 144,
+ 124,
+ 0,
+ 79,
+ 0,
+ "STRING"
+ ],
+ [
+ 145,
+ 123,
+ 0,
+ 83,
+ 0,
+ "INT"
+ ],
+ [
+ 146,
+ 121,
+ 0,
+ 128,
+ 0,
+ "SCHEDULE"
+ ]
+ ],
+ "groups": [
+ {
+ "title": "Central Schedule",
+ "bounding": [
+ -205,
+ -562,
+ 1381,
+ 656
+ ],
+ "color": "#3f789e",
+ "locked": false
+ },
+ {
+ "title": "Schedulers",
+ "bounding": [
+ 323,
+ 136,
+ 1580,
+ 765
+ ],
+ "color": "#3f789e",
+ "locked": false
+ }
+ ],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_B2_LoadScheduleFromFile_Demo_v01a.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_B2_LoadScheduleFromFile_Demo_v01a.json
new file mode 100644
index 0000000000000000000000000000000000000000..f10768be74e69c65ae1edc0911866cb1c56b96f3
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_B2_LoadScheduleFromFile_Demo_v01a.json
@@ -0,0 +1,705 @@
+{
+ "last_node_id": 133,
+ "last_link_id": 154,
+ "nodes": [
+ {
+ "id": 59,
+ "type": "Note",
+ "pos": [
+ 928.237579825001,
+ 372.36553706933677
+ ],
+ "size": {
+ "0": 210,
+ "1": 130
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The CR Current Frame node prints the current frame index to console so that you can see which frame is currently being processed"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 78,
+ "type": "CR Integer To String",
+ "pos": [
+ 928.237579825001,
+ 682.3655370693361
+ ],
+ "size": {
+ "0": 210,
+ "1": 34
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "int_",
+ "type": "INT",
+ "link": 91,
+ "widget": {
+ "name": "int_",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 18446744073709552000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 90
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Integer To String"
+ },
+ "widgets_values": [
+ 0
+ ]
+ },
+ {
+ "id": 77,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 928.237579825001,
+ 772.3655370693361
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 90
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "5"
+ ]
+ },
+ {
+ "id": 114,
+ "type": "Note",
+ "pos": [
+ 368.2375798250001,
+ 572.3655370693361
+ ],
+ "size": {
+ "0": 210,
+ "1": 170
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The primitive node increments the current_frame on each batch\n\nReset the value to 0 before each run\n\nIn normal workflows the Primitive node can replace the Animation Builder"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 79,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 1648.2375798250027,
+ 622.3655370693361
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 144
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "Animation"
+ ]
+ },
+ {
+ "id": 84,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 1648.2375798250027,
+ 482.3655370693361
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 99
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "896"
+ ]
+ },
+ {
+ "id": 83,
+ "type": "CR Integer To String",
+ "pos": [
+ 1648.2375798250027,
+ 392.36553706933677
+ ],
+ "size": {
+ "0": 210,
+ "1": 34
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "int_",
+ "type": "INT",
+ "link": 145,
+ "widget": {
+ "name": "int_",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 18446744073709552000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 99
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Integer To String"
+ },
+ "widgets_values": [
+ 0
+ ]
+ },
+ {
+ "id": 68,
+ "type": "Note",
+ "pos": [
+ 928.237579825001,
+ 242.3655370693365
+ ],
+ "size": {
+ "0": 210,
+ "1": 70
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Frames are processed in sequence starting from frame index 0"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 120,
+ "type": "Note",
+ "pos": [
+ 1260,
+ 240
+ ],
+ "size": {
+ "0": 210,
+ "1": 70
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Always convert the current_frame widget to input"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 25,
+ "type": "CR Current Frame",
+ "pos": [
+ 928.237579825001,
+ 572.3655370693361
+ ],
+ "size": {
+ "0": 210,
+ "1": 58
+ },
+ "flags": {
+ "collapsed": false
+ },
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "link": 134,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "links": [
+ 91,
+ 141,
+ 142
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Current Frame"
+ },
+ "widgets_values": [
+ 5,
+ "Yes"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 124,
+ "type": "CR Text Scheduler",
+ "pos": [
+ 1258.2375798250025,
+ 622.3655370693361
+ ],
+ "size": {
+ "0": 320,
+ "1": 150
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": 153
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 142,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 144
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Text Scheduler"
+ },
+ "widgets_values": [
+ "Schedule",
+ 0,
+ "T1",
+ "default text",
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 123,
+ "type": "CR Value Scheduler",
+ "pos": [
+ 1258.2375798250025,
+ 392.36553706933677
+ ],
+ "size": {
+ "0": 320,
+ "1": 150
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": 152
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 141,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 145
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Value Scheduler"
+ },
+ "widgets_values": [
+ "Schedule",
+ 0,
+ "V1",
+ 1024,
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 130,
+ "type": "Note",
+ "pos": [
+ 360,
+ 250
+ ],
+ "size": {
+ "0": 210,
+ "1": 90
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Load scheduling data from a file"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 113,
+ "type": "PrimitiveNode",
+ "pos": [
+ 618.2375798249993,
+ 572.3655370693361
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 134
+ ],
+ "slot_index": 0,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ 6,
+ "increment"
+ ]
+ },
+ {
+ "id": 132,
+ "type": "CR Load Schedule From File",
+ "pos": [
+ 620,
+ 350
+ ],
+ "size": [
+ 260,
+ 130
+ ],
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": [
+ 152,
+ 153
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": [],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Load Schedule From File"
+ },
+ "widgets_values": [
+ "F:\\ComfyUI\\ComfyUI_windows_portable\\ComfyUI\\output\\Schedules",
+ "TestSchedule2",
+ "csv"
+ ]
+ }
+ ],
+ "links": [
+ [
+ 90,
+ 78,
+ 0,
+ 77,
+ 0,
+ "STRING"
+ ],
+ [
+ 91,
+ 25,
+ 0,
+ 78,
+ 0,
+ "INT"
+ ],
+ [
+ 99,
+ 83,
+ 0,
+ 84,
+ 0,
+ "STRING"
+ ],
+ [
+ 123,
+ 98,
+ 0,
+ 91,
+ 0,
+ "*"
+ ],
+ [
+ 134,
+ 113,
+ 0,
+ 25,
+ 0,
+ "INT"
+ ],
+ [
+ 141,
+ 25,
+ 0,
+ 123,
+ 1,
+ "INT"
+ ],
+ [
+ 142,
+ 25,
+ 0,
+ 124,
+ 1,
+ "INT"
+ ],
+ [
+ 144,
+ 124,
+ 0,
+ 79,
+ 0,
+ "STRING"
+ ],
+ [
+ 145,
+ 123,
+ 0,
+ 83,
+ 0,
+ "INT"
+ ],
+ [
+ 152,
+ 132,
+ 0,
+ 123,
+ 0,
+ "SCHEDULE"
+ ],
+ [
+ 153,
+ 132,
+ 0,
+ 124,
+ 0,
+ "SCHEDULE"
+ ]
+ ],
+ "groups": [
+ {
+ "title": "Schedulers",
+ "bounding": [
+ 323,
+ 136,
+ 1580,
+ 765
+ ],
+ "color": "#3f789e",
+ "locked": false
+ }
+ ],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_B3_OutputScheduleToFile_Demo_v01a.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_B3_OutputScheduleToFile_Demo_v01a.json
new file mode 100644
index 0000000000000000000000000000000000000000..2987eda2bef941cdede696e2cc4bdf7875540cbb
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_B3_OutputScheduleToFile_Demo_v01a.json
@@ -0,0 +1,193 @@
+{
+ "last_node_id": 128,
+ "last_link_id": 146,
+ "nodes": [
+ {
+ "id": 122,
+ "type": "CR Central Schedule",
+ "pos": [
+ -160,
+ -460
+ ],
+ "size": {
+ "0": 360,
+ "1": 510
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": [
+ 137
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Central Schedule"
+ },
+ "widgets_values": [
+ "schedule",
+ "LoRA",
+ "",
+ "schedule",
+ "Upscale",
+ "",
+ "schedule",
+ "Model",
+ "",
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 121,
+ "type": "CR Central Schedule",
+ "pos": [
+ 240,
+ -460
+ ],
+ "size": {
+ "0": 360,
+ "1": 510
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": 137
+ }
+ ],
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": [
+ 146
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Central Schedule"
+ },
+ "widgets_values": [
+ "0, 512\n2, 640\n3, 768\n4, 896\n8, 1024",
+ "Value",
+ "V1",
+ "0, Art Nouveau\n2, Antarctica\n4, 2D Game Art\n5, Animation\n8, Airbrushing",
+ "Text",
+ "T1",
+ "schedule",
+ "Camera",
+ "",
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 128,
+ "type": "CR Output Schedule To File",
+ "pos": [
+ 780,
+ -460
+ ],
+ "size": {
+ "0": 315,
+ "1": 106
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": 146
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Output Schedule To File"
+ },
+ "widgets_values": [
+ "F:\\ComfyUI\\ComfyUI_windows_portable\\ComfyUI\\output\\Schedules",
+ "TestSchedule",
+ "csv"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ }
+ ],
+ "links": [
+ [
+ 123,
+ 98,
+ 0,
+ 91,
+ 0,
+ "*"
+ ],
+ [
+ 137,
+ 122,
+ 0,
+ 121,
+ 0,
+ "SCHEDULE"
+ ],
+ [
+ 146,
+ 121,
+ 0,
+ 128,
+ 0,
+ "SCHEDULE"
+ ]
+ ],
+ "groups": [
+ {
+ "title": "Central Schedule",
+ "bounding": [
+ -205,
+ -562,
+ 1381,
+ 656
+ ],
+ "color": "#3f789e",
+ "font_size": 24,
+ "locked": false
+ }
+ ],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_B4_CombineSchedules_Demo_v01a.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_B4_CombineSchedules_Demo_v01a.json
new file mode 100644
index 0000000000000000000000000000000000000000..371a29308f8e69022bfbfe1012d9ac3e3450c643
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_B4_CombineSchedules_Demo_v01a.json
@@ -0,0 +1,1102 @@
+{
+ "last_node_id": 135,
+ "last_link_id": 164,
+ "nodes": [
+ {
+ "id": 59,
+ "type": "Note",
+ "pos": [
+ 928.237579825001,
+ 372.36553706933677
+ ],
+ "size": {
+ "0": 210,
+ "1": 130
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The CR Current Frame node prints the current frame index to console so that you can see which frame is currently being processed"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 78,
+ "type": "CR Integer To String",
+ "pos": [
+ 928.237579825001,
+ 682.3655370693361
+ ],
+ "size": {
+ "0": 210,
+ "1": 34
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "int_",
+ "type": "INT",
+ "link": 91,
+ "widget": {
+ "name": "int_",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 18446744073709552000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 90
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Integer To String"
+ },
+ "widgets_values": [
+ 0
+ ]
+ },
+ {
+ "id": 77,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 928.237579825001,
+ 772.3655370693361
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 90
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "4"
+ ]
+ },
+ {
+ "id": 114,
+ "type": "Note",
+ "pos": [
+ 368.2375798250001,
+ 572.3655370693361
+ ],
+ "size": {
+ "0": 210,
+ "1": 170
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The primitive node increments the current_frame on each batch\n\nReset the value to 0 before each run\n\nIn normal workflows the Primitive node can replace the Animation Builder"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 79,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 1648.2375798250027,
+ 622.3655370693361
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 20,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 144
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "2D Game Art"
+ ]
+ },
+ {
+ "id": 84,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 1648.2375798250027,
+ 482.3655370693361
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 21,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 99
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "896"
+ ]
+ },
+ {
+ "id": 83,
+ "type": "CR Integer To String",
+ "pos": [
+ 1648.2375798250027,
+ 392.36553706933677
+ ],
+ "size": {
+ "0": 210,
+ "1": 34
+ },
+ "flags": {},
+ "order": 19,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "int_",
+ "type": "INT",
+ "link": 145,
+ "widget": {
+ "name": "int_",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 18446744073709552000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 99
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Integer To String"
+ },
+ "widgets_values": [
+ 0
+ ]
+ },
+ {
+ "id": 68,
+ "type": "Note",
+ "pos": [
+ 928.237579825001,
+ 242.3655370693365
+ ],
+ "size": {
+ "0": 210,
+ "1": 70
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Frames are processed in sequence starting from frame index 0"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 120,
+ "type": "Note",
+ "pos": [
+ 1260,
+ 240
+ ],
+ "size": {
+ "0": 210,
+ "1": 70
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Always convert the current_frame widget to input"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 25,
+ "type": "CR Current Frame",
+ "pos": [
+ 928.237579825001,
+ 572.3655370693361
+ ],
+ "size": {
+ "0": 210,
+ "1": 58
+ },
+ "flags": {
+ "collapsed": false
+ },
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "link": 134,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "links": [
+ 91,
+ 141,
+ 142
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Current Frame"
+ },
+ "widgets_values": [
+ 0,
+ "Yes"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 124,
+ "type": "CR Text Scheduler",
+ "pos": [
+ 1258.2375798250025,
+ 622.3655370693361
+ ],
+ "size": {
+ "0": 320,
+ "1": 150
+ },
+ "flags": {},
+ "order": 18,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": 140
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 142,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 144
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Text Scheduler"
+ },
+ "widgets_values": [
+ "Schedule",
+ 0,
+ "T1",
+ "default text",
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 123,
+ "type": "CR Value Scheduler",
+ "pos": [
+ 1258.2375798250025,
+ 392.36553706933677
+ ],
+ "size": {
+ "0": 320,
+ "1": 150
+ },
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": 139
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 141,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 145
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Value Scheduler"
+ },
+ "widgets_values": [
+ "Schedule",
+ 0,
+ "V1",
+ 1024,
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 128,
+ "type": "CR Output Schedule To File",
+ "pos": [
+ 732.5905286035164,
+ -717.3633016601559
+ ],
+ "size": {
+ "0": 315,
+ "1": 106
+ },
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": 159
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Output Schedule To File"
+ },
+ "widgets_values": [
+ "",
+ "",
+ "txt"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 91,
+ "type": "Reroute",
+ "pos": [
+ 980,
+ 30
+ ],
+ "size": [
+ 107.2,
+ 26
+ ],
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 158
+ }
+ ],
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": [
+ 139,
+ 140
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 131,
+ "type": "CR Combine Schedules",
+ "pos": [
+ 280,
+ -510
+ ],
+ "size": {
+ "0": 254.40000915527344,
+ "1": 106
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule_1",
+ "type": "SCHEDULE",
+ "link": 163
+ },
+ {
+ "name": "schedule_2",
+ "type": "SCHEDULE",
+ "link": 161
+ },
+ {
+ "name": "schedule_3",
+ "type": "SCHEDULE",
+ "link": 162
+ },
+ {
+ "name": "schedule_4",
+ "type": "SCHEDULE",
+ "link": 164
+ }
+ ],
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": [
+ 158,
+ 159
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": [
+ 157
+ ],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Combine Schedules"
+ },
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 132,
+ "type": "CR Simple Schedule",
+ "pos": [
+ -207.40947139648455,
+ -137.36330166015622
+ ],
+ "size": {
+ "0": 360,
+ "1": 200
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": [
+ 162
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Schedule"
+ },
+ "widgets_values": [
+ "0, 512\n2, 640\n3, 768\n4, 896\n8, 1024",
+ "Value",
+ "V1",
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 133,
+ "type": "CR Combine Schedules",
+ "pos": [
+ 280,
+ -670
+ ],
+ "size": {
+ "0": 254.40000915527344,
+ "1": 106
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule_1",
+ "type": "SCHEDULE",
+ "link": null
+ },
+ {
+ "name": "schedule_2",
+ "type": "SCHEDULE",
+ "link": null
+ },
+ {
+ "name": "schedule_3",
+ "type": "SCHEDULE",
+ "link": null
+ },
+ {
+ "name": "schedule_4",
+ "type": "SCHEDULE",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": [
+ 163
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": [],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Combine Schedules"
+ },
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 122,
+ "type": "CR Central Schedule",
+ "pos": [
+ -207.40947139648455,
+ -717.3633016601559
+ ],
+ "size": {
+ "0": 360,
+ "1": 510
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": [
+ 161
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Central Schedule"
+ },
+ "widgets_values": [
+ "0, Art Nouveau\n2, Antarctica\n4, 2D Game Art\n5, Animation\n8, Airbrushing",
+ "Text",
+ "T1",
+ "schedule",
+ "Upscale",
+ "",
+ "schedule",
+ "Model",
+ "",
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 134,
+ "type": "Note",
+ "pos": [
+ 660,
+ -510
+ ],
+ "size": [
+ 230,
+ 160
+ ],
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Four different methods of combining schedules are shown\n\n1. Combine from Central Schedules\n2. Combine from Simple Schedules\n3. Combine from combination nodes\n4. Combine from File\n\n"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 135,
+ "type": "CR Load Schedule From File",
+ "pos": [
+ 240,
+ -70
+ ],
+ "size": {
+ "0": 315,
+ "1": 126
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": [
+ 164
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Load Schedule From File"
+ },
+ "widgets_values": [
+ "",
+ "",
+ "txt"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 113,
+ "type": "PrimitiveNode",
+ "pos": [
+ 618.2375798249993,
+ 572.3655370693361
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 134
+ ],
+ "slot_index": 0,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ 0,
+ "increment"
+ ]
+ },
+ {
+ "id": 130,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 670,
+ -260
+ ],
+ "size": {
+ "0": 310,
+ "1": 130
+ },
+ "flags": {},
+ "order": 15,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 157,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "forceInput": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "[('T1', '0, Art Nouveau'), ('T1', '2, Antarctica'), ('T1', '4, 2D Game Art'), ('T1', '5, Animation'), ('T1', '8, Airbrushing'), ('V1', '0, 512'), ('V1', '2, 640'), ('V1', '3, 768'), ('V1', '4, 896'), ('V1', '8, 1024'), ['V1', '0, 512'], ['V1', '2, 640'], ['V1', '3, 768'], ['V1', '4, 896'], ['V1', '8, 1024'], ['T1', '0, Art Nouveau'], ['T1', '2, Antarctica'], ['T1', '4, 2D Game Art'], ['T1', '5, Animation'], ['T1', '8, Airbrushing']]"
+ ]
+ }
+ ],
+ "links": [
+ [
+ 90,
+ 78,
+ 0,
+ 77,
+ 0,
+ "STRING"
+ ],
+ [
+ 91,
+ 25,
+ 0,
+ 78,
+ 0,
+ "INT"
+ ],
+ [
+ 99,
+ 83,
+ 0,
+ 84,
+ 0,
+ "STRING"
+ ],
+ [
+ 123,
+ 98,
+ 0,
+ 91,
+ 0,
+ "*"
+ ],
+ [
+ 134,
+ 113,
+ 0,
+ 25,
+ 0,
+ "INT"
+ ],
+ [
+ 139,
+ 91,
+ 0,
+ 123,
+ 0,
+ "SCHEDULE"
+ ],
+ [
+ 140,
+ 91,
+ 0,
+ 124,
+ 0,
+ "SCHEDULE"
+ ],
+ [
+ 141,
+ 25,
+ 0,
+ 123,
+ 1,
+ "INT"
+ ],
+ [
+ 142,
+ 25,
+ 0,
+ 124,
+ 1,
+ "INT"
+ ],
+ [
+ 144,
+ 124,
+ 0,
+ 79,
+ 0,
+ "STRING"
+ ],
+ [
+ 145,
+ 123,
+ 0,
+ 83,
+ 0,
+ "INT"
+ ],
+ [
+ 157,
+ 131,
+ 1,
+ 130,
+ 0,
+ "STRING"
+ ],
+ [
+ 158,
+ 131,
+ 0,
+ 91,
+ 0,
+ "*"
+ ],
+ [
+ 159,
+ 131,
+ 0,
+ 128,
+ 0,
+ "SCHEDULE"
+ ],
+ [
+ 161,
+ 122,
+ 0,
+ 131,
+ 1,
+ "SCHEDULE"
+ ],
+ [
+ 162,
+ 132,
+ 0,
+ 131,
+ 2,
+ "SCHEDULE"
+ ],
+ [
+ 163,
+ 133,
+ 0,
+ 131,
+ 0,
+ "SCHEDULE"
+ ],
+ [
+ 164,
+ 135,
+ 0,
+ 131,
+ 3,
+ "SCHEDULE"
+ ]
+ ],
+ "groups": [
+ {
+ "title": "Central Schedule",
+ "bounding": [
+ -253,
+ -819,
+ 1389,
+ 919
+ ],
+ "color": "#3f789e",
+ "locked": false
+ },
+ {
+ "title": "Schedulers",
+ "bounding": [
+ 323,
+ 136,
+ 1580,
+ 765
+ ],
+ "color": "#3f789e",
+ "locked": false
+ }
+ ],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_C1_SimpleValueScheduler_Demo_v01b.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_C1_SimpleValueScheduler_Demo_v01b.json
new file mode 100644
index 0000000000000000000000000000000000000000..5c722e888873aee17dcc4b3309459297cb5d28b9
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_C1_SimpleValueScheduler_Demo_v01b.json
@@ -0,0 +1,1258 @@
+{
+ "last_node_id": 90,
+ "last_link_id": 103,
+ "nodes": [
+ {
+ "id": 11,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 1120,
+ 660
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 54
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 8
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "1girl, period costume"
+ ]
+ },
+ {
+ "id": 13,
+ "type": "KSampler",
+ "pos": [
+ 1480,
+ 580
+ ],
+ "size": {
+ "0": 320,
+ "1": 470
+ },
+ "flags": {},
+ "order": 21,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 53
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 8
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 9
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 13
+ },
+ {
+ "name": "denoise",
+ "type": "FLOAT",
+ "link": 93,
+ "widget": {
+ "name": "denoise",
+ "config": [
+ "FLOAT",
+ {
+ "default": 1,
+ "min": 0,
+ "max": 1,
+ "step": 0.01
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 14
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 32603574575332,
+ "fixed",
+ 20,
+ 8,
+ "euler",
+ "normal",
+ 1
+ ]
+ },
+ {
+ "id": 17,
+ "type": "VAELoader",
+ "pos": [
+ 1860,
+ 710
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 15
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "vae-ft-mse-840000-ema-pruned.safetensors"
+ ]
+ },
+ {
+ "id": 59,
+ "type": "Note",
+ "pos": [
+ 660,
+ 20
+ ],
+ "size": {
+ "0": 210,
+ "1": 130
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The CR Current Frame node prints the current frame index to console so that you can see which frame is currently being processed"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 47,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ 630,
+ 590
+ ],
+ "size": {
+ "0": 380,
+ "1": 100
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 53
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 54,
+ 55
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "SD1_5\\ComfyrollAnime_v1_fp16_pruned.safetensors"
+ ]
+ },
+ {
+ "id": 68,
+ "type": "Note",
+ "pos": [
+ 660,
+ -100
+ ],
+ "size": {
+ "0": 210,
+ "1": 70
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Frames are processed in sequence starting from frame index 0"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 15,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 1580,
+ 420
+ ],
+ "size": {
+ "0": 210,
+ "1": 74
+ },
+ "flags": {},
+ "order": 20,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "link": 11,
+ "widget": {
+ "name": "width",
+ "config": [
+ "INT",
+ {
+ "default": 512,
+ "min": 64,
+ "max": 8192,
+ "step": 8
+ }
+ ]
+ }
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "link": 12,
+ "widget": {
+ "name": "height",
+ "config": [
+ "INT",
+ {
+ "default": 512,
+ "min": 64,
+ "max": 8192,
+ "step": 8
+ }
+ ]
+ }
+ },
+ {
+ "name": "batch_size",
+ "type": "INT",
+ "link": 72,
+ "widget": {
+ "name": "batch_size",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": 1,
+ "max": 64
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 13
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ 1
+ ]
+ },
+ {
+ "id": 78,
+ "type": "CR Integer To String",
+ "pos": [
+ 660,
+ 330
+ ],
+ "size": {
+ "0": 210,
+ "1": 34
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "int_",
+ "type": "INT",
+ "link": 91,
+ "widget": {
+ "name": "int_",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 18446744073709552000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 90
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Integer To String"
+ },
+ "widgets_values": [
+ 0
+ ]
+ },
+ {
+ "id": 77,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 660,
+ 420
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 15,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 90
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "0"
+ ]
+ },
+ {
+ "id": 12,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 1120,
+ 830
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 55
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 9
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "embedding:EasyNegative,\nnsfw"
+ ]
+ },
+ {
+ "id": 79,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 1210,
+ 400
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 18,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 97
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "1.0"
+ ]
+ },
+ {
+ "id": 72,
+ "type": "CR Simple Value Scheduler",
+ "pos": [
+ 950,
+ 310
+ ],
+ "size": {
+ "0": 220,
+ "1": 150
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 87,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 93,
+ 96
+ ],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Value Scheduler"
+ },
+ "widgets_values": [
+ "0, 0.7\n2, 0.8\n4, 0.9\n6, 0.95\n8, 1.0",
+ 0
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 81,
+ "type": "CR Float To String",
+ "pos": [
+ 1210,
+ 280
+ ],
+ "size": {
+ "0": 210,
+ "1": 60
+ },
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "float_",
+ "type": "FLOAT",
+ "link": 96,
+ "widget": {
+ "name": "float_",
+ "config": [
+ "FLOAT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 1000000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 97
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Float To String"
+ },
+ "widgets_values": [
+ 0
+ ]
+ },
+ {
+ "id": 84,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 1210,
+ 90
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 19,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 99
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "1024"
+ ]
+ },
+ {
+ "id": 83,
+ "type": "CR Integer To String",
+ "pos": [
+ 1210,
+ 10
+ ],
+ "size": {
+ "0": 210,
+ "1": 34
+ },
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "int_",
+ "type": "INT",
+ "link": 98,
+ "widget": {
+ "name": "int_",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 18446744073709552000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 99
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Integer To String"
+ },
+ "widgets_values": [
+ 0
+ ]
+ },
+ {
+ "id": 25,
+ "type": "CR Current Frame",
+ "pos": [
+ 660,
+ 220
+ ],
+ "size": {
+ "0": 210,
+ "1": 58
+ },
+ "flags": {
+ "collapsed": false
+ },
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "link": 102,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "links": [
+ 87,
+ 91,
+ 100
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Current Frame"
+ },
+ "widgets_values": [
+ 0,
+ "Yes"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 82,
+ "type": "CR Simple Value Scheduler",
+ "pos": [
+ 950,
+ 30
+ ],
+ "size": {
+ "0": 220,
+ "1": 150
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 100,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 98,
+ 101
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Value Scheduler"
+ },
+ "widgets_values": [
+ "0, 512\n2, 640\n4, 768\n6, 896\n8, 1024",
+ 0
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 14,
+ "type": "CR SD1.5 Aspect Ratio",
+ "pos": [
+ 1480,
+ 110
+ ],
+ "size": {
+ "0": 320,
+ "1": 240
+ },
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "link": 101,
+ "widget": {
+ "name": "width",
+ "config": [
+ "INT",
+ {
+ "default": 512,
+ "min": 64,
+ "max": 2048
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "links": [
+ 11
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "links": [
+ 12
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "upscale_factor",
+ "type": "FLOAT",
+ "links": [],
+ "shape": 3,
+ "slot_index": 2
+ },
+ {
+ "name": "batch_size",
+ "type": "INT",
+ "links": [
+ 72
+ ],
+ "shape": 3,
+ "slot_index": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR SD1.5 Aspect Ratio"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ "custom",
+ "Off",
+ 1,
+ 1
+ ]
+ },
+ {
+ "id": 86,
+ "type": "Note",
+ "pos": [
+ 950,
+ -100
+ ],
+ "size": {
+ "0": 210,
+ "1": 70
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Simple value schedulers must have a line for frame 0"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 87,
+ "type": "Note",
+ "pos": [
+ 1540,
+ -110
+ ],
+ "size": {
+ "0": 210,
+ "1": 130
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The first value scheduler is increasing the width in CR SD1.5 Aspect Ratio\n\nThe second value scheduler is increasing the denoise in the KSampler"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 88,
+ "type": "PrimitiveNode",
+ "pos": [
+ 360,
+ 220
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 102
+ ],
+ "slot_index": 0,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ 1,
+ "increment"
+ ]
+ },
+ {
+ "id": 89,
+ "type": "Note",
+ "pos": [
+ 100,
+ 220
+ ],
+ "size": {
+ "0": 210,
+ "1": 170
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The primitive node increments the current_frame on each batch\n\nReset the value to 0 before each run\n\nIn normal workflows the Primitive node can replace the Animation Builder"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 16,
+ "type": "VAEDecode",
+ "pos": [
+ 1910,
+ 580
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 22,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 14
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 15
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 103
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 90,
+ "type": "PreviewImage",
+ "pos": [
+ 2250,
+ 580
+ ],
+ "size": {
+ "0": 210,
+ "1": 26
+ },
+ "flags": {},
+ "order": 23,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 103
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ }
+ ],
+ "links": [
+ [
+ 8,
+ 11,
+ 0,
+ 13,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 9,
+ 12,
+ 0,
+ 13,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 11,
+ 14,
+ 0,
+ 15,
+ 0,
+ "INT"
+ ],
+ [
+ 12,
+ 14,
+ 1,
+ 15,
+ 1,
+ "INT"
+ ],
+ [
+ 13,
+ 15,
+ 0,
+ 13,
+ 3,
+ "LATENT"
+ ],
+ [
+ 14,
+ 13,
+ 0,
+ 16,
+ 0,
+ "LATENT"
+ ],
+ [
+ 15,
+ 17,
+ 0,
+ 16,
+ 1,
+ "VAE"
+ ],
+ [
+ 53,
+ 47,
+ 0,
+ 13,
+ 0,
+ "MODEL"
+ ],
+ [
+ 54,
+ 47,
+ 1,
+ 11,
+ 0,
+ "CLIP"
+ ],
+ [
+ 55,
+ 47,
+ 1,
+ 12,
+ 0,
+ "CLIP"
+ ],
+ [
+ 72,
+ 14,
+ 3,
+ 15,
+ 2,
+ "INT"
+ ],
+ [
+ 87,
+ 25,
+ 0,
+ 72,
+ 0,
+ "INT"
+ ],
+ [
+ 90,
+ 78,
+ 0,
+ 77,
+ 0,
+ "STRING"
+ ],
+ [
+ 91,
+ 25,
+ 0,
+ 78,
+ 0,
+ "INT"
+ ],
+ [
+ 93,
+ 72,
+ 1,
+ 13,
+ 4,
+ "FLOAT"
+ ],
+ [
+ 96,
+ 72,
+ 1,
+ 81,
+ 0,
+ "FLOAT"
+ ],
+ [
+ 97,
+ 81,
+ 0,
+ 79,
+ 0,
+ "STRING"
+ ],
+ [
+ 98,
+ 82,
+ 0,
+ 83,
+ 0,
+ "INT"
+ ],
+ [
+ 99,
+ 83,
+ 0,
+ 84,
+ 0,
+ "STRING"
+ ],
+ [
+ 100,
+ 25,
+ 0,
+ 82,
+ 0,
+ "INT"
+ ],
+ [
+ 101,
+ 82,
+ 0,
+ 14,
+ 0,
+ "INT"
+ ],
+ [
+ 102,
+ 88,
+ 0,
+ 25,
+ 0,
+ "INT"
+ ],
+ [
+ 103,
+ 16,
+ 0,
+ 90,
+ 0,
+ "IMAGE"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_C2_ValueScheduler_Demo_v01a.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_C2_ValueScheduler_Demo_v01a.json
new file mode 100644
index 0000000000000000000000000000000000000000..2ccfa8a732d5d3bb83cd41d13463562fec9a11c7
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_C2_ValueScheduler_Demo_v01a.json
@@ -0,0 +1,1355 @@
+{
+ "last_node_id": 123,
+ "last_link_id": 141,
+ "nodes": [
+ {
+ "id": 17,
+ "type": "VAELoader",
+ "pos": [
+ 2540,
+ 510
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 15
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "vae-ft-mse-840000-ema-pruned.safetensors"
+ ]
+ },
+ {
+ "id": 16,
+ "type": "VAEDecode",
+ "pos": [
+ 2590,
+ 380
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 24,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 14
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 15
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 133
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 60,
+ "type": "Note",
+ "pos": [
+ -80,
+ 320
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "To run this workflow, first press Reset in the Animation Builder and then press the Queue button, Do not use queue prompt in the ComfyUI menu."
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 59,
+ "type": "Note",
+ "pos": [
+ 510,
+ 30
+ ],
+ "size": {
+ "0": 210,
+ "1": 130
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The CR Current Frame node prints the current frame index to console so that you can see which frame is currently being processed"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 15,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 2260,
+ 220
+ ],
+ "size": {
+ "0": 210,
+ "1": 74
+ },
+ "flags": {},
+ "order": 22,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "link": 11,
+ "widget": {
+ "name": "width",
+ "config": [
+ "INT",
+ {
+ "default": 512,
+ "min": 64,
+ "max": 8192,
+ "step": 8
+ }
+ ]
+ }
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "link": 12,
+ "widget": {
+ "name": "height",
+ "config": [
+ "INT",
+ {
+ "default": 512,
+ "min": 64,
+ "max": 8192,
+ "step": 8
+ }
+ ]
+ }
+ },
+ {
+ "name": "batch_size",
+ "type": "INT",
+ "link": 72,
+ "widget": {
+ "name": "batch_size",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": 1,
+ "max": 64
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 13
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ 1
+ ]
+ },
+ {
+ "id": 78,
+ "type": "CR Integer To String",
+ "pos": [
+ 510,
+ 340
+ ],
+ "size": {
+ "0": 210,
+ "1": 34
+ },
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "int_",
+ "type": "INT",
+ "link": 91,
+ "widget": {
+ "name": "int_",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 18446744073709552000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 90
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Integer To String"
+ },
+ "widgets_values": [
+ 0
+ ]
+ },
+ {
+ "id": 77,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 510,
+ 430
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 18,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 90
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "0"
+ ]
+ },
+ {
+ "id": 68,
+ "type": "Note",
+ "pos": [
+ 510,
+ -90
+ ],
+ "size": {
+ "0": 210,
+ "1": 70
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Frames are processed in sequence starting from frame index 0"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 13,
+ "type": "KSampler",
+ "pos": [
+ 2160,
+ 380
+ ],
+ "size": {
+ "0": 320,
+ "1": 470
+ },
+ "flags": {},
+ "order": 23,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 53
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 8
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 9
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 13
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 14
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 32603574575332,
+ "fixed",
+ 20,
+ 8,
+ "euler",
+ "normal",
+ 1
+ ]
+ },
+ {
+ "id": 14,
+ "type": "CR SD1.5 Aspect Ratio",
+ "pos": [
+ 2160,
+ -90
+ ],
+ "size": {
+ "0": 320,
+ "1": 240
+ },
+ "flags": {},
+ "order": 20,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "link": 140,
+ "widget": {
+ "name": "width",
+ "config": [
+ "INT",
+ {
+ "default": 512,
+ "min": 64,
+ "max": 2048
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "links": [
+ 11
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "links": [
+ 12
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "upscale_factor",
+ "type": "FLOAT",
+ "links": [],
+ "shape": 3,
+ "slot_index": 2
+ },
+ {
+ "name": "batch_size",
+ "type": "INT",
+ "links": [
+ 72
+ ],
+ "shape": 3,
+ "slot_index": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR SD1.5 Aspect Ratio"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ "custom",
+ "Off",
+ 1,
+ 1
+ ]
+ },
+ {
+ "id": 26,
+ "type": "Save Image Sequence (mtb)",
+ "pos": [
+ 2900,
+ 210
+ ],
+ "size": {
+ "0": 550,
+ "1": 730
+ },
+ "flags": {},
+ "order": 25,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 133,
+ "slot_index": 0
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 56,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999999
+ }
+ ]
+ },
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Save Image Sequence (mtb)"
+ },
+ "widgets_values": [
+ "F:\\ComfyUI\\ComfyUI_windows_portable\\ComfyUI\\output\\Test\\",
+ 5
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 11,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 1880,
+ 520
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 54
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 8
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "1girl, period costume"
+ ]
+ },
+ {
+ "id": 12,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 1880,
+ 680
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 55
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 9
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "embedding:EasyNegative,\nnsfw"
+ ]
+ },
+ {
+ "id": 83,
+ "type": "CR Integer To String",
+ "pos": [
+ 1870,
+ 20
+ ],
+ "size": {
+ "0": 210,
+ "1": 34
+ },
+ "flags": {},
+ "order": 19,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "int_",
+ "type": "INT",
+ "link": 134,
+ "widget": {
+ "name": "int_",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 18446744073709552000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 99
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Integer To String"
+ },
+ "widgets_values": [
+ 0
+ ]
+ },
+ {
+ "id": 87,
+ "type": "Note",
+ "pos": [
+ 2160,
+ -250
+ ],
+ "size": {
+ "0": 210,
+ "1": 90
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The Value Scheduler is increasing the width in CR SD1.5 Aspect Ratio\n"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 47,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ 1420,
+ 380
+ ],
+ "size": {
+ "0": 380,
+ "1": 100
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 53
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 54,
+ 55
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "SD1_5\\ComfyrollAnime_v1_fp16_pruned.safetensors"
+ ]
+ },
+ {
+ "id": 91,
+ "type": "Reroute",
+ "pos": [
+ 990,
+ -100
+ ],
+ "size": [
+ 107.2,
+ 26
+ ],
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 135
+ }
+ ],
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": [
+ 138
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 119,
+ "type": "CR Central Schedule",
+ "pos": [
+ 10,
+ -450
+ ],
+ "size": {
+ "0": 390,
+ "1": 520
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": [
+ 135
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Central Schedule"
+ },
+ "widgets_values": [
+ "0, 512\n2, 640\n3, 768\n4, 896\n8, 1024",
+ "Value",
+ "V1",
+ "0, Art Nouveau\n2, Antarctica\n4, 2D Game Art\n5, Animation\n8, Airbrushing",
+ "Text",
+ "T1",
+ "schedule",
+ "Model",
+ "",
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 84,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 1870,
+ 110
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 21,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 99
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "512"
+ ]
+ },
+ {
+ "id": 118,
+ "type": "CR Value Scheduler",
+ "pos": [
+ 1460,
+ 0
+ ],
+ "size": {
+ "0": 320,
+ "1": 170
+ },
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": 139
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 141,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 134,
+ 140
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Value Scheduler"
+ },
+ "widgets_values": [
+ "Schedule",
+ 0,
+ "V1",
+ 512,
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 86,
+ "type": "Note",
+ "pos": [
+ 850,
+ 410
+ ],
+ "size": {
+ "0": 210,
+ "1": 120
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Schedules should always have a line for frame 0\n\nIf frame 0 is missing the default value will be used"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 114,
+ "type": "Note",
+ "pos": [
+ 1180,
+ -220
+ ],
+ "size": {
+ "0": 210,
+ "1": 140
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "You can define either local or central schedules\n\nThis workflow shows both. You can switch between the two.\n\nThis switch would not normally be needed."
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 120,
+ "type": "CR Simple Schedule",
+ "pos": [
+ 840,
+ 150
+ ],
+ "size": {
+ "0": 290,
+ "1": 200
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": [
+ 137
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Schedule"
+ },
+ "widgets_values": [
+ "0, 512\n2, 640\n3, 768\n4, 896\n8, 1024",
+ "Value",
+ "V1",
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 25,
+ "type": "CR Current Frame",
+ "pos": [
+ 510,
+ 230
+ ],
+ "size": {
+ "0": 210,
+ "1": 58
+ },
+ "flags": {
+ "collapsed": false
+ },
+ "order": 14,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "link": 23,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "links": [
+ 56,
+ 91,
+ 141
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Current Frame"
+ },
+ "widgets_values": [
+ 1,
+ "Yes"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 24,
+ "type": "Animation Builder (mtb)",
+ "pos": [
+ 180,
+ 230
+ ],
+ "size": {
+ "0": 210,
+ "1": 320
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "frame",
+ "type": "INT",
+ "links": [
+ 23
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "0-1 (scaled)",
+ "type": "FLOAT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "count",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "loop_ended",
+ "type": "BOOLEAN",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Animation Builder (mtb)"
+ },
+ "widgets_values": [
+ 10,
+ 1,
+ 1,
+ 11,
+ 1,
+ "frame: 1 / 9",
+ "Done 😎!",
+ "reset",
+ "queue"
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 121,
+ "type": "CR Schedule Input Switch",
+ "pos": [
+ 1170,
+ 0
+ ],
+ "size": {
+ "0": 240,
+ "1": 80
+ },
+ "flags": {},
+ "order": 15,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule1",
+ "type": "SCHEDULE",
+ "link": 138
+ },
+ {
+ "name": "schedule2",
+ "type": "SCHEDULE",
+ "link": 137
+ }
+ ],
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": [
+ 139
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Schedule Input Switch"
+ },
+ "widgets_values": [
+ 1
+ ]
+ }
+ ],
+ "links": [
+ [
+ 8,
+ 11,
+ 0,
+ 13,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 9,
+ 12,
+ 0,
+ 13,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 11,
+ 14,
+ 0,
+ 15,
+ 0,
+ "INT"
+ ],
+ [
+ 12,
+ 14,
+ 1,
+ 15,
+ 1,
+ "INT"
+ ],
+ [
+ 13,
+ 15,
+ 0,
+ 13,
+ 3,
+ "LATENT"
+ ],
+ [
+ 14,
+ 13,
+ 0,
+ 16,
+ 0,
+ "LATENT"
+ ],
+ [
+ 15,
+ 17,
+ 0,
+ 16,
+ 1,
+ "VAE"
+ ],
+ [
+ 23,
+ 24,
+ 0,
+ 25,
+ 0,
+ "INT"
+ ],
+ [
+ 53,
+ 47,
+ 0,
+ 13,
+ 0,
+ "MODEL"
+ ],
+ [
+ 54,
+ 47,
+ 1,
+ 11,
+ 0,
+ "CLIP"
+ ],
+ [
+ 55,
+ 47,
+ 1,
+ 12,
+ 0,
+ "CLIP"
+ ],
+ [
+ 56,
+ 25,
+ 0,
+ 26,
+ 1,
+ "INT"
+ ],
+ [
+ 72,
+ 14,
+ 3,
+ 15,
+ 2,
+ "INT"
+ ],
+ [
+ 90,
+ 78,
+ 0,
+ 77,
+ 0,
+ "STRING"
+ ],
+ [
+ 91,
+ 25,
+ 0,
+ 78,
+ 0,
+ "INT"
+ ],
+ [
+ 99,
+ 83,
+ 0,
+ 84,
+ 0,
+ "STRING"
+ ],
+ [
+ 123,
+ 98,
+ 0,
+ 91,
+ 0,
+ "*"
+ ],
+ [
+ 133,
+ 16,
+ 0,
+ 26,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 134,
+ 118,
+ 0,
+ 83,
+ 0,
+ "INT"
+ ],
+ [
+ 135,
+ 119,
+ 0,
+ 91,
+ 0,
+ "*"
+ ],
+ [
+ 137,
+ 120,
+ 0,
+ 121,
+ 1,
+ "SCHEDULE"
+ ],
+ [
+ 138,
+ 91,
+ 0,
+ 121,
+ 0,
+ "SCHEDULE"
+ ],
+ [
+ 139,
+ 121,
+ 0,
+ 118,
+ 0,
+ "SCHEDULE"
+ ],
+ [
+ 140,
+ 118,
+ 0,
+ 14,
+ 0,
+ "INT"
+ ],
+ [
+ 141,
+ 25,
+ 0,
+ 118,
+ 1,
+ "INT"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_C3_SimpleTextScheduler_Demo_v01a.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_C3_SimpleTextScheduler_Demo_v01a.json
new file mode 100644
index 0000000000000000000000000000000000000000..1ba4bee895b265e38dd6facf057d4645840ba547
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_C3_SimpleTextScheduler_Demo_v01a.json
@@ -0,0 +1,829 @@
+{
+ "last_node_id": 91,
+ "last_link_id": 107,
+ "nodes": [
+ {
+ "id": 13,
+ "type": "KSampler",
+ "pos": [
+ 1480,
+ 580
+ ],
+ "size": [
+ 320,
+ 470
+ ],
+ "flags": {},
+ "order": 15,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 53
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 8
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 9
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 13
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 14
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 32603574575332,
+ "fixed",
+ 20,
+ 8,
+ "euler",
+ "normal",
+ 1
+ ]
+ },
+ {
+ "id": 17,
+ "type": "VAELoader",
+ "pos": [
+ 1860,
+ 710
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 15
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "vae-ft-mse-840000-ema-pruned.safetensors"
+ ]
+ },
+ {
+ "id": 59,
+ "type": "Note",
+ "pos": [
+ 660,
+ 20
+ ],
+ "size": {
+ "0": 210,
+ "1": 130
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The CR Current Frame node prints the current frame index to console so that you can see which frame is currently being processed"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 47,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ 630,
+ 590
+ ],
+ "size": {
+ "0": 380,
+ "1": 100
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 53
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 54,
+ 55
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "SD1_5\\ComfyrollAnime_v1_fp16_pruned.safetensors"
+ ]
+ },
+ {
+ "id": 68,
+ "type": "Note",
+ "pos": [
+ 660,
+ -100
+ ],
+ "size": {
+ "0": 210,
+ "1": 70
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Frames are processed in sequence starting from frame index 0"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 78,
+ "type": "CR Integer To String",
+ "pos": [
+ 660,
+ 330
+ ],
+ "size": {
+ "0": 210,
+ "1": 34
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "int_",
+ "type": "INT",
+ "link": 91,
+ "widget": {
+ "name": "int_",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 18446744073709552000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 90
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Integer To String"
+ },
+ "widgets_values": [
+ 0
+ ]
+ },
+ {
+ "id": 77,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 660,
+ 420
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 90
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "9"
+ ]
+ },
+ {
+ "id": 89,
+ "type": "Note",
+ "pos": [
+ 100,
+ 220
+ ],
+ "size": {
+ "0": 210,
+ "1": 170
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The primitive node increments the current_frame on each batch\n\nReset the value to 0 before each run\n\nIn normal workflows the Primitive node can replace the Animation Builder"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 16,
+ "type": "VAEDecode",
+ "pos": [
+ 1910,
+ 580
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 14
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 15
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 103
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 90,
+ "type": "PreviewImage",
+ "pos": [
+ 2250,
+ 580
+ ],
+ "size": {
+ "0": 210,
+ "1": 250
+ },
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 103
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 25,
+ "type": "CR Current Frame",
+ "pos": [
+ 660,
+ 220
+ ],
+ "size": {
+ "0": 210,
+ "1": 58
+ },
+ "flags": {
+ "collapsed": false
+ },
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "link": 102,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "links": [
+ 91,
+ 105
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Current Frame"
+ },
+ "widgets_values": [
+ 9,
+ "Yes"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 91,
+ "type": "CR Simple Text Scheduler",
+ "pos": [
+ 940,
+ 220
+ ],
+ "size": {
+ "0": 230,
+ "1": 150
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 105,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 106,
+ 107
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Text Scheduler"
+ },
+ "widgets_values": [
+ "0, 1girl, long grey hair\n2, 1girl, long blue hair\n4, 1girl, long red hair,\n6, 1girl, long black hair\n8, 1girl, long pink hair",
+ 0
+ ]
+ },
+ {
+ "id": 84,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 1210,
+ 220
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 106
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "1girl, long pink hair"
+ ]
+ },
+ {
+ "id": 86,
+ "type": "Note",
+ "pos": [
+ 940,
+ 80
+ ],
+ "size": {
+ "0": 210,
+ "1": 70
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Simple value schedulers must have a line for frame 0"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 11,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 1120,
+ 660
+ ],
+ "size": {
+ "0": 210,
+ "1": 60
+ },
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 54
+ },
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 107,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "multiline": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 8
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "1girl, period costume"
+ ]
+ },
+ {
+ "id": 12,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 1120,
+ 770
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 55
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 9
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "embedding:EasyNegative,\nnsfw"
+ ]
+ },
+ {
+ "id": 88,
+ "type": "PrimitiveNode",
+ "pos": [
+ 360,
+ 220
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 102
+ ],
+ "slot_index": 0,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ 10,
+ "increment"
+ ]
+ },
+ {
+ "id": 15,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 1120,
+ 940
+ ],
+ "size": [
+ 210,
+ 110
+ ],
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 13
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ 1
+ ]
+ }
+ ],
+ "links": [
+ [
+ 8,
+ 11,
+ 0,
+ 13,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 9,
+ 12,
+ 0,
+ 13,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 13,
+ 15,
+ 0,
+ 13,
+ 3,
+ "LATENT"
+ ],
+ [
+ 14,
+ 13,
+ 0,
+ 16,
+ 0,
+ "LATENT"
+ ],
+ [
+ 15,
+ 17,
+ 0,
+ 16,
+ 1,
+ "VAE"
+ ],
+ [
+ 53,
+ 47,
+ 0,
+ 13,
+ 0,
+ "MODEL"
+ ],
+ [
+ 54,
+ 47,
+ 1,
+ 11,
+ 0,
+ "CLIP"
+ ],
+ [
+ 55,
+ 47,
+ 1,
+ 12,
+ 0,
+ "CLIP"
+ ],
+ [
+ 90,
+ 78,
+ 0,
+ 77,
+ 0,
+ "STRING"
+ ],
+ [
+ 91,
+ 25,
+ 0,
+ 78,
+ 0,
+ "INT"
+ ],
+ [
+ 102,
+ 88,
+ 0,
+ 25,
+ 0,
+ "INT"
+ ],
+ [
+ 103,
+ 16,
+ 0,
+ 90,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 105,
+ 25,
+ 0,
+ 91,
+ 0,
+ "INT"
+ ],
+ [
+ 106,
+ 91,
+ 0,
+ 84,
+ 0,
+ "STRING"
+ ],
+ [
+ 107,
+ 91,
+ 0,
+ 11,
+ 1,
+ "STRING"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_C4_TextScheduler_Demo_v01b.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_C4_TextScheduler_Demo_v01b.json
new file mode 100644
index 0000000000000000000000000000000000000000..f8a8c5e0d73a0b7258f83ec8fe560ce7580b8432
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_C4_TextScheduler_Demo_v01b.json
@@ -0,0 +1,2921 @@
+{
+ "last_node_id": 135,
+ "last_link_id": 159,
+ "nodes": [
+ {
+ "id": 17,
+ "type": "VAELoader",
+ "pos": [
+ 3010,
+ 500
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 15
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "vae-ft-mse-840000-ema-pruned.safetensors"
+ ]
+ },
+ {
+ "id": 16,
+ "type": "VAEDecode",
+ "pos": [
+ 3060,
+ 370
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 25,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 14
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 15
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 133
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 59,
+ "type": "Note",
+ "pos": [
+ 810,
+ 40
+ ],
+ "size": {
+ "0": 210,
+ "1": 130
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The CR Current Frame node prints the current frame index to console so that you can see which frame is currently being processed"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 15,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 2730,
+ 210
+ ],
+ "size": {
+ "0": 210,
+ "1": 74
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "link": 11,
+ "widget": {
+ "name": "width",
+ "config": [
+ "INT",
+ {
+ "default": 512,
+ "min": 64,
+ "max": 8192,
+ "step": 8
+ }
+ ]
+ }
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "link": 12,
+ "widget": {
+ "name": "height",
+ "config": [
+ "INT",
+ {
+ "default": 512,
+ "min": 64,
+ "max": 8192,
+ "step": 8
+ }
+ ]
+ }
+ },
+ {
+ "name": "batch_size",
+ "type": "INT",
+ "link": 72,
+ "widget": {
+ "name": "batch_size",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": 1,
+ "max": 64
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 13
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ 1
+ ]
+ },
+ {
+ "id": 78,
+ "type": "CR Integer To String",
+ "pos": [
+ 810,
+ 350
+ ],
+ "size": {
+ "0": 210,
+ "1": 34
+ },
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "int_",
+ "type": "INT",
+ "link": 91,
+ "widget": {
+ "name": "int_",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 18446744073709552000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 90
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Integer To String"
+ },
+ "widgets_values": [
+ 0
+ ]
+ },
+ {
+ "id": 77,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 810,
+ 440
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 18,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 90
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "5"
+ ]
+ },
+ {
+ "id": 13,
+ "type": "KSampler",
+ "pos": [
+ 2630,
+ 370
+ ],
+ "size": {
+ "0": 320,
+ "1": 470
+ },
+ "flags": {},
+ "order": 24,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 53
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 8
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 9
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 13
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 14
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 32603574575332,
+ "fixed",
+ 20,
+ 8,
+ "euler",
+ "normal",
+ 1
+ ]
+ },
+ {
+ "id": 26,
+ "type": "Save Image Sequence (mtb)",
+ "pos": [
+ 3370,
+ 200
+ ],
+ "size": {
+ "0": 550,
+ "1": 730
+ },
+ "flags": {},
+ "order": 26,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 133,
+ "slot_index": 0
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 56,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999999
+ }
+ ]
+ },
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Save Image Sequence (mtb)"
+ },
+ "widgets_values": [
+ "F:\\ComfyUI\\ComfyUI_windows_portable\\ComfyUI\\output\\Test\\",
+ 5
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 14,
+ "type": "CR SD1.5 Aspect Ratio",
+ "pos": [
+ 2630,
+ -100
+ ],
+ "size": {
+ "0": 320,
+ "1": 240
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "links": [
+ 11
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "links": [
+ 12
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "upscale_factor",
+ "type": "FLOAT",
+ "links": [],
+ "shape": 3,
+ "slot_index": 2
+ },
+ {
+ "name": "batch_size",
+ "type": "INT",
+ "links": [
+ 72
+ ],
+ "shape": 3,
+ "slot_index": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR SD1.5 Aspect Ratio"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ "2:3 portrait 512x768",
+ "Off",
+ 1,
+ 1
+ ]
+ },
+ {
+ "id": 11,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 2350,
+ 510
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 23,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 54
+ },
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 155,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "multiline": true
+ }
+ ]
+ },
+ "slot_index": 1
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 8
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "1girl, period costume"
+ ]
+ },
+ {
+ "id": 130,
+ "type": "MileHighStyler",
+ "pos": [
+ 1590,
+ 760
+ ],
+ "size": {
+ "0": 400,
+ "1": 210
+ },
+ "flags": {},
+ "order": 21,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "style",
+ "type": "no style,2D Game Art,3D Animation,3D Game Art,3D Modeling,3D Printing Art,3D Printing in Art,AR Art Variant_Uncategorized,Aardman_Uncategorized,Abandoned Asylum_Uncategorized,Aboriginal Dot Painting,Abstract Expressionism,Abstract Painting,Abstract Photography,Abstract Sculpture,Absurdist Theater,Academic Art,Acrylic Painting,Action Films,Addams Family_Portraiture_Horror,Adrian Ghenie,Adventure,Adventure Films,Aerial Dance,Aerial Photography,African Beadwork,African Beadwork Art,African Cuisine,African Mask Art,African Mask Making,Agnes Martin,Ai Weiwei_1,Ai Weiwei_2,Air Art,Airbrushing,Albrecht Durer,Album Cover Art,Alchemist's Study_Uncategorized,Amazon Rainforest,American Cuisine,American Traditional_Retro_Tattoo Art,Amsterdam,Amsterdam cityscape,Analytical Cubism,Ancient Maya_Uncategorized,Andy Warhol,Anger Art,Animated Corpse_Animation,Animated Films,Animation,Anish Kapoor,Ankama_Animation,Anselm Kiefer,Antarctica,Appropriation (1)_Culture,Après-Ski_Uncategorized,Arachnid Swarm_Uncategorized,Architectural Design,Architectural Photography,Argentinian Art,Art Activism,Art Collaborations with Musicians,Art Collaborations with Writers,Art Conservation,Art Criticism,Art Curation,Art Deco,Art Deco Architecture,Art Deco Architecture_Architecture,Art Deco Design,Art Education,Art Education for Adults,Art Education for Children,Art Education for Remote Areas,Art Education for Special Needs,Art Gallery Management,Art Games,Art Historical Writing,Art History,Art History Research,Art Informatics,Art Informel (1)_Uncategorized,Art Inspired by Ancient Civilizations,Art Inspired by the Digital Age,Art Inspired by the Renaissance,Art Inspired by the Roaring Twenties,Art Inspired by the Victorian Era,Art Installations,Art Journalism,Art Marketing,Art Nouveau,Art Nouveau Architecture,Art Nouveau Design,Art Nouveau Poster_Uncategorized,Art Nouveau Variant_Uncategorized,Art Restoration,Art Sales and Auctions,Art Therapy,Art Therapy for Adults,Art Therapy for Children,Art Workshop Facilitation,Art and AI Collaboration,Art and Architecture Collaboration,Art and Cultural Heritage Preservation,Art and Environmental Sustainability,Art and Literature Collaboration,Art and Medical Collaboration,Art and Mental Health,Art and Music Collaboration,Art and Science Collaboration,Art and Social Justice Projects,Art and Technology Collaboration,Art and Urban Development,Art for Agricultural Industry,Art for Agricultural Sector,Art for Airports,Art for Animal Welfare Organizations,Art for Anniversaries,Art for Aquariums,Art for Architectural Visualization,Art for Asian Cultures,Art for Augmented Reality Experiences,Art for Automotive Design,Art for Automotive Industry,Art for Aviation Industry,Art for Baby Showers,Art for Birthdays,Art for Botanical Gardens,Art for Cafes and Restaurants,Art for Charity Fundraisers,Art for Children,Art for Children's Hospitals,Art for Climate Change Initiatives,Art for Construction Industry,Art for Corporate Spaces,Art for Cruise Ships,Art for Culinary Presentation,Art for E-Commerce Platforms,Art for Educational Institutions,Art for Educational Technology,Art for Elderly,Art for Emergency Services,Art for Energy Industry,Art for Entertainment Industry,Art for Environmental Activism,Art for Environmental Campaigns,Art for Factories and Workshops,Art for Fashion Industry,Art for Festivals and Events,Art for Financial Institutions,Art for Financial Sector,Art for Fitness Centers,Art for Funerals,Art for Gender Equality,Art for Government Entities,Art for Graduations,Art for Health Care Facilities,Art for Home Decor,Art for Hospitality Industry,Art for Hotels,Art for Human Anatomy Studies,Art for Human Rights Campaigns,Art for Indigenous Cultures,Art for LGBTQ+ Celebrations,Art for Libraries,Art for Marine Industry,Art for Maritime Industry,Art for Medical Illustrations,Art for Military and Defense Sector,Art for Military and Veterans,Art for Mobile Apps,Art for Museums,Art for Music Videos,Art for National Holidays,Art for Nautical Navigation,Art for Non-Profit Organizations,Art for Office Spaces,Art for Outdoor Advertising,Art for Packaging Design,Art for Pet Products,Art for Pharmaceutical Industry,Art for Political Campaigns,Art for Prisons,Art for Public Transportation,Art for Real Estate Marketing,Art for Religious Celebrations,Art for Religious Institutions,Art for Renewable Energy Sector,Art for Retail Spaces,Art for Retirement Parties,Art for Robotics,Art for Schools and Colleges,Art for Science Centers,Art for Scientific Exploration,Art for Security and Defense,Art for Seniors,Art for Shopping Malls,Art for Smart City Projects,Art for Social Media Platforms,Art for Social Networking Sites,Art for Spa and Wellness Centers,Art for Space Exploration,Art for Space Industry,Art for Spaceships and Aerospace,Art for Sports Industry,Art for Sports Venues,Art for Technical Manuals,Art for Teenagers,Art for Teens,Art for Television Shows,Art for Theme Parks,Art for Toddlers,Art for Train Stations,Art for Underwater Exploration,Art for Video Game Development,Art for Virtual Assistants and AI,Art for Virtual Events,Art for Virtual Reality Experiences,Art for Wearable Technology,Art for Wearables,Art for Web Platforms,Art for Weddings,Art for Zoos,Art in Public Transportation,Art with Light and Projection,Art with Metalwork,Art with Organic Materials,Art with Recycled Materials,Artist's Books,Artware Variant_Sci-Fi_Graffiti_Digital Media,Aspen,Assemblage Art,Astrophotography,Athens,Athleisure Fashion,Atlantis,Augmented Reality (AR) Art,Augmented Reality Art,Australian Aboriginal Art,Autobiography,Automotive Design,Autumn Art,Avant-Garde Fashion,Aztec Calendar_Uncategorized,Back Alley Rogue_Uncategorized,Ballet Dance,Ballet_Uncategorized,Ballroom Dance,Bangkok,Banksy_1,Banksy_2,Barbara Kruger,Barcelona,Baroque,Baroque Architecture,Baroque Art,Baroque Music,Bas-Relief Sculpture_Sculpture,Basket Weaving,Basket Weaving Art,Battle_Uncategorized,Bauhaus,Bauhaus (1)_Architecture,Bauhaus Architecture,Bauhaus Design,Bauhaus Design_Uncategorized,Beachwear Fashion,Beijing,Belly Dance,Berlin,Bharatanatyam Dance,Bikini Bottom,Bio Art,Bio Art_Nature,Biographical Films,Biographical Literature,Biography,Biomorphic Architecture,Black Hole,Black Velvet Painting_Portraiture,Black and White Photography,Blacklight Poster_Uncategorized,Blockbuster Films,Bloodthirsty Vampire_Horror,Bluegrass Music,Blueprint_Uncategorized,Blues Music,Blues Music Illustration,Body Art,Body Art Performance,Body Painting,Bohemian Fashion,Bomber Jacket_Retro,Bookbinding,Botanical Illustration,Boudoir Photography_Photography,Brazil,Brazilian Art,Brazilian Cuisine,Brazilian Graffiti Art,Breakdance,Bridal Fashion,Brightwater Variant_Nature,British Art,Bronze Sculpture,Bruce Nauman,Bruges,Brutalism,Brutalist Architecture,Budapest cityscape,Cabinet of Curiosities_Occult,Cai Guo-Qiang,Cake Decorating,Canada,Candid Portrait Photography,Caravaggio,Caribbean Carnival Art,Caribbean Cuisine,Caricature,Carnival Freakshow_Retro,Caspar David Friedrich,Cassette Bedroom_Retro,Cassette Collage_Sci-Fi_Surrealism_Retro,Cassette Futurism_Retro,Cassette Graphics_Retro_Surrealism,Cassette J-Card_Retro,Cassette Wall_Retro,Casual Fashion,Caveopolis Variant_Lifestyle,Cecily Brown,Celtic Knotwork Art,Celtic Mythology Art,Cemetery Statue_Uncategorized,Central African Art,Central American_Uncategorized,Ceramic Art,Ceramic Design,Ceramic Sculpture,Ceramics,Chalk Art,Charcoal Drawing,Charles Ray,Chicago,Children's Fashion,Children's Theater,Chilean Art,Chinese Architecture,Chinese Art,Chinese Calligraphy,Chinese Cuisine,Chinese Ink Painting,Chinese Jade Carving,Chinese Landscape Painting,Chinese Mythology Art,Chinese Paper Cutting,Chinese Scroll Painting,Chris Ofili,Cindy Sherman_1,Cindy Sherman_2,Cinematography,Cinque Terre,Circuit Bending_Uncategorized,Circus Arts,Circus Performer_Retro,Classic Western,Classical Architecture,Classical Art,Classical Music,Classical Music Illustration,Classical Realism,Classical Realism_Portraiture,Classical Theater,Claude Monet,Clockwork City Variant_Architecture_Location,Collaborative Art Projects,Collage,Collage Art,Colombian Art,Colonial Architecture,Colosseum,Combine Painting_Sci-Fi_Still Life,Comedy,Comedy Literature,Commercial Photography,Community Mural Projects,Computer art,Concept Art for Movies,Concept Art for Video Games,Conceptual Art,Concert Poster Design,Conjoined Twins_Uncategorized,Constructivism,Constructivism Art,Contemporary Ballet,Contemporary Dance,Copenhagen,Copenhagen cityscape,Corporate Identity Design,Cosplay Design,Cottagecore Fashion_Fashion,Country Music,Country Music Graphics,Crawler Mimicry_Uncategorized,Creepy Children_Portraiture,Creepy Porcelain Doll_Fashion_Portraiture,Crime Films,Critical Realism_Uncategorized,Cross-Disciplinary Art,Crucifixion_Uncategorized,Crystal Caverns Variant_Architecture,Cuban Art,Cuban Cuisine,Cubism,Cubism Art,Cult Films,Cyberpunk,Cyberpunk Fantasy Art,Dadaism,Dadaism Art,Damien Hirst_1,Damien Hirst_2,Dan Flavin,Dance Choreography,Dance Performance Art,Dark Carnival_Gothic,Dark Fantasy Art,Data Art Variant_Uncategorized,Data Art_Uncategorized,Data Visualization Art,Day of the Dead_Uncategorized,De Stijl_Uncategorized,Death Masque_Uncategorized,Deconstructivist Architecture,Demonic Clown_Uncategorized,Demonic Portal_Horror,Demonic Possession_Uncategorized,Demoscene_Animation,Desaturated_Uncategorized,Die Brücke_Graffiti,Diego Velazquez,Dieselpunk_Retro,Digital Animation,Digital Art,Digital Art_Digital Media,Digital Drawing Tablets,Digital Illustration,Digital Painting,Digital Sculpture,Digital Storytelling,Diorama_Uncategorized,Disco Music,Disney Animation_Animation,Disrespectful Grave Robber_Uncategorized,Documentary Films,Documentary Photography,Drama,Drama Films,Dubai,Dublin,Dublin cityscape,Dunder Mifflin,Dutch Art,Dwarvendom Variant_Uncategorized,Dwarvenholm Variant_Uncategorized,Earth Art,East African Art,Eco Art,Eco-Art,Ed Ruscha,Edgar Degas,Edinburgh cityscape,Editorial Design,Edvard Munch,Edward Hopper,Egyptian Hieroglyphs_Uncategorized,Egyptian Mythology Art,Egyptian Wall Art,Egyptology_Uncategorized,El Anatsui,Electronic Music,Electronic Music Visuals,Elegant_Erotic_Photography,Elfheim Variant_Architecture_Fantasy,Elven City Variant_Architecture_Location,Embroidery,Emerging_Artist,Engraving,Environmental Art,Environmental Design,Ephemeral Art,Etching,Eugene Delacroix,Exhibition Design,Exoplanet,Exorcism_Uncategorized,Experimental Art,Experimental Films,Experimental Music Video,Experimental Photography,Experimental Theater,Expressionism,Expressionist Architecture,Expressionist painting,Fairy Tale Art,Fantasy,Fantasy Films,Fantasy Literature,Farce,Fashion Design,Fashion Illustration,Fashion Photography,Fast Fashion,Fauvism,Fauvism Art,Ferocious Werewolf_Uncategorized,Festival Fashion,Fiction,Figurative Expressionism_Uncategorized,Figurine Shelf_Fantasy_Sculpture,Filipino Art,Film Direction,Film Editing,Film Noir,Fine Art Photography,Fine_Art_Black_and_White_Photography,Fire Art,Flamenco Dance,Folk Art Variant_Folk Art,Folk Art_Folk Art,Folk Dance,Folk Music,Folk Music Art,Food Art,Food Photography,Formal Fashion,Fortune Teller_Occult,Fortune Telling_Occult,France,Francisco Goya,Frankfurt cityscape,French Art,French Cuisine,French Impressionism,Fresco Painting Technique,Frida Kahlo,Funk Music,Furniture Design,Futurism,Futurist Architecture,GAYZ_Portraiture,Gabriel Orozco,Galactic_Sci-Fi,Game Design,Generative Art,Genetic Art_Uncategorized,Geometric Abstraction,Geometric abstract painting,Georg Baselitz,Georgia O'Keeffe,Gerhard Richter_1,Gerhard Richter_2,German Art,Ghibli_Surrealism,Ghoul City Variant_Architecture_Location,Giant Robot_Sci-Fi_Retro_Architecture,Glamorous Portrait_Fashion_Portraiture,Glamorous_Erotic_Photography,Glasgow cityscape,Glass Sculpture,Glassblowing,Glazing Technique in Painting,Glenn Ligon,Glitch Art_Uncategorized,Glitchcore_Digital Media,Gongfu Tea_Uncategorized,Gospel Music,Goth Boudoir_Gothic,Gotham City,Gothic Architecture,Gothic Architecture_Architecture_Gothic,Gothic Fashion,Gothic Literature,Gothic Monster_Architecture_Gothic,Gothic Revival Architecture,Gothic Revival Architecture_Architecture_Gothic,Graffiti Art,Graffiti Style_Graffiti,Grand Canyon,Grant Wood,Graphic Design,Graveyard Mist_Horror,Great Barrier Reef,Great Wall of China,Greek Art,Greek Classical Sculpture,Greek Mythology Art,Greek Pottery Art,Greendale,Gritty_Voyeuristic_Photography,Grotesque Gargoyle_Uncategorized,Grunge Flyer_Uncategorized,Gustav Klimt,Gutai_Sci-Fi_Event,H.P. Lovecraft Cover_Horror,Hackersville Variant_Architecture,Hallstatt,Hard-edge Painting_Uncategorized,Hate Crime_Uncategorized,Haunted Carnival_Horror,Haunted Portrait_Portraiture_Horror,Haute Couture,Haute Couture Fashion,Haute Cuisine,Hawkins,Headless Horseman_Uncategorized,Heavy Metal,Henri Matisse,Hieronymus Bosch,High Fantasy,High Fantasy Art,Hip-Hop Album Art,Hip-Hop Dance,Hip-Hop Fashion,Hip-Hop Music,Historical Fiction,Hogwarts,Hong Kong,Hong Kong cityscape,Horror,Horror Films,Horror Movie Poster_Horror_Gothic,Hyperrealism_Uncategorized,Ice Sculpture,Illustration Design,Illustration for Children's Books,Impressionism,Impressionism Art,Impressionist Landscape Painting,Impressionist Portrait Painting,Improvisational Theater,Inca Mythology Art,Indian Art,Indian Cuisine,Indian Miniature Painting,Indian Mythology Art,Indie Films,Indie Music Art,Indigenous Australian Art,Indigenous Painting,Indigenous Pottery,Indonesian Art,Industrial Architecture,Industrial Design,Information Art_Uncategorized,Ink Drawing,Insectoid Mutant_Portraiture,Installation Art,Interaction Design,Interactive Art,Interactive Art Installations,Interactive artwork,Interior Design,Internet Art_Sci-Fi_Digital Media,Intimate_Naturist_Photography,Intuitive Art_Uncategorized,Irish Art,Irish Dance,Islamic Architecture,Islamic Art,Islamic Calligraphy,Islamic Geometric Art,Islamic Geometric Patterns,Island Luau_Uncategorized_Location,Istanbul,Istanbul cityscape,Italian Art,Italian Cuisine,Italian Renaissance Art,J.M.W. Turner,Jackson Pollock,Jakarta cityscape,Japan,Japanese Architecture,Japanese Art,Japanese Cuisine,Japanese Mythology Art,Jazz Dance,Jazz Music,Jazz Poster Art,Jean-Honore Fragonard,Jeff Koons,Jenny Holzer,Jerusalem,Jewelry Design,Johannes Vermeer,John Baldessari,Joyful Art,Julie Mehretu,Kabuki Theater,Kara Walker,Kathak Dance,Katsushika Hokusai,Kawaii Character_Uncategorized,Kawaii Fashion_Fashion,Kehinde Wiley,Kerry James Marshall,Kiki Smith,Kinetic Art,Kinetic Sculpture,Kintsugi (Japanese Gold Repair),Kitsch Movement_Uncategorized,Knitting,Korean Art,Korean Celadon Ceramics,Korean Celadon Pottery,Korean Cuisine,Kuala Lumpur,Kyoto,Kyoto cityscape,LAIKA_Animation,Land Art,Land Art (1)_Fantasy_Nature_Sculpture_Landscape,Landscape Architecture,Landscape Design,Landscape Photography,Laser Grid_Uncategorized,Later European abstraction (1)_Uncategorized,Leonardo da Vinci,Lettrist artwork,Leviathan Variant_Architecture,Light Art,Line Dance,Lisbon cityscape,Lithography,Living Burial_Uncategorized,London,Los Angeles,Lost Vegas Variant_Architecture,Lounge Singer_Retro,Lovecraftian Horror_Horror,Low Fantasy,Lowbrow Art Variant_Surrealism_Culture,Lowbrow Art_Culture,Luau Fire Dancer_Fashion,Luchador_Uncategorized,Luxury Fashion,Lynching_Uncategorized,Lyrical abstract painting,Macabre Memento Mori_Horror_Horror & Dark_Still Life,Machinima Variant_Uncategorized,Machu Picchu,Macro Photography,Mad Scientist Machinery_Uncategorized,Madhubani Painting,Madhubani Painting (Indian Folk Art),Mage City Variant_Architecture_Fantasy_Location,Magic Realist painting,Mall Goth_Portraiture_Gothic,Mannerism,Mannerist Architecture,Maori Wood Carving,Mardi Gras_Uncategorized,Marina Abramović,Mark Bradford,Mark Grotjahn,Martin Puryear,Masked Killer_Uncategorized,Masked Stalker_Uncategorized,Maurizio Cattelan,Maximalism,Mecca,Mech City Variant_Sci-Fi_Architecture_Location,Mech City_Sci-Fi_Architecture_Location,Mech City__Location,Media Art,Medical Oddities_Uncategorized,Mediterranean Cuisine,Melancholy Art,Melodrama,Melting Skull_Uncategorized,Memento Mori_Horror_Horror & Dark,Memoir,Menacing Scarecrow_Uncategorized,Menswear Fashion,Mesoamerican Mythology Art,Mesopotamian Mythology Art,Metabolist Architecture,Metal Music,Metal Music Artwork,Metalwork,Metropolis,Mexican Art,Mexican Cuisine,Mexican Muralism,Mexican Skull Art_Uncategorized,Miami,Michelangelo,Middle Eastern Cuisine,Middle-earth,Midgard Variant_Architecture,Milky Way Galaxy,Mime,Mime City Variant_Architecture_Location,Minimalism,Minimalist Web Design,Mixed Media Art,Mixer_Animation,Modern Architecture,Modern Dance,Modernist Architecture,Mona Hatoum,Monoprinting Technique,Mosaic,Mosaic Art,Motion Design,Motion Graphics Design,Mount Everest,Mount Olympus,Movie Storyboard_Uncategorized,Mughal Miniature Painting,Mumbai,Mummy Portrait_Portraiture,Munich cityscape,Music Video Direction,Musica Variant_Architecture_Culture,Musical Films,Musical Theater,Mutated Beast_Uncategorized,My Little Pony_Uncategorized,Mystery,Mystery Literature,Mythic Fantasy Art,Nantucket,Native American Art,Native American Basketry,Native American Mythology Art,Native American Pottery,Naturalism in Literature,Nature Landscape Photography,Nature Photography,Nautical_Retro,Naïve Art (1)_Uncategorized,Nebula,Neo Pop_Pop Culture_Culture,Neo Rauch,Neo-Dada_Uncategorized,Neo-Expressionism_Uncategorized,Neo-Gothic Architecture,Neo-Noir,Neo-Pop (1)_Pop Culture_Culture,Neo-primitivism (1)_Still Life,Neoclassical Architecture,Neoclassicism,Neon Lighting_Uncategorized,Neon Racer_Sci-Fi,Neon Tokyo_Retro,Neoplasticism,Neotokyo Variant_Sci-Fi_Architecture,Neue Sachlichkeit Variant_Portraiture,Neue Wilde (1)_Uncategorized,New Caelum Variant_Architecture,New Caelum_Architecture,New Media Art_Digital Media,New Orleans,New Perpendicular art_Uncategorized,New Simplicity_Architecture,New York City,New York cityscape,Niagara Falls,Nicole Eisenman,Night Photography,Nightmare Beast_Uncategorized,Non-Fiction,Nordic Viking Art,Norse Mythology Art,North African Art,Norwegian romantic nationalism_Nature_Landscape,Nouveau Circus_Uncategorized,Nova Alexandria Variant_Architecture_Culture,Occult Ritual_Occult,Occult Sacrifice_Occult,Oil Painting,Olafur Eliasson,Ominous Fog_Uncategorized,Ominous Warning_Uncategorized,Op Art,Op Art_Uncategorized,Opera,Opera Music,Opera Music Illustration,Osaka cityscape,Outsider Art_Uncategorized,Pablo Picasso,Package Design,Pandora,Paper Cutting,Paper Mache Art,Parametric Architecture,Paris,Participatory Art,Patchwork Creature_Uncategorized,Paul Cezanne,Performance Art,Performance Sculpture,Peruvian Art,Petra,Photography,Photojournalism,Photorealism,Photorealistic painting,Physical Theater,Pinup_Retro,Pixel Art,Pizza Making,Plague Mass Grave_Uncategorized,Plein Air Painting,Plotter Art Variant_Uncategorized,Plotter Art_Uncategorized,Plus-Size Fashion,Poetry,Pointillism,Pointillism Art,Pole Dance,Polynesian Mythology Art,Polynesian Tattoo Art,Pop Art,Pop Music,Pop Music Branding,Pop Surrealism_Nature_Surrealism_Landscape_Still Life,Pop art style,Porcelain Art,Portrait Photography,Portuguese Art,Post-Impressionism,Postmodern Architecture,Pottery,Prague,Prague cityscape,Prairie Dress_Retro_Fashion,Pre-Raphaelite_Uncategorized,Preppy Fashion,Printmaking,Prismatic_Uncategorized,Projection Mapping Art,Propaganda Art_Retro,Propaganda Poster_Uncategorized,Prose Literature,Provocative_Surreal_Photography,Pseudorealism_Uncategorized,Psychedelic Concert Posters,Psychedelic Pop Art_Surrealism,Public Art Installations,Public Installations,Public Sculptures,Punk Fashion,Punk Music,Punk Poster_Uncategorized,Puppetry,Pyramids of Giza,Quahog,Quilting,Quilting Art,Quito cityscape,R&B Music,Rachel Whiteread,Radical Realism (1)_Still Life,Rangoli (Indian Floor Art),Rap Music Graphics,Raphael,Rashid Johnson,Rat Infestation_Uncategorized,Rat King_Uncategorized,Realism Art,Realism in Literature,Realistic Fiction,Reanimated Corpse_Animation,Recycled Art,Reggae Music,Reggae Music Design,Rembrandt,Remodernism Variant_Uncategorized,Remodernism_Architecture,Renaissance,Renaissance Architecture,Renaissance Art,Rene Magritte,Responsive Web Design,Richard Serra,Richard Tuttle,Rio de Janeiro,Rio de Janeiro cityscape,Robert Gober,Robotics Art,Rock Album Art,Rock Music,Rococo,Rococo Architecture,Rococo Art,Rococo Interior_Uncategorized,Roman Mosaic Art,Roman Mythology Art,Romance,Romance Literature,Romanesque Architecture,Romantic Comedy,Romantic Films,Romanticism,Romanticism Art,Romanticism in Literature,Rome,Rural Photography,Russia,Russian Art,Russian Icon Painting,Sahara Desert,Salem,Salsa Dance,Salsa Music,Salvador Dali,Samurai_Uncategorized,Sanctuary Variant_Uncategorized,Sand Sculpture,Sandro Botticelli,Sarah Sze,Satanic_Horror_Occult,Satire,Satire Literature,Scandinavian Architecture,Scandinavian Art,Scandinavian Design,Scarecrow_Horror,Scary Pumpkin_Uncategorized,Scary Stories at Campfire_Horror_Horror & Dark,Scary Stories_Horror,Sci-Fi Films,Science Fiction,Scientific Illustration_Retro,Screen Printing,Screwball Comedy,Sculpture,Self-taught Art (1)_Fantasy,Seoul,Serial Killer_Horror,Set Design for Theater,Shadow City Variant_Architecture_Occult_Gothic_Location,Shadow City_Architecture_Occult_Gothic_Location,Shadow City_Horror_Occult_Horror & Dark_Gothic_Location,Shanghai,Shangri-La Variant_Uncategorized,Shepard Fairey,Shirakawa-go,Shirin Neshat,Sideshow Poster_Retro,Silent Films,Singapore,Sinister Crone_Uncategorized,Sinister Laboratory_Horror_Occult_Still Life,Sinister Ritual_Uncategorized,Situationist International Variant_Uncategorized,Situationist International_Uncategorized,Skateboarding Fashion,Skeleton Dance_Animation,Skeleton Dance_Horror_Horror & Dark_Animation,Slavic Mythology Art,Slow Fashion,Smothering Earth_Fantasy,Social Realism painting,Sonnet,Soul Music,Sound Art,Sound Design,Sound Sculpture,South African Art,South American Textile Art,Southern Gothic_Gothic,Southwest Kachina Dolls,Spaghetti Western,Spanish Art,Spanish Cuisine,Spider Queen_Uncategorized,Sports Card_Photography_Portraiture,Sports Photography,Spring Art,Springfield,St Ives School Variant_Nature_Landscape,St Ives School_Nature_Landscape,Stained Glass Art,Stained Glass_Uncategorized,Stand-Up Comedy,Stars Hollow,Steampunk,Steampunk City Variant_Architecture_Location,Steampunk Fantasy Art,Steampunk Fashion,Steampunk Portrait_Fantasy_Portraiture,Steampunk_Fantasy_Fashion,Steamtown Variant_Architecture_Retro,Steeltown Variant_Architecture,Stockholm cityscape,Stone Sculpture,Stop Motion_Animation,Streamer Bike_Retro,Street Art,Street Art Performance,Street Art and Graffiti,Street Photography,Street Theater,Streetwear,Streetwear Fashion,Stuckism Variant_Uncategorized,Stuckism_Uncategorized,Studio Ghibli_Fantasy_Surrealism,Studio Portrait Photography,Sub Anaheim Variant_Fantasy_Location,Sub Annapolis Variant_Sculpture_Location,Sub Atlanta Variant_Uncategorized_Location,Sub Baton Rouge Variant_Culture_Location,Sub Baton Rouge_Culture_Location,Sub Baton Rouge__Location,Sub Berkeley Variant_Retro_Location,Sub Boise Variant_Uncategorized_Location,Sub Boise_Uncategorized_Location,Sub Boise__Location,Sub Bozeman Variant_Architecture_Location,Sub Carlsbad Variant_Architecture_Culture_Location,Sub Carson City Variant_Architecture_Location,Sub Casper Variant_Uncategorized_Location,Sub Cheyenne Variant_Uncategorized_Location,Sub Columbia Variant_Architecture_Culture_Location,Sub Concord Variant_Uncategorized_Location,Sub Costa Mesa Variant_Culture_Location,Sub Denver Variant_Uncategorized_Location,Sub Des Moines Variant_Architecture_Location,Sub Dover Variant_Uncategorized_Location,Sub Downey Variant_Sci-Fi_Location,Sub El Monte Variant_Sci-Fi_Location,Sub Fontana Variant_Culture_Location,Sub Frankfort Variant_Uncategorized_Location,Sub Fresno Variant_Architecture_Nature_Landscape_Location,Sub Garden Grove Variant_Architecture_Location,Sub Glendale Variant_Uncategorized_Location,Sub Indianapolis Variant_Uncategorized_Location,Sub Inglewood Variant_Sci-Fi_Pop Culture_Culture_Location,Sub Irvine Variant_Uncategorized_Location,Sub Jackson Variant_Folk Art_Location,Sub Jefferson City Variant_Architecture_Folk Art_Location,Sub Juneau Variant_Architecture_Location,Sub Lancaster Variant_Sci-Fi_Retro_Location,Sub Montgomery Variant_Uncategorized_Location,Sub Montpelier Variant_Sculpture_Location,Sub Moreno Valley Variant_Uncategorized_Location,Sub Oakland Variant_Sci-Fi_Culture_Location,Sub Ontario Variant_Uncategorized_Location,Sub Orange Variant_Retro_Location,Sub Oxnard Variant_Uncategorized_Location,Sub Oxnard_Uncategorized_Location,Sub Oxnard__Location,Sub Palmdale Variant_Sci-Fi_Location,Sub Pasadena Variant_Uncategorized_Location,Sub Pierre Variant_Uncategorized_Location,Sub Pomona Variant_Retro_Location,Sub Providence Variant_Uncategorized_Location,Sub Rancho Cucamonga Variant_Architecture_Lifestyle_Location,Sub Richmond Variant_Architecture_Location,Sub Roseville Variant_Architecture_Location,Sub Salem Variant_Sci-Fi_Culture_Location,Sub Santa Ana Variant_Sci-Fi_Culture_Location,Sub Santa Clarita Variant_Uncategorized_Location,Sub Santa Rosa Variant_Sci-Fi_Nature_Location,Sub Santa Rosa_Sci-Fi_Nature_Location,Sub Santa Rosa__Location,Sub Simi Valley Variant_Pop Culture_Culture_Retro_Location,Sub Spokane Variant_Architecture_Location,Sub Tacoma Variant_Architecture_Culture_Retro_Location,Sub Temecula Variant_Lifestyle_Location,Sub Thousand Oaks Variant_Uncategorized_Location,Sub Topeka Variant_Architecture_Folk Art_Location,Sub Torrance Variant_Sci-Fi_Location,Sub Victorville Variant_Uncategorized_Location,Sumi-e Painting,Summer Art,Summer Fashion,Surf Wood Sign_Retro,Surrealism,Surrealism Art,Surrealist Painting,Surrealist Sculpture,Sushi Making,Sustainable Architecture,Sustainable Art Variant_Uncategorized,Sustainable Art_Uncategorized,Sustainable Fashion,Swing Dance,Sydney,Symbolism Art,Synthetic Cubism,Taj Mahal,Takashi Murakami,Talavera Pottery,Tamara de Lempicka,Tango Dance,Tap Dance,Tarot Cards_Occult,Tarot_Occult,Tatooine,Tattoo Print_Retro_Tattoo Art,Tech City Variant_Architecture_Nature_Location,Techno Music Visuals,Technotopia Variant_Architecture_Nature,Temporary Art Installations,Terrarium Bottle_Still Life,Terrarium_Uncategorized,Teslapunk_Portraiture,Textile Art,Textile Design,Textile Sculpture,Thai Art,Thai Cuisine,Thomas Gainsborough,Thriller,Thriller Films,Thriller Literature,Tibetan Thangka Painting,Tiki Bar_Uncategorized,Tiki Cocktail_Uncategorized,Tiki Idol_Uncategorized,Tiki Mug_Retro,Tiki Outdoor Shower_Uncategorized,Tiki Totem_Sculpture,Titian,Toei_Retro_Animation,Tokyo,Tokyo cityscape,Torture Chamber_Uncategorized,Torture Device_Horror_Horror & Dark,Tortured Prisoner_Uncategorized,Tortured Soul_Uncategorized,Toy Design,Traditional Animation,Traditional Dance,Traditional Japanese Architecture,Traditional Pottery,Tragedy,Tragedy Literature,Tranquil Art,Transavantgarde Variant_Uncategorized,Transavantgarde_Uncategorized,Transgressive Art Variant_Uncategorized,Transgressive Art_Uncategorized,Travel Photography,Tropical Bathroom_Uncategorized,Tropical Cocktail_Uncategorized,Tropical Hotel_Uncategorized,Tropical Luau_Uncategorized,Twin Peaks,Typography Design,UPA_Comics_Animation,Ukiyo-e (Japanese Woodblock Printing),Ukiyo-e Art,Undead Gluttony_Architecture,Undead Portrait_Portraiture,Undefined_Emerging_Artist,Under Albany Variant_Architecture_Surrealism_Location,Under Bakersfield Variant_Uncategorized_Location,Under Berlin Variant_Retro_Surrealism_Location,Under Berlin_Retro_Surrealism_Location,Under Berlin__Location,Under Bismarck Variant_Uncategorized_Location,Under Charleston Variant_Architecture_Location,Under Chicago Variant_Architecture_Portraiture_Culture_Retro_Location,Under Eugene Variant_Folk Art_Location,Under Fargo Variant_Architecture_Location,Under Hartford Variant_Architecture_Location,Under Honolulu Variant_Architecture_Location,Under Istanbul Variant_Architecture_Location,Under Jackson Variant_Folk Art_Location,Under Juneau Variant_Architecture_Location,Under London Variant_Architecture_Location,Under Montreal Variant_Architecture_Location,Under Nashville Variant_Uncategorized_Location,Under Oklahoma City Variant_Architecture_Location,Under Omaha Variant_Culture_Location,Under Paris Variant_Uncategorized_Location,Under Sacramento Variant_Uncategorized_Location,Under Santa Fe Variant_Uncategorized_Location,Under St. Paul Variant_Architecture_Location,Under Tallahassee Variant_Sci-Fi_Retro_Architecture_Location,Under Trenton Variant_Uncategorized_Location,Underground Anchorage Variant_Architecture_Location,Underground Austin Variant_Uncategorized_Location,Underground Chula Vista Variant_Uncategorized_Location,Underground Columbus Variant_Retro_Location,Underground Concord Variant_Culture_Location,Underground Helena Variant_Architecture_Location,Underground Huntington Beach Variant_Architecture_Culture_Location,Underground Lansing Variant_Culture_Location,Underground Lincoln Variant_Uncategorized_Location,Underground Little Rock Variant_Uncategorized_Location,Underground Portland Variant_Sci-Fi_Location,Underground Riverside Variant_Culture_Location,Underground Rome Variant_Architecture_Location,Underground Salt Lake City Variant_Architecture_Location,Underground San Jose Variant_Uncategorized_Location,Underground Seattle Variant_Uncategorized_Location,Underground Springfield Variant_Folk Art_Location,Underground Wichita Variant_Folk Art_Location,Underwater Photography,Urban Fantasy Art,Urban Landscape Photography,Urban Photography,Urban Sculpture,User-Centered Design,Utrecht cityscape,VR Art Variant_Uncategorized,Vacuous Grimace_Uncategorized,Valhalla,Valve,Vampire_Portraiture_Horror,Vaporgram_Retro,Vaporwave City_Sci-Fi_Dystopia_Architecture_Location,Vaporwave Graphics_Retro_Surrealism_Graphic Design,Vaporwave Retro_Sci-Fi_Retro,Vaporwave Sunset_Uncategorized,Vaporwave_Architecture_Retro,Vatican City,Vector Portrait_Portraiture,Venezuelan Art,Venice,Verbatim Theater,Victorian Architecture,Victorian Fashion,Victorian Laboratory_Occult_Still Life,Video Art,Video Art_Uncategorized,Video Games Variant_Games,Video Games_Games_Culture,Video Mapping,Vienna,Vienna cityscape,Vietnamese Art,Vietnamese Cuisine,Vija Celmins,Vincent Van Gogh,Vintage Baseball_Retro_Photography,Vintage Fashion,Vintage Halloween Costume_Retro,Vintage Halloween Mask_Retro,Vintage Halloween_Retro,Vintage Robot Toy_Sci-Fi_Retro,Vintage Tattoo Flash_Retro_Tattoo Art,Vintage Tattoo Print_Retro_Tattoo Art,Vintage Travel Poster_Retro_Nature_Landscape,Virtual Art Variant_Uncategorized,Virtual Art_Sci-Fi,Virtual Reality (VR) Art,Virtual Reality Art,Visionary Art (1)_Uncategorized,Visual Effects (VFX) Design,Vogue Cover_Photography_Fashion,Volcano Lair_Uncategorized,Voodoo Altar_Occult,Voodoo Ceremony_Occult,Voodoo Doll_Retro_Occult,Voodoo Queen_Portraiture_Occult,Voodoo Shop_Occult,Voodoo_Occult,Vorticism_Uncategorized,Wallace and Gromit,Waltz Dance,War Films,Wassily Kandinsky,Water Art,Watercolor Painting,Weaving,Web Design,Wedding Fashion,Wedding Photography,Wellington cityscape,West African Art,Westeros,Wildlife Photography,William Kentridge,Winter Art,Winter Fashion,Wolfgang Tillmans,Womenswear Fashion,Wonderland,Wood Carving,Woodblock Art_Nature,Woodblock Print_Uncategorized,Woodblock Printing,Woodcut,Workwear Fashion,World Music,Xiamen cityscape,Xilam_Comics_Animation,Yayoi Kusama,Yellowstone National Park,Yokohama cityscape,Zion Variant_Culture,Zurich cityscape,_Uncategorized,ads-advertising_Uncategorized,ads-automotive_Uncategorized,ads-corporate_Uncategorized,ads-fashion editorial_Fashion,ads-food photography_Photography,ads-luxury_Uncategorized,ads-real estate_Photography,ads-retail_Uncategorized,artstyle-abstract expressionism_Uncategorized,artstyle-abstract_Uncategorized,artstyle-art deco_Uncategorized,artstyle-art nouveau_Nature,artstyle-constructivist_Uncategorized,artstyle-cubist_Uncategorized,artstyle-expressionist_Uncategorized,artstyle-graffiti_Architecture_Graffiti,artstyle-hyperrealism_Photography,artstyle-impressionist_Uncategorized,artstyle-pointillism_Uncategorized,artstyle-pop art_Culture,artstyle-psychedelic_Surrealism,artstyle-renaissance_Uncategorized,artstyle-steampunk_Uncategorized,artstyle-surrealist_Surrealism,artstyle-typography_Uncategorized,artstyle-watercolor_Uncategorized,carpint_Gothic,citz_Sci-Fi_Architecture,coolio_Portraiture,enhance_Uncategorized,futuristic-biomechanical cyberpunk_Sci-Fi_Dystopia,futuristic-biomechanical_Sci-Fi,futuristic-cybernetic robot_Sci-Fi,futuristic-cybernetic_Sci-Fi,futuristic-cyberpunk cityscape_Sci-Fi_Architecture,futuristic-futuristic_Sci-Fi,futuristic-retro cyberpunk_Sci-Fi_Retro,futuristic-retro futurism_Sci-Fi_Retro,futuristic-sci-fi_Sci-Fi,futuristic-vaporwave_Sci-Fi_Retro,game-bubble bobble_Fantasy,game-cyberpunk game_Sci-Fi_Dystopia_Games_Digital Media,game-fighting game_Games,game-gta_Uncategorized,game-mario_Fantasy_Comics,game-minecraft_Still Life,game-pokemon_Fantasy,game-retro arcade_Retro_Games,game-retro game_Retro,game-rpg fantasy game_Fantasy_Games,game-strategy game_Games,game-streetfighter_Uncategorized,game-zelda_Fantasy,getting there_Portraiture,girlz_Fashion_Horror_Horror & Dark_Gothic,gotit jinx_Tattoo Art,greatz_Portraiture,gsssggg_Portraiture,hoop_Portraiture,jinx_Tattoo Art,jinxed_Portraiture,kjkjkjj_Digital Media_Still Life_Comics,kool_Portraiture,misc-architectural_Uncategorized,misc-disco_Retro,misc-dreamscape_Fantasy_Surrealism,misc-dystopian_Dystopia,misc-fairy tale_Fantasy,misc-gothic_Gothic,misc-grunge_Retro,misc-horror_Horror,misc-horror_Horror_Horror & Dark,misc-kawaii_Uncategorized,misc-lovecraftian_Surrealism_Horror,misc-macabre_Gothic,misc-manga_Uncategorized,misc-metropolis_Sci-Fi_Architecture,misc-minimalist_Uncategorized,misc-monochrome_Uncategorized,misc-nautical_Uncategorized,misc-space_Sci-Fi,misc-stained glass_Uncategorized,misc-techwear fashion_Sci-Fi_Fashion_Architecture,misc-tribal_Uncategorized,misc-zentangle_Uncategorized,mkkk_Portraiture_Digital Media_Animation,papercraft-collage_Uncategorized,papercraft-flat papercut_Uncategorized,papercraft-kirigami_Uncategorized,papercraft-paper mache_Uncategorized,papercraft-paper quilling_Uncategorized,papercraft-papercut collage_Uncategorized,papercraft-papercut shadow box_Uncategorized,papercraft-stacked papercut_Uncategorized,papercraft-thick layered papercut_Uncategorized,photo-alien_Sci-Fi_Photography,photo-film noir_Photography,photo-hdr_Photography,photo-long exposure_Photography_Surrealism,photo-neon noir_Photography,photo-silhouette_Photography,photo-tilt-shift_Photography,sai-3d-model_Uncategorized,sai-analog film_Retro_Photography,sai-anime_Uncategorized,sai-cinematic_Uncategorized,sai-comic book_Uncategorized,sai-craft clay_Sculpture,sai-digital art_Digital Media,sai-fantasy art_Fantasy_Surrealism,sai-isometric_Uncategorized,sai-line art_Uncategorized,sai-lowpoly_Uncategorized,sai-neonpunk_Uncategorized,sai-origami_Uncategorized,sai-photographic_Photography,sai-pixel art_Uncategorized,sai-texture_Uncategorized,stfhgff_Photography",
+ "link": 154,
+ "widget": {
+ "name": "style",
+ "config": [
+ [
+ "no style",
+ "2D Game Art",
+ "3D Animation",
+ "3D Game Art",
+ "3D Modeling",
+ "3D Printing Art",
+ "3D Printing in Art",
+ "AR Art Variant_Uncategorized",
+ "Aardman_Uncategorized",
+ "Abandoned Asylum_Uncategorized",
+ "Aboriginal Dot Painting",
+ "Abstract Expressionism",
+ "Abstract Painting",
+ "Abstract Photography",
+ "Abstract Sculpture",
+ "Absurdist Theater",
+ "Academic Art",
+ "Acrylic Painting",
+ "Action Films",
+ "Addams Family_Portraiture_Horror",
+ "Adrian Ghenie",
+ "Adventure",
+ "Adventure Films",
+ "Aerial Dance",
+ "Aerial Photography",
+ "African Beadwork",
+ "African Beadwork Art",
+ "African Cuisine",
+ "African Mask Art",
+ "African Mask Making",
+ "Agnes Martin",
+ "Ai Weiwei_1",
+ "Ai Weiwei_2",
+ "Air Art",
+ "Airbrushing",
+ "Albrecht Durer",
+ "Album Cover Art",
+ "Alchemist's Study_Uncategorized",
+ "Amazon Rainforest",
+ "American Cuisine",
+ "American Traditional_Retro_Tattoo Art",
+ "Amsterdam",
+ "Amsterdam cityscape",
+ "Analytical Cubism",
+ "Ancient Maya_Uncategorized",
+ "Andy Warhol",
+ "Anger Art",
+ "Animated Corpse_Animation",
+ "Animated Films",
+ "Animation",
+ "Anish Kapoor",
+ "Ankama_Animation",
+ "Anselm Kiefer",
+ "Antarctica",
+ "Appropriation (1)_Culture",
+ "Après-Ski_Uncategorized",
+ "Arachnid Swarm_Uncategorized",
+ "Architectural Design",
+ "Architectural Photography",
+ "Argentinian Art",
+ "Art Activism",
+ "Art Collaborations with Musicians",
+ "Art Collaborations with Writers",
+ "Art Conservation",
+ "Art Criticism",
+ "Art Curation",
+ "Art Deco",
+ "Art Deco Architecture",
+ "Art Deco Architecture_Architecture",
+ "Art Deco Design",
+ "Art Education",
+ "Art Education for Adults",
+ "Art Education for Children",
+ "Art Education for Remote Areas",
+ "Art Education for Special Needs",
+ "Art Gallery Management",
+ "Art Games",
+ "Art Historical Writing",
+ "Art History",
+ "Art History Research",
+ "Art Informatics",
+ "Art Informel (1)_Uncategorized",
+ "Art Inspired by Ancient Civilizations",
+ "Art Inspired by the Digital Age",
+ "Art Inspired by the Renaissance",
+ "Art Inspired by the Roaring Twenties",
+ "Art Inspired by the Victorian Era",
+ "Art Installations",
+ "Art Journalism",
+ "Art Marketing",
+ "Art Nouveau",
+ "Art Nouveau Architecture",
+ "Art Nouveau Design",
+ "Art Nouveau Poster_Uncategorized",
+ "Art Nouveau Variant_Uncategorized",
+ "Art Restoration",
+ "Art Sales and Auctions",
+ "Art Therapy",
+ "Art Therapy for Adults",
+ "Art Therapy for Children",
+ "Art Workshop Facilitation",
+ "Art and AI Collaboration",
+ "Art and Architecture Collaboration",
+ "Art and Cultural Heritage Preservation",
+ "Art and Environmental Sustainability",
+ "Art and Literature Collaboration",
+ "Art and Medical Collaboration",
+ "Art and Mental Health",
+ "Art and Music Collaboration",
+ "Art and Science Collaboration",
+ "Art and Social Justice Projects",
+ "Art and Technology Collaboration",
+ "Art and Urban Development",
+ "Art for Agricultural Industry",
+ "Art for Agricultural Sector",
+ "Art for Airports",
+ "Art for Animal Welfare Organizations",
+ "Art for Anniversaries",
+ "Art for Aquariums",
+ "Art for Architectural Visualization",
+ "Art for Asian Cultures",
+ "Art for Augmented Reality Experiences",
+ "Art for Automotive Design",
+ "Art for Automotive Industry",
+ "Art for Aviation Industry",
+ "Art for Baby Showers",
+ "Art for Birthdays",
+ "Art for Botanical Gardens",
+ "Art for Cafes and Restaurants",
+ "Art for Charity Fundraisers",
+ "Art for Children",
+ "Art for Children's Hospitals",
+ "Art for Climate Change Initiatives",
+ "Art for Construction Industry",
+ "Art for Corporate Spaces",
+ "Art for Cruise Ships",
+ "Art for Culinary Presentation",
+ "Art for E-Commerce Platforms",
+ "Art for Educational Institutions",
+ "Art for Educational Technology",
+ "Art for Elderly",
+ "Art for Emergency Services",
+ "Art for Energy Industry",
+ "Art for Entertainment Industry",
+ "Art for Environmental Activism",
+ "Art for Environmental Campaigns",
+ "Art for Factories and Workshops",
+ "Art for Fashion Industry",
+ "Art for Festivals and Events",
+ "Art for Financial Institutions",
+ "Art for Financial Sector",
+ "Art for Fitness Centers",
+ "Art for Funerals",
+ "Art for Gender Equality",
+ "Art for Government Entities",
+ "Art for Graduations",
+ "Art for Health Care Facilities",
+ "Art for Home Decor",
+ "Art for Hospitality Industry",
+ "Art for Hotels",
+ "Art for Human Anatomy Studies",
+ "Art for Human Rights Campaigns",
+ "Art for Indigenous Cultures",
+ "Art for LGBTQ+ Celebrations",
+ "Art for Libraries",
+ "Art for Marine Industry",
+ "Art for Maritime Industry",
+ "Art for Medical Illustrations",
+ "Art for Military and Defense Sector",
+ "Art for Military and Veterans",
+ "Art for Mobile Apps",
+ "Art for Museums",
+ "Art for Music Videos",
+ "Art for National Holidays",
+ "Art for Nautical Navigation",
+ "Art for Non-Profit Organizations",
+ "Art for Office Spaces",
+ "Art for Outdoor Advertising",
+ "Art for Packaging Design",
+ "Art for Pet Products",
+ "Art for Pharmaceutical Industry",
+ "Art for Political Campaigns",
+ "Art for Prisons",
+ "Art for Public Transportation",
+ "Art for Real Estate Marketing",
+ "Art for Religious Celebrations",
+ "Art for Religious Institutions",
+ "Art for Renewable Energy Sector",
+ "Art for Retail Spaces",
+ "Art for Retirement Parties",
+ "Art for Robotics",
+ "Art for Schools and Colleges",
+ "Art for Science Centers",
+ "Art for Scientific Exploration",
+ "Art for Security and Defense",
+ "Art for Seniors",
+ "Art for Shopping Malls",
+ "Art for Smart City Projects",
+ "Art for Social Media Platforms",
+ "Art for Social Networking Sites",
+ "Art for Spa and Wellness Centers",
+ "Art for Space Exploration",
+ "Art for Space Industry",
+ "Art for Spaceships and Aerospace",
+ "Art for Sports Industry",
+ "Art for Sports Venues",
+ "Art for Technical Manuals",
+ "Art for Teenagers",
+ "Art for Teens",
+ "Art for Television Shows",
+ "Art for Theme Parks",
+ "Art for Toddlers",
+ "Art for Train Stations",
+ "Art for Underwater Exploration",
+ "Art for Video Game Development",
+ "Art for Virtual Assistants and AI",
+ "Art for Virtual Events",
+ "Art for Virtual Reality Experiences",
+ "Art for Wearable Technology",
+ "Art for Wearables",
+ "Art for Web Platforms",
+ "Art for Weddings",
+ "Art for Zoos",
+ "Art in Public Transportation",
+ "Art with Light and Projection",
+ "Art with Metalwork",
+ "Art with Organic Materials",
+ "Art with Recycled Materials",
+ "Artist's Books",
+ "Artware Variant_Sci-Fi_Graffiti_Digital Media",
+ "Aspen",
+ "Assemblage Art",
+ "Astrophotography",
+ "Athens",
+ "Athleisure Fashion",
+ "Atlantis",
+ "Augmented Reality (AR) Art",
+ "Augmented Reality Art",
+ "Australian Aboriginal Art",
+ "Autobiography",
+ "Automotive Design",
+ "Autumn Art",
+ "Avant-Garde Fashion",
+ "Aztec Calendar_Uncategorized",
+ "Back Alley Rogue_Uncategorized",
+ "Ballet Dance",
+ "Ballet_Uncategorized",
+ "Ballroom Dance",
+ "Bangkok",
+ "Banksy_1",
+ "Banksy_2",
+ "Barbara Kruger",
+ "Barcelona",
+ "Baroque",
+ "Baroque Architecture",
+ "Baroque Art",
+ "Baroque Music",
+ "Bas-Relief Sculpture_Sculpture",
+ "Basket Weaving",
+ "Basket Weaving Art",
+ "Battle_Uncategorized",
+ "Bauhaus",
+ "Bauhaus (1)_Architecture",
+ "Bauhaus Architecture",
+ "Bauhaus Design",
+ "Bauhaus Design_Uncategorized",
+ "Beachwear Fashion",
+ "Beijing",
+ "Belly Dance",
+ "Berlin",
+ "Bharatanatyam Dance",
+ "Bikini Bottom",
+ "Bio Art",
+ "Bio Art_Nature",
+ "Biographical Films",
+ "Biographical Literature",
+ "Biography",
+ "Biomorphic Architecture",
+ "Black Hole",
+ "Black Velvet Painting_Portraiture",
+ "Black and White Photography",
+ "Blacklight Poster_Uncategorized",
+ "Blockbuster Films",
+ "Bloodthirsty Vampire_Horror",
+ "Bluegrass Music",
+ "Blueprint_Uncategorized",
+ "Blues Music",
+ "Blues Music Illustration",
+ "Body Art",
+ "Body Art Performance",
+ "Body Painting",
+ "Bohemian Fashion",
+ "Bomber Jacket_Retro",
+ "Bookbinding",
+ "Botanical Illustration",
+ "Boudoir Photography_Photography",
+ "Brazil",
+ "Brazilian Art",
+ "Brazilian Cuisine",
+ "Brazilian Graffiti Art",
+ "Breakdance",
+ "Bridal Fashion",
+ "Brightwater Variant_Nature",
+ "British Art",
+ "Bronze Sculpture",
+ "Bruce Nauman",
+ "Bruges",
+ "Brutalism",
+ "Brutalist Architecture",
+ "Budapest cityscape",
+ "Cabinet of Curiosities_Occult",
+ "Cai Guo-Qiang",
+ "Cake Decorating",
+ "Canada",
+ "Candid Portrait Photography",
+ "Caravaggio",
+ "Caribbean Carnival Art",
+ "Caribbean Cuisine",
+ "Caricature",
+ "Carnival Freakshow_Retro",
+ "Caspar David Friedrich",
+ "Cassette Bedroom_Retro",
+ "Cassette Collage_Sci-Fi_Surrealism_Retro",
+ "Cassette Futurism_Retro",
+ "Cassette Graphics_Retro_Surrealism",
+ "Cassette J-Card_Retro",
+ "Cassette Wall_Retro",
+ "Casual Fashion",
+ "Caveopolis Variant_Lifestyle",
+ "Cecily Brown",
+ "Celtic Knotwork Art",
+ "Celtic Mythology Art",
+ "Cemetery Statue_Uncategorized",
+ "Central African Art",
+ "Central American_Uncategorized",
+ "Ceramic Art",
+ "Ceramic Design",
+ "Ceramic Sculpture",
+ "Ceramics",
+ "Chalk Art",
+ "Charcoal Drawing",
+ "Charles Ray",
+ "Chicago",
+ "Children's Fashion",
+ "Children's Theater",
+ "Chilean Art",
+ "Chinese Architecture",
+ "Chinese Art",
+ "Chinese Calligraphy",
+ "Chinese Cuisine",
+ "Chinese Ink Painting",
+ "Chinese Jade Carving",
+ "Chinese Landscape Painting",
+ "Chinese Mythology Art",
+ "Chinese Paper Cutting",
+ "Chinese Scroll Painting",
+ "Chris Ofili",
+ "Cindy Sherman_1",
+ "Cindy Sherman_2",
+ "Cinematography",
+ "Cinque Terre",
+ "Circuit Bending_Uncategorized",
+ "Circus Arts",
+ "Circus Performer_Retro",
+ "Classic Western",
+ "Classical Architecture",
+ "Classical Art",
+ "Classical Music",
+ "Classical Music Illustration",
+ "Classical Realism",
+ "Classical Realism_Portraiture",
+ "Classical Theater",
+ "Claude Monet",
+ "Clockwork City Variant_Architecture_Location",
+ "Collaborative Art Projects",
+ "Collage",
+ "Collage Art",
+ "Colombian Art",
+ "Colonial Architecture",
+ "Colosseum",
+ "Combine Painting_Sci-Fi_Still Life",
+ "Comedy",
+ "Comedy Literature",
+ "Commercial Photography",
+ "Community Mural Projects",
+ "Computer art",
+ "Concept Art for Movies",
+ "Concept Art for Video Games",
+ "Conceptual Art",
+ "Concert Poster Design",
+ "Conjoined Twins_Uncategorized",
+ "Constructivism",
+ "Constructivism Art",
+ "Contemporary Ballet",
+ "Contemporary Dance",
+ "Copenhagen",
+ "Copenhagen cityscape",
+ "Corporate Identity Design",
+ "Cosplay Design",
+ "Cottagecore Fashion_Fashion",
+ "Country Music",
+ "Country Music Graphics",
+ "Crawler Mimicry_Uncategorized",
+ "Creepy Children_Portraiture",
+ "Creepy Porcelain Doll_Fashion_Portraiture",
+ "Crime Films",
+ "Critical Realism_Uncategorized",
+ "Cross-Disciplinary Art",
+ "Crucifixion_Uncategorized",
+ "Crystal Caverns Variant_Architecture",
+ "Cuban Art",
+ "Cuban Cuisine",
+ "Cubism",
+ "Cubism Art",
+ "Cult Films",
+ "Cyberpunk",
+ "Cyberpunk Fantasy Art",
+ "Dadaism",
+ "Dadaism Art",
+ "Damien Hirst_1",
+ "Damien Hirst_2",
+ "Dan Flavin",
+ "Dance Choreography",
+ "Dance Performance Art",
+ "Dark Carnival_Gothic",
+ "Dark Fantasy Art",
+ "Data Art Variant_Uncategorized",
+ "Data Art_Uncategorized",
+ "Data Visualization Art",
+ "Day of the Dead_Uncategorized",
+ "De Stijl_Uncategorized",
+ "Death Masque_Uncategorized",
+ "Deconstructivist Architecture",
+ "Demonic Clown_Uncategorized",
+ "Demonic Portal_Horror",
+ "Demonic Possession_Uncategorized",
+ "Demoscene_Animation",
+ "Desaturated_Uncategorized",
+ "Die Brücke_Graffiti",
+ "Diego Velazquez",
+ "Dieselpunk_Retro",
+ "Digital Animation",
+ "Digital Art",
+ "Digital Art_Digital Media",
+ "Digital Drawing Tablets",
+ "Digital Illustration",
+ "Digital Painting",
+ "Digital Sculpture",
+ "Digital Storytelling",
+ "Diorama_Uncategorized",
+ "Disco Music",
+ "Disney Animation_Animation",
+ "Disrespectful Grave Robber_Uncategorized",
+ "Documentary Films",
+ "Documentary Photography",
+ "Drama",
+ "Drama Films",
+ "Dubai",
+ "Dublin",
+ "Dublin cityscape",
+ "Dunder Mifflin",
+ "Dutch Art",
+ "Dwarvendom Variant_Uncategorized",
+ "Dwarvenholm Variant_Uncategorized",
+ "Earth Art",
+ "East African Art",
+ "Eco Art",
+ "Eco-Art",
+ "Ed Ruscha",
+ "Edgar Degas",
+ "Edinburgh cityscape",
+ "Editorial Design",
+ "Edvard Munch",
+ "Edward Hopper",
+ "Egyptian Hieroglyphs_Uncategorized",
+ "Egyptian Mythology Art",
+ "Egyptian Wall Art",
+ "Egyptology_Uncategorized",
+ "El Anatsui",
+ "Electronic Music",
+ "Electronic Music Visuals",
+ "Elegant_Erotic_Photography",
+ "Elfheim Variant_Architecture_Fantasy",
+ "Elven City Variant_Architecture_Location",
+ "Embroidery",
+ "Emerging_Artist",
+ "Engraving",
+ "Environmental Art",
+ "Environmental Design",
+ "Ephemeral Art",
+ "Etching",
+ "Eugene Delacroix",
+ "Exhibition Design",
+ "Exoplanet",
+ "Exorcism_Uncategorized",
+ "Experimental Art",
+ "Experimental Films",
+ "Experimental Music Video",
+ "Experimental Photography",
+ "Experimental Theater",
+ "Expressionism",
+ "Expressionist Architecture",
+ "Expressionist painting",
+ "Fairy Tale Art",
+ "Fantasy",
+ "Fantasy Films",
+ "Fantasy Literature",
+ "Farce",
+ "Fashion Design",
+ "Fashion Illustration",
+ "Fashion Photography",
+ "Fast Fashion",
+ "Fauvism",
+ "Fauvism Art",
+ "Ferocious Werewolf_Uncategorized",
+ "Festival Fashion",
+ "Fiction",
+ "Figurative Expressionism_Uncategorized",
+ "Figurine Shelf_Fantasy_Sculpture",
+ "Filipino Art",
+ "Film Direction",
+ "Film Editing",
+ "Film Noir",
+ "Fine Art Photography",
+ "Fine_Art_Black_and_White_Photography",
+ "Fire Art",
+ "Flamenco Dance",
+ "Folk Art Variant_Folk Art",
+ "Folk Art_Folk Art",
+ "Folk Dance",
+ "Folk Music",
+ "Folk Music Art",
+ "Food Art",
+ "Food Photography",
+ "Formal Fashion",
+ "Fortune Teller_Occult",
+ "Fortune Telling_Occult",
+ "France",
+ "Francisco Goya",
+ "Frankfurt cityscape",
+ "French Art",
+ "French Cuisine",
+ "French Impressionism",
+ "Fresco Painting Technique",
+ "Frida Kahlo",
+ "Funk Music",
+ "Furniture Design",
+ "Futurism",
+ "Futurist Architecture",
+ "GAYZ_Portraiture",
+ "Gabriel Orozco",
+ "Galactic_Sci-Fi",
+ "Game Design",
+ "Generative Art",
+ "Genetic Art_Uncategorized",
+ "Geometric Abstraction",
+ "Geometric abstract painting",
+ "Georg Baselitz",
+ "Georgia O'Keeffe",
+ "Gerhard Richter_1",
+ "Gerhard Richter_2",
+ "German Art",
+ "Ghibli_Surrealism",
+ "Ghoul City Variant_Architecture_Location",
+ "Giant Robot_Sci-Fi_Retro_Architecture",
+ "Glamorous Portrait_Fashion_Portraiture",
+ "Glamorous_Erotic_Photography",
+ "Glasgow cityscape",
+ "Glass Sculpture",
+ "Glassblowing",
+ "Glazing Technique in Painting",
+ "Glenn Ligon",
+ "Glitch Art_Uncategorized",
+ "Glitchcore_Digital Media",
+ "Gongfu Tea_Uncategorized",
+ "Gospel Music",
+ "Goth Boudoir_Gothic",
+ "Gotham City",
+ "Gothic Architecture",
+ "Gothic Architecture_Architecture_Gothic",
+ "Gothic Fashion",
+ "Gothic Literature",
+ "Gothic Monster_Architecture_Gothic",
+ "Gothic Revival Architecture",
+ "Gothic Revival Architecture_Architecture_Gothic",
+ "Graffiti Art",
+ "Graffiti Style_Graffiti",
+ "Grand Canyon",
+ "Grant Wood",
+ "Graphic Design",
+ "Graveyard Mist_Horror",
+ "Great Barrier Reef",
+ "Great Wall of China",
+ "Greek Art",
+ "Greek Classical Sculpture",
+ "Greek Mythology Art",
+ "Greek Pottery Art",
+ "Greendale",
+ "Gritty_Voyeuristic_Photography",
+ "Grotesque Gargoyle_Uncategorized",
+ "Grunge Flyer_Uncategorized",
+ "Gustav Klimt",
+ "Gutai_Sci-Fi_Event",
+ "H.P. Lovecraft Cover_Horror",
+ "Hackersville Variant_Architecture",
+ "Hallstatt",
+ "Hard-edge Painting_Uncategorized",
+ "Hate Crime_Uncategorized",
+ "Haunted Carnival_Horror",
+ "Haunted Portrait_Portraiture_Horror",
+ "Haute Couture",
+ "Haute Couture Fashion",
+ "Haute Cuisine",
+ "Hawkins",
+ "Headless Horseman_Uncategorized",
+ "Heavy Metal",
+ "Henri Matisse",
+ "Hieronymus Bosch",
+ "High Fantasy",
+ "High Fantasy Art",
+ "Hip-Hop Album Art",
+ "Hip-Hop Dance",
+ "Hip-Hop Fashion",
+ "Hip-Hop Music",
+ "Historical Fiction",
+ "Hogwarts",
+ "Hong Kong",
+ "Hong Kong cityscape",
+ "Horror",
+ "Horror Films",
+ "Horror Movie Poster_Horror_Gothic",
+ "Hyperrealism_Uncategorized",
+ "Ice Sculpture",
+ "Illustration Design",
+ "Illustration for Children's Books",
+ "Impressionism",
+ "Impressionism Art",
+ "Impressionist Landscape Painting",
+ "Impressionist Portrait Painting",
+ "Improvisational Theater",
+ "Inca Mythology Art",
+ "Indian Art",
+ "Indian Cuisine",
+ "Indian Miniature Painting",
+ "Indian Mythology Art",
+ "Indie Films",
+ "Indie Music Art",
+ "Indigenous Australian Art",
+ "Indigenous Painting",
+ "Indigenous Pottery",
+ "Indonesian Art",
+ "Industrial Architecture",
+ "Industrial Design",
+ "Information Art_Uncategorized",
+ "Ink Drawing",
+ "Insectoid Mutant_Portraiture",
+ "Installation Art",
+ "Interaction Design",
+ "Interactive Art",
+ "Interactive Art Installations",
+ "Interactive artwork",
+ "Interior Design",
+ "Internet Art_Sci-Fi_Digital Media",
+ "Intimate_Naturist_Photography",
+ "Intuitive Art_Uncategorized",
+ "Irish Art",
+ "Irish Dance",
+ "Islamic Architecture",
+ "Islamic Art",
+ "Islamic Calligraphy",
+ "Islamic Geometric Art",
+ "Islamic Geometric Patterns",
+ "Island Luau_Uncategorized_Location",
+ "Istanbul",
+ "Istanbul cityscape",
+ "Italian Art",
+ "Italian Cuisine",
+ "Italian Renaissance Art",
+ "J.M.W. Turner",
+ "Jackson Pollock",
+ "Jakarta cityscape",
+ "Japan",
+ "Japanese Architecture",
+ "Japanese Art",
+ "Japanese Cuisine",
+ "Japanese Mythology Art",
+ "Jazz Dance",
+ "Jazz Music",
+ "Jazz Poster Art",
+ "Jean-Honore Fragonard",
+ "Jeff Koons",
+ "Jenny Holzer",
+ "Jerusalem",
+ "Jewelry Design",
+ "Johannes Vermeer",
+ "John Baldessari",
+ "Joyful Art",
+ "Julie Mehretu",
+ "Kabuki Theater",
+ "Kara Walker",
+ "Kathak Dance",
+ "Katsushika Hokusai",
+ "Kawaii Character_Uncategorized",
+ "Kawaii Fashion_Fashion",
+ "Kehinde Wiley",
+ "Kerry James Marshall",
+ "Kiki Smith",
+ "Kinetic Art",
+ "Kinetic Sculpture",
+ "Kintsugi (Japanese Gold Repair)",
+ "Kitsch Movement_Uncategorized",
+ "Knitting",
+ "Korean Art",
+ "Korean Celadon Ceramics",
+ "Korean Celadon Pottery",
+ "Korean Cuisine",
+ "Kuala Lumpur",
+ "Kyoto",
+ "Kyoto cityscape",
+ "LAIKA_Animation",
+ "Land Art",
+ "Land Art (1)_Fantasy_Nature_Sculpture_Landscape",
+ "Landscape Architecture",
+ "Landscape Design",
+ "Landscape Photography",
+ "Laser Grid_Uncategorized",
+ "Later European abstraction (1)_Uncategorized",
+ "Leonardo da Vinci",
+ "Lettrist artwork",
+ "Leviathan Variant_Architecture",
+ "Light Art",
+ "Line Dance",
+ "Lisbon cityscape",
+ "Lithography",
+ "Living Burial_Uncategorized",
+ "London",
+ "Los Angeles",
+ "Lost Vegas Variant_Architecture",
+ "Lounge Singer_Retro",
+ "Lovecraftian Horror_Horror",
+ "Low Fantasy",
+ "Lowbrow Art Variant_Surrealism_Culture",
+ "Lowbrow Art_Culture",
+ "Luau Fire Dancer_Fashion",
+ "Luchador_Uncategorized",
+ "Luxury Fashion",
+ "Lynching_Uncategorized",
+ "Lyrical abstract painting",
+ "Macabre Memento Mori_Horror_Horror & Dark_Still Life",
+ "Machinima Variant_Uncategorized",
+ "Machu Picchu",
+ "Macro Photography",
+ "Mad Scientist Machinery_Uncategorized",
+ "Madhubani Painting",
+ "Madhubani Painting (Indian Folk Art)",
+ "Mage City Variant_Architecture_Fantasy_Location",
+ "Magic Realist painting",
+ "Mall Goth_Portraiture_Gothic",
+ "Mannerism",
+ "Mannerist Architecture",
+ "Maori Wood Carving",
+ "Mardi Gras_Uncategorized",
+ "Marina Abramović",
+ "Mark Bradford",
+ "Mark Grotjahn",
+ "Martin Puryear",
+ "Masked Killer_Uncategorized",
+ "Masked Stalker_Uncategorized",
+ "Maurizio Cattelan",
+ "Maximalism",
+ "Mecca",
+ "Mech City Variant_Sci-Fi_Architecture_Location",
+ "Mech City_Sci-Fi_Architecture_Location",
+ "Mech City__Location",
+ "Media Art",
+ "Medical Oddities_Uncategorized",
+ "Mediterranean Cuisine",
+ "Melancholy Art",
+ "Melodrama",
+ "Melting Skull_Uncategorized",
+ "Memento Mori_Horror_Horror & Dark",
+ "Memoir",
+ "Menacing Scarecrow_Uncategorized",
+ "Menswear Fashion",
+ "Mesoamerican Mythology Art",
+ "Mesopotamian Mythology Art",
+ "Metabolist Architecture",
+ "Metal Music",
+ "Metal Music Artwork",
+ "Metalwork",
+ "Metropolis",
+ "Mexican Art",
+ "Mexican Cuisine",
+ "Mexican Muralism",
+ "Mexican Skull Art_Uncategorized",
+ "Miami",
+ "Michelangelo",
+ "Middle Eastern Cuisine",
+ "Middle-earth",
+ "Midgard Variant_Architecture",
+ "Milky Way Galaxy",
+ "Mime",
+ "Mime City Variant_Architecture_Location",
+ "Minimalism",
+ "Minimalist Web Design",
+ "Mixed Media Art",
+ "Mixer_Animation",
+ "Modern Architecture",
+ "Modern Dance",
+ "Modernist Architecture",
+ "Mona Hatoum",
+ "Monoprinting Technique",
+ "Mosaic",
+ "Mosaic Art",
+ "Motion Design",
+ "Motion Graphics Design",
+ "Mount Everest",
+ "Mount Olympus",
+ "Movie Storyboard_Uncategorized",
+ "Mughal Miniature Painting",
+ "Mumbai",
+ "Mummy Portrait_Portraiture",
+ "Munich cityscape",
+ "Music Video Direction",
+ "Musica Variant_Architecture_Culture",
+ "Musical Films",
+ "Musical Theater",
+ "Mutated Beast_Uncategorized",
+ "My Little Pony_Uncategorized",
+ "Mystery",
+ "Mystery Literature",
+ "Mythic Fantasy Art",
+ "Nantucket",
+ "Native American Art",
+ "Native American Basketry",
+ "Native American Mythology Art",
+ "Native American Pottery",
+ "Naturalism in Literature",
+ "Nature Landscape Photography",
+ "Nature Photography",
+ "Nautical_Retro",
+ "Naïve Art (1)_Uncategorized",
+ "Nebula",
+ "Neo Pop_Pop Culture_Culture",
+ "Neo Rauch",
+ "Neo-Dada_Uncategorized",
+ "Neo-Expressionism_Uncategorized",
+ "Neo-Gothic Architecture",
+ "Neo-Noir",
+ "Neo-Pop (1)_Pop Culture_Culture",
+ "Neo-primitivism (1)_Still Life",
+ "Neoclassical Architecture",
+ "Neoclassicism",
+ "Neon Lighting_Uncategorized",
+ "Neon Racer_Sci-Fi",
+ "Neon Tokyo_Retro",
+ "Neoplasticism",
+ "Neotokyo Variant_Sci-Fi_Architecture",
+ "Neue Sachlichkeit Variant_Portraiture",
+ "Neue Wilde (1)_Uncategorized",
+ "New Caelum Variant_Architecture",
+ "New Caelum_Architecture",
+ "New Media Art_Digital Media",
+ "New Orleans",
+ "New Perpendicular art_Uncategorized",
+ "New Simplicity_Architecture",
+ "New York City",
+ "New York cityscape",
+ "Niagara Falls",
+ "Nicole Eisenman",
+ "Night Photography",
+ "Nightmare Beast_Uncategorized",
+ "Non-Fiction",
+ "Nordic Viking Art",
+ "Norse Mythology Art",
+ "North African Art",
+ "Norwegian romantic nationalism_Nature_Landscape",
+ "Nouveau Circus_Uncategorized",
+ "Nova Alexandria Variant_Architecture_Culture",
+ "Occult Ritual_Occult",
+ "Occult Sacrifice_Occult",
+ "Oil Painting",
+ "Olafur Eliasson",
+ "Ominous Fog_Uncategorized",
+ "Ominous Warning_Uncategorized",
+ "Op Art",
+ "Op Art_Uncategorized",
+ "Opera",
+ "Opera Music",
+ "Opera Music Illustration",
+ "Osaka cityscape",
+ "Outsider Art_Uncategorized",
+ "Pablo Picasso",
+ "Package Design",
+ "Pandora",
+ "Paper Cutting",
+ "Paper Mache Art",
+ "Parametric Architecture",
+ "Paris",
+ "Participatory Art",
+ "Patchwork Creature_Uncategorized",
+ "Paul Cezanne",
+ "Performance Art",
+ "Performance Sculpture",
+ "Peruvian Art",
+ "Petra",
+ "Photography",
+ "Photojournalism",
+ "Photorealism",
+ "Photorealistic painting",
+ "Physical Theater",
+ "Pinup_Retro",
+ "Pixel Art",
+ "Pizza Making",
+ "Plague Mass Grave_Uncategorized",
+ "Plein Air Painting",
+ "Plotter Art Variant_Uncategorized",
+ "Plotter Art_Uncategorized",
+ "Plus-Size Fashion",
+ "Poetry",
+ "Pointillism",
+ "Pointillism Art",
+ "Pole Dance",
+ "Polynesian Mythology Art",
+ "Polynesian Tattoo Art",
+ "Pop Art",
+ "Pop Music",
+ "Pop Music Branding",
+ "Pop Surrealism_Nature_Surrealism_Landscape_Still Life",
+ "Pop art style",
+ "Porcelain Art",
+ "Portrait Photography",
+ "Portuguese Art",
+ "Post-Impressionism",
+ "Postmodern Architecture",
+ "Pottery",
+ "Prague",
+ "Prague cityscape",
+ "Prairie Dress_Retro_Fashion",
+ "Pre-Raphaelite_Uncategorized",
+ "Preppy Fashion",
+ "Printmaking",
+ "Prismatic_Uncategorized",
+ "Projection Mapping Art",
+ "Propaganda Art_Retro",
+ "Propaganda Poster_Uncategorized",
+ "Prose Literature",
+ "Provocative_Surreal_Photography",
+ "Pseudorealism_Uncategorized",
+ "Psychedelic Concert Posters",
+ "Psychedelic Pop Art_Surrealism",
+ "Public Art Installations",
+ "Public Installations",
+ "Public Sculptures",
+ "Punk Fashion",
+ "Punk Music",
+ "Punk Poster_Uncategorized",
+ "Puppetry",
+ "Pyramids of Giza",
+ "Quahog",
+ "Quilting",
+ "Quilting Art",
+ "Quito cityscape",
+ "R&B Music",
+ "Rachel Whiteread",
+ "Radical Realism (1)_Still Life",
+ "Rangoli (Indian Floor Art)",
+ "Rap Music Graphics",
+ "Raphael",
+ "Rashid Johnson",
+ "Rat Infestation_Uncategorized",
+ "Rat King_Uncategorized",
+ "Realism Art",
+ "Realism in Literature",
+ "Realistic Fiction",
+ "Reanimated Corpse_Animation",
+ "Recycled Art",
+ "Reggae Music",
+ "Reggae Music Design",
+ "Rembrandt",
+ "Remodernism Variant_Uncategorized",
+ "Remodernism_Architecture",
+ "Renaissance",
+ "Renaissance Architecture",
+ "Renaissance Art",
+ "Rene Magritte",
+ "Responsive Web Design",
+ "Richard Serra",
+ "Richard Tuttle",
+ "Rio de Janeiro",
+ "Rio de Janeiro cityscape",
+ "Robert Gober",
+ "Robotics Art",
+ "Rock Album Art",
+ "Rock Music",
+ "Rococo",
+ "Rococo Architecture",
+ "Rococo Art",
+ "Rococo Interior_Uncategorized",
+ "Roman Mosaic Art",
+ "Roman Mythology Art",
+ "Romance",
+ "Romance Literature",
+ "Romanesque Architecture",
+ "Romantic Comedy",
+ "Romantic Films",
+ "Romanticism",
+ "Romanticism Art",
+ "Romanticism in Literature",
+ "Rome",
+ "Rural Photography",
+ "Russia",
+ "Russian Art",
+ "Russian Icon Painting",
+ "Sahara Desert",
+ "Salem",
+ "Salsa Dance",
+ "Salsa Music",
+ "Salvador Dali",
+ "Samurai_Uncategorized",
+ "Sanctuary Variant_Uncategorized",
+ "Sand Sculpture",
+ "Sandro Botticelli",
+ "Sarah Sze",
+ "Satanic_Horror_Occult",
+ "Satire",
+ "Satire Literature",
+ "Scandinavian Architecture",
+ "Scandinavian Art",
+ "Scandinavian Design",
+ "Scarecrow_Horror",
+ "Scary Pumpkin_Uncategorized",
+ "Scary Stories at Campfire_Horror_Horror & Dark",
+ "Scary Stories_Horror",
+ "Sci-Fi Films",
+ "Science Fiction",
+ "Scientific Illustration_Retro",
+ "Screen Printing",
+ "Screwball Comedy",
+ "Sculpture",
+ "Self-taught Art (1)_Fantasy",
+ "Seoul",
+ "Serial Killer_Horror",
+ "Set Design for Theater",
+ "Shadow City Variant_Architecture_Occult_Gothic_Location",
+ "Shadow City_Architecture_Occult_Gothic_Location",
+ "Shadow City_Horror_Occult_Horror & Dark_Gothic_Location",
+ "Shanghai",
+ "Shangri-La Variant_Uncategorized",
+ "Shepard Fairey",
+ "Shirakawa-go",
+ "Shirin Neshat",
+ "Sideshow Poster_Retro",
+ "Silent Films",
+ "Singapore",
+ "Sinister Crone_Uncategorized",
+ "Sinister Laboratory_Horror_Occult_Still Life",
+ "Sinister Ritual_Uncategorized",
+ "Situationist International Variant_Uncategorized",
+ "Situationist International_Uncategorized",
+ "Skateboarding Fashion",
+ "Skeleton Dance_Animation",
+ "Skeleton Dance_Horror_Horror & Dark_Animation",
+ "Slavic Mythology Art",
+ "Slow Fashion",
+ "Smothering Earth_Fantasy",
+ "Social Realism painting",
+ "Sonnet",
+ "Soul Music",
+ "Sound Art",
+ "Sound Design",
+ "Sound Sculpture",
+ "South African Art",
+ "South American Textile Art",
+ "Southern Gothic_Gothic",
+ "Southwest Kachina Dolls",
+ "Spaghetti Western",
+ "Spanish Art",
+ "Spanish Cuisine",
+ "Spider Queen_Uncategorized",
+ "Sports Card_Photography_Portraiture",
+ "Sports Photography",
+ "Spring Art",
+ "Springfield",
+ "St Ives School Variant_Nature_Landscape",
+ "St Ives School_Nature_Landscape",
+ "Stained Glass Art",
+ "Stained Glass_Uncategorized",
+ "Stand-Up Comedy",
+ "Stars Hollow",
+ "Steampunk",
+ "Steampunk City Variant_Architecture_Location",
+ "Steampunk Fantasy Art",
+ "Steampunk Fashion",
+ "Steampunk Portrait_Fantasy_Portraiture",
+ "Steampunk_Fantasy_Fashion",
+ "Steamtown Variant_Architecture_Retro",
+ "Steeltown Variant_Architecture",
+ "Stockholm cityscape",
+ "Stone Sculpture",
+ "Stop Motion_Animation",
+ "Streamer Bike_Retro",
+ "Street Art",
+ "Street Art Performance",
+ "Street Art and Graffiti",
+ "Street Photography",
+ "Street Theater",
+ "Streetwear",
+ "Streetwear Fashion",
+ "Stuckism Variant_Uncategorized",
+ "Stuckism_Uncategorized",
+ "Studio Ghibli_Fantasy_Surrealism",
+ "Studio Portrait Photography",
+ "Sub Anaheim Variant_Fantasy_Location",
+ "Sub Annapolis Variant_Sculpture_Location",
+ "Sub Atlanta Variant_Uncategorized_Location",
+ "Sub Baton Rouge Variant_Culture_Location",
+ "Sub Baton Rouge_Culture_Location",
+ "Sub Baton Rouge__Location",
+ "Sub Berkeley Variant_Retro_Location",
+ "Sub Boise Variant_Uncategorized_Location",
+ "Sub Boise_Uncategorized_Location",
+ "Sub Boise__Location",
+ "Sub Bozeman Variant_Architecture_Location",
+ "Sub Carlsbad Variant_Architecture_Culture_Location",
+ "Sub Carson City Variant_Architecture_Location",
+ "Sub Casper Variant_Uncategorized_Location",
+ "Sub Cheyenne Variant_Uncategorized_Location",
+ "Sub Columbia Variant_Architecture_Culture_Location",
+ "Sub Concord Variant_Uncategorized_Location",
+ "Sub Costa Mesa Variant_Culture_Location",
+ "Sub Denver Variant_Uncategorized_Location",
+ "Sub Des Moines Variant_Architecture_Location",
+ "Sub Dover Variant_Uncategorized_Location",
+ "Sub Downey Variant_Sci-Fi_Location",
+ "Sub El Monte Variant_Sci-Fi_Location",
+ "Sub Fontana Variant_Culture_Location",
+ "Sub Frankfort Variant_Uncategorized_Location",
+ "Sub Fresno Variant_Architecture_Nature_Landscape_Location",
+ "Sub Garden Grove Variant_Architecture_Location",
+ "Sub Glendale Variant_Uncategorized_Location",
+ "Sub Indianapolis Variant_Uncategorized_Location",
+ "Sub Inglewood Variant_Sci-Fi_Pop Culture_Culture_Location",
+ "Sub Irvine Variant_Uncategorized_Location",
+ "Sub Jackson Variant_Folk Art_Location",
+ "Sub Jefferson City Variant_Architecture_Folk Art_Location",
+ "Sub Juneau Variant_Architecture_Location",
+ "Sub Lancaster Variant_Sci-Fi_Retro_Location",
+ "Sub Montgomery Variant_Uncategorized_Location",
+ "Sub Montpelier Variant_Sculpture_Location",
+ "Sub Moreno Valley Variant_Uncategorized_Location",
+ "Sub Oakland Variant_Sci-Fi_Culture_Location",
+ "Sub Ontario Variant_Uncategorized_Location",
+ "Sub Orange Variant_Retro_Location",
+ "Sub Oxnard Variant_Uncategorized_Location",
+ "Sub Oxnard_Uncategorized_Location",
+ "Sub Oxnard__Location",
+ "Sub Palmdale Variant_Sci-Fi_Location",
+ "Sub Pasadena Variant_Uncategorized_Location",
+ "Sub Pierre Variant_Uncategorized_Location",
+ "Sub Pomona Variant_Retro_Location",
+ "Sub Providence Variant_Uncategorized_Location",
+ "Sub Rancho Cucamonga Variant_Architecture_Lifestyle_Location",
+ "Sub Richmond Variant_Architecture_Location",
+ "Sub Roseville Variant_Architecture_Location",
+ "Sub Salem Variant_Sci-Fi_Culture_Location",
+ "Sub Santa Ana Variant_Sci-Fi_Culture_Location",
+ "Sub Santa Clarita Variant_Uncategorized_Location",
+ "Sub Santa Rosa Variant_Sci-Fi_Nature_Location",
+ "Sub Santa Rosa_Sci-Fi_Nature_Location",
+ "Sub Santa Rosa__Location",
+ "Sub Simi Valley Variant_Pop Culture_Culture_Retro_Location",
+ "Sub Spokane Variant_Architecture_Location",
+ "Sub Tacoma Variant_Architecture_Culture_Retro_Location",
+ "Sub Temecula Variant_Lifestyle_Location",
+ "Sub Thousand Oaks Variant_Uncategorized_Location",
+ "Sub Topeka Variant_Architecture_Folk Art_Location",
+ "Sub Torrance Variant_Sci-Fi_Location",
+ "Sub Victorville Variant_Uncategorized_Location",
+ "Sumi-e Painting",
+ "Summer Art",
+ "Summer Fashion",
+ "Surf Wood Sign_Retro",
+ "Surrealism",
+ "Surrealism Art",
+ "Surrealist Painting",
+ "Surrealist Sculpture",
+ "Sushi Making",
+ "Sustainable Architecture",
+ "Sustainable Art Variant_Uncategorized",
+ "Sustainable Art_Uncategorized",
+ "Sustainable Fashion",
+ "Swing Dance",
+ "Sydney",
+ "Symbolism Art",
+ "Synthetic Cubism",
+ "Taj Mahal",
+ "Takashi Murakami",
+ "Talavera Pottery",
+ "Tamara de Lempicka",
+ "Tango Dance",
+ "Tap Dance",
+ "Tarot Cards_Occult",
+ "Tarot_Occult",
+ "Tatooine",
+ "Tattoo Print_Retro_Tattoo Art",
+ "Tech City Variant_Architecture_Nature_Location",
+ "Techno Music Visuals",
+ "Technotopia Variant_Architecture_Nature",
+ "Temporary Art Installations",
+ "Terrarium Bottle_Still Life",
+ "Terrarium_Uncategorized",
+ "Teslapunk_Portraiture",
+ "Textile Art",
+ "Textile Design",
+ "Textile Sculpture",
+ "Thai Art",
+ "Thai Cuisine",
+ "Thomas Gainsborough",
+ "Thriller",
+ "Thriller Films",
+ "Thriller Literature",
+ "Tibetan Thangka Painting",
+ "Tiki Bar_Uncategorized",
+ "Tiki Cocktail_Uncategorized",
+ "Tiki Idol_Uncategorized",
+ "Tiki Mug_Retro",
+ "Tiki Outdoor Shower_Uncategorized",
+ "Tiki Totem_Sculpture",
+ "Titian",
+ "Toei_Retro_Animation",
+ "Tokyo",
+ "Tokyo cityscape",
+ "Torture Chamber_Uncategorized",
+ "Torture Device_Horror_Horror & Dark",
+ "Tortured Prisoner_Uncategorized",
+ "Tortured Soul_Uncategorized",
+ "Toy Design",
+ "Traditional Animation",
+ "Traditional Dance",
+ "Traditional Japanese Architecture",
+ "Traditional Pottery",
+ "Tragedy",
+ "Tragedy Literature",
+ "Tranquil Art",
+ "Transavantgarde Variant_Uncategorized",
+ "Transavantgarde_Uncategorized",
+ "Transgressive Art Variant_Uncategorized",
+ "Transgressive Art_Uncategorized",
+ "Travel Photography",
+ "Tropical Bathroom_Uncategorized",
+ "Tropical Cocktail_Uncategorized",
+ "Tropical Hotel_Uncategorized",
+ "Tropical Luau_Uncategorized",
+ "Twin Peaks",
+ "Typography Design",
+ "UPA_Comics_Animation",
+ "Ukiyo-e (Japanese Woodblock Printing)",
+ "Ukiyo-e Art",
+ "Undead Gluttony_Architecture",
+ "Undead Portrait_Portraiture",
+ "Undefined_Emerging_Artist",
+ "Under Albany Variant_Architecture_Surrealism_Location",
+ "Under Bakersfield Variant_Uncategorized_Location",
+ "Under Berlin Variant_Retro_Surrealism_Location",
+ "Under Berlin_Retro_Surrealism_Location",
+ "Under Berlin__Location",
+ "Under Bismarck Variant_Uncategorized_Location",
+ "Under Charleston Variant_Architecture_Location",
+ "Under Chicago Variant_Architecture_Portraiture_Culture_Retro_Location",
+ "Under Eugene Variant_Folk Art_Location",
+ "Under Fargo Variant_Architecture_Location",
+ "Under Hartford Variant_Architecture_Location",
+ "Under Honolulu Variant_Architecture_Location",
+ "Under Istanbul Variant_Architecture_Location",
+ "Under Jackson Variant_Folk Art_Location",
+ "Under Juneau Variant_Architecture_Location",
+ "Under London Variant_Architecture_Location",
+ "Under Montreal Variant_Architecture_Location",
+ "Under Nashville Variant_Uncategorized_Location",
+ "Under Oklahoma City Variant_Architecture_Location",
+ "Under Omaha Variant_Culture_Location",
+ "Under Paris Variant_Uncategorized_Location",
+ "Under Sacramento Variant_Uncategorized_Location",
+ "Under Santa Fe Variant_Uncategorized_Location",
+ "Under St. Paul Variant_Architecture_Location",
+ "Under Tallahassee Variant_Sci-Fi_Retro_Architecture_Location",
+ "Under Trenton Variant_Uncategorized_Location",
+ "Underground Anchorage Variant_Architecture_Location",
+ "Underground Austin Variant_Uncategorized_Location",
+ "Underground Chula Vista Variant_Uncategorized_Location",
+ "Underground Columbus Variant_Retro_Location",
+ "Underground Concord Variant_Culture_Location",
+ "Underground Helena Variant_Architecture_Location",
+ "Underground Huntington Beach Variant_Architecture_Culture_Location",
+ "Underground Lansing Variant_Culture_Location",
+ "Underground Lincoln Variant_Uncategorized_Location",
+ "Underground Little Rock Variant_Uncategorized_Location",
+ "Underground Portland Variant_Sci-Fi_Location",
+ "Underground Riverside Variant_Culture_Location",
+ "Underground Rome Variant_Architecture_Location",
+ "Underground Salt Lake City Variant_Architecture_Location",
+ "Underground San Jose Variant_Uncategorized_Location",
+ "Underground Seattle Variant_Uncategorized_Location",
+ "Underground Springfield Variant_Folk Art_Location",
+ "Underground Wichita Variant_Folk Art_Location",
+ "Underwater Photography",
+ "Urban Fantasy Art",
+ "Urban Landscape Photography",
+ "Urban Photography",
+ "Urban Sculpture",
+ "User-Centered Design",
+ "Utrecht cityscape",
+ "VR Art Variant_Uncategorized",
+ "Vacuous Grimace_Uncategorized",
+ "Valhalla",
+ "Valve",
+ "Vampire_Portraiture_Horror",
+ "Vaporgram_Retro",
+ "Vaporwave City_Sci-Fi_Dystopia_Architecture_Location",
+ "Vaporwave Graphics_Retro_Surrealism_Graphic Design",
+ "Vaporwave Retro_Sci-Fi_Retro",
+ "Vaporwave Sunset_Uncategorized",
+ "Vaporwave_Architecture_Retro",
+ "Vatican City",
+ "Vector Portrait_Portraiture",
+ "Venezuelan Art",
+ "Venice",
+ "Verbatim Theater",
+ "Victorian Architecture",
+ "Victorian Fashion",
+ "Victorian Laboratory_Occult_Still Life",
+ "Video Art",
+ "Video Art_Uncategorized",
+ "Video Games Variant_Games",
+ "Video Games_Games_Culture",
+ "Video Mapping",
+ "Vienna",
+ "Vienna cityscape",
+ "Vietnamese Art",
+ "Vietnamese Cuisine",
+ "Vija Celmins",
+ "Vincent Van Gogh",
+ "Vintage Baseball_Retro_Photography",
+ "Vintage Fashion",
+ "Vintage Halloween Costume_Retro",
+ "Vintage Halloween Mask_Retro",
+ "Vintage Halloween_Retro",
+ "Vintage Robot Toy_Sci-Fi_Retro",
+ "Vintage Tattoo Flash_Retro_Tattoo Art",
+ "Vintage Tattoo Print_Retro_Tattoo Art",
+ "Vintage Travel Poster_Retro_Nature_Landscape",
+ "Virtual Art Variant_Uncategorized",
+ "Virtual Art_Sci-Fi",
+ "Virtual Reality (VR) Art",
+ "Virtual Reality Art",
+ "Visionary Art (1)_Uncategorized",
+ "Visual Effects (VFX) Design",
+ "Vogue Cover_Photography_Fashion",
+ "Volcano Lair_Uncategorized",
+ "Voodoo Altar_Occult",
+ "Voodoo Ceremony_Occult",
+ "Voodoo Doll_Retro_Occult",
+ "Voodoo Queen_Portraiture_Occult",
+ "Voodoo Shop_Occult",
+ "Voodoo_Occult",
+ "Vorticism_Uncategorized",
+ "Wallace and Gromit",
+ "Waltz Dance",
+ "War Films",
+ "Wassily Kandinsky",
+ "Water Art",
+ "Watercolor Painting",
+ "Weaving",
+ "Web Design",
+ "Wedding Fashion",
+ "Wedding Photography",
+ "Wellington cityscape",
+ "West African Art",
+ "Westeros",
+ "Wildlife Photography",
+ "William Kentridge",
+ "Winter Art",
+ "Winter Fashion",
+ "Wolfgang Tillmans",
+ "Womenswear Fashion",
+ "Wonderland",
+ "Wood Carving",
+ "Woodblock Art_Nature",
+ "Woodblock Print_Uncategorized",
+ "Woodblock Printing",
+ "Woodcut",
+ "Workwear Fashion",
+ "World Music",
+ "Xiamen cityscape",
+ "Xilam_Comics_Animation",
+ "Yayoi Kusama",
+ "Yellowstone National Park",
+ "Yokohama cityscape",
+ "Zion Variant_Culture",
+ "Zurich cityscape",
+ "_Uncategorized",
+ "ads-advertising_Uncategorized",
+ "ads-automotive_Uncategorized",
+ "ads-corporate_Uncategorized",
+ "ads-fashion editorial_Fashion",
+ "ads-food photography_Photography",
+ "ads-luxury_Uncategorized",
+ "ads-real estate_Photography",
+ "ads-retail_Uncategorized",
+ "artstyle-abstract expressionism_Uncategorized",
+ "artstyle-abstract_Uncategorized",
+ "artstyle-art deco_Uncategorized",
+ "artstyle-art nouveau_Nature",
+ "artstyle-constructivist_Uncategorized",
+ "artstyle-cubist_Uncategorized",
+ "artstyle-expressionist_Uncategorized",
+ "artstyle-graffiti_Architecture_Graffiti",
+ "artstyle-hyperrealism_Photography",
+ "artstyle-impressionist_Uncategorized",
+ "artstyle-pointillism_Uncategorized",
+ "artstyle-pop art_Culture",
+ "artstyle-psychedelic_Surrealism",
+ "artstyle-renaissance_Uncategorized",
+ "artstyle-steampunk_Uncategorized",
+ "artstyle-surrealist_Surrealism",
+ "artstyle-typography_Uncategorized",
+ "artstyle-watercolor_Uncategorized",
+ "carpint_Gothic",
+ "citz_Sci-Fi_Architecture",
+ "coolio_Portraiture",
+ "enhance_Uncategorized",
+ "futuristic-biomechanical cyberpunk_Sci-Fi_Dystopia",
+ "futuristic-biomechanical_Sci-Fi",
+ "futuristic-cybernetic robot_Sci-Fi",
+ "futuristic-cybernetic_Sci-Fi",
+ "futuristic-cyberpunk cityscape_Sci-Fi_Architecture",
+ "futuristic-futuristic_Sci-Fi",
+ "futuristic-retro cyberpunk_Sci-Fi_Retro",
+ "futuristic-retro futurism_Sci-Fi_Retro",
+ "futuristic-sci-fi_Sci-Fi",
+ "futuristic-vaporwave_Sci-Fi_Retro",
+ "game-bubble bobble_Fantasy",
+ "game-cyberpunk game_Sci-Fi_Dystopia_Games_Digital Media",
+ "game-fighting game_Games",
+ "game-gta_Uncategorized",
+ "game-mario_Fantasy_Comics",
+ "game-minecraft_Still Life",
+ "game-pokemon_Fantasy",
+ "game-retro arcade_Retro_Games",
+ "game-retro game_Retro",
+ "game-rpg fantasy game_Fantasy_Games",
+ "game-strategy game_Games",
+ "game-streetfighter_Uncategorized",
+ "game-zelda_Fantasy",
+ "getting there_Portraiture",
+ "girlz_Fashion_Horror_Horror & Dark_Gothic",
+ "gotit jinx_Tattoo Art",
+ "greatz_Portraiture",
+ "gsssggg_Portraiture",
+ "hoop_Portraiture",
+ "jinx_Tattoo Art",
+ "jinxed_Portraiture",
+ "kjkjkjj_Digital Media_Still Life_Comics",
+ "kool_Portraiture",
+ "misc-architectural_Uncategorized",
+ "misc-disco_Retro",
+ "misc-dreamscape_Fantasy_Surrealism",
+ "misc-dystopian_Dystopia",
+ "misc-fairy tale_Fantasy",
+ "misc-gothic_Gothic",
+ "misc-grunge_Retro",
+ "misc-horror_Horror",
+ "misc-horror_Horror_Horror & Dark",
+ "misc-kawaii_Uncategorized",
+ "misc-lovecraftian_Surrealism_Horror",
+ "misc-macabre_Gothic",
+ "misc-manga_Uncategorized",
+ "misc-metropolis_Sci-Fi_Architecture",
+ "misc-minimalist_Uncategorized",
+ "misc-monochrome_Uncategorized",
+ "misc-nautical_Uncategorized",
+ "misc-space_Sci-Fi",
+ "misc-stained glass_Uncategorized",
+ "misc-techwear fashion_Sci-Fi_Fashion_Architecture",
+ "misc-tribal_Uncategorized",
+ "misc-zentangle_Uncategorized",
+ "mkkk_Portraiture_Digital Media_Animation",
+ "papercraft-collage_Uncategorized",
+ "papercraft-flat papercut_Uncategorized",
+ "papercraft-kirigami_Uncategorized",
+ "papercraft-paper mache_Uncategorized",
+ "papercraft-paper quilling_Uncategorized",
+ "papercraft-papercut collage_Uncategorized",
+ "papercraft-papercut shadow box_Uncategorized",
+ "papercraft-stacked papercut_Uncategorized",
+ "papercraft-thick layered papercut_Uncategorized",
+ "photo-alien_Sci-Fi_Photography",
+ "photo-film noir_Photography",
+ "photo-hdr_Photography",
+ "photo-long exposure_Photography_Surrealism",
+ "photo-neon noir_Photography",
+ "photo-silhouette_Photography",
+ "photo-tilt-shift_Photography",
+ "sai-3d-model_Uncategorized",
+ "sai-analog film_Retro_Photography",
+ "sai-anime_Uncategorized",
+ "sai-cinematic_Uncategorized",
+ "sai-comic book_Uncategorized",
+ "sai-craft clay_Sculpture",
+ "sai-digital art_Digital Media",
+ "sai-fantasy art_Fantasy_Surrealism",
+ "sai-isometric_Uncategorized",
+ "sai-line art_Uncategorized",
+ "sai-lowpoly_Uncategorized",
+ "sai-neonpunk_Uncategorized",
+ "sai-origami_Uncategorized",
+ "sai-photographic_Photography",
+ "sai-pixel art_Uncategorized",
+ "sai-texture_Uncategorized",
+ "stfhgff_Photography"
+ ]
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "positive_prompt_text_g",
+ "type": "STRING",
+ "links": [
+ 153,
+ 155
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "negative_prompt_text_g",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "MileHighStyler"
+ },
+ "widgets_values": [
+ "1girl, period costume",
+ "",
+ "2D Game Art",
+ "No"
+ ]
+ },
+ {
+ "id": 129,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 2050,
+ 830
+ ],
+ "size": {
+ "0": 210,
+ "1": 130
+ },
+ "flags": {},
+ "order": 22,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 153
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "Animation 1girl, period costume, Creating art with Animation, often for moving images, storytelling, or dynamic visuals."
+ ]
+ },
+ {
+ "id": 25,
+ "type": "CR Current Frame",
+ "pos": [
+ 810,
+ 240
+ ],
+ "size": {
+ "0": 210,
+ "1": 58
+ },
+ "flags": {
+ "collapsed": false
+ },
+ "order": 15,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "link": 23,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "links": [
+ 56,
+ 91,
+ 159
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Current Frame"
+ },
+ "widgets_values": [
+ 1,
+ "Yes"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 12,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 2350,
+ 670
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 55
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 9
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "embedding:EasyNegative,\nnsfw"
+ ]
+ },
+ {
+ "id": 47,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ 1890,
+ 370
+ ],
+ "size": {
+ "0": 380,
+ "1": 100
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 53
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 54,
+ 55
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "SD1_5\\ComfyrollAnime_v1_fp16_pruned.safetensors"
+ ]
+ },
+ {
+ "id": 126,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 1590,
+ 610
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 19,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 157
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "Animation"
+ ]
+ },
+ {
+ "id": 87,
+ "type": "Note",
+ "pos": [
+ 2360,
+ 840
+ ],
+ "size": {
+ "0": 210,
+ "1": 90
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The Text Scheduler is changing the style applied in the prompt\n"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 122,
+ "type": "CR Central Schedule",
+ "pos": [
+ 210,
+ -350
+ ],
+ "size": {
+ "0": 340,
+ "1": 490
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": [
+ 149
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Central Schedule"
+ },
+ "widgets_values": [
+ "0, 512\n2, 640\n3, 768\n4, 896\n8, 1024\n",
+ "Value",
+ "V1",
+ "0, Art Nouveau\n2, Antarctica\n4, 2D Game Art\n5, Animation\n8, Airbrushing",
+ "Text",
+ "T1",
+ "schedule",
+ "Model",
+ "",
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 68,
+ "type": "Note",
+ "pos": [
+ 810,
+ -90
+ ],
+ "size": {
+ "0": 210,
+ "1": 70
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Frames are processed in sequence starting from frame index 0"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 60,
+ "type": "Note",
+ "pos": [
+ 230,
+ 310
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "To run this workflow, first press Reset in the Animation Builder and then press the Queue button, Do not use queue prompt in the ComfyUI menu."
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 124,
+ "type": "CR Simple Schedule",
+ "pos": [
+ 540,
+ 710
+ ],
+ "size": {
+ "0": 290,
+ "1": 200
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": [
+ 150
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Schedule"
+ },
+ "widgets_values": [
+ "0, Art Nouveau\n2, Antarctica\n4, 2D Game Art\n5, Animation\n8, Airbrushing",
+ "Text",
+ "T1",
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 86,
+ "type": "Note",
+ "pos": [
+ 290,
+ 710
+ ],
+ "size": {
+ "0": 210,
+ "1": 120
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Schedules should have a line for frame 0\n\nIf frame 0 is missing the default value will be used"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 128,
+ "type": "CR String To Combo",
+ "pos": [
+ 1280,
+ 890
+ ],
+ "size": {
+ "0": 210,
+ "1": 34
+ },
+ "flags": {},
+ "order": 20,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 158
+ }
+ ],
+ "outputs": [
+ {
+ "name": "*",
+ "type": "*",
+ "links": [
+ 154
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR String To Combo"
+ },
+ "widgets_values": [
+ ""
+ ]
+ },
+ {
+ "id": 135,
+ "type": "CR Text Scheduler",
+ "pos": [
+ 1180,
+ 650
+ ],
+ "size": {
+ "0": 320,
+ "1": 170
+ },
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": 156
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 159,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 157,
+ 158
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Text Scheduler"
+ },
+ "widgets_values": [
+ "Schedule",
+ 0,
+ "T1",
+ "default text",
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 125,
+ "type": "CR Schedule Input Switch",
+ "pos": [
+ 910,
+ 650
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule1",
+ "type": "SCHEDULE",
+ "link": 149
+ },
+ {
+ "name": "schedule2",
+ "type": "SCHEDULE",
+ "link": 150
+ }
+ ],
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": [
+ 156
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Schedule Input Switch"
+ },
+ "widgets_values": [
+ 2
+ ]
+ },
+ {
+ "id": 114,
+ "type": "Note",
+ "pos": [
+ 910,
+ 790
+ ],
+ "size": {
+ "0": 210,
+ "1": 140
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "You can define either local or central schedules\n\nThis workflow shows both. You can switch between the two.\n\nThis switch would not normally be needed."
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 24,
+ "type": "Animation Builder (mtb)",
+ "pos": [
+ 480,
+ 240
+ ],
+ "size": {
+ "0": 210,
+ "1": 320
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "frame",
+ "type": "INT",
+ "links": [
+ 23
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "0-1 (scaled)",
+ "type": "FLOAT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "count",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "loop_ended",
+ "type": "BOOLEAN",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Animation Builder (mtb)"
+ },
+ "widgets_values": [
+ 10,
+ 1,
+ 1,
+ 10,
+ 1,
+ "frame: 0 / 9",
+ "Done 😎!",
+ "reset",
+ "queue"
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ }
+ ],
+ "links": [
+ [
+ 8,
+ 11,
+ 0,
+ 13,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 9,
+ 12,
+ 0,
+ 13,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 11,
+ 14,
+ 0,
+ 15,
+ 0,
+ "INT"
+ ],
+ [
+ 12,
+ 14,
+ 1,
+ 15,
+ 1,
+ "INT"
+ ],
+ [
+ 13,
+ 15,
+ 0,
+ 13,
+ 3,
+ "LATENT"
+ ],
+ [
+ 14,
+ 13,
+ 0,
+ 16,
+ 0,
+ "LATENT"
+ ],
+ [
+ 15,
+ 17,
+ 0,
+ 16,
+ 1,
+ "VAE"
+ ],
+ [
+ 23,
+ 24,
+ 0,
+ 25,
+ 0,
+ "INT"
+ ],
+ [
+ 53,
+ 47,
+ 0,
+ 13,
+ 0,
+ "MODEL"
+ ],
+ [
+ 54,
+ 47,
+ 1,
+ 11,
+ 0,
+ "CLIP"
+ ],
+ [
+ 55,
+ 47,
+ 1,
+ 12,
+ 0,
+ "CLIP"
+ ],
+ [
+ 56,
+ 25,
+ 0,
+ 26,
+ 1,
+ "INT"
+ ],
+ [
+ 72,
+ 14,
+ 3,
+ 15,
+ 2,
+ "INT"
+ ],
+ [
+ 90,
+ 78,
+ 0,
+ 77,
+ 0,
+ "STRING"
+ ],
+ [
+ 91,
+ 25,
+ 0,
+ 78,
+ 0,
+ "INT"
+ ],
+ [
+ 123,
+ 98,
+ 0,
+ 91,
+ 0,
+ "*"
+ ],
+ [
+ 133,
+ 16,
+ 0,
+ 26,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 149,
+ 122,
+ 0,
+ 125,
+ 0,
+ "SCHEDULE"
+ ],
+ [
+ 150,
+ 124,
+ 0,
+ 125,
+ 1,
+ "SCHEDULE"
+ ],
+ [
+ 153,
+ 130,
+ 0,
+ 129,
+ 0,
+ "STRING"
+ ],
+ [
+ 154,
+ 128,
+ 0,
+ 130,
+ 0,
+ "no style,2D Game Art,3D Animation,3D Game Art,3D Modeling,3D Printing Art,3D Printing in Art,AR Art Variant_Uncategorized,Aardman_Uncategorized,Abandoned Asylum_Uncategorized,Aboriginal Dot Painting,Abstract Expressionism,Abstract Painting,Abstract Photography,Abstract Sculpture,Absurdist Theater,Academic Art,Acrylic Painting,Action Films,Addams Family_Portraiture_Horror,Adrian Ghenie,Adventure,Adventure Films,Aerial Dance,Aerial Photography,African Beadwork,African Beadwork Art,African Cuisine,African Mask Art,African Mask Making,Agnes Martin,Ai Weiwei_1,Ai Weiwei_2,Air Art,Airbrushing,Albrecht Durer,Album Cover Art,Alchemist's Study_Uncategorized,Amazon Rainforest,American Cuisine,American Traditional_Retro_Tattoo Art,Amsterdam,Amsterdam cityscape,Analytical Cubism,Ancient Maya_Uncategorized,Andy Warhol,Anger Art,Animated Corpse_Animation,Animated Films,Animation,Anish Kapoor,Ankama_Animation,Anselm Kiefer,Antarctica,Appropriation (1)_Culture,Après-Ski_Uncategorized,Arachnid Swarm_Uncategorized,Architectural Design,Architectural Photography,Argentinian Art,Art Activism,Art Collaborations with Musicians,Art Collaborations with Writers,Art Conservation,Art Criticism,Art Curation,Art Deco,Art Deco Architecture,Art Deco Architecture_Architecture,Art Deco Design,Art Education,Art Education for Adults,Art Education for Children,Art Education for Remote Areas,Art Education for Special Needs,Art Gallery Management,Art Games,Art Historical Writing,Art History,Art History Research,Art Informatics,Art Informel (1)_Uncategorized,Art Inspired by Ancient Civilizations,Art Inspired by the Digital Age,Art Inspired by the Renaissance,Art Inspired by the Roaring Twenties,Art Inspired by the Victorian Era,Art Installations,Art Journalism,Art Marketing,Art Nouveau,Art Nouveau Architecture,Art Nouveau Design,Art Nouveau Poster_Uncategorized,Art Nouveau Variant_Uncategorized,Art Restoration,Art Sales and Auctions,Art Therapy,Art Therapy for Adults,Art Therapy for Children,Art Workshop Facilitation,Art and AI Collaboration,Art and Architecture Collaboration,Art and Cultural Heritage Preservation,Art and Environmental Sustainability,Art and Literature Collaboration,Art and Medical Collaboration,Art and Mental Health,Art and Music Collaboration,Art and Science Collaboration,Art and Social Justice Projects,Art and Technology Collaboration,Art and Urban Development,Art for Agricultural Industry,Art for Agricultural Sector,Art for Airports,Art for Animal Welfare Organizations,Art for Anniversaries,Art for Aquariums,Art for Architectural Visualization,Art for Asian Cultures,Art for Augmented Reality Experiences,Art for Automotive Design,Art for Automotive Industry,Art for Aviation Industry,Art for Baby Showers,Art for Birthdays,Art for Botanical Gardens,Art for Cafes and Restaurants,Art for Charity Fundraisers,Art for Children,Art for Children's Hospitals,Art for Climate Change Initiatives,Art for Construction Industry,Art for Corporate Spaces,Art for Cruise Ships,Art for Culinary Presentation,Art for E-Commerce Platforms,Art for Educational Institutions,Art for Educational Technology,Art for Elderly,Art for Emergency Services,Art for Energy Industry,Art for Entertainment Industry,Art for Environmental Activism,Art for Environmental Campaigns,Art for Factories and Workshops,Art for Fashion Industry,Art for Festivals and Events,Art for Financial Institutions,Art for Financial Sector,Art for Fitness Centers,Art for Funerals,Art for Gender Equality,Art for Government Entities,Art for Graduations,Art for Health Care Facilities,Art for Home Decor,Art for Hospitality Industry,Art for Hotels,Art for Human Anatomy Studies,Art for Human Rights Campaigns,Art for Indigenous Cultures,Art for LGBTQ+ Celebrations,Art for Libraries,Art for Marine Industry,Art for Maritime Industry,Art for Medical Illustrations,Art for Military and Defense Sector,Art for Military and Veterans,Art for Mobile Apps,Art for Museums,Art for Music Videos,Art for National Holidays,Art for Nautical Navigation,Art for Non-Profit Organizations,Art for Office Spaces,Art for Outdoor Advertising,Art for Packaging Design,Art for Pet Products,Art for Pharmaceutical Industry,Art for Political Campaigns,Art for Prisons,Art for Public Transportation,Art for Real Estate Marketing,Art for Religious Celebrations,Art for Religious Institutions,Art for Renewable Energy Sector,Art for Retail Spaces,Art for Retirement Parties,Art for Robotics,Art for Schools and Colleges,Art for Science Centers,Art for Scientific Exploration,Art for Security and Defense,Art for Seniors,Art for Shopping Malls,Art for Smart City Projects,Art for Social Media Platforms,Art for Social Networking Sites,Art for Spa and Wellness Centers,Art for Space Exploration,Art for Space Industry,Art for Spaceships and Aerospace,Art for Sports Industry,Art for Sports Venues,Art for Technical Manuals,Art for Teenagers,Art for Teens,Art for Television Shows,Art for Theme Parks,Art for Toddlers,Art for Train Stations,Art for Underwater Exploration,Art for Video Game Development,Art for Virtual Assistants and AI,Art for Virtual Events,Art for Virtual Reality Experiences,Art for Wearable Technology,Art for Wearables,Art for Web Platforms,Art for Weddings,Art for Zoos,Art in Public Transportation,Art with Light and Projection,Art with Metalwork,Art with Organic Materials,Art with Recycled Materials,Artist's Books,Artware Variant_Sci-Fi_Graffiti_Digital Media,Aspen,Assemblage Art,Astrophotography,Athens,Athleisure Fashion,Atlantis,Augmented Reality (AR) Art,Augmented Reality Art,Australian Aboriginal Art,Autobiography,Automotive Design,Autumn Art,Avant-Garde Fashion,Aztec Calendar_Uncategorized,Back Alley Rogue_Uncategorized,Ballet Dance,Ballet_Uncategorized,Ballroom Dance,Bangkok,Banksy_1,Banksy_2,Barbara Kruger,Barcelona,Baroque,Baroque Architecture,Baroque Art,Baroque Music,Bas-Relief Sculpture_Sculpture,Basket Weaving,Basket Weaving Art,Battle_Uncategorized,Bauhaus,Bauhaus (1)_Architecture,Bauhaus Architecture,Bauhaus Design,Bauhaus Design_Uncategorized,Beachwear Fashion,Beijing,Belly Dance,Berlin,Bharatanatyam Dance,Bikini Bottom,Bio Art,Bio Art_Nature,Biographical Films,Biographical Literature,Biography,Biomorphic Architecture,Black Hole,Black Velvet Painting_Portraiture,Black and White Photography,Blacklight Poster_Uncategorized,Blockbuster Films,Bloodthirsty Vampire_Horror,Bluegrass Music,Blueprint_Uncategorized,Blues Music,Blues Music Illustration,Body Art,Body Art Performance,Body Painting,Bohemian Fashion,Bomber Jacket_Retro,Bookbinding,Botanical Illustration,Boudoir Photography_Photography,Brazil,Brazilian Art,Brazilian Cuisine,Brazilian Graffiti Art,Breakdance,Bridal Fashion,Brightwater Variant_Nature,British Art,Bronze Sculpture,Bruce Nauman,Bruges,Brutalism,Brutalist Architecture,Budapest cityscape,Cabinet of Curiosities_Occult,Cai Guo-Qiang,Cake Decorating,Canada,Candid Portrait Photography,Caravaggio,Caribbean Carnival Art,Caribbean Cuisine,Caricature,Carnival Freakshow_Retro,Caspar David Friedrich,Cassette Bedroom_Retro,Cassette Collage_Sci-Fi_Surrealism_Retro,Cassette Futurism_Retro,Cassette Graphics_Retro_Surrealism,Cassette J-Card_Retro,Cassette Wall_Retro,Casual Fashion,Caveopolis Variant_Lifestyle,Cecily Brown,Celtic Knotwork Art,Celtic Mythology Art,Cemetery Statue_Uncategorized,Central African Art,Central American_Uncategorized,Ceramic Art,Ceramic Design,Ceramic Sculpture,Ceramics,Chalk Art,Charcoal Drawing,Charles Ray,Chicago,Children's Fashion,Children's Theater,Chilean Art,Chinese Architecture,Chinese Art,Chinese Calligraphy,Chinese Cuisine,Chinese Ink Painting,Chinese Jade Carving,Chinese Landscape Painting,Chinese Mythology Art,Chinese Paper Cutting,Chinese Scroll Painting,Chris Ofili,Cindy Sherman_1,Cindy Sherman_2,Cinematography,Cinque Terre,Circuit Bending_Uncategorized,Circus Arts,Circus Performer_Retro,Classic Western,Classical Architecture,Classical Art,Classical Music,Classical Music Illustration,Classical Realism,Classical Realism_Portraiture,Classical Theater,Claude Monet,Clockwork City Variant_Architecture_Location,Collaborative Art Projects,Collage,Collage Art,Colombian Art,Colonial Architecture,Colosseum,Combine Painting_Sci-Fi_Still Life,Comedy,Comedy Literature,Commercial Photography,Community Mural Projects,Computer art,Concept Art for Movies,Concept Art for Video Games,Conceptual Art,Concert Poster Design,Conjoined Twins_Uncategorized,Constructivism,Constructivism Art,Contemporary Ballet,Contemporary Dance,Copenhagen,Copenhagen cityscape,Corporate Identity Design,Cosplay Design,Cottagecore Fashion_Fashion,Country Music,Country Music Graphics,Crawler Mimicry_Uncategorized,Creepy Children_Portraiture,Creepy Porcelain Doll_Fashion_Portraiture,Crime Films,Critical Realism_Uncategorized,Cross-Disciplinary Art,Crucifixion_Uncategorized,Crystal Caverns Variant_Architecture,Cuban Art,Cuban Cuisine,Cubism,Cubism Art,Cult Films,Cyberpunk,Cyberpunk Fantasy Art,Dadaism,Dadaism Art,Damien Hirst_1,Damien Hirst_2,Dan Flavin,Dance Choreography,Dance Performance Art,Dark Carnival_Gothic,Dark Fantasy Art,Data Art Variant_Uncategorized,Data Art_Uncategorized,Data Visualization Art,Day of the Dead_Uncategorized,De Stijl_Uncategorized,Death Masque_Uncategorized,Deconstructivist Architecture,Demonic Clown_Uncategorized,Demonic Portal_Horror,Demonic Possession_Uncategorized,Demoscene_Animation,Desaturated_Uncategorized,Die Brücke_Graffiti,Diego Velazquez,Dieselpunk_Retro,Digital Animation,Digital Art,Digital Art_Digital Media,Digital Drawing Tablets,Digital Illustration,Digital Painting,Digital Sculpture,Digital Storytelling,Diorama_Uncategorized,Disco Music,Disney Animation_Animation,Disrespectful Grave Robber_Uncategorized,Documentary Films,Documentary Photography,Drama,Drama Films,Dubai,Dublin,Dublin cityscape,Dunder Mifflin,Dutch Art,Dwarvendom Variant_Uncategorized,Dwarvenholm Variant_Uncategorized,Earth Art,East African Art,Eco Art,Eco-Art,Ed Ruscha,Edgar Degas,Edinburgh cityscape,Editorial Design,Edvard Munch,Edward Hopper,Egyptian Hieroglyphs_Uncategorized,Egyptian Mythology Art,Egyptian Wall Art,Egyptology_Uncategorized,El Anatsui,Electronic Music,Electronic Music Visuals,Elegant_Erotic_Photography,Elfheim Variant_Architecture_Fantasy,Elven City Variant_Architecture_Location,Embroidery,Emerging_Artist,Engraving,Environmental Art,Environmental Design,Ephemeral Art,Etching,Eugene Delacroix,Exhibition Design,Exoplanet,Exorcism_Uncategorized,Experimental Art,Experimental Films,Experimental Music Video,Experimental Photography,Experimental Theater,Expressionism,Expressionist Architecture,Expressionist painting,Fairy Tale Art,Fantasy,Fantasy Films,Fantasy Literature,Farce,Fashion Design,Fashion Illustration,Fashion Photography,Fast Fashion,Fauvism,Fauvism Art,Ferocious Werewolf_Uncategorized,Festival Fashion,Fiction,Figurative Expressionism_Uncategorized,Figurine Shelf_Fantasy_Sculpture,Filipino Art,Film Direction,Film Editing,Film Noir,Fine Art Photography,Fine_Art_Black_and_White_Photography,Fire Art,Flamenco Dance,Folk Art Variant_Folk Art,Folk Art_Folk Art,Folk Dance,Folk Music,Folk Music Art,Food Art,Food Photography,Formal Fashion,Fortune Teller_Occult,Fortune Telling_Occult,France,Francisco Goya,Frankfurt cityscape,French Art,French Cuisine,French Impressionism,Fresco Painting Technique,Frida Kahlo,Funk Music,Furniture Design,Futurism,Futurist Architecture,GAYZ_Portraiture,Gabriel Orozco,Galactic_Sci-Fi,Game Design,Generative Art,Genetic Art_Uncategorized,Geometric Abstraction,Geometric abstract painting,Georg Baselitz,Georgia O'Keeffe,Gerhard Richter_1,Gerhard Richter_2,German Art,Ghibli_Surrealism,Ghoul City Variant_Architecture_Location,Giant Robot_Sci-Fi_Retro_Architecture,Glamorous Portrait_Fashion_Portraiture,Glamorous_Erotic_Photography,Glasgow cityscape,Glass Sculpture,Glassblowing,Glazing Technique in Painting,Glenn Ligon,Glitch Art_Uncategorized,Glitchcore_Digital Media,Gongfu Tea_Uncategorized,Gospel Music,Goth Boudoir_Gothic,Gotham City,Gothic Architecture,Gothic Architecture_Architecture_Gothic,Gothic Fashion,Gothic Literature,Gothic Monster_Architecture_Gothic,Gothic Revival Architecture,Gothic Revival Architecture_Architecture_Gothic,Graffiti Art,Graffiti Style_Graffiti,Grand Canyon,Grant Wood,Graphic Design,Graveyard Mist_Horror,Great Barrier Reef,Great Wall of China,Greek Art,Greek Classical Sculpture,Greek Mythology Art,Greek Pottery Art,Greendale,Gritty_Voyeuristic_Photography,Grotesque Gargoyle_Uncategorized,Grunge Flyer_Uncategorized,Gustav Klimt,Gutai_Sci-Fi_Event,H.P. Lovecraft Cover_Horror,Hackersville Variant_Architecture,Hallstatt,Hard-edge Painting_Uncategorized,Hate Crime_Uncategorized,Haunted Carnival_Horror,Haunted Portrait_Portraiture_Horror,Haute Couture,Haute Couture Fashion,Haute Cuisine,Hawkins,Headless Horseman_Uncategorized,Heavy Metal,Henri Matisse,Hieronymus Bosch,High Fantasy,High Fantasy Art,Hip-Hop Album Art,Hip-Hop Dance,Hip-Hop Fashion,Hip-Hop Music,Historical Fiction,Hogwarts,Hong Kong,Hong Kong cityscape,Horror,Horror Films,Horror Movie Poster_Horror_Gothic,Hyperrealism_Uncategorized,Ice Sculpture,Illustration Design,Illustration for Children's Books,Impressionism,Impressionism Art,Impressionist Landscape Painting,Impressionist Portrait Painting,Improvisational Theater,Inca Mythology Art,Indian Art,Indian Cuisine,Indian Miniature Painting,Indian Mythology Art,Indie Films,Indie Music Art,Indigenous Australian Art,Indigenous Painting,Indigenous Pottery,Indonesian Art,Industrial Architecture,Industrial Design,Information Art_Uncategorized,Ink Drawing,Insectoid Mutant_Portraiture,Installation Art,Interaction Design,Interactive Art,Interactive Art Installations,Interactive artwork,Interior Design,Internet Art_Sci-Fi_Digital Media,Intimate_Naturist_Photography,Intuitive Art_Uncategorized,Irish Art,Irish Dance,Islamic Architecture,Islamic Art,Islamic Calligraphy,Islamic Geometric Art,Islamic Geometric Patterns,Island Luau_Uncategorized_Location,Istanbul,Istanbul cityscape,Italian Art,Italian Cuisine,Italian Renaissance Art,J.M.W. Turner,Jackson Pollock,Jakarta cityscape,Japan,Japanese Architecture,Japanese Art,Japanese Cuisine,Japanese Mythology Art,Jazz Dance,Jazz Music,Jazz Poster Art,Jean-Honore Fragonard,Jeff Koons,Jenny Holzer,Jerusalem,Jewelry Design,Johannes Vermeer,John Baldessari,Joyful Art,Julie Mehretu,Kabuki Theater,Kara Walker,Kathak Dance,Katsushika Hokusai,Kawaii Character_Uncategorized,Kawaii Fashion_Fashion,Kehinde Wiley,Kerry James Marshall,Kiki Smith,Kinetic Art,Kinetic Sculpture,Kintsugi (Japanese Gold Repair),Kitsch Movement_Uncategorized,Knitting,Korean Art,Korean Celadon Ceramics,Korean Celadon Pottery,Korean Cuisine,Kuala Lumpur,Kyoto,Kyoto cityscape,LAIKA_Animation,Land Art,Land Art (1)_Fantasy_Nature_Sculpture_Landscape,Landscape Architecture,Landscape Design,Landscape Photography,Laser Grid_Uncategorized,Later European abstraction (1)_Uncategorized,Leonardo da Vinci,Lettrist artwork,Leviathan Variant_Architecture,Light Art,Line Dance,Lisbon cityscape,Lithography,Living Burial_Uncategorized,London,Los Angeles,Lost Vegas Variant_Architecture,Lounge Singer_Retro,Lovecraftian Horror_Horror,Low Fantasy,Lowbrow Art Variant_Surrealism_Culture,Lowbrow Art_Culture,Luau Fire Dancer_Fashion,Luchador_Uncategorized,Luxury Fashion,Lynching_Uncategorized,Lyrical abstract painting,Macabre Memento Mori_Horror_Horror & Dark_Still Life,Machinima Variant_Uncategorized,Machu Picchu,Macro Photography,Mad Scientist Machinery_Uncategorized,Madhubani Painting,Madhubani Painting (Indian Folk Art),Mage City Variant_Architecture_Fantasy_Location,Magic Realist painting,Mall Goth_Portraiture_Gothic,Mannerism,Mannerist Architecture,Maori Wood Carving,Mardi Gras_Uncategorized,Marina Abramović,Mark Bradford,Mark Grotjahn,Martin Puryear,Masked Killer_Uncategorized,Masked Stalker_Uncategorized,Maurizio Cattelan,Maximalism,Mecca,Mech City Variant_Sci-Fi_Architecture_Location,Mech City_Sci-Fi_Architecture_Location,Mech City__Location,Media Art,Medical Oddities_Uncategorized,Mediterranean Cuisine,Melancholy Art,Melodrama,Melting Skull_Uncategorized,Memento Mori_Horror_Horror & Dark,Memoir,Menacing Scarecrow_Uncategorized,Menswear Fashion,Mesoamerican Mythology Art,Mesopotamian Mythology Art,Metabolist Architecture,Metal Music,Metal Music Artwork,Metalwork,Metropolis,Mexican Art,Mexican Cuisine,Mexican Muralism,Mexican Skull Art_Uncategorized,Miami,Michelangelo,Middle Eastern Cuisine,Middle-earth,Midgard Variant_Architecture,Milky Way Galaxy,Mime,Mime City Variant_Architecture_Location,Minimalism,Minimalist Web Design,Mixed Media Art,Mixer_Animation,Modern Architecture,Modern Dance,Modernist Architecture,Mona Hatoum,Monoprinting Technique,Mosaic,Mosaic Art,Motion Design,Motion Graphics Design,Mount Everest,Mount Olympus,Movie Storyboard_Uncategorized,Mughal Miniature Painting,Mumbai,Mummy Portrait_Portraiture,Munich cityscape,Music Video Direction,Musica Variant_Architecture_Culture,Musical Films,Musical Theater,Mutated Beast_Uncategorized,My Little Pony_Uncategorized,Mystery,Mystery Literature,Mythic Fantasy Art,Nantucket,Native American Art,Native American Basketry,Native American Mythology Art,Native American Pottery,Naturalism in Literature,Nature Landscape Photography,Nature Photography,Nautical_Retro,Naïve Art (1)_Uncategorized,Nebula,Neo Pop_Pop Culture_Culture,Neo Rauch,Neo-Dada_Uncategorized,Neo-Expressionism_Uncategorized,Neo-Gothic Architecture,Neo-Noir,Neo-Pop (1)_Pop Culture_Culture,Neo-primitivism (1)_Still Life,Neoclassical Architecture,Neoclassicism,Neon Lighting_Uncategorized,Neon Racer_Sci-Fi,Neon Tokyo_Retro,Neoplasticism,Neotokyo Variant_Sci-Fi_Architecture,Neue Sachlichkeit Variant_Portraiture,Neue Wilde (1)_Uncategorized,New Caelum Variant_Architecture,New Caelum_Architecture,New Media Art_Digital Media,New Orleans,New Perpendicular art_Uncategorized,New Simplicity_Architecture,New York City,New York cityscape,Niagara Falls,Nicole Eisenman,Night Photography,Nightmare Beast_Uncategorized,Non-Fiction,Nordic Viking Art,Norse Mythology Art,North African Art,Norwegian romantic nationalism_Nature_Landscape,Nouveau Circus_Uncategorized,Nova Alexandria Variant_Architecture_Culture,Occult Ritual_Occult,Occult Sacrifice_Occult,Oil Painting,Olafur Eliasson,Ominous Fog_Uncategorized,Ominous Warning_Uncategorized,Op Art,Op Art_Uncategorized,Opera,Opera Music,Opera Music Illustration,Osaka cityscape,Outsider Art_Uncategorized,Pablo Picasso,Package Design,Pandora,Paper Cutting,Paper Mache Art,Parametric Architecture,Paris,Participatory Art,Patchwork Creature_Uncategorized,Paul Cezanne,Performance Art,Performance Sculpture,Peruvian Art,Petra,Photography,Photojournalism,Photorealism,Photorealistic painting,Physical Theater,Pinup_Retro,Pixel Art,Pizza Making,Plague Mass Grave_Uncategorized,Plein Air Painting,Plotter Art Variant_Uncategorized,Plotter Art_Uncategorized,Plus-Size Fashion,Poetry,Pointillism,Pointillism Art,Pole Dance,Polynesian Mythology Art,Polynesian Tattoo Art,Pop Art,Pop Music,Pop Music Branding,Pop Surrealism_Nature_Surrealism_Landscape_Still Life,Pop art style,Porcelain Art,Portrait Photography,Portuguese Art,Post-Impressionism,Postmodern Architecture,Pottery,Prague,Prague cityscape,Prairie Dress_Retro_Fashion,Pre-Raphaelite_Uncategorized,Preppy Fashion,Printmaking,Prismatic_Uncategorized,Projection Mapping Art,Propaganda Art_Retro,Propaganda Poster_Uncategorized,Prose Literature,Provocative_Surreal_Photography,Pseudorealism_Uncategorized,Psychedelic Concert Posters,Psychedelic Pop Art_Surrealism,Public Art Installations,Public Installations,Public Sculptures,Punk Fashion,Punk Music,Punk Poster_Uncategorized,Puppetry,Pyramids of Giza,Quahog,Quilting,Quilting Art,Quito cityscape,R&B Music,Rachel Whiteread,Radical Realism (1)_Still Life,Rangoli (Indian Floor Art),Rap Music Graphics,Raphael,Rashid Johnson,Rat Infestation_Uncategorized,Rat King_Uncategorized,Realism Art,Realism in Literature,Realistic Fiction,Reanimated Corpse_Animation,Recycled Art,Reggae Music,Reggae Music Design,Rembrandt,Remodernism Variant_Uncategorized,Remodernism_Architecture,Renaissance,Renaissance Architecture,Renaissance Art,Rene Magritte,Responsive Web Design,Richard Serra,Richard Tuttle,Rio de Janeiro,Rio de Janeiro cityscape,Robert Gober,Robotics Art,Rock Album Art,Rock Music,Rococo,Rococo Architecture,Rococo Art,Rococo Interior_Uncategorized,Roman Mosaic Art,Roman Mythology Art,Romance,Romance Literature,Romanesque Architecture,Romantic Comedy,Romantic Films,Romanticism,Romanticism Art,Romanticism in Literature,Rome,Rural Photography,Russia,Russian Art,Russian Icon Painting,Sahara Desert,Salem,Salsa Dance,Salsa Music,Salvador Dali,Samurai_Uncategorized,Sanctuary Variant_Uncategorized,Sand Sculpture,Sandro Botticelli,Sarah Sze,Satanic_Horror_Occult,Satire,Satire Literature,Scandinavian Architecture,Scandinavian Art,Scandinavian Design,Scarecrow_Horror,Scary Pumpkin_Uncategorized,Scary Stories at Campfire_Horror_Horror & Dark,Scary Stories_Horror,Sci-Fi Films,Science Fiction,Scientific Illustration_Retro,Screen Printing,Screwball Comedy,Sculpture,Self-taught Art (1)_Fantasy,Seoul,Serial Killer_Horror,Set Design for Theater,Shadow City Variant_Architecture_Occult_Gothic_Location,Shadow City_Architecture_Occult_Gothic_Location,Shadow City_Horror_Occult_Horror & Dark_Gothic_Location,Shanghai,Shangri-La Variant_Uncategorized,Shepard Fairey,Shirakawa-go,Shirin Neshat,Sideshow Poster_Retro,Silent Films,Singapore,Sinister Crone_Uncategorized,Sinister Laboratory_Horror_Occult_Still Life,Sinister Ritual_Uncategorized,Situationist International Variant_Uncategorized,Situationist International_Uncategorized,Skateboarding Fashion,Skeleton Dance_Animation,Skeleton Dance_Horror_Horror & Dark_Animation,Slavic Mythology Art,Slow Fashion,Smothering Earth_Fantasy,Social Realism painting,Sonnet,Soul Music,Sound Art,Sound Design,Sound Sculpture,South African Art,South American Textile Art,Southern Gothic_Gothic,Southwest Kachina Dolls,Spaghetti Western,Spanish Art,Spanish Cuisine,Spider Queen_Uncategorized,Sports Card_Photography_Portraiture,Sports Photography,Spring Art,Springfield,St Ives School Variant_Nature_Landscape,St Ives School_Nature_Landscape,Stained Glass Art,Stained Glass_Uncategorized,Stand-Up Comedy,Stars Hollow,Steampunk,Steampunk City Variant_Architecture_Location,Steampunk Fantasy Art,Steampunk Fashion,Steampunk Portrait_Fantasy_Portraiture,Steampunk_Fantasy_Fashion,Steamtown Variant_Architecture_Retro,Steeltown Variant_Architecture,Stockholm cityscape,Stone Sculpture,Stop Motion_Animation,Streamer Bike_Retro,Street Art,Street Art Performance,Street Art and Graffiti,Street Photography,Street Theater,Streetwear,Streetwear Fashion,Stuckism Variant_Uncategorized,Stuckism_Uncategorized,Studio Ghibli_Fantasy_Surrealism,Studio Portrait Photography,Sub Anaheim Variant_Fantasy_Location,Sub Annapolis Variant_Sculpture_Location,Sub Atlanta Variant_Uncategorized_Location,Sub Baton Rouge Variant_Culture_Location,Sub Baton Rouge_Culture_Location,Sub Baton Rouge__Location,Sub Berkeley Variant_Retro_Location,Sub Boise Variant_Uncategorized_Location,Sub Boise_Uncategorized_Location,Sub Boise__Location,Sub Bozeman Variant_Architecture_Location,Sub Carlsbad Variant_Architecture_Culture_Location,Sub Carson City Variant_Architecture_Location,Sub Casper Variant_Uncategorized_Location,Sub Cheyenne Variant_Uncategorized_Location,Sub Columbia Variant_Architecture_Culture_Location,Sub Concord Variant_Uncategorized_Location,Sub Costa Mesa Variant_Culture_Location,Sub Denver Variant_Uncategorized_Location,Sub Des Moines Variant_Architecture_Location,Sub Dover Variant_Uncategorized_Location,Sub Downey Variant_Sci-Fi_Location,Sub El Monte Variant_Sci-Fi_Location,Sub Fontana Variant_Culture_Location,Sub Frankfort Variant_Uncategorized_Location,Sub Fresno Variant_Architecture_Nature_Landscape_Location,Sub Garden Grove Variant_Architecture_Location,Sub Glendale Variant_Uncategorized_Location,Sub Indianapolis Variant_Uncategorized_Location,Sub Inglewood Variant_Sci-Fi_Pop Culture_Culture_Location,Sub Irvine Variant_Uncategorized_Location,Sub Jackson Variant_Folk Art_Location,Sub Jefferson City Variant_Architecture_Folk Art_Location,Sub Juneau Variant_Architecture_Location,Sub Lancaster Variant_Sci-Fi_Retro_Location,Sub Montgomery Variant_Uncategorized_Location,Sub Montpelier Variant_Sculpture_Location,Sub Moreno Valley Variant_Uncategorized_Location,Sub Oakland Variant_Sci-Fi_Culture_Location,Sub Ontario Variant_Uncategorized_Location,Sub Orange Variant_Retro_Location,Sub Oxnard Variant_Uncategorized_Location,Sub Oxnard_Uncategorized_Location,Sub Oxnard__Location,Sub Palmdale Variant_Sci-Fi_Location,Sub Pasadena Variant_Uncategorized_Location,Sub Pierre Variant_Uncategorized_Location,Sub Pomona Variant_Retro_Location,Sub Providence Variant_Uncategorized_Location,Sub Rancho Cucamonga Variant_Architecture_Lifestyle_Location,Sub Richmond Variant_Architecture_Location,Sub Roseville Variant_Architecture_Location,Sub Salem Variant_Sci-Fi_Culture_Location,Sub Santa Ana Variant_Sci-Fi_Culture_Location,Sub Santa Clarita Variant_Uncategorized_Location,Sub Santa Rosa Variant_Sci-Fi_Nature_Location,Sub Santa Rosa_Sci-Fi_Nature_Location,Sub Santa Rosa__Location,Sub Simi Valley Variant_Pop Culture_Culture_Retro_Location,Sub Spokane Variant_Architecture_Location,Sub Tacoma Variant_Architecture_Culture_Retro_Location,Sub Temecula Variant_Lifestyle_Location,Sub Thousand Oaks Variant_Uncategorized_Location,Sub Topeka Variant_Architecture_Folk Art_Location,Sub Torrance Variant_Sci-Fi_Location,Sub Victorville Variant_Uncategorized_Location,Sumi-e Painting,Summer Art,Summer Fashion,Surf Wood Sign_Retro,Surrealism,Surrealism Art,Surrealist Painting,Surrealist Sculpture,Sushi Making,Sustainable Architecture,Sustainable Art Variant_Uncategorized,Sustainable Art_Uncategorized,Sustainable Fashion,Swing Dance,Sydney,Symbolism Art,Synthetic Cubism,Taj Mahal,Takashi Murakami,Talavera Pottery,Tamara de Lempicka,Tango Dance,Tap Dance,Tarot Cards_Occult,Tarot_Occult,Tatooine,Tattoo Print_Retro_Tattoo Art,Tech City Variant_Architecture_Nature_Location,Techno Music Visuals,Technotopia Variant_Architecture_Nature,Temporary Art Installations,Terrarium Bottle_Still Life,Terrarium_Uncategorized,Teslapunk_Portraiture,Textile Art,Textile Design,Textile Sculpture,Thai Art,Thai Cuisine,Thomas Gainsborough,Thriller,Thriller Films,Thriller Literature,Tibetan Thangka Painting,Tiki Bar_Uncategorized,Tiki Cocktail_Uncategorized,Tiki Idol_Uncategorized,Tiki Mug_Retro,Tiki Outdoor Shower_Uncategorized,Tiki Totem_Sculpture,Titian,Toei_Retro_Animation,Tokyo,Tokyo cityscape,Torture Chamber_Uncategorized,Torture Device_Horror_Horror & Dark,Tortured Prisoner_Uncategorized,Tortured Soul_Uncategorized,Toy Design,Traditional Animation,Traditional Dance,Traditional Japanese Architecture,Traditional Pottery,Tragedy,Tragedy Literature,Tranquil Art,Transavantgarde Variant_Uncategorized,Transavantgarde_Uncategorized,Transgressive Art Variant_Uncategorized,Transgressive Art_Uncategorized,Travel Photography,Tropical Bathroom_Uncategorized,Tropical Cocktail_Uncategorized,Tropical Hotel_Uncategorized,Tropical Luau_Uncategorized,Twin Peaks,Typography Design,UPA_Comics_Animation,Ukiyo-e (Japanese Woodblock Printing),Ukiyo-e Art,Undead Gluttony_Architecture,Undead Portrait_Portraiture,Undefined_Emerging_Artist,Under Albany Variant_Architecture_Surrealism_Location,Under Bakersfield Variant_Uncategorized_Location,Under Berlin Variant_Retro_Surrealism_Location,Under Berlin_Retro_Surrealism_Location,Under Berlin__Location,Under Bismarck Variant_Uncategorized_Location,Under Charleston Variant_Architecture_Location,Under Chicago Variant_Architecture_Portraiture_Culture_Retro_Location,Under Eugene Variant_Folk Art_Location,Under Fargo Variant_Architecture_Location,Under Hartford Variant_Architecture_Location,Under Honolulu Variant_Architecture_Location,Under Istanbul Variant_Architecture_Location,Under Jackson Variant_Folk Art_Location,Under Juneau Variant_Architecture_Location,Under London Variant_Architecture_Location,Under Montreal Variant_Architecture_Location,Under Nashville Variant_Uncategorized_Location,Under Oklahoma City Variant_Architecture_Location,Under Omaha Variant_Culture_Location,Under Paris Variant_Uncategorized_Location,Under Sacramento Variant_Uncategorized_Location,Under Santa Fe Variant_Uncategorized_Location,Under St. Paul Variant_Architecture_Location,Under Tallahassee Variant_Sci-Fi_Retro_Architecture_Location,Under Trenton Variant_Uncategorized_Location,Underground Anchorage Variant_Architecture_Location,Underground Austin Variant_Uncategorized_Location,Underground Chula Vista Variant_Uncategorized_Location,Underground Columbus Variant_Retro_Location,Underground Concord Variant_Culture_Location,Underground Helena Variant_Architecture_Location,Underground Huntington Beach Variant_Architecture_Culture_Location,Underground Lansing Variant_Culture_Location,Underground Lincoln Variant_Uncategorized_Location,Underground Little Rock Variant_Uncategorized_Location,Underground Portland Variant_Sci-Fi_Location,Underground Riverside Variant_Culture_Location,Underground Rome Variant_Architecture_Location,Underground Salt Lake City Variant_Architecture_Location,Underground San Jose Variant_Uncategorized_Location,Underground Seattle Variant_Uncategorized_Location,Underground Springfield Variant_Folk Art_Location,Underground Wichita Variant_Folk Art_Location,Underwater Photography,Urban Fantasy Art,Urban Landscape Photography,Urban Photography,Urban Sculpture,User-Centered Design,Utrecht cityscape,VR Art Variant_Uncategorized,Vacuous Grimace_Uncategorized,Valhalla,Valve,Vampire_Portraiture_Horror,Vaporgram_Retro,Vaporwave City_Sci-Fi_Dystopia_Architecture_Location,Vaporwave Graphics_Retro_Surrealism_Graphic Design,Vaporwave Retro_Sci-Fi_Retro,Vaporwave Sunset_Uncategorized,Vaporwave_Architecture_Retro,Vatican City,Vector Portrait_Portraiture,Venezuelan Art,Venice,Verbatim Theater,Victorian Architecture,Victorian Fashion,Victorian Laboratory_Occult_Still Life,Video Art,Video Art_Uncategorized,Video Games Variant_Games,Video Games_Games_Culture,Video Mapping,Vienna,Vienna cityscape,Vietnamese Art,Vietnamese Cuisine,Vija Celmins,Vincent Van Gogh,Vintage Baseball_Retro_Photography,Vintage Fashion,Vintage Halloween Costume_Retro,Vintage Halloween Mask_Retro,Vintage Halloween_Retro,Vintage Robot Toy_Sci-Fi_Retro,Vintage Tattoo Flash_Retro_Tattoo Art,Vintage Tattoo Print_Retro_Tattoo Art,Vintage Travel Poster_Retro_Nature_Landscape,Virtual Art Variant_Uncategorized,Virtual Art_Sci-Fi,Virtual Reality (VR) Art,Virtual Reality Art,Visionary Art (1)_Uncategorized,Visual Effects (VFX) Design,Vogue Cover_Photography_Fashion,Volcano Lair_Uncategorized,Voodoo Altar_Occult,Voodoo Ceremony_Occult,Voodoo Doll_Retro_Occult,Voodoo Queen_Portraiture_Occult,Voodoo Shop_Occult,Voodoo_Occult,Vorticism_Uncategorized,Wallace and Gromit,Waltz Dance,War Films,Wassily Kandinsky,Water Art,Watercolor Painting,Weaving,Web Design,Wedding Fashion,Wedding Photography,Wellington cityscape,West African Art,Westeros,Wildlife Photography,William Kentridge,Winter Art,Winter Fashion,Wolfgang Tillmans,Womenswear Fashion,Wonderland,Wood Carving,Woodblock Art_Nature,Woodblock Print_Uncategorized,Woodblock Printing,Woodcut,Workwear Fashion,World Music,Xiamen cityscape,Xilam_Comics_Animation,Yayoi Kusama,Yellowstone National Park,Yokohama cityscape,Zion Variant_Culture,Zurich cityscape,_Uncategorized,ads-advertising_Uncategorized,ads-automotive_Uncategorized,ads-corporate_Uncategorized,ads-fashion editorial_Fashion,ads-food photography_Photography,ads-luxury_Uncategorized,ads-real estate_Photography,ads-retail_Uncategorized,artstyle-abstract expressionism_Uncategorized,artstyle-abstract_Uncategorized,artstyle-art deco_Uncategorized,artstyle-art nouveau_Nature,artstyle-constructivist_Uncategorized,artstyle-cubist_Uncategorized,artstyle-expressionist_Uncategorized,artstyle-graffiti_Architecture_Graffiti,artstyle-hyperrealism_Photography,artstyle-impressionist_Uncategorized,artstyle-pointillism_Uncategorized,artstyle-pop art_Culture,artstyle-psychedelic_Surrealism,artstyle-renaissance_Uncategorized,artstyle-steampunk_Uncategorized,artstyle-surrealist_Surrealism,artstyle-typography_Uncategorized,artstyle-watercolor_Uncategorized,carpint_Gothic,citz_Sci-Fi_Architecture,coolio_Portraiture,enhance_Uncategorized,futuristic-biomechanical cyberpunk_Sci-Fi_Dystopia,futuristic-biomechanical_Sci-Fi,futuristic-cybernetic robot_Sci-Fi,futuristic-cybernetic_Sci-Fi,futuristic-cyberpunk cityscape_Sci-Fi_Architecture,futuristic-futuristic_Sci-Fi,futuristic-retro cyberpunk_Sci-Fi_Retro,futuristic-retro futurism_Sci-Fi_Retro,futuristic-sci-fi_Sci-Fi,futuristic-vaporwave_Sci-Fi_Retro,game-bubble bobble_Fantasy,game-cyberpunk game_Sci-Fi_Dystopia_Games_Digital Media,game-fighting game_Games,game-gta_Uncategorized,game-mario_Fantasy_Comics,game-minecraft_Still Life,game-pokemon_Fantasy,game-retro arcade_Retro_Games,game-retro game_Retro,game-rpg fantasy game_Fantasy_Games,game-strategy game_Games,game-streetfighter_Uncategorized,game-zelda_Fantasy,getting there_Portraiture,girlz_Fashion_Horror_Horror & Dark_Gothic,gotit jinx_Tattoo Art,greatz_Portraiture,gsssggg_Portraiture,hoop_Portraiture,jinx_Tattoo Art,jinxed_Portraiture,kjkjkjj_Digital Media_Still Life_Comics,kool_Portraiture,misc-architectural_Uncategorized,misc-disco_Retro,misc-dreamscape_Fantasy_Surrealism,misc-dystopian_Dystopia,misc-fairy tale_Fantasy,misc-gothic_Gothic,misc-grunge_Retro,misc-horror_Horror,misc-horror_Horror_Horror & Dark,misc-kawaii_Uncategorized,misc-lovecraftian_Surrealism_Horror,misc-macabre_Gothic,misc-manga_Uncategorized,misc-metropolis_Sci-Fi_Architecture,misc-minimalist_Uncategorized,misc-monochrome_Uncategorized,misc-nautical_Uncategorized,misc-space_Sci-Fi,misc-stained glass_Uncategorized,misc-techwear fashion_Sci-Fi_Fashion_Architecture,misc-tribal_Uncategorized,misc-zentangle_Uncategorized,mkkk_Portraiture_Digital Media_Animation,papercraft-collage_Uncategorized,papercraft-flat papercut_Uncategorized,papercraft-kirigami_Uncategorized,papercraft-paper mache_Uncategorized,papercraft-paper quilling_Uncategorized,papercraft-papercut collage_Uncategorized,papercraft-papercut shadow box_Uncategorized,papercraft-stacked papercut_Uncategorized,papercraft-thick layered papercut_Uncategorized,photo-alien_Sci-Fi_Photography,photo-film noir_Photography,photo-hdr_Photography,photo-long exposure_Photography_Surrealism,photo-neon noir_Photography,photo-silhouette_Photography,photo-tilt-shift_Photography,sai-3d-model_Uncategorized,sai-analog film_Retro_Photography,sai-anime_Uncategorized,sai-cinematic_Uncategorized,sai-comic book_Uncategorized,sai-craft clay_Sculpture,sai-digital art_Digital Media,sai-fantasy art_Fantasy_Surrealism,sai-isometric_Uncategorized,sai-line art_Uncategorized,sai-lowpoly_Uncategorized,sai-neonpunk_Uncategorized,sai-origami_Uncategorized,sai-photographic_Photography,sai-pixel art_Uncategorized,sai-texture_Uncategorized,stfhgff_Photography"
+ ],
+ [
+ 155,
+ 130,
+ 0,
+ 11,
+ 1,
+ "STRING"
+ ],
+ [
+ 156,
+ 125,
+ 0,
+ 135,
+ 0,
+ "SCHEDULE"
+ ],
+ [
+ 157,
+ 135,
+ 0,
+ 126,
+ 0,
+ "STRING"
+ ],
+ [
+ 158,
+ 135,
+ 0,
+ 128,
+ 0,
+ "STRING"
+ ],
+ [
+ 159,
+ 25,
+ 0,
+ 135,
+ 1,
+ "INT"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_C5_SimpleLoadScheduledModels_Demo_v01b.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_C5_SimpleLoadScheduledModels_Demo_v01b.json
new file mode 100644
index 0000000000000000000000000000000000000000..07c5dbc518176b05a310c5b2b8daa854fe9cdfb8
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_C5_SimpleLoadScheduledModels_Demo_v01b.json
@@ -0,0 +1,658 @@
+{
+ "last_node_id": 16,
+ "last_link_id": 24,
+ "nodes": [
+ {
+ "id": 6,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 1240,
+ 680
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 20
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 8
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "1girl, period costume"
+ ]
+ },
+ {
+ "id": 7,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 1240,
+ 840
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 21
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 9
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "embedding:EasyNegative, \nnsfw"
+ ]
+ },
+ {
+ "id": 8,
+ "type": "KSampler",
+ "pos": [
+ 1580,
+ 600
+ ],
+ "size": [
+ 320,
+ 470
+ ],
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 19
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 8
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 9
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 24
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 10
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 485968570890081,
+ "randomize",
+ 20,
+ 8,
+ "euler",
+ "normal",
+ 1
+ ]
+ },
+ {
+ "id": 10,
+ "type": "VAELoader",
+ "pos": [
+ 2000,
+ 450
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 11
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "vae-ft-mse-840000-ema-pruned.safetensors"
+ ]
+ },
+ {
+ "id": 9,
+ "type": "VAEDecode",
+ "pos": [
+ 1990,
+ 600
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 10
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 11
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 12
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 11,
+ "type": "PreviewImage",
+ "pos": [
+ 2270,
+ 600
+ ],
+ "size": [
+ 210,
+ 250
+ ],
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 12
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 1,
+ "type": "CR Model List",
+ "pos": [
+ 640,
+ 240
+ ],
+ "size": {
+ "0": 460,
+ "1": 294
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model_list",
+ "type": "MODEL_LIST",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL_LIST",
+ "type": "MODEL_LIST",
+ "links": [
+ 18
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": [],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Model List"
+ },
+ "widgets_values": [
+ "SD1_5\\dalcefoV3Anime_dalcefoV3Anime.safetensors",
+ "DAL",
+ "SD1_5\\CounterfeitV25_25.safetensors",
+ "COU",
+ "SD1_5\\epicrealism_newEra.safetensors",
+ "EPI",
+ "SD1_5\\aZovyaPhotoreal_v2.safetensors",
+ "ZOV",
+ "None",
+ ""
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 4,
+ "type": "CR Current Frame",
+ "pos": [
+ 320,
+ 640
+ ],
+ "size": {
+ "0": 240,
+ "1": 80
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "link": 4,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ },
+ "slot_index": 0
+ }
+ ],
+ "outputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "links": [
+ 22
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Current Frame"
+ },
+ "widgets_values": [
+ 0,
+ "Yes"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 15,
+ "type": "CR Simple Schedule",
+ "pos": [
+ 330,
+ 330
+ ],
+ "size": {
+ "0": 250,
+ "1": 200
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": [
+ 23
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Schedule"
+ },
+ "widgets_values": [
+ "0, ZOV\n3, COU\n6, DAL\n9, EPI",
+ "Model",
+ "M1",
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 14,
+ "type": "CR Load Scheduled Models",
+ "pos": [
+ 640,
+ 600
+ ],
+ "size": {
+ "0": 460,
+ "1": 200
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model_list",
+ "type": "MODEL_LIST",
+ "link": 18
+ },
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": 23
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 22,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 19
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 20,
+ 21
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Load Scheduled Models"
+ },
+ "widgets_values": [
+ "Schedule",
+ "SD1_5\\ComfyrollAnime_v1_fp16_pruned.safetensors",
+ "M1",
+ "SD1_5\\Comfyroll_v1_fp16_pruned.safetensors",
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 16,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 1580,
+ 430
+ ],
+ "size": {
+ "0": 210,
+ "1": 110
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 24
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ 1
+ ]
+ },
+ {
+ "id": 5,
+ "type": "PrimitiveNode",
+ "pos": [
+ 50,
+ 640
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 4
+ ],
+ "slot_index": 0,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ 0,
+ "increment"
+ ]
+ }
+ ],
+ "links": [
+ [
+ 4,
+ 5,
+ 0,
+ 4,
+ 0,
+ "INT"
+ ],
+ [
+ 8,
+ 6,
+ 0,
+ 8,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 9,
+ 7,
+ 0,
+ 8,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 10,
+ 8,
+ 0,
+ 9,
+ 0,
+ "LATENT"
+ ],
+ [
+ 11,
+ 10,
+ 0,
+ 9,
+ 1,
+ "VAE"
+ ],
+ [
+ 12,
+ 9,
+ 0,
+ 11,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 18,
+ 1,
+ 0,
+ 14,
+ 0,
+ "MODEL_LIST"
+ ],
+ [
+ 19,
+ 14,
+ 0,
+ 8,
+ 0,
+ "MODEL"
+ ],
+ [
+ 20,
+ 14,
+ 1,
+ 6,
+ 0,
+ "CLIP"
+ ],
+ [
+ 21,
+ 14,
+ 1,
+ 7,
+ 0,
+ "CLIP"
+ ],
+ [
+ 22,
+ 4,
+ 0,
+ 14,
+ 2,
+ "INT"
+ ],
+ [
+ 23,
+ 15,
+ 0,
+ 14,
+ 1,
+ "SCHEDULE"
+ ],
+ [
+ 24,
+ 16,
+ 0,
+ 8,
+ 3,
+ "LATENT"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_C6_LoadScheduledModelsLoRAs_Demo_v0.1a.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_C6_LoadScheduledModelsLoRAs_Demo_v0.1a.json
new file mode 100644
index 0000000000000000000000000000000000000000..5020c834903f36e82fc68e7b8542a90d09283471
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_C6_LoadScheduledModelsLoRAs_Demo_v0.1a.json
@@ -0,0 +1,866 @@
+{
+ "last_node_id": 26,
+ "last_link_id": 53,
+ "nodes": [
+ {
+ "id": 10,
+ "type": "VAELoader",
+ "pos": [
+ 2380,
+ 460
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 11
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "vae-ft-mse-840000-ema-pruned.safetensors"
+ ]
+ },
+ {
+ "id": 16,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 1990,
+ 430
+ ],
+ "size": [
+ 320,
+ 110
+ ],
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 26
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ 1
+ ]
+ },
+ {
+ "id": 4,
+ "type": "CR Current Frame",
+ "pos": [
+ 320,
+ 660
+ ],
+ "size": {
+ "0": 240,
+ "1": 80
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "link": 4,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ },
+ "slot_index": 0
+ }
+ ],
+ "outputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "links": [
+ 20,
+ 43
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Current Frame"
+ },
+ "widgets_values": [
+ 0,
+ "Yes"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 13,
+ "type": "CR Central Schedule",
+ "pos": [
+ 60,
+ 40
+ ],
+ "size": {
+ "0": 400,
+ "1": 530
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": [
+ 18,
+ 47
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Central Schedule"
+ },
+ "widgets_values": [
+ "0, AYO\n3, COU\n6, ZOV\n9, EPI",
+ "Model",
+ "M1",
+ "0, MAY, 1.0, 1.0\n4, HIL, 1.0, 1.0\n8, LIL, 1.0, 1.0",
+ "LoRA",
+ "L1",
+ "schedule",
+ "Text",
+ "",
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 11,
+ "type": "PreviewImage",
+ "pos": [
+ 2650,
+ 620
+ ],
+ "size": {
+ "0": 210,
+ "1": 250
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 12
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 9,
+ "type": "VAEDecode",
+ "pos": [
+ 2370,
+ 620
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 10
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 11
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 12
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 17,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 1630,
+ 690
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 48
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 28
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "1girl, period costume"
+ ]
+ },
+ {
+ "id": 7,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 1640,
+ 850
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 49
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 9
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "embedding:EasyNegative, \nnsfw"
+ ]
+ },
+ {
+ "id": 14,
+ "type": "CR Load Scheduled Models",
+ "pos": [
+ 640,
+ 620
+ ],
+ "size": {
+ "0": 460,
+ "1": 170
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model_list",
+ "type": "MODEL_LIST",
+ "link": 19
+ },
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": 18
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 20,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 53
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 44
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Load Scheduled Models"
+ },
+ "widgets_values": [
+ "Schedule",
+ "SD1_5\\ComfyrollAnime_v1_fp16_pruned.safetensors",
+ "M1",
+ "SD1_5\\ComfyrollAnime_v1_fp16_pruned.safetensors",
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 8,
+ "type": "KSampler",
+ "pos": [
+ 1990,
+ 620
+ ],
+ "size": [
+ 320,
+ 470
+ ],
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 50
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 28
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 9
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 26
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 10
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 1018201769555609,
+ "fixed",
+ 20,
+ 8,
+ "euler",
+ "normal",
+ 1
+ ]
+ },
+ {
+ "id": 21,
+ "type": "CR LoRA List",
+ "pos": [
+ 1190,
+ 210
+ ],
+ "size": {
+ "0": 315,
+ "1": 342
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "lora_list",
+ "type": "lora_LIST",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LORA_LIST",
+ "type": "LORA_LIST",
+ "links": [
+ 46
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR LoRA List"
+ },
+ "widgets_values": [
+ "SD1_5\\character_pokemon_hilda_v3.safetensors",
+ "HIL",
+ 1,
+ 1,
+ "SD1_5\\character_pokemon_lillie_v5.safetensors",
+ "LIL",
+ 1,
+ 1,
+ "SD1_5\\character_pokemon_may_v6.safetensors",
+ "MAY",
+ 1,
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 23,
+ "type": "CR Load Scheduled LoRAs",
+ "pos": [
+ 1190,
+ 620
+ ],
+ "size": {
+ "0": 320,
+ "1": 260
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 53
+ },
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 44
+ },
+ {
+ "name": "lora_list",
+ "type": "LORA_LIST",
+ "link": 46
+ },
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": 47
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 43,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 50
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 48,
+ 49
+ ],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Load Scheduled LoRAs"
+ },
+ "widgets_values": [
+ "Schedule",
+ 0,
+ "L1",
+ "SD1_5\\ArknightsSuzuran_20.safetensors",
+ 1,
+ 1,
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 1,
+ "type": "CR Model List",
+ "pos": [
+ 640,
+ 280
+ ],
+ "size": {
+ "0": 460,
+ "1": 294
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model_list",
+ "type": "MODEL_LIST",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL_LIST",
+ "type": "MODEL_LIST",
+ "links": [
+ 19
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": [],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Model List"
+ },
+ "widgets_values": [
+ "SD1_5\\ayonimix_V4VAEBaked.safetensors",
+ "AYO",
+ "SD1_5\\CounterfeitV25_25.safetensors",
+ "COU",
+ "SD1_5\\Comfyroll_v1_fp16_pruned.safetensors",
+ "EPI",
+ "SD1_5\\cocotifamix_v20This.safetensors",
+ "ZOV",
+ "None",
+ ""
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 5,
+ "type": "PrimitiveNode",
+ "pos": [
+ 50,
+ 660
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 4
+ ],
+ "slot_index": 0,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ 0,
+ "increment"
+ ]
+ }
+ ],
+ "links": [
+ [
+ 4,
+ 5,
+ 0,
+ 4,
+ 0,
+ "INT"
+ ],
+ [
+ 9,
+ 7,
+ 0,
+ 8,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 10,
+ 8,
+ 0,
+ 9,
+ 0,
+ "LATENT"
+ ],
+ [
+ 11,
+ 10,
+ 0,
+ 9,
+ 1,
+ "VAE"
+ ],
+ [
+ 12,
+ 9,
+ 0,
+ 11,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 18,
+ 13,
+ 0,
+ 14,
+ 1,
+ "SCHEDULE"
+ ],
+ [
+ 19,
+ 1,
+ 0,
+ 14,
+ 0,
+ "MODEL_LIST"
+ ],
+ [
+ 20,
+ 4,
+ 0,
+ 14,
+ 2,
+ "INT"
+ ],
+ [
+ 26,
+ 16,
+ 0,
+ 8,
+ 3,
+ "LATENT"
+ ],
+ [
+ 28,
+ 17,
+ 0,
+ 8,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 43,
+ 4,
+ 0,
+ 23,
+ 4,
+ "INT"
+ ],
+ [
+ 44,
+ 14,
+ 1,
+ 23,
+ 1,
+ "CLIP"
+ ],
+ [
+ 46,
+ 21,
+ 0,
+ 23,
+ 2,
+ "LORA_LIST"
+ ],
+ [
+ 47,
+ 13,
+ 0,
+ 23,
+ 3,
+ "SCHEDULE"
+ ],
+ [
+ 48,
+ 23,
+ 1,
+ 17,
+ 0,
+ "CLIP"
+ ],
+ [
+ 49,
+ 23,
+ 1,
+ 7,
+ 0,
+ "CLIP"
+ ],
+ [
+ 50,
+ 23,
+ 0,
+ 8,
+ 0,
+ "MODEL"
+ ],
+ [
+ 53,
+ 14,
+ 0,
+ 23,
+ 0,
+ "MODEL"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_D1_CyclerNodes_Demo_v01b.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_D1_CyclerNodes_Demo_v01b.json
new file mode 100644
index 0000000000000000000000000000000000000000..17ff2d7aca664328200b991d130427be96f80654
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_D1_CyclerNodes_Demo_v01b.json
@@ -0,0 +1,1287 @@
+{
+ "last_node_id": 56,
+ "last_link_id": 56,
+ "nodes": [
+ {
+ "id": 15,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 1480,
+ 410
+ ],
+ "size": [
+ 320,
+ 110
+ ],
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "link": 11,
+ "widget": {
+ "name": "width",
+ "config": [
+ "INT",
+ {
+ "default": 512,
+ "min": 64,
+ "max": 8192,
+ "step": 8
+ }
+ ]
+ }
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "link": 12,
+ "widget": {
+ "name": "height",
+ "config": [
+ "INT",
+ {
+ "default": 512,
+ "min": 64,
+ "max": 8192,
+ "step": 8
+ }
+ ]
+ }
+ },
+ {
+ "name": "batch_size",
+ "type": "INT",
+ "link": 56,
+ "widget": {
+ "name": "batch_size",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": 1,
+ "max": 64
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 13
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ 1
+ ]
+ },
+ {
+ "id": 13,
+ "type": "KSampler",
+ "pos": [
+ 1480,
+ 580
+ ],
+ "size": [
+ 320,
+ 470
+ ],
+ "flags": {},
+ "order": 19,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 32
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 8
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 9
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 13
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 14
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 305779246565633,
+ "randomize",
+ 20,
+ 8,
+ "euler",
+ "normal",
+ 1
+ ]
+ },
+ {
+ "id": 11,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 1120,
+ 660
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 30
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 8
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "1girl, period costume"
+ ]
+ },
+ {
+ "id": 12,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 1120,
+ 820
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 18,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 31
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 9
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "embedding:EasyNegative, \nnsfw"
+ ]
+ },
+ {
+ "id": 16,
+ "type": "VAEDecode",
+ "pos": [
+ 1910,
+ 580
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 20,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 14
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 15
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 25
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 17,
+ "type": "VAELoader",
+ "pos": [
+ 1870,
+ 730
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 15
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "vae-ft-mse-840000-ema-pruned.safetensors"
+ ]
+ },
+ {
+ "id": 35,
+ "type": "CR LoRA List",
+ "pos": [
+ 630,
+ 170
+ ],
+ "size": {
+ "0": 300,
+ "1": 342
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "lora_list",
+ "type": "lora_LIST",
+ "link": 39
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LORA_LIST",
+ "type": "LORA_LIST",
+ "links": [
+ 38
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": [],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR LoRA List"
+ },
+ "widgets_values": [
+ "SD1_5\\add_detail.safetensors",
+ "ADD",
+ 1,
+ 1,
+ "SD1_5\\Cyberpunk-000010.safetensors",
+ "CYB",
+ 1,
+ 1,
+ "None",
+ "",
+ 1,
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 28,
+ "type": "CR Cycle LoRAs",
+ "pos": [
+ 630,
+ 580
+ ],
+ "size": {
+ "0": 310,
+ "1": 190
+ },
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 48
+ },
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 49
+ },
+ {
+ "name": "lora_list",
+ "type": "LORA_LIST",
+ "link": 38
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 54,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 32
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 30,
+ 31
+ ],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Cycle LoRAs"
+ },
+ "widgets_values": [
+ "Sequential",
+ 3,
+ 1,
+ "Sequential"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 48,
+ "type": "CR Cycle Models",
+ "pos": [
+ 200,
+ 580
+ ],
+ "size": {
+ "0": 320,
+ "1": 190
+ },
+ "flags": {},
+ "order": 15,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 45
+ },
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 46
+ },
+ {
+ "name": "model_list",
+ "type": "MODEL_LIST",
+ "link": 47
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 53,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 48
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 49
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Cycle Models"
+ },
+ "widgets_values": [
+ "Sequential",
+ 2,
+ 2,
+ 0
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 47,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ -230,
+ 620
+ ],
+ "size": {
+ "0": 315,
+ "1": 98
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 45
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 46
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "SD1_5\\ayonimix_V4VAEBaked.safetensors"
+ ]
+ },
+ {
+ "id": 50,
+ "type": "Note",
+ "pos": [
+ 370,
+ 840
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The cycler nodes process each model or LoRA in the lists in sequence based on the keyframe interval and number of loops."
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 52,
+ "type": "Note",
+ "pos": [
+ 600,
+ 840
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Aiases are used by scheduler nodes, they are not used by cycler nodes."
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 5,
+ "type": "CR Model List",
+ "pos": [
+ 210,
+ 220
+ ],
+ "size": {
+ "0": 315,
+ "1": 294
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model_list",
+ "type": "MODEL_LIST",
+ "link": 35
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL_LIST",
+ "type": "MODEL_LIST",
+ "links": [
+ 47
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": [],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Model List"
+ },
+ "widgets_values": [
+ "SD1_5\\dalcefoV3Anime_dalcefoV3Anime.safetensors",
+ "DAL",
+ "None",
+ "",
+ "SD1_5\\epicrealism_newEra.safetensors",
+ "EPI",
+ "None",
+ "",
+ "None",
+ ""
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 49,
+ "type": "Note",
+ "pos": [
+ -780,
+ 370
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "To run this workflow, first press Reset in the Animation Builder and then press the Queue button, Do not use queue prompt in the ComfyUI menu."
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 14,
+ "type": "CR SD1.5 Aspect Ratio",
+ "pos": [
+ 1480,
+ 100
+ ],
+ "size": {
+ "0": 315,
+ "1": 238
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "links": [
+ 11
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "links": [
+ 12
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "upscale_factor",
+ "type": "FLOAT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "batch_size",
+ "type": "INT",
+ "links": [
+ 56
+ ],
+ "shape": 3,
+ "slot_index": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR SD1.5 Aspect Ratio"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ "2:3 portrait 512x768",
+ "Off",
+ 1,
+ 1
+ ]
+ },
+ {
+ "id": 54,
+ "type": "CR Current Frame",
+ "pos": [
+ -200,
+ 350
+ ],
+ "size": {
+ "0": 240,
+ "1": 80
+ },
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "link": 52,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "links": [
+ 53,
+ 54,
+ 55
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Current Frame"
+ },
+ "widgets_values": [
+ 1,
+ "Yes"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 55,
+ "type": "Note",
+ "pos": [
+ -200,
+ 190
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The CR Current Frame node prints the current frame index to console so that you can see which frame is currently being processed"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 56,
+ "type": "Note",
+ "pos": [
+ -190,
+ 70
+ ],
+ "size": {
+ "0": 210,
+ "1": 70
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Frames are processed in sequence starting from frame index 0"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 26,
+ "type": "Save Image Sequence (mtb)",
+ "pos": [
+ 2280,
+ 330
+ ],
+ "size": {
+ "0": 380,
+ "1": 290
+ },
+ "flags": {},
+ "order": 21,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 25,
+ "slot_index": 0
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 55,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999999
+ }
+ ]
+ },
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Save Image Sequence (mtb)"
+ },
+ "widgets_values": [
+ "F:\\ComfyUI\\ComfyUI_windows_portable\\ComfyUI\\output\\Test\\",
+ 5
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 29,
+ "type": "CR Model List",
+ "pos": [
+ 210,
+ -120
+ ],
+ "size": {
+ "0": 315,
+ "1": 294
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model_list",
+ "type": "MODEL_LIST",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL_LIST",
+ "type": "MODEL_LIST",
+ "links": [
+ 35
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": [],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Model List"
+ },
+ "widgets_values": [
+ "SD1_5\\aZovyaPhotoreal_v2.safetensors",
+ "ZOV",
+ "SD1_5\\CounterfeitV25_25.safetensors",
+ "COU",
+ "None",
+ "",
+ "None",
+ "",
+ "None",
+ ""
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 39,
+ "type": "CR LoRA List",
+ "pos": [
+ 630,
+ -210
+ ],
+ "size": {
+ "0": 290,
+ "1": 342
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "lora_list",
+ "type": "lora_LIST",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LORA_LIST",
+ "type": "LORA_LIST",
+ "links": [
+ 39
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": [],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR LoRA List"
+ },
+ "widgets_values": [
+ "None",
+ "",
+ 1,
+ 1,
+ "SD1_5\\ArknightsSuzuran_20.safetensors",
+ "SUZ",
+ 1,
+ 1,
+ "None",
+ "",
+ 1,
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 24,
+ "type": "Animation Builder (mtb)",
+ "pos": [
+ -530,
+ 350
+ ],
+ "size": {
+ "0": 210,
+ "1": 320
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "frame",
+ "type": "INT",
+ "links": [
+ 52
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "0-1 (scaled)",
+ "type": "FLOAT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "count",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "loop_ended",
+ "type": "BOOLEAN",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Animation Builder (mtb)"
+ },
+ "widgets_values": [
+ 12,
+ 1,
+ 1,
+ 12,
+ 1,
+ "frame: 0 / 11",
+ "Done 😎!",
+ "reset",
+ "queue"
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ }
+ ],
+ "links": [
+ [
+ 8,
+ 11,
+ 0,
+ 13,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 9,
+ 12,
+ 0,
+ 13,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 11,
+ 14,
+ 0,
+ 15,
+ 0,
+ "INT"
+ ],
+ [
+ 12,
+ 14,
+ 1,
+ 15,
+ 1,
+ "INT"
+ ],
+ [
+ 13,
+ 15,
+ 0,
+ 13,
+ 3,
+ "LATENT"
+ ],
+ [
+ 14,
+ 13,
+ 0,
+ 16,
+ 0,
+ "LATENT"
+ ],
+ [
+ 15,
+ 17,
+ 0,
+ 16,
+ 1,
+ "VAE"
+ ],
+ [
+ 25,
+ 16,
+ 0,
+ 26,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 30,
+ 28,
+ 1,
+ 11,
+ 0,
+ "CLIP"
+ ],
+ [
+ 31,
+ 28,
+ 1,
+ 12,
+ 0,
+ "CLIP"
+ ],
+ [
+ 32,
+ 28,
+ 0,
+ 13,
+ 0,
+ "MODEL"
+ ],
+ [
+ 35,
+ 29,
+ 0,
+ 5,
+ 0,
+ "MODEL_LIST"
+ ],
+ [
+ 38,
+ 35,
+ 0,
+ 28,
+ 2,
+ "LORA_LIST"
+ ],
+ [
+ 39,
+ 39,
+ 0,
+ 35,
+ 0,
+ "lora_LIST"
+ ],
+ [
+ 45,
+ 47,
+ 0,
+ 48,
+ 0,
+ "MODEL"
+ ],
+ [
+ 46,
+ 47,
+ 1,
+ 48,
+ 1,
+ "CLIP"
+ ],
+ [
+ 47,
+ 5,
+ 0,
+ 48,
+ 2,
+ "MODEL_LIST"
+ ],
+ [
+ 48,
+ 48,
+ 0,
+ 28,
+ 0,
+ "MODEL"
+ ],
+ [
+ 49,
+ 48,
+ 1,
+ 28,
+ 1,
+ "CLIP"
+ ],
+ [
+ 52,
+ 24,
+ 0,
+ 54,
+ 0,
+ "INT"
+ ],
+ [
+ 53,
+ 54,
+ 0,
+ 48,
+ 3,
+ "INT"
+ ],
+ [
+ 54,
+ 54,
+ 0,
+ 28,
+ 3,
+ "INT"
+ ],
+ [
+ 55,
+ 54,
+ 0,
+ 26,
+ 1,
+ "INT"
+ ],
+ [
+ 56,
+ 14,
+ 3,
+ 15,
+ 2,
+ "INT"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_D2_TextCycler_Demo_v01.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_D2_TextCycler_Demo_v01.json
new file mode 100644
index 0000000000000000000000000000000000000000..29e9205029c4a3f616948c4fb1c1c2322c07a3fc
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_D2_TextCycler_Demo_v01.json
@@ -0,0 +1,1459 @@
+{
+ "last_node_id": 133,
+ "last_link_id": 174,
+ "nodes": [
+ {
+ "id": 52,
+ "type": "Note",
+ "pos": [
+ -189.97908726170903,
+ 139.1117772231442
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "To run this workflow, first press Reset in the Animation Builder and then press the Queue button, Do not use queue prompt in the ComfyUI menu."
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 47,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ 600.0209127382922,
+ 329.1117772231446
+ ],
+ "size": {
+ "0": 315,
+ "1": 98
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 78
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 83,
+ 84
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "title": "Load Initial Checkpoint",
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "SD1_5\\dreamshaper_7.safetensors"
+ ]
+ },
+ {
+ "id": 16,
+ "type": "VAEDecode",
+ "pos": [
+ 1960.020912738287,
+ 229.11177722314451
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 24,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 85
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 15
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 25
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 17,
+ "type": "VAELoader",
+ "pos": [
+ 1910.0209127382873,
+ 349.11177722314466
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 15
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "vae-ft-mse-840000-ema-pruned.safetensors"
+ ]
+ },
+ {
+ "id": 116,
+ "type": "CR SD1.5 Aspect Ratio",
+ "pos": [
+ 780.0209127382919,
+ -150.88822277685603
+ ],
+ "size": {
+ "0": 320,
+ "1": 240
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "links": [
+ 145
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "links": [
+ 146
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "upscale_factor",
+ "type": "FLOAT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "batch_size",
+ "type": "INT",
+ "links": [
+ 147
+ ],
+ "shape": 3,
+ "slot_index": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR SD1.5 Aspect Ratio"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ "2:3 portrait 512x768",
+ "Off",
+ 1,
+ 1
+ ]
+ },
+ {
+ "id": 115,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 1140.0209127382889,
+ -140.88822277685603
+ ],
+ "size": {
+ "0": 210,
+ "1": 90
+ },
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "link": 145,
+ "widget": {
+ "name": "width",
+ "config": [
+ "INT",
+ {
+ "default": 512,
+ "min": 64,
+ "max": 8192,
+ "step": 8
+ }
+ ]
+ }
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "link": 146,
+ "widget": {
+ "name": "height",
+ "config": [
+ "INT",
+ {
+ "default": 512,
+ "min": 64,
+ "max": 8192,
+ "step": 8
+ }
+ ]
+ }
+ },
+ {
+ "name": "batch_size",
+ "type": "INT",
+ "link": 147,
+ "widget": {
+ "name": "batch_size",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": 1,
+ "max": 64
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 144
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ 1
+ ]
+ },
+ {
+ "id": 26,
+ "type": "Save Image Sequence (mtb)",
+ "pos": [
+ 2330.0209127382855,
+ 109.111777223144
+ ],
+ "size": {
+ "0": 380,
+ "1": 290
+ },
+ "flags": {},
+ "order": 25,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 25,
+ "slot_index": 0
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 168,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999999
+ }
+ ]
+ },
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Save Image Sequence (mtb)"
+ },
+ "widgets_values": [
+ "F:\\ComfyUI\\ComfyUI_windows_portable\\ComfyUI\\output\\Test\\",
+ 5
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 117,
+ "type": "Reroute",
+ "pos": [
+ -199.58456613570323,
+ 782.2872045326562
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 167
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 150,
+ 169,
+ 170
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 124,
+ "type": "CR Cycle Text Simple",
+ "pos": [
+ 53.128190699999955,
+ 1478.0512829
+ ],
+ "size": {
+ "0": 310,
+ "1": 250
+ },
+ "flags": {},
+ "order": 18,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text_list_simple",
+ "type": "TEXT_LIST_SIMPLE",
+ "link": null
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 169,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 160
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Cycle Text Simple"
+ },
+ "widgets_values": [
+ "Sequential",
+ 1,
+ 1,
+ "rainbow",
+ "castle",
+ "tropical island",
+ "mountain covered in snow",
+ "",
+ ""
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 111,
+ "type": "CR Prompt Text",
+ "pos": [
+ 713.1281906999992,
+ 968.0512829
+ ],
+ "size": {
+ "0": 320,
+ "1": 90
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "prompt",
+ "type": "STRING",
+ "links": [
+ 137
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Prompt Text"
+ },
+ "widgets_values": [
+ "in background"
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 110,
+ "type": "CR Prompt Text",
+ "pos": [
+ 713.1281906999992,
+ 838.0512829
+ ],
+ "size": {
+ "0": 320,
+ "1": 80
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "prompt",
+ "type": "STRING",
+ "links": [
+ 140
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Prompt Text"
+ },
+ "widgets_values": [
+ "1girl with "
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 93,
+ "type": "CR Text List Simple",
+ "pos": [
+ 53.128190699999955,
+ 1158.0512829
+ ],
+ "size": {
+ "0": 310,
+ "1": 154
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text_list_simple",
+ "type": "TEXT_LIST_SIMPLE",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "TEXT_LIST_SIMPLE",
+ "type": "TEXT_LIST_SIMPLE",
+ "links": [
+ 113
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Text List Simple"
+ },
+ "widgets_values": [
+ "tropical island",
+ "mountain covered in snow",
+ "",
+ "",
+ ""
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 91,
+ "type": "CR Cycle Text Simple",
+ "pos": [
+ 433.1281907000002,
+ 1158.0512829
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text_list_simple",
+ "type": "TEXT_LIST_SIMPLE",
+ "link": 113
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 150,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 172
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Cycle Text Simple"
+ },
+ "widgets_values": [
+ "Sequential",
+ 3,
+ 2,
+ "rainbow",
+ "castle",
+ "house",
+ "village",
+ "mine",
+ "shop"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 125,
+ "type": "CR Cycle Text Simple",
+ "pos": [
+ 433.1281907000002,
+ 1478.0512829
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 19,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text_list_simple",
+ "type": "TEXT_LIST_SIMPLE",
+ "link": null
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 170,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ },
+ {
+ "name": "text_1",
+ "type": "STRING",
+ "link": 160,
+ "widget": {
+ "name": "text_1",
+ "config": [
+ "STRING",
+ {
+ "multiline": false,
+ "default": ""
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 173
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Cycle Text Simple"
+ },
+ "widgets_values": [
+ "Sequential",
+ 3,
+ 2,
+ "rainbow",
+ "castle",
+ "house",
+ "village",
+ "mine",
+ "shop"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 130,
+ "type": "CR Text Input Switch",
+ "pos": [
+ 833.1281906999992,
+ 1158.0512829
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 20,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text1",
+ "type": "STRING",
+ "link": 172
+ },
+ {
+ "name": "text2",
+ "type": "STRING",
+ "link": 173
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 174
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Text Input Switch"
+ },
+ "widgets_values": [
+ 1
+ ]
+ },
+ {
+ "id": 24,
+ "type": "Animation Builder (mtb)",
+ "pos": [
+ 50.020912738290995,
+ 139.1117772231442
+ ],
+ "size": {
+ "0": 210,
+ "1": 320
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "frame",
+ "type": "INT",
+ "links": [
+ 166
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "0-1 (scaled)",
+ "type": "FLOAT",
+ "links": null,
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "count",
+ "type": "INT",
+ "links": [],
+ "shape": 3,
+ "slot_index": 2
+ },
+ {
+ "name": "loop_ended",
+ "type": "BOOLEAN",
+ "links": null,
+ "shape": 3,
+ "slot_index": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Animation Builder (mtb)"
+ },
+ "widgets_values": [
+ 12,
+ 1,
+ 1,
+ 9,
+ 1,
+ "frame: 0 / 8",
+ "Done 😎!",
+ "reset",
+ "queue"
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 127,
+ "type": "CR Current Frame",
+ "pos": [
+ 340.0209127382915,
+ 139.1117772231442
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 15,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "link": 166,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "links": [
+ 167,
+ 168
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "max_frames",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Current Frame"
+ },
+ "widgets_values": [
+ 1,
+ 0
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 131,
+ "type": "Note",
+ "pos": [
+ 833.1281906999992,
+ 1288.0512829
+ ],
+ "size": {
+ "0": 210,
+ "1": 70
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Switch betten the two scenarios"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 56,
+ "type": "Note",
+ "pos": [
+ 370.0209127382916,
+ -40.88822277685599
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Frames are processed in sequence. The CR Current Frame node prints the current frame index to console so that you can see which frame is currently being processed"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 133,
+ "type": "Note",
+ "pos": [
+ 2330.0209127382855,
+ -70.88822277685595
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Use the GIF Writer workflow to compile the output images into a GIF"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 75,
+ "type": "KSampler",
+ "pos": [
+ 1470.0209127382889,
+ 149.1117772231443
+ ],
+ "size": {
+ "0": 320,
+ "1": 470
+ },
+ "flags": {},
+ "order": 23,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 78
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 81
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 82
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 144
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 85
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 927131295014992,
+ "fixed",
+ 20,
+ 10,
+ "dpmpp_3m_sde_gpu",
+ "karras",
+ 0.7
+ ]
+ },
+ {
+ "id": 109,
+ "type": "Text Concatenate",
+ "pos": [
+ 1113.1281907000011,
+ 898.0512829
+ ],
+ "size": {
+ "0": 315,
+ "1": 118
+ },
+ "flags": {},
+ "order": 21,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text_a",
+ "type": "STRING",
+ "link": 140
+ },
+ {
+ "name": "text_b",
+ "type": "STRING",
+ "link": 174
+ },
+ {
+ "name": "text_c",
+ "type": "STRING",
+ "link": 137
+ },
+ {
+ "name": "text_d",
+ "type": "STRING",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 130
+ ],
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Text Concatenate"
+ },
+ "widgets_values": [
+ "false"
+ ]
+ },
+ {
+ "id": 76,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 1130,
+ 330
+ ],
+ "size": {
+ "0": 230,
+ "1": 90
+ },
+ "flags": {},
+ "order": 22,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 84
+ },
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 130,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "multiline": true
+ }
+ ]
+ },
+ "slot_index": 1
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 81
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "kaleidoscope, colorful, vivid, crystals, centered, radial symmetry"
+ ]
+ },
+ {
+ "id": 77,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 1120,
+ 480
+ ],
+ "size": {
+ "0": 240,
+ "1": 76
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 83
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 82
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "embedding:EasyNegative,\nnsfw"
+ ]
+ },
+ {
+ "id": 129,
+ "type": "Note",
+ "pos": [
+ -210,
+ 1160
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The text list can be extended with additional Text List nodes"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 128,
+ "type": "Note",
+ "pos": [
+ -220,
+ 1480
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Text cyclers can be chained together to create complex sequences"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 132,
+ "type": "Note",
+ "pos": [
+ 420,
+ 890
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "In the demo, the cyclers are being use to generate variable prompts"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ }
+ ],
+ "links": [
+ [
+ 15,
+ 17,
+ 0,
+ 16,
+ 1,
+ "VAE"
+ ],
+ [
+ 25,
+ 16,
+ 0,
+ 26,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 78,
+ 47,
+ 0,
+ 75,
+ 0,
+ "MODEL"
+ ],
+ [
+ 81,
+ 76,
+ 0,
+ 75,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 82,
+ 77,
+ 0,
+ 75,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 83,
+ 47,
+ 1,
+ 77,
+ 0,
+ "CLIP"
+ ],
+ [
+ 84,
+ 47,
+ 1,
+ 76,
+ 0,
+ "CLIP"
+ ],
+ [
+ 85,
+ 75,
+ 0,
+ 16,
+ 0,
+ "LATENT"
+ ],
+ [
+ 113,
+ 93,
+ 0,
+ 91,
+ 0,
+ "TEXT_LIST_SIMPLE"
+ ],
+ [
+ 130,
+ 109,
+ 0,
+ 76,
+ 1,
+ "STRING"
+ ],
+ [
+ 137,
+ 111,
+ 0,
+ 109,
+ 2,
+ "STRING"
+ ],
+ [
+ 140,
+ 110,
+ 0,
+ 109,
+ 0,
+ "STRING"
+ ],
+ [
+ 144,
+ 115,
+ 0,
+ 75,
+ 3,
+ "LATENT"
+ ],
+ [
+ 145,
+ 116,
+ 0,
+ 115,
+ 0,
+ "INT"
+ ],
+ [
+ 146,
+ 116,
+ 1,
+ 115,
+ 1,
+ "INT"
+ ],
+ [
+ 147,
+ 116,
+ 3,
+ 115,
+ 2,
+ "INT"
+ ],
+ [
+ 150,
+ 117,
+ 0,
+ 91,
+ 1,
+ "INT"
+ ],
+ [
+ 160,
+ 124,
+ 0,
+ 125,
+ 2,
+ "STRING"
+ ],
+ [
+ 166,
+ 24,
+ 0,
+ 127,
+ 0,
+ "INT"
+ ],
+ [
+ 167,
+ 127,
+ 0,
+ 117,
+ 0,
+ "*"
+ ],
+ [
+ 168,
+ 127,
+ 0,
+ 26,
+ 1,
+ "INT"
+ ],
+ [
+ 169,
+ 117,
+ 0,
+ 124,
+ 1,
+ "INT"
+ ],
+ [
+ 170,
+ 117,
+ 0,
+ 125,
+ 1,
+ "INT"
+ ],
+ [
+ 172,
+ 91,
+ 0,
+ 130,
+ 0,
+ "STRING"
+ ],
+ [
+ 173,
+ 125,
+ 0,
+ 130,
+ 1,
+ "STRING"
+ ],
+ [
+ 174,
+ 130,
+ 0,
+ 109,
+ 1,
+ "STRING"
+ ]
+ ],
+ "groups": [
+ {
+ "title": "Cycle Text",
+ "bounding": [
+ -253,
+ 707,
+ 1763,
+ 1077
+ ],
+ "color": "#3f789e",
+ "locked": false
+ },
+ {
+ "title": "Simple Animation Flow (for SD1.5)",
+ "bounding": [
+ -257,
+ -263,
+ 3059,
+ 933
+ ],
+ "color": "#3f789e",
+ "locked": false
+ }
+ ],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_D3_ImageCycler_Demo_v01.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_D3_ImageCycler_Demo_v01.json
new file mode 100644
index 0000000000000000000000000000000000000000..8ce69def1965b2d09216fb6c14a3706549426ab2
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_D3_ImageCycler_Demo_v01.json
@@ -0,0 +1,569 @@
+{
+ "last_node_id": 30,
+ "last_link_id": 55,
+ "nodes": [
+ {
+ "id": 2,
+ "type": "CR Cycle Images Simple",
+ "pos": [
+ 600,
+ 590
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image_1",
+ "type": "IMAGE",
+ "link": null
+ },
+ {
+ "name": "image_2",
+ "type": "IMAGE",
+ "link": null
+ },
+ {
+ "name": "image_3",
+ "type": "IMAGE",
+ "link": null
+ },
+ {
+ "name": "image_4",
+ "type": "IMAGE",
+ "link": null
+ },
+ {
+ "name": "image_5",
+ "type": "IMAGE",
+ "link": null
+ },
+ {
+ "name": "image_list_simple",
+ "type": "IMAGE_LIST_SIMPLE",
+ "link": 34
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 48,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 45
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Cycle Images Simple"
+ },
+ "widgets_values": [
+ "Sequential",
+ 1,
+ 9,
+ ""
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 1,
+ "type": "PreviewImage",
+ "pos": [
+ 1020,
+ 590
+ ],
+ "size": {
+ "0": 210,
+ "1": 250
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 45
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 17,
+ "type": "CR Image List Simple",
+ "pos": [
+ 220,
+ 600
+ ],
+ "size": {
+ "0": 300,
+ "1": 130
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image_1",
+ "type": "IMAGE",
+ "link": 46
+ },
+ {
+ "name": "image_2",
+ "type": "IMAGE",
+ "link": 52
+ },
+ {
+ "name": "image_3",
+ "type": "IMAGE",
+ "link": 54
+ },
+ {
+ "name": "image_4",
+ "type": "IMAGE",
+ "link": 55
+ },
+ {
+ "name": "image_5",
+ "type": "IMAGE",
+ "link": null
+ },
+ {
+ "name": "image_list_simple",
+ "type": "IMAGE_LIST_SIMPLE",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE_LIST_SIMPLE",
+ "type": "IMAGE_LIST_SIMPLE",
+ "links": [
+ 34
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Image List Simple"
+ },
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 25,
+ "type": "Note",
+ "pos": [
+ -170,
+ 240
+ ],
+ "size": [
+ 210,
+ 90
+ ],
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Reset to 0 after each test run\n\nSet the btach count in Queue Prompt to the number of frames you want to process "
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 30,
+ "type": "Note",
+ "pos": [
+ 1270,
+ 590
+ ],
+ "size": {
+ "0": 210,
+ "1": 90
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The images will cycle in the preview"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 26,
+ "type": "LoadImage",
+ "pos": [
+ -50,
+ 580
+ ],
+ "size": [
+ 210,
+ 310
+ ],
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 52
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "SDXL10__00008_ (2).png",
+ "image"
+ ]
+ },
+ {
+ "id": 28,
+ "type": "LoadImage",
+ "pos": [
+ -290,
+ 950
+ ],
+ "size": [
+ 210,
+ 310
+ ],
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 54
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "SDXL10__00029_.png",
+ "image"
+ ]
+ },
+ {
+ "id": 29,
+ "type": "LoadImage",
+ "pos": [
+ -50,
+ 950
+ ],
+ "size": [
+ 210,
+ 310
+ ],
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 55
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "SDXL10__00014_.png",
+ "image"
+ ]
+ },
+ {
+ "id": 22,
+ "type": "LoadImage",
+ "pos": [
+ -290,
+ 580
+ ],
+ "size": [
+ 210,
+ 310
+ ],
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 46
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "SDXL10__00017_ (2) (3).png",
+ "image"
+ ]
+ },
+ {
+ "id": 27,
+ "type": "Note",
+ "pos": [
+ -540,
+ 590
+ ],
+ "size": {
+ "0": 210,
+ "1": 90
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Drag some images into these Load Image Nodes"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 23,
+ "type": "CR Current Frame",
+ "pos": [
+ 170,
+ 400
+ ],
+ "size": [
+ 320,
+ 80
+ ],
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "link": 51,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "links": [
+ 48
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Current Frame"
+ },
+ "widgets_values": [
+ 0,
+ "Yes"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 24,
+ "type": "PrimitiveNode",
+ "pos": [
+ -170,
+ 400
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 51
+ ],
+ "slot_index": 0,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ 0,
+ "increment"
+ ]
+ }
+ ],
+ "links": [
+ [
+ 34,
+ 17,
+ 0,
+ 2,
+ 5,
+ "IMAGE_LIST_SIMPLE"
+ ],
+ [
+ 45,
+ 2,
+ 0,
+ 1,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 46,
+ 22,
+ 0,
+ 17,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 48,
+ 23,
+ 0,
+ 2,
+ 6,
+ "INT"
+ ],
+ [
+ 51,
+ 24,
+ 0,
+ 23,
+ 0,
+ "INT"
+ ],
+ [
+ 52,
+ 26,
+ 0,
+ 17,
+ 1,
+ "IMAGE"
+ ],
+ [
+ 54,
+ 28,
+ 0,
+ 17,
+ 2,
+ "IMAGE"
+ ],
+ [
+ 55,
+ 29,
+ 0,
+ 17,
+ 3,
+ "IMAGE"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_E1_GradientNodes_Demo_v01a.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_E1_GradientNodes_Demo_v01a.json
new file mode 100644
index 0000000000000000000000000000000000000000..d57cc9469ebdbbccc88921e722306207da07f35d
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_E1_GradientNodes_Demo_v01a.json
@@ -0,0 +1,965 @@
+{
+ "last_node_id": 56,
+ "last_link_id": 60,
+ "nodes": [
+ {
+ "id": 15,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 1480,
+ 410
+ ],
+ "size": {
+ "0": 320,
+ "1": 110
+ },
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "link": 11,
+ "widget": {
+ "name": "width",
+ "config": [
+ "INT",
+ {
+ "default": 512,
+ "min": 64,
+ "max": 8192,
+ "step": 8
+ }
+ ]
+ }
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "link": 12,
+ "widget": {
+ "name": "height",
+ "config": [
+ "INT",
+ {
+ "default": 512,
+ "min": 64,
+ "max": 8192,
+ "step": 8
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 13
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ 1
+ ]
+ },
+ {
+ "id": 11,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 1120,
+ 660
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 54
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 8
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "1girl, period costume"
+ ]
+ },
+ {
+ "id": 12,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 1120,
+ 820
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 55
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 9
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "embedding:EasyNegative, "
+ ]
+ },
+ {
+ "id": 16,
+ "type": "VAEDecode",
+ "pos": [
+ 1910,
+ 580
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 14
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 15
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 25
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 13,
+ "type": "KSampler",
+ "pos": [
+ 1480,
+ 580
+ ],
+ "size": {
+ "0": 320,
+ "1": 470
+ },
+ "flags": {},
+ "order": 15,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 53
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 8
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 9
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 13
+ },
+ {
+ "name": "cfg",
+ "type": "FLOAT",
+ "link": 57,
+ "widget": {
+ "name": "cfg",
+ "config": [
+ "FLOAT",
+ {
+ "default": 8,
+ "min": 0,
+ "max": 100
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 14
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 32603574575332,
+ "fixed",
+ 20,
+ 8,
+ "euler",
+ "normal",
+ 1
+ ]
+ },
+ {
+ "id": 14,
+ "type": "CR SD1.5 Aspect Ratio",
+ "pos": [
+ 1480,
+ 90
+ ],
+ "size": {
+ "0": 320,
+ "1": 240
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "link": 58,
+ "widget": {
+ "name": "width",
+ "config": [
+ "INT",
+ {
+ "default": 512,
+ "min": 64,
+ "max": 2048
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "links": [
+ 11
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "links": [
+ 12
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "upscale_factor",
+ "type": "FLOAT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "batch_size",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR SD1.5 Aspect Ratio"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ "custom",
+ "Off",
+ 1,
+ 1
+ ]
+ },
+ {
+ "id": 49,
+ "type": "CR Gradient Float",
+ "pos": [
+ 1030,
+ 360
+ ],
+ "size": {
+ "0": 320,
+ "1": 180
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 60,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 57
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Gradient Float"
+ },
+ "widgets_values": [
+ 10,
+ 20,
+ 3,
+ 8,
+ 0,
+ "Lerp"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 26,
+ "type": "Save Image Sequence (mtb)",
+ "pos": [
+ 2250,
+ 330
+ ],
+ "size": {
+ "0": 380,
+ "1": 290
+ },
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 25,
+ "slot_index": 0
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 56,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999999
+ }
+ ]
+ },
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Save Image Sequence (mtb)"
+ },
+ "widgets_values": [
+ "F:\\ComfyUI\\ComfyUI_windows_portable\\ComfyUI\\output\\Test\\",
+ 5
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 17,
+ "type": "VAELoader",
+ "pos": [
+ 1860,
+ 710
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 15
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "vae-ft-mse-840000-ema-pruned.safetensors"
+ ]
+ },
+ {
+ "id": 47,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ 580,
+ 540
+ ],
+ "size": {
+ "0": 315,
+ "1": 98
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 53
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 54,
+ 55
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "title": "Load Initial Checkpoint",
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "SD1_5\\ComfyrollAnime_v1_fp16_pruned.safetensors"
+ ]
+ },
+ {
+ "id": 25,
+ "type": "CR Current Frame",
+ "pos": [
+ 630,
+ 290
+ ],
+ "size": {
+ "0": 210,
+ "1": 58
+ },
+ "flags": {
+ "collapsed": false
+ },
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "link": 23,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "links": [
+ 56,
+ 59,
+ 60
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Current Frame"
+ },
+ "widgets_values": [
+ 1,
+ "Yes"
+ ]
+ },
+ {
+ "id": 51,
+ "type": "CR Gradient Integer",
+ "pos": [
+ 1030,
+ 90
+ ],
+ "size": {
+ "0": 320,
+ "1": 180
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 59,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 58
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Gradient Integer"
+ },
+ "widgets_values": [
+ 512,
+ 1024,
+ 3,
+ 8,
+ 0,
+ "Lerp"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 54,
+ "type": "Note",
+ "pos": [
+ 1270,
+ -70
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The float gradient is changing the the cfg with each frame with each frame, starting from the third frame"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 52,
+ "type": "Note",
+ "pos": [
+ 70,
+ 330
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "To run this workflow, first press Reset in the Animation Builder and then press the Queue button, Do not use queue prompt in the ComfyUI menu."
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 53,
+ "type": "Note",
+ "pos": [
+ 1030,
+ -70
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "In this example the integer gradiant changes the width of the image with each frame, starting from the third frame"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 55,
+ "type": "Note",
+ "pos": [
+ 1510,
+ -70
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Integer and float gradients can be attached to any widget with the same data type"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 56,
+ "type": "Note",
+ "pos": [
+ 630,
+ 120
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Frames are processed in sequence. The CR Current Frame node prints the current frame index to console so that you can see which frame is currently being processed"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 24,
+ "type": "Animation Builder (mtb)",
+ "pos": [
+ 310,
+ 330
+ ],
+ "size": {
+ "0": 210,
+ "1": 320
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "frame",
+ "type": "INT",
+ "links": [
+ 23
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "0-1 (scaled)",
+ "type": "FLOAT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "count",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "loop_ended",
+ "type": "BOOLEAN",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Animation Builder (mtb)"
+ },
+ "widgets_values": [
+ 12,
+ 1,
+ 1,
+ 12,
+ 1,
+ "frame: 0 / 11",
+ "Done 😎!",
+ "reset",
+ "queue"
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ }
+ ],
+ "links": [
+ [
+ 8,
+ 11,
+ 0,
+ 13,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 9,
+ 12,
+ 0,
+ 13,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 11,
+ 14,
+ 0,
+ 15,
+ 0,
+ "INT"
+ ],
+ [
+ 12,
+ 14,
+ 1,
+ 15,
+ 1,
+ "INT"
+ ],
+ [
+ 13,
+ 15,
+ 0,
+ 13,
+ 3,
+ "LATENT"
+ ],
+ [
+ 14,
+ 13,
+ 0,
+ 16,
+ 0,
+ "LATENT"
+ ],
+ [
+ 15,
+ 17,
+ 0,
+ 16,
+ 1,
+ "VAE"
+ ],
+ [
+ 23,
+ 24,
+ 0,
+ 25,
+ 0,
+ "INT"
+ ],
+ [
+ 25,
+ 16,
+ 0,
+ 26,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 53,
+ 47,
+ 0,
+ 13,
+ 0,
+ "MODEL"
+ ],
+ [
+ 54,
+ 47,
+ 1,
+ 11,
+ 0,
+ "CLIP"
+ ],
+ [
+ 55,
+ 47,
+ 1,
+ 12,
+ 0,
+ "CLIP"
+ ],
+ [
+ 56,
+ 25,
+ 0,
+ 26,
+ 1,
+ "INT"
+ ],
+ [
+ 57,
+ 49,
+ 0,
+ 13,
+ 4,
+ "FLOAT"
+ ],
+ [
+ 58,
+ 51,
+ 0,
+ 14,
+ 0,
+ "INT"
+ ],
+ [
+ 59,
+ 25,
+ 0,
+ 51,
+ 0,
+ "INT"
+ ],
+ [
+ 60,
+ 25,
+ 0,
+ 49,
+ 0,
+ "INT"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_E2_IncrementNodes_Demo_v01b.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_E2_IncrementNodes_Demo_v01b.json
new file mode 100644
index 0000000000000000000000000000000000000000..3e45389b1aca5242882117b07fdaefdadbd449a8
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_E2_IncrementNodes_Demo_v01b.json
@@ -0,0 +1,1136 @@
+{
+ "last_node_id": 69,
+ "last_link_id": 81,
+ "nodes": [
+ {
+ "id": 15,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 1480,
+ 410
+ ],
+ "size": {
+ "0": 320,
+ "1": 110
+ },
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "link": 11,
+ "widget": {
+ "name": "width",
+ "config": [
+ "INT",
+ {
+ "default": 512,
+ "min": 64,
+ "max": 8192,
+ "step": 8
+ }
+ ]
+ }
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "link": 12,
+ "widget": {
+ "name": "height",
+ "config": [
+ "INT",
+ {
+ "default": 512,
+ "min": 64,
+ "max": 8192,
+ "step": 8
+ }
+ ]
+ }
+ },
+ {
+ "name": "batch_size",
+ "type": "INT",
+ "link": 72,
+ "widget": {
+ "name": "batch_size",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": 1,
+ "max": 64
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 13
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ 1
+ ]
+ },
+ {
+ "id": 11,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 1120,
+ 660
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 54
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 8
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "1girl, period costume"
+ ]
+ },
+ {
+ "id": 12,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 1120,
+ 820
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 55
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 9
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "embedding:EasyNegative,\nnsfw"
+ ]
+ },
+ {
+ "id": 13,
+ "type": "KSampler",
+ "pos": [
+ 1480,
+ 580
+ ],
+ "size": {
+ "0": 320,
+ "1": 470
+ },
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 53
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 8
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 9
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 13
+ },
+ {
+ "name": "denoise",
+ "type": "FLOAT",
+ "link": 65,
+ "widget": {
+ "name": "denoise",
+ "config": [
+ "FLOAT",
+ {
+ "default": 1,
+ "min": 0,
+ "max": 1,
+ "step": 0.01
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 14
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 32603574575332,
+ "fixed",
+ 20,
+ 8,
+ "euler",
+ "normal",
+ 1
+ ]
+ },
+ {
+ "id": 17,
+ "type": "VAELoader",
+ "pos": [
+ 1860,
+ 710
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 15
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "vae-ft-mse-840000-ema-pruned.safetensors"
+ ]
+ },
+ {
+ "id": 16,
+ "type": "VAEDecode",
+ "pos": [
+ 1910,
+ 580
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 18,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 14
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 15
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 68
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 64,
+ "type": "Note",
+ "pos": [
+ 1480,
+ -70
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Integer and float increment nodes can be attached to any widget with the same data type"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 58,
+ "type": "CR Increment Float",
+ "pos": [
+ 1920,
+ 70
+ ],
+ "size": {
+ "0": 320,
+ "1": 130
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 71,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 78
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Increment Float"
+ },
+ "widgets_values": [
+ 2,
+ 0.25,
+ 2,
+ 8,
+ 0
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 26,
+ "type": "Save Image Sequence (mtb)",
+ "pos": [
+ 2700,
+ 200
+ ],
+ "size": {
+ "0": 550,
+ "1": 730
+ },
+ "flags": {},
+ "order": 20,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 69,
+ "slot_index": 0
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 56,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999999
+ }
+ ]
+ },
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Save Image Sequence (mtb)"
+ },
+ "widgets_values": [
+ "F:\\ComfyUI\\ComfyUI_windows_portable\\ComfyUI\\output\\Test\\",
+ 5
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 56,
+ "type": "ImageScaleBy",
+ "pos": [
+ 2310,
+ 300
+ ],
+ "size": {
+ "0": 320,
+ "1": 80
+ },
+ "flags": {},
+ "order": 19,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 68
+ },
+ {
+ "name": "scale_by",
+ "type": "FLOAT",
+ "link": 78,
+ "widget": {
+ "name": "scale_by",
+ "config": [
+ "FLOAT",
+ {
+ "default": 1,
+ "min": 0.01,
+ "max": 8,
+ "step": 0.01
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 69
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ImageScaleBy"
+ },
+ "widgets_values": [
+ "nearest-exact",
+ 1
+ ]
+ },
+ {
+ "id": 66,
+ "type": "CR Increment Integer",
+ "pos": [
+ 1070,
+ 20
+ ],
+ "size": {
+ "0": 320,
+ "1": 150
+ },
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 80,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 79
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Increment Integer"
+ },
+ "widgets_values": [
+ 768,
+ 16,
+ 0,
+ 8,
+ 0
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 25,
+ "type": "CR Current Frame",
+ "pos": [
+ 650,
+ 250
+ ],
+ "size": {
+ "0": 210,
+ "1": 58
+ },
+ "flags": {
+ "collapsed": false
+ },
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "link": 81,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "links": [
+ 56,
+ 63,
+ 71,
+ 80
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Current Frame"
+ },
+ "widgets_values": [
+ 0,
+ "Yes"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 14,
+ "type": "CR SD1.5 Aspect Ratio",
+ "pos": [
+ 1480,
+ 100
+ ],
+ "size": {
+ "0": 320,
+ "1": 240
+ },
+ "flags": {},
+ "order": 15,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "height",
+ "type": "INT",
+ "link": 79,
+ "widget": {
+ "name": "height",
+ "config": [
+ "INT",
+ {
+ "default": 512,
+ "min": 64,
+ "max": 2048
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "links": [
+ 11
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "links": [
+ 12
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "upscale_factor",
+ "type": "FLOAT",
+ "links": [],
+ "shape": 3,
+ "slot_index": 2
+ },
+ {
+ "name": "batch_size",
+ "type": "INT",
+ "links": [
+ 72
+ ],
+ "shape": 3,
+ "slot_index": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR SD1.5 Aspect Ratio"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ "custom",
+ "Off",
+ 1,
+ 1
+ ]
+ },
+ {
+ "id": 59,
+ "type": "Note",
+ "pos": [
+ 650,
+ 50
+ ],
+ "size": {
+ "0": 210,
+ "1": 130
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The CR Current Frame node prints the current frame index to console so that you can see which frame is currently being processed"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 68,
+ "type": "Note",
+ "pos": [
+ 650,
+ -70
+ ],
+ "size": {
+ "0": 210,
+ "1": 70
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Frames are processed in sequence starting from frame index 0"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 67,
+ "type": "Note",
+ "pos": [
+ 1080,
+ -150
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "This integer increment is changing the height of the image with each frame starting from the first frame"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 62,
+ "type": "Note",
+ "pos": [
+ 1920,
+ -80
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "This float increment is changing the upscale factor with each frame with each frame, starting from the second frame"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 52,
+ "type": "CR Increment Float",
+ "pos": [
+ 1070,
+ 360
+ ],
+ "size": {
+ "0": 320,
+ "1": 130
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 63,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 65
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Increment Float"
+ },
+ "widgets_values": [
+ 0.75,
+ 0.04,
+ 2,
+ 8,
+ 0
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 63,
+ "type": "Note",
+ "pos": [
+ 820,
+ 360
+ ],
+ "size": {
+ "0": 210,
+ "1": 100
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "This float increment is changing the denoise with each frame with each frame, starting from the second frame"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 47,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ 630,
+ 590
+ ],
+ "size": {
+ "0": 380,
+ "1": 100
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 53
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 54,
+ 55
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "SD1_5\\ComfyrollAnime_v1_fp16_pruned.safetensors"
+ ]
+ },
+ {
+ "id": 69,
+ "type": "PrimitiveNode",
+ "pos": [
+ 360,
+ 250
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 81
+ ],
+ "slot_index": 0,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ 0,
+ "increment"
+ ]
+ }
+ ],
+ "links": [
+ [
+ 8,
+ 11,
+ 0,
+ 13,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 9,
+ 12,
+ 0,
+ 13,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 11,
+ 14,
+ 0,
+ 15,
+ 0,
+ "INT"
+ ],
+ [
+ 12,
+ 14,
+ 1,
+ 15,
+ 1,
+ "INT"
+ ],
+ [
+ 13,
+ 15,
+ 0,
+ 13,
+ 3,
+ "LATENT"
+ ],
+ [
+ 14,
+ 13,
+ 0,
+ 16,
+ 0,
+ "LATENT"
+ ],
+ [
+ 15,
+ 17,
+ 0,
+ 16,
+ 1,
+ "VAE"
+ ],
+ [
+ 53,
+ 47,
+ 0,
+ 13,
+ 0,
+ "MODEL"
+ ],
+ [
+ 54,
+ 47,
+ 1,
+ 11,
+ 0,
+ "CLIP"
+ ],
+ [
+ 55,
+ 47,
+ 1,
+ 12,
+ 0,
+ "CLIP"
+ ],
+ [
+ 56,
+ 25,
+ 0,
+ 26,
+ 1,
+ "INT"
+ ],
+ [
+ 63,
+ 25,
+ 0,
+ 52,
+ 0,
+ "INT"
+ ],
+ [
+ 65,
+ 52,
+ 0,
+ 13,
+ 4,
+ "FLOAT"
+ ],
+ [
+ 68,
+ 16,
+ 0,
+ 56,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 69,
+ 56,
+ 0,
+ 26,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 71,
+ 25,
+ 0,
+ 58,
+ 0,
+ "INT"
+ ],
+ [
+ 72,
+ 14,
+ 3,
+ 15,
+ 2,
+ "INT"
+ ],
+ [
+ 78,
+ 58,
+ 0,
+ 56,
+ 1,
+ "FLOAT"
+ ],
+ [
+ 79,
+ 66,
+ 0,
+ 14,
+ 0,
+ "INT"
+ ],
+ [
+ 80,
+ 25,
+ 0,
+ 66,
+ 0,
+ "INT"
+ ],
+ [
+ 81,
+ 69,
+ 0,
+ 25,
+ 0,
+ "INT"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_F1_IONodes_Demo_v01.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_F1_IONodes_Demo_v01.json
new file mode 100644
index 0000000000000000000000000000000000000000..b246a0221231706f9968aeae8825f9756053252e
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/CR_Animation_F1_IONodes_Demo_v01.json
@@ -0,0 +1,489 @@
+{
+ "last_node_id": 40,
+ "last_link_id": 66,
+ "nodes": [
+ {
+ "id": 38,
+ "type": "VAEEncode",
+ "pos": [
+ 1380,
+ 590
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 3,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "pixels",
+ "type": "IMAGE",
+ "link": 60
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 64
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 59
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEEncode"
+ }
+ },
+ {
+ "id": 37,
+ "type": "CR Interpolate Latents",
+ "pos": [
+ 1580,
+ 590
+ ],
+ "size": {
+ "0": 250,
+ "1": 102
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "latent1",
+ "type": "LATENT",
+ "link": 59
+ },
+ {
+ "name": "latent2",
+ "type": "LATENT",
+ "link": 62
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 55
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Interpolate Latents"
+ },
+ "widgets_values": [
+ 0.5,
+ "lerp"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 31,
+ "type": "CR Load Flow Frames",
+ "pos": [
+ 1000,
+ 480
+ ],
+ "size": {
+ "0": 300,
+ "1": 194
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 44,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "current_image",
+ "type": "IMAGE",
+ "links": [
+ 57,
+ 60
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "previous_image",
+ "type": "IMAGE",
+ "links": [
+ 66
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "links": [
+ 53
+ ],
+ "shape": 3,
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Load Flow Frames"
+ },
+ "widgets_values": [
+ "TikTok_frames",
+ "Index",
+ 0,
+ 0,
+ "",
+ "*.png"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 39,
+ "type": "VAEEncode",
+ "pos": [
+ 1380,
+ 650
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 4,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "pixels",
+ "type": "IMAGE",
+ "link": 66
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 63
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 62
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEEncode"
+ }
+ },
+ {
+ "id": 35,
+ "type": "VAEDecode",
+ "pos": [
+ 1910,
+ 620
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 6,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 55
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 65
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 56
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 34,
+ "type": "CR Output Flow Frames",
+ "pos": [
+ 2110,
+ 480
+ ],
+ "size": {
+ "0": 320,
+ "1": 380
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "current_image",
+ "type": "IMAGE",
+ "link": 57
+ },
+ {
+ "name": "interpolated_img",
+ "type": "IMAGE",
+ "link": 56
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 53,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999999
+ }
+ ]
+ }
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Output Flow Frames"
+ },
+ "widgets_values": [
+ "Video",
+ "CR",
+ 0,
+ ""
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 40,
+ "type": "VAELoader",
+ "pos": [
+ 990,
+ 750
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 63,
+ 64,
+ 65
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "vae-ft-mse-840000-ema-pruned.safetensors"
+ ]
+ },
+ {
+ "id": 2,
+ "type": "PrimitiveNode",
+ "pos": [
+ 730,
+ 480
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 44
+ ],
+ "slot_index": 0,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ 1,
+ "increment"
+ ]
+ }
+ ],
+ "links": [
+ [
+ 44,
+ 2,
+ 0,
+ 31,
+ 0,
+ "INT"
+ ],
+ [
+ 53,
+ 31,
+ 2,
+ 34,
+ 2,
+ "INT"
+ ],
+ [
+ 55,
+ 37,
+ 0,
+ 35,
+ 0,
+ "LATENT"
+ ],
+ [
+ 56,
+ 35,
+ 0,
+ 34,
+ 1,
+ "IMAGE"
+ ],
+ [
+ 57,
+ 31,
+ 0,
+ 34,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 59,
+ 38,
+ 0,
+ 37,
+ 0,
+ "LATENT"
+ ],
+ [
+ 60,
+ 31,
+ 0,
+ 38,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 62,
+ 39,
+ 0,
+ 37,
+ 1,
+ "LATENT"
+ ],
+ [
+ 63,
+ 40,
+ 0,
+ 39,
+ 1,
+ "VAE"
+ ],
+ [
+ 64,
+ 40,
+ 0,
+ 38,
+ 1,
+ "VAE"
+ ],
+ [
+ 65,
+ 40,
+ 0,
+ 35,
+ 1,
+ "VAE"
+ ],
+ [
+ 66,
+ 31,
+ 1,
+ 39,
+ 0,
+ "IMAGE"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/Node Schema/CR_Animation_Nodes_Status_drop10a.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/Node Schema/CR_Animation_Nodes_Status_drop10a.json
new file mode 100644
index 0000000000000000000000000000000000000000..9508b1d5deeb38ab8755aae7484262efc2268c1e
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Animation/Node Schema/CR_Animation_Nodes_Status_drop10a.json
@@ -0,0 +1,3310 @@
+{
+ "last_node_id": 274,
+ "last_link_id": 234,
+ "nodes": [
+ {
+ "id": 90,
+ "type": "CR Debatch Frames",
+ "pos": [
+ -924.2428318353343,
+ 575.3365003734204
+ ],
+ "size": {
+ "0": 292.20001220703125,
+ "1": 26
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frames",
+ "type": "IMAGE",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "debatched_frames",
+ "type": "IMAGE",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Debatch Frames"
+ },
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 91,
+ "type": "CR Text List To String",
+ "pos": [
+ -924.031854638067,
+ 477.3582341136594
+ ],
+ "size": {
+ "0": 210,
+ "1": 34
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text_list",
+ "type": "STRING",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Text List To String"
+ },
+ "widgets_values": [
+ ""
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 83,
+ "type": "CR Increment Float",
+ "pos": [
+ 1931.619638004882,
+ 1113.8662124388768
+ ],
+ "size": {
+ "0": 330,
+ "1": 140
+ },
+ "flags": {},
+ "order": 50,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 159,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ },
+ "slot_index": 0
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Increment Float"
+ },
+ "widgets_values": [
+ 1,
+ 0.1,
+ 0,
+ 1,
+ 0
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 84,
+ "type": "CR Increment Integer",
+ "pos": [
+ 2311.619638004884,
+ 1113.8662124388768
+ ],
+ "size": {
+ "0": 320,
+ "1": 140
+ },
+ "flags": {},
+ "order": 49,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 158,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ },
+ "slot_index": 0
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Increment Integer"
+ },
+ "widgets_values": [
+ 1,
+ 1,
+ 0,
+ 1,
+ 0
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 65,
+ "type": "CR Gradient Integer",
+ "pos": [
+ 2311.619638004884,
+ 863.8662124388808
+ ],
+ "size": {
+ "0": 330,
+ "1": 170
+ },
+ "flags": {},
+ "order": 48,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 157,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Gradient Integer"
+ },
+ "widgets_values": [
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ "Lerp"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 64,
+ "type": "CR Gradient Float",
+ "pos": [
+ 1931.080418403318,
+ 863.8662124388808
+ ],
+ "size": {
+ "0": 330,
+ "1": 170
+ },
+ "flags": {},
+ "order": 47,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 156,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ },
+ "slot_index": 0
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Gradient Float"
+ },
+ "widgets_values": [
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ "Lerp"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 186,
+ "type": "CR Cycle LoRAs",
+ "pos": [
+ 717.7845361871587,
+ 1542.5758248663394
+ ],
+ "size": {
+ "0": 320,
+ "1": 190
+ },
+ "flags": {},
+ "order": 54,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 129
+ },
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 130
+ },
+ {
+ "name": "lora_list",
+ "type": "LORA_LIST",
+ "link": null
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 161,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Cycle LoRAs"
+ },
+ "widgets_values": [
+ "Sequential",
+ 30,
+ 1,
+ 0
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 185,
+ "type": "CR Cycle Models",
+ "pos": [
+ 337.7845361871574,
+ 1542.5758248663394
+ ],
+ "size": {
+ "0": 320,
+ "1": 190
+ },
+ "flags": {},
+ "order": 42,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": null
+ },
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": null
+ },
+ {
+ "name": "model_list",
+ "type": "MODEL_LIST",
+ "link": null
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 160,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 129
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 130
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Cycle Models"
+ },
+ "widgets_values": [
+ "Sequential",
+ 30,
+ 1,
+ 0
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 171,
+ "type": "CR Cycle Images",
+ "pos": [
+ 1094.548181208022,
+ 1543.7795145350904
+ ],
+ "size": {
+ "0": 320,
+ "1": 150
+ },
+ "flags": {},
+ "order": 43,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image_list",
+ "type": "image_LIST",
+ "link": null
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 162,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Cycle Images"
+ },
+ "widgets_values": [
+ "Off",
+ 30,
+ 1,
+ "Sequential"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 187,
+ "type": "CR Model List",
+ "pos": [
+ 1245.5496187605943,
+ -56.18786894116704
+ ],
+ "size": {
+ "0": 320,
+ "1": 294
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model_list",
+ "type": "MODEL_LIST",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL_LIST",
+ "type": "MODEL_LIST",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Model List"
+ },
+ "widgets_values": [
+ "None",
+ "",
+ "None",
+ "",
+ "None",
+ "",
+ "None",
+ "",
+ "None",
+ ""
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 188,
+ "type": "CR LoRA List",
+ "pos": [
+ 1245.5496187605943,
+ 293.81213105883563
+ ],
+ "size": {
+ "0": 320,
+ "1": 342
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "lora_list",
+ "type": "lora_LIST",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LORA_LIST",
+ "type": "LORA_LIST",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR LoRA List"
+ },
+ "widgets_values": [
+ "None",
+ "",
+ "",
+ "",
+ "None",
+ "",
+ "",
+ "",
+ "None",
+ "",
+ "",
+ ""
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 190,
+ "type": "CR Text List",
+ "pos": [
+ 1615.549618760595,
+ -56.18786894116704
+ ],
+ "size": {
+ "0": 290,
+ "1": 294
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text_list",
+ "type": "text_LIST",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "TEXT_LIST",
+ "type": "TEXT_LIST",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Text List"
+ },
+ "widgets_values": [
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ ""
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 189,
+ "type": "CR Text List Simple",
+ "pos": [
+ 1615.549618760595,
+ 293.81213105883563
+ ],
+ "size": {
+ "0": 300,
+ "1": 154
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text_list_simple",
+ "type": "TEXT_LIST_SIMPLE",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "TEXT_LIST_SIMPLE",
+ "type": "TEXT_LIST_SIMPLE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Text List Simple"
+ },
+ "widgets_values": [
+ "",
+ "",
+ "",
+ "",
+ ""
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 192,
+ "type": "CR Image List Simple",
+ "pos": [
+ 1955.5496187605954,
+ 293.81213105883563
+ ],
+ "size": {
+ "0": 310,
+ "1": 130
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image_1",
+ "type": "IMAGE",
+ "link": null
+ },
+ {
+ "name": "image_2",
+ "type": "IMAGE",
+ "link": null
+ },
+ {
+ "name": "image_3",
+ "type": "IMAGE",
+ "link": null
+ },
+ {
+ "name": "image_4",
+ "type": "IMAGE",
+ "link": null
+ },
+ {
+ "name": "image_5",
+ "type": "IMAGE",
+ "link": null
+ },
+ {
+ "name": "image_list_simple",
+ "type": "IMAGE_LIST_SIMPLE",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE_LIST_SIMPLE",
+ "type": "IMAGE_LIST_SIMPLE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Image List Simple"
+ },
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 184,
+ "type": "CR Image List",
+ "pos": [
+ 1955.5496187605954,
+ -56.18786894116704
+ ],
+ "size": {
+ "0": 310,
+ "1": 294
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image_list",
+ "type": "image_LIST",
+ "link": null
+ },
+ {
+ "name": "image_2",
+ "type": "IMAGE",
+ "link": null
+ },
+ {
+ "name": "image_3",
+ "type": "IMAGE",
+ "link": null
+ },
+ {
+ "name": "image_4",
+ "type": "IMAGE",
+ "link": null
+ },
+ {
+ "name": "image_5",
+ "type": "IMAGE",
+ "link": null
+ },
+ {
+ "name": "image_list",
+ "type": "image_LIST",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE_LIST",
+ "type": "IMAGE_LIST",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Image List"
+ },
+ "widgets_values": [
+ "None",
+ "",
+ "None",
+ "",
+ "None"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 194,
+ "type": "CR Cycle Images Simple",
+ "pos": [
+ 1094.548181208022,
+ 1763.7795145350904
+ ],
+ "size": {
+ "0": 320,
+ "1": 230
+ },
+ "flags": {},
+ "order": 45,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image_1",
+ "type": "IMAGE",
+ "link": null
+ },
+ {
+ "name": "image_2",
+ "type": "IMAGE",
+ "link": null
+ },
+ {
+ "name": "image_3",
+ "type": "IMAGE",
+ "link": null
+ },
+ {
+ "name": "image_4",
+ "type": "IMAGE",
+ "link": null
+ },
+ {
+ "name": "image_5",
+ "type": "IMAGE",
+ "link": null
+ },
+ {
+ "name": "image_list_simple",
+ "type": "IMAGE_LIST_SIMPLE",
+ "link": null
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 165,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Cycle Images Simple"
+ },
+ "widgets_values": [
+ "Sequential",
+ 30,
+ 1,
+ 0
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 53,
+ "type": "CR Interpolate Latents",
+ "pos": [
+ 2397.0391363635654,
+ 535.6252973667436
+ ],
+ "size": {
+ "0": 315,
+ "1": 102
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "latent1",
+ "type": "LATENT",
+ "link": null
+ },
+ {
+ "name": "latent2",
+ "type": "LATENT",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Interpolate Latents"
+ },
+ "widgets_values": [
+ 0.5,
+ "lerp"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 10,
+ "type": "CR Index Reset",
+ "pos": [
+ -59.91286945504303,
+ 55.54442351082017
+ ],
+ "size": {
+ "0": 240,
+ "1": 100
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "link": null,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": 0,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "links": [],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "reset_to",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Index Reset"
+ },
+ "widgets_values": [
+ 1,
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 11,
+ "type": "CR Index Multiply",
+ "pos": [
+ -59.91286945504303,
+ 215.5444235108203
+ ],
+ "size": {
+ "0": 240,
+ "1": 90
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "link": null,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": 0,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "links": [],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "factor",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Index Multiply"
+ },
+ "widgets_values": [
+ 107,
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 231,
+ "type": "CR Input Text List",
+ "pos": [
+ 1616.7185560109374,
+ 507.5897292398438
+ ],
+ "size": {
+ "0": 300,
+ "1": 120
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "TEXT_LIST_SIMPLE",
+ "type": "TEXT_LIST_SIMPLE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Input Text List"
+ },
+ "widgets_values": [
+ ""
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 239,
+ "type": "CR Simple Prompt List Keyframes",
+ "pos": [
+ -651.0531598311474,
+ 871.1768746588539
+ ],
+ "size": {
+ "0": 420,
+ "1": 180
+ },
+ "flags": {},
+ "order": 30,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "simple_prompt_list",
+ "type": "SIMPLE_PROMPT_LIST",
+ "link": 213
+ }
+ ],
+ "outputs": [
+ {
+ "name": "keyframe_list",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Prompt List Keyframes"
+ },
+ "widgets_values": [
+ 30,
+ 1,
+ "Default",
+ "Default",
+ "Default",
+ "Deforum"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 240,
+ "type": "CR Simple Prompt List",
+ "pos": [
+ -1171.0531598311436,
+ 871.1768746588539
+ ],
+ "size": {
+ "0": 468.5999755859375,
+ "1": 276.0000305175781
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "simple_prompt_list",
+ "type": "SIMPLE_PROMPT_LIST",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "SIMPLE_PROMPT_LIST",
+ "type": "SIMPLE_PROMPT_LIST",
+ "links": [
+ 213
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Prompt List"
+ },
+ "widgets_values": [
+ "prompt",
+ "prompt",
+ "prompt",
+ "prompt",
+ "prompt"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 243,
+ "type": "CR Prompt List",
+ "pos": [
+ -1171.0531598311436,
+ 1221.1768746588546
+ ],
+ "size": {
+ "0": 470,
+ "1": 684
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "prompt_list",
+ "type": "PROMPT_LIST",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "PROMPT_LIST",
+ "type": "PROMPT_LIST",
+ "links": [
+ 214
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Prompt List"
+ },
+ "widgets_values": [
+ 30,
+ 1,
+ "prompt",
+ "Default",
+ "Default",
+ "Default",
+ "prompt",
+ "Default",
+ "Default",
+ "Default",
+ "prompt",
+ "Default",
+ "Default",
+ "Default",
+ "prompt",
+ "Default",
+ "Default",
+ "Default",
+ "prompt",
+ "Default",
+ "Default",
+ "Default"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 244,
+ "type": "CR Prompt List Keyframes",
+ "pos": [
+ -631.0531598311474,
+ 1221.1768746588546
+ ],
+ "size": {
+ "0": 317.4000244140625,
+ "1": 58
+ },
+ "flags": {},
+ "order": 31,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "prompt_list",
+ "type": "PROMPT_LIST",
+ "link": 214
+ }
+ ],
+ "outputs": [
+ {
+ "name": "keyframe_list",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Prompt List Keyframes"
+ },
+ "widgets_values": [
+ "Deforum"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 250,
+ "type": "CR Simple Schedule",
+ "pos": [
+ 331.5431064101562,
+ -114.01670582187359
+ ],
+ "size": {
+ "0": 400,
+ "1": 200
+ },
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Schedule"
+ },
+ "widgets_values": [
+ "frame_number, item_alias, [attr_value1, attr_value2]",
+ "Value",
+ "",
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 251,
+ "type": "CR Load Scheduled Models",
+ "pos": [
+ 335.13422812533,
+ 932.7062050850162
+ ],
+ "size": {
+ "0": 320,
+ "1": 190
+ },
+ "flags": {},
+ "order": 34,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model_list",
+ "type": "MODEL_LIST",
+ "link": null
+ },
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": 219
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": null,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Load Scheduled Models"
+ },
+ "widgets_values": [
+ "Load Default Model",
+ "SD1_5\\7th_anime_v3_A.safetensors",
+ 0,
+ "",
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 249,
+ "type": "CR Central Schedule",
+ "pos": [
+ 338.4568325546874,
+ 150.6133610892579
+ ],
+ "size": {
+ "0": 390,
+ "1": 480
+ },
+ "flags": {},
+ "order": 15,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": [
+ 220
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Central Schedule"
+ },
+ "widgets_values": [
+ "schedule",
+ "Value",
+ "",
+ "schedule",
+ "Value",
+ "",
+ "schedule",
+ "Value",
+ "",
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 252,
+ "type": "CR Load Scheduled LoRAs",
+ "pos": [
+ 705.1342281253314,
+ 932.7062050850162
+ ],
+ "size": {
+ "0": 320,
+ "1": 258
+ },
+ "flags": {},
+ "order": 35,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": null
+ },
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": null
+ },
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": 221
+ },
+ {
+ "name": "lora_list",
+ "type": "LORA_LIST",
+ "link": null
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": null,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Load Scheduled LoRAs"
+ },
+ "widgets_values": [
+ "Off",
+ 0,
+ 120,
+ "",
+ "CR",
+ 1,
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 209,
+ "type": "CR String To Combo",
+ "pos": [
+ -922.7771217524553,
+ 659.7320834823254
+ ],
+ "size": {
+ "0": 210,
+ "1": 34
+ },
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "*",
+ "type": "*",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR String To Combo"
+ },
+ "widgets_values": [
+ ""
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 159,
+ "type": "CR Cycle Text",
+ "pos": [
+ 1471.5431674453125,
+ 1535.1876997187492
+ ],
+ "size": {
+ "0": 320,
+ "1": 150
+ },
+ "flags": {},
+ "order": 44,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text_list",
+ "type": "STYLE_LIST",
+ "link": null
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 163,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Cycle Text"
+ },
+ "widgets_values": [
+ "Off",
+ 30,
+ 1,
+ "Sequential"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 191,
+ "type": "CR Cycle Text Simple",
+ "pos": [
+ 1471.5431674453125,
+ 1755.1876997187492
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 46,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text_list_simple",
+ "type": "TEXT_LIST_SIMPLE",
+ "link": null
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 166,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Cycle Text Simple"
+ },
+ "widgets_values": [
+ "",
+ "",
+ "",
+ "",
+ "",
+ "Sequential",
+ 30,
+ 1,
+ 0
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 203,
+ "type": "CR Simple Value Scheduler",
+ "pos": [
+ 1934.4145575758114,
+ 1539.07570596151
+ ],
+ "size": {
+ "0": 380,
+ "1": 190
+ },
+ "flags": {},
+ "order": 51,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 228,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Value Scheduler"
+ },
+ "widgets_values": [
+ "frame_number, value",
+ 0
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 207,
+ "type": "CR Simple Text Scheduler",
+ "pos": [
+ 1934.4145575758114,
+ 1799.07570596151
+ ],
+ "size": {
+ "0": 380,
+ "1": 200
+ },
+ "flags": {},
+ "order": 52,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 229,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Text Scheduler"
+ },
+ "widgets_values": [
+ "frame_number, value",
+ 0
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 7,
+ "type": "CR Index Increment",
+ "pos": [
+ -59.47699937965249,
+ 358.4312166894532
+ ],
+ "size": {
+ "0": 240,
+ "1": 90
+ },
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "link": null,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": 1,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "links": [],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "interval",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Index Increment"
+ },
+ "widgets_values": [
+ 1,
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 268,
+ "type": "CR Output Flow Frames",
+ "pos": [
+ -516.1425520625003,
+ 531.9835967714845
+ ],
+ "size": {
+ "0": 320,
+ "1": 146
+ },
+ "flags": {},
+ "order": 18,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "current_img",
+ "type": "IMAGE",
+ "link": null
+ },
+ {
+ "name": "interpolated_img",
+ "type": "IMAGE",
+ "link": null
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": null,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999999
+ }
+ ]
+ }
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Output Flow Frames"
+ },
+ "widgets_values": [
+ "1.5 template tests",
+ "Sequence",
+ 0,
+ ""
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 266,
+ "type": "CR Load Flow Frames",
+ "pos": [
+ -516.1425520625003,
+ 281.9835967714843
+ ],
+ "size": {
+ "0": 320,
+ "1": 194
+ },
+ "flags": {},
+ "order": 19,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": null,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "current_img",
+ "type": "IMAGE",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "previous_img",
+ "type": "IMAGE",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Load Flow Frames"
+ },
+ "widgets_values": [
+ "FlowBack",
+ "Index",
+ 0,
+ 0,
+ "",
+ "*.png"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 97,
+ "type": "Reroute",
+ "pos": [
+ 335.13422812533,
+ 842.7062050850162
+ ],
+ "size": [
+ 107.2,
+ 26
+ ],
+ "flags": {},
+ "order": 32,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 220
+ }
+ ],
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": [
+ 219,
+ 221,
+ 223,
+ 224,
+ 230
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 245,
+ "type": "CR Keyframe List",
+ "pos": [
+ -631.0531598311474,
+ 1421.1768746588546
+ ],
+ "size": {
+ "0": 400,
+ "1": 200
+ },
+ "flags": {},
+ "order": 20,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "keyframe_list",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "keyframe_format",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Keyframe List"
+ },
+ "widgets_values": [
+ "keyframes",
+ "Deforum"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 271,
+ "type": "CR Prompt Scheduler",
+ "pos": [
+ 1080,
+ 930
+ ],
+ "size": {
+ "0": 330,
+ "1": 340
+ },
+ "flags": {},
+ "order": 41,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": 230
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 234,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "current_prompt",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "next_prompt",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "weight",
+ "type": "FLOAT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Prompt Scheduler"
+ },
+ "widgets_values": [
+ "Default Prompt",
+ 0,
+ "default prompt",
+ "CR",
+ "",
+ "keyframe list",
+ "prepend text",
+ "append text",
+ "append text"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 256,
+ "type": "CR Value Scheduler",
+ "pos": [
+ 1460,
+ 930
+ ],
+ "size": {
+ "0": 320,
+ "1": 150
+ },
+ "flags": {},
+ "order": 36,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": 223
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": null,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Value Scheduler"
+ },
+ "widgets_values": [
+ "Default Value",
+ "",
+ "",
+ "",
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 257,
+ "type": "CR Text Scheduler",
+ "pos": [
+ 1460,
+ 1140
+ ],
+ "size": {
+ "0": 320,
+ "1": 150
+ },
+ "flags": {},
+ "order": 37,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": 224
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": null,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Text Scheduler"
+ },
+ "widgets_values": [
+ "Default Text",
+ 0,
+ "",
+ "default text",
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 260,
+ "type": "Reroute",
+ "pos": [
+ 1730,
+ 1380
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 40,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 227
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 228,
+ 229,
+ 231
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 87,
+ "type": "Reroute",
+ "pos": [
+ 300,
+ 1380
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 38,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 167
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 160,
+ 161,
+ 162,
+ 163,
+ 165,
+ 166
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 269,
+ "type": "CR Simple Prompt Scheduler",
+ "pos": [
+ 2362.0920513924993,
+ 1537.8720122265615
+ ],
+ "size": {
+ "0": 370,
+ "1": 200
+ },
+ "flags": {},
+ "order": 53,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 231,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "current_prompt",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "next_prompt",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "weight",
+ "type": "FLOAT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Prompt Scheduler"
+ },
+ "widgets_values": [
+ "frame_number, text",
+ 0,
+ "CR"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 85,
+ "type": "Reroute",
+ "pos": [
+ 1890,
+ 700
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 39,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 189
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 156,
+ 157,
+ 158,
+ 159
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 95,
+ "type": "CR Load Animation Frames",
+ "pos": [
+ -516.1425520625003,
+ 61.9835967714843
+ ],
+ "size": {
+ "0": 320,
+ "1": 150
+ },
+ "flags": {},
+ "order": 21,
+ "mode": 0,
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "frames",
+ "type": "IMAGE",
+ "links": [],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "masks",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "links": [],
+ "shape": 3,
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Load Animation Frames"
+ },
+ "widgets_values": [
+ "FlowBack",
+ 1,
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 201,
+ "type": "CR Current Frame",
+ "pos": [
+ -57.17248667578124,
+ 589.9743383583987
+ ],
+ "size": {
+ "0": 250,
+ "1": 80
+ },
+ "flags": {},
+ "order": 22,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "link": null,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "links": [
+ 146
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Current Frame"
+ },
+ "widgets_values": [
+ 1,
+ "Yes"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 86,
+ "type": "Reroute",
+ "pos": [
+ 510,
+ 700
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 33,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 146
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 167,
+ 189,
+ 227,
+ 234
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 92,
+ "type": "CR Prompt Text",
+ "pos": [
+ -113.23711794591394,
+ 866.3292853335626
+ ],
+ "size": {
+ "0": 310,
+ "1": 110
+ },
+ "flags": {},
+ "order": 23,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "prompt",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Prompt Text"
+ },
+ "widgets_values": [
+ "prompt"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 248,
+ "type": "CR Output Schedule To File",
+ "pos": [
+ 790,
+ 520
+ ],
+ "size": {
+ "0": 315,
+ "1": 106
+ },
+ "flags": {},
+ "order": 25,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule",
+ "type": "SCHEDULE",
+ "link": null
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Output Schedule To File"
+ },
+ "widgets_values": [
+ "",
+ "",
+ "txt"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 263,
+ "type": "CR Load Schedule From File",
+ "pos": [
+ 790,
+ 330
+ ],
+ "size": {
+ "0": 315,
+ "1": 126
+ },
+ "flags": {},
+ "order": 26,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Load Schedule From File"
+ },
+ "widgets_values": [
+ "",
+ "",
+ "csv"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 264,
+ "type": "CR Combine Schedules",
+ "pos": [
+ 800,
+ -112.30761668222642
+ ],
+ "size": {
+ "0": 280,
+ "1": 90
+ },
+ "flags": {},
+ "order": 27,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule_1",
+ "type": "SCHEDULE",
+ "link": null
+ },
+ {
+ "name": "schedule_2",
+ "type": "SCHEDULE",
+ "link": null
+ },
+ {
+ "name": "schedule_3",
+ "type": "SCHEDULE",
+ "link": null
+ },
+ {
+ "name": "schedule_4",
+ "type": "SCHEDULE",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "show_text",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Combine Schedules"
+ },
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 258,
+ "type": "CR Schedule Input Switch",
+ "pos": [
+ 800,
+ 30
+ ],
+ "size": {
+ "0": 280,
+ "1": 80
+ },
+ "flags": {},
+ "order": 28,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "schedule1",
+ "type": "SCHEDULE",
+ "link": null
+ },
+ {
+ "name": "schedule2",
+ "type": "SCHEDULE",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "SCHEDULE",
+ "type": "SCHEDULE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Schedule Input Switch"
+ },
+ "widgets_values": [
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 273,
+ "type": "CR Encode Scheduled Prompts",
+ "pos": [
+ -110,
+ 1220
+ ],
+ "size": [
+ 310,
+ 120
+ ],
+ "flags": {},
+ "order": 24,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": null
+ },
+ {
+ "name": "current_prompt",
+ "type": "STRING",
+ "link": null,
+ "widget": {
+ "name": "current_prompt",
+ "config": [
+ "STRING",
+ {
+ "multiline": true
+ }
+ ]
+ }
+ },
+ {
+ "name": "next_prompt",
+ "type": "STRING",
+ "link": null,
+ "widget": {
+ "name": "next_prompt",
+ "config": [
+ "STRING",
+ {
+ "multiline": true
+ }
+ ]
+ }
+ },
+ {
+ "name": "weight",
+ "type": "FLOAT",
+ "link": null,
+ "widget": {
+ "name": "weight",
+ "config": [
+ "FLOAT",
+ {
+ "default": 0,
+ "min": -9999,
+ "max": 9999,
+ "step": 0.01
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Encode Scheduled Prompts"
+ },
+ "widgets_values": [
+ "",
+ "",
+ 0
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 274,
+ "type": "CR Load Prompt Style",
+ "pos": [
+ -110,
+ 1040
+ ],
+ "size": [
+ 310,
+ 120
+ ],
+ "flags": {},
+ "order": 29,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "prepend_text",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "append_text",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "negative_text",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Load Prompt Style"
+ },
+ "widgets_values": [
+ "None",
+ "json"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ }
+ ],
+ "links": [
+ [
+ 129,
+ 185,
+ 0,
+ 186,
+ 0,
+ "MODEL"
+ ],
+ [
+ 130,
+ 185,
+ 1,
+ 186,
+ 1,
+ "CLIP"
+ ],
+ [
+ 146,
+ 201,
+ 0,
+ 86,
+ 0,
+ "*"
+ ],
+ [
+ 156,
+ 85,
+ 0,
+ 64,
+ 0,
+ "INT"
+ ],
+ [
+ 157,
+ 85,
+ 0,
+ 65,
+ 0,
+ "INT"
+ ],
+ [
+ 158,
+ 85,
+ 0,
+ 84,
+ 0,
+ "INT"
+ ],
+ [
+ 159,
+ 85,
+ 0,
+ 83,
+ 0,
+ "INT"
+ ],
+ [
+ 160,
+ 87,
+ 0,
+ 185,
+ 3,
+ "INT"
+ ],
+ [
+ 161,
+ 87,
+ 0,
+ 186,
+ 3,
+ "INT"
+ ],
+ [
+ 162,
+ 87,
+ 0,
+ 171,
+ 1,
+ "INT"
+ ],
+ [
+ 163,
+ 87,
+ 0,
+ 159,
+ 1,
+ "INT"
+ ],
+ [
+ 165,
+ 87,
+ 0,
+ 194,
+ 6,
+ "INT"
+ ],
+ [
+ 166,
+ 87,
+ 0,
+ 191,
+ 1,
+ "INT"
+ ],
+ [
+ 167,
+ 86,
+ 0,
+ 87,
+ 0,
+ "*"
+ ],
+ [
+ 189,
+ 86,
+ 0,
+ 85,
+ 0,
+ "*"
+ ],
+ [
+ 213,
+ 240,
+ 0,
+ 239,
+ 0,
+ "SIMPLE_PROMPT_LIST"
+ ],
+ [
+ 214,
+ 243,
+ 0,
+ 244,
+ 0,
+ "PROMPT_LIST"
+ ],
+ [
+ 219,
+ 97,
+ 0,
+ 251,
+ 1,
+ "SCHEDULE"
+ ],
+ [
+ 220,
+ 249,
+ 0,
+ 97,
+ 0,
+ "*"
+ ],
+ [
+ 221,
+ 97,
+ 0,
+ 252,
+ 2,
+ "SCHEDULE"
+ ],
+ [
+ 223,
+ 97,
+ 0,
+ 256,
+ 0,
+ "SCHEDULE"
+ ],
+ [
+ 224,
+ 97,
+ 0,
+ 257,
+ 0,
+ "SCHEDULE"
+ ],
+ [
+ 227,
+ 86,
+ 0,
+ 260,
+ 0,
+ "*"
+ ],
+ [
+ 228,
+ 260,
+ 0,
+ 203,
+ 0,
+ "INT"
+ ],
+ [
+ 229,
+ 260,
+ 0,
+ 207,
+ 0,
+ "INT"
+ ],
+ [
+ 230,
+ 97,
+ 0,
+ 271,
+ 0,
+ "SCHEDULE"
+ ],
+ [
+ 231,
+ 260,
+ 0,
+ 269,
+ 0,
+ "INT"
+ ],
+ [
+ 234,
+ 86,
+ 0,
+ 271,
+ 1,
+ "INT"
+ ]
+ ],
+ "groups": [
+ {
+ "title": "Schedulers",
+ "bounding": [
+ 291,
+ 766,
+ 1535,
+ 576
+ ],
+ "color": "#8AA",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Index",
+ "bounding": [
+ -109,
+ -40,
+ 342,
+ 763
+ ],
+ "color": "#3f789e",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Interpolations",
+ "bounding": [
+ 1886,
+ 766,
+ 799,
+ 523
+ ],
+ "color": "#8AA",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Cyclers",
+ "bounding": [
+ 290,
+ 1441,
+ 1540,
+ 598
+ ],
+ "color": "#8AA",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Lists",
+ "bounding": [
+ 1190,
+ -158,
+ 1122,
+ 827
+ ],
+ "color": "#a1309b",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Prompt Keyframes",
+ "bounding": [
+ -1219,
+ 768,
+ 1031,
+ 1181
+ ],
+ "color": "#8A8",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Schedules",
+ "bounding": [
+ 290,
+ -224,
+ 858,
+ 893
+ ],
+ "color": "#a1309b",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Utils",
+ "bounding": [
+ -951,
+ 383,
+ 355,
+ 342
+ ],
+ "color": "#3f789e",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "IO",
+ "bounding": [
+ -556,
+ -41,
+ 409,
+ 765
+ ],
+ "color": "#3f789e",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Interpolation",
+ "bounding": [
+ 2363,
+ 433,
+ 383,
+ 234
+ ],
+ "color": "#3f789e",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Simple Schedulers",
+ "bounding": [
+ 1890,
+ 1438,
+ 878,
+ 600
+ ],
+ "color": "#8AA",
+ "font_size": 24,
+ "locked": false
+ },
+ {
+ "title": "Prompt",
+ "bounding": [
+ -146,
+ 769,
+ 378,
+ 603
+ ],
+ "color": "#8A8",
+ "font_size": 24,
+ "locked": false
+ }
+ ],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Aspect Ratio/CR_Aspect_Ratio_demo_v01.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Aspect Ratio/CR_Aspect_Ratio_demo_v01.json
new file mode 100644
index 0000000000000000000000000000000000000000..43ed3bed9c2d5fe309feb6111b2583eeaae522b6
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Aspect Ratio/CR_Aspect_Ratio_demo_v01.json
@@ -0,0 +1,394 @@
+{
+ "last_node_id": 7,
+ "last_link_id": 9,
+ "nodes": [
+ {
+ "id": 2,
+ "type": "VAEDecode",
+ "pos": [
+ 1300,
+ 560
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 1
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 3
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 2
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 5,
+ "type": "VAEDecode",
+ "pos": [
+ 1300,
+ 290
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 5
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 6
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 4
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 7,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 1010,
+ 290
+ ],
+ "size": [
+ 240,
+ 110
+ ],
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "link": 7,
+ "widget": {
+ "name": "width"
+ }
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "link": 8,
+ "widget": {
+ "name": "height"
+ }
+ },
+ {
+ "name": "batch_size",
+ "type": "INT",
+ "link": 9,
+ "widget": {
+ "name": "batch_size"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 5
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ 1
+ ]
+ },
+ {
+ "id": 6,
+ "type": "PreviewImage",
+ "pos": [
+ 1560,
+ 290
+ ],
+ "size": [
+ 260,
+ 200
+ ],
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 4
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 3,
+ "type": "PreviewImage",
+ "pos": [
+ 1560,
+ 560
+ ],
+ "size": [
+ 260,
+ 200
+ ],
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 1,
+ "type": "CR Aspect Ratio",
+ "pos": [
+ 580,
+ 410
+ ],
+ "size": {
+ "0": 315,
+ "1": 322
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "links": [
+ 7
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "links": [
+ 8
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "upscale_factor",
+ "type": "FLOAT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "prescale_factor",
+ "type": "FLOAT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "batch_size",
+ "type": "INT",
+ "links": [
+ 9
+ ],
+ "shape": 3,
+ "slot_index": 4
+ },
+ {
+ "name": "empty_latent",
+ "type": "LATENT",
+ "links": [
+ 1
+ ],
+ "shape": 3,
+ "slot_index": 5
+ },
+ {
+ "name": "show_help",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Aspect Ratio"
+ },
+ "widgets_values": [
+ 1024,
+ 1024,
+ "SDXL - 3:4 portrait 896x1152",
+ "Off",
+ 1,
+ 1,
+ 2
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 4,
+ "type": "VAELoader",
+ "pos": [
+ 580,
+ 260
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 3,
+ 6
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "sdxl_vae_fixed.safetensors"
+ ]
+ }
+ ],
+ "links": [
+ [
+ 1,
+ 1,
+ 5,
+ 2,
+ 0,
+ "LATENT"
+ ],
+ [
+ 2,
+ 2,
+ 0,
+ 3,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 3,
+ 4,
+ 0,
+ 2,
+ 1,
+ "VAE"
+ ],
+ [
+ 4,
+ 5,
+ 0,
+ 6,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5,
+ 7,
+ 0,
+ 5,
+ 0,
+ "LATENT"
+ ],
+ [
+ 6,
+ 4,
+ 0,
+ 5,
+ 1,
+ "VAE"
+ ],
+ [
+ 7,
+ 1,
+ 0,
+ 7,
+ 0,
+ "INT"
+ ],
+ [
+ 8,
+ 1,
+ 1,
+ 7,
+ 1,
+ "INT"
+ ],
+ [
+ 9,
+ 1,
+ 4,
+ 7,
+ 2,
+ "INT"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Graphics/Pattern/CR_Gradient_Nodes_v1.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Graphics/Pattern/CR_Gradient_Nodes_v1.json
new file mode 100644
index 0000000000000000000000000000000000000000..f8856b35b0e3a752817bf625363f5e0af79cc7de
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Graphics/Pattern/CR_Gradient_Nodes_v1.json
@@ -0,0 +1,1586 @@
+{
+ "last_node_id": 75,
+ "last_link_id": 136,
+ "nodes": [
+ {
+ "id": 40,
+ "type": "PreviewImage",
+ "pos": [
+ 1700,
+ 400
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 40,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 117
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 42,
+ "type": "PreviewImage",
+ "pos": [
+ 2480,
+ 90
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 26,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 120
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 57,
+ "type": "PreviewImage",
+ "pos": [
+ 2480,
+ 400
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 25,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 127
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 38,
+ "type": "PreviewImage",
+ "pos": [
+ 2480,
+ 710
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 21,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 116
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 44,
+ "type": "PreviewImage",
+ "pos": [
+ 2480,
+ 1330
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 23,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 121
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 59,
+ "type": "PreviewImage",
+ "pos": [
+ 2480,
+ 1020
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 22,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 128
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 34,
+ "type": "CR Style Bars",
+ "pos": [
+ 2110,
+ 710
+ ],
+ "size": {
+ "0": 315,
+ "1": 178
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 116
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Style Bars"
+ },
+ "widgets_values": [
+ "sin wave",
+ 512,
+ 512,
+ "plasma",
+ "vertical",
+ 3
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 58,
+ "type": "CR Style Bars",
+ "pos": [
+ 2110,
+ 1020
+ ],
+ "size": {
+ "0": 315,
+ "1": 178
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 128
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Style Bars"
+ },
+ "widgets_values": [
+ "sin wave",
+ 512,
+ 512,
+ "ocean",
+ "horizontal",
+ 3
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 61,
+ "type": "PreviewImage",
+ "pos": [
+ 2480,
+ 1650
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 24,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 129
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 43,
+ "type": "CR Style Bars",
+ "pos": [
+ 2110,
+ 1330
+ ],
+ "size": {
+ "0": 315,
+ "1": 178
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 121
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Style Bars"
+ },
+ "widgets_values": [
+ "gradient bars",
+ 512,
+ 512,
+ "Pastel1",
+ "vertical",
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 60,
+ "type": "CR Style Bars",
+ "pos": [
+ 2110,
+ 1650
+ ],
+ "size": {
+ "0": 315,
+ "1": 178
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 129
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Style Bars"
+ },
+ "widgets_values": [
+ "gradient bars",
+ 512,
+ 512,
+ "magma",
+ "horizontal",
+ 2
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 56,
+ "type": "CR Style Bars",
+ "pos": [
+ 2110,
+ 400
+ ],
+ "size": {
+ "0": 315,
+ "1": 178
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 127
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Style Bars"
+ },
+ "widgets_values": [
+ "color bars",
+ 512,
+ 512,
+ "cubehelix",
+ "horizontal",
+ 5
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 41,
+ "type": "CR Style Bars",
+ "pos": [
+ 2110,
+ 90
+ ],
+ "size": {
+ "0": 315,
+ "1": 178
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 120
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Style Bars"
+ },
+ "widgets_values": [
+ "color bars",
+ 512,
+ 512,
+ "Accent",
+ "vertical",
+ 4
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 37,
+ "type": "PreviewImage",
+ "pos": [
+ 900,
+ 90
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 32,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 119
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 50,
+ "type": "PreviewImage",
+ "pos": [
+ 900,
+ 420
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 29,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 123
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 52,
+ "type": "PreviewImage",
+ "pos": [
+ 900,
+ 760
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 33,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 124
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 72,
+ "type": "PreviewImage",
+ "pos": [
+ 900,
+ 1100
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 34,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 134
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 68,
+ "type": "PreviewImage",
+ "pos": [
+ 900,
+ 1490
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 35,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 132
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 64,
+ "type": "PreviewImage",
+ "pos": [
+ 120,
+ 530
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 28,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 130
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 48,
+ "type": "PreviewImage",
+ "pos": [
+ 1700,
+ 1660
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 37,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 136
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 39,
+ "type": "PreviewImage",
+ "pos": [
+ 1700,
+ 940
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 39,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 118
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 73,
+ "type": "CR Starburst Colors",
+ "pos": [
+ -250,
+ 1310
+ ],
+ "size": {
+ "0": 315,
+ "1": 298
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 135
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Starburst Colors"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ 14,
+ "fuchsia",
+ "lightgray",
+ 0,
+ 0,
+ 15,
+ 2,
+ "#0011AA",
+ "#007711"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 62,
+ "type": "CR Starburst Lines",
+ "pos": [
+ -250,
+ 530
+ ],
+ "size": {
+ "0": 315,
+ "1": 322
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 130
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Starburst Lines"
+ },
+ "widgets_values": [
+ 511,
+ 512,
+ 6,
+ 256,
+ 5,
+ "orange",
+ "green",
+ 0,
+ 0,
+ 30,
+ "#00FF33",
+ "#0033AA"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 49,
+ "type": "CR Color Bars",
+ "pos": [
+ 530,
+ 420
+ ],
+ "size": {
+ "0": 315,
+ "1": 274
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 123
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Color Bars"
+ },
+ "widgets_values": [
+ "2-color",
+ 512,
+ 512,
+ "black",
+ "red",
+ "horizontal",
+ 3,
+ 0,
+ "#000000",
+ "#000000"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 69,
+ "type": "PreviewImage",
+ "pos": [
+ 900,
+ 1880
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 36,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 133
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 65,
+ "type": "PreviewImage",
+ "pos": [
+ 120,
+ 930
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 30,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 131
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 74,
+ "type": "PreviewImage",
+ "pos": [
+ 120,
+ 1310
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 27,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 135
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 2,
+ "type": "PreviewImage",
+ "pos": [
+ 120,
+ 90
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 31,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 115
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 46,
+ "type": "PreviewImage",
+ "pos": [
+ 1700,
+ 90
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 41,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 122
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 54,
+ "type": "PreviewImage",
+ "pos": [
+ 1700,
+ 1270
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 38,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 126
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 63,
+ "type": "CR Starburst Colors",
+ "pos": [
+ -250,
+ 930
+ ],
+ "size": {
+ "0": 310,
+ "1": 300
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 131
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Starburst Colors"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ 14,
+ "navy",
+ "yellow",
+ 0,
+ 0,
+ 15,
+ 1,
+ "#440000",
+ "#003377"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 32,
+ "type": "CR Halftone Grid",
+ "pos": [
+ -240,
+ 90
+ ],
+ "size": {
+ "0": 310,
+ "1": 250
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 115
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Halftone Grid"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ "plasma",
+ "No",
+ 20,
+ "black",
+ 0,
+ 0,
+ "#001133"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 33,
+ "type": "CR Color Bars",
+ "pos": [
+ 530,
+ 90
+ ],
+ "size": {
+ "0": 315,
+ "1": 274
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 119
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Color Bars"
+ },
+ "widgets_values": [
+ "2-color",
+ 512,
+ 512,
+ "yellow",
+ "pink",
+ "vertical",
+ 5,
+ 0,
+ "#220077",
+ "#880000"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 51,
+ "type": "CR Color Bars",
+ "pos": [
+ 530,
+ 760
+ ],
+ "size": {
+ "0": 315,
+ "1": 274
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 124
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Color Bars"
+ },
+ "widgets_values": [
+ "2-color",
+ 512,
+ 512,
+ "orange",
+ "white",
+ "alt_diagonal",
+ 10,
+ 0,
+ "#00BB00",
+ "#0000CC"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 71,
+ "type": "CR Color Bars",
+ "pos": [
+ 530,
+ 1100
+ ],
+ "size": {
+ "0": 315,
+ "1": 274
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 134
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Color Bars"
+ },
+ "widgets_values": [
+ "2-color",
+ 512,
+ 512,
+ "navy",
+ "black",
+ "diagonal",
+ 10,
+ 0,
+ "#550000",
+ "#007700"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 66,
+ "type": "CR Polygons",
+ "pos": [
+ 530,
+ 1490
+ ],
+ "size": {
+ "0": 315,
+ "1": 322
+ },
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 132
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Polygons"
+ },
+ "widgets_values": [
+ "hexagons",
+ 512,
+ 512,
+ 5,
+ 5,
+ "yellow",
+ "white",
+ "black",
+ 5,
+ "#55BB00",
+ "#335500",
+ "#003344"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 67,
+ "type": "CR Polygons",
+ "pos": [
+ 530,
+ 1880
+ ],
+ "size": {
+ "0": 315,
+ "1": 322
+ },
+ "flags": {},
+ "order": 15,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 133
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Polygons"
+ },
+ "widgets_values": [
+ "triangles",
+ 512,
+ 512,
+ 5,
+ 5,
+ "maroon",
+ "blue",
+ "lavender",
+ 9,
+ "#770099",
+ "#770000",
+ "#006699"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 75,
+ "type": "CR Radial Gradient",
+ "pos": [
+ 1310,
+ 1660
+ ],
+ "size": {
+ "0": 315,
+ "1": 274
+ },
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 136
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Radial Gradient"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ "orange",
+ "purple",
+ 1,
+ 0.5,
+ 0.5,
+ "#6600FF",
+ "#004400"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 53,
+ "type": "CR Color Gradient",
+ "pos": [
+ 1310,
+ 1270
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 126
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Color Gradient"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ "green",
+ "red",
+ 0.75,
+ 0.25,
+ "horizontal",
+ "#770000",
+ "#008800"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 36,
+ "type": "CR Color Gradient",
+ "pos": [
+ 1310,
+ 940
+ ],
+ "size": {
+ "0": 310,
+ "1": 250
+ },
+ "flags": {},
+ "order": 18,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 118
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Color Gradient"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ "yellow",
+ "blue",
+ 0.5,
+ 0.5,
+ "vertical",
+ "#5555000",
+ "#004444"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 35,
+ "type": "CR Checker Pattern",
+ "pos": [
+ 1310,
+ 400
+ ],
+ "size": {
+ "0": 315,
+ "1": 250
+ },
+ "flags": {},
+ "order": 19,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 117
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Checker Pattern"
+ },
+ "widgets_values": [
+ "stepped",
+ 512,
+ 512,
+ "aqua",
+ "fuchsia",
+ 8,
+ 3,
+ "#004400",
+ "#000099"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 45,
+ "type": "CR Checker Pattern",
+ "pos": [
+ 1310,
+ 90
+ ],
+ "size": {
+ "0": 315,
+ "1": 250
+ },
+ "flags": {},
+ "order": 20,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 122
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Checker Pattern"
+ },
+ "widgets_values": [
+ "regular",
+ 512,
+ 512,
+ "black",
+ "white",
+ 8,
+ 2,
+ "#0044BB",
+ "#007788"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ }
+ ],
+ "links": [
+ [
+ 115,
+ 32,
+ 0,
+ 2,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 116,
+ 34,
+ 0,
+ 38,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 117,
+ 35,
+ 0,
+ 40,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 118,
+ 36,
+ 0,
+ 39,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 119,
+ 33,
+ 0,
+ 37,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 120,
+ 41,
+ 0,
+ 42,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 121,
+ 43,
+ 0,
+ 44,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 122,
+ 45,
+ 0,
+ 46,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 123,
+ 49,
+ 0,
+ 50,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 124,
+ 51,
+ 0,
+ 52,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 126,
+ 53,
+ 0,
+ 54,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 127,
+ 56,
+ 0,
+ 57,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 128,
+ 58,
+ 0,
+ 59,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 129,
+ 60,
+ 0,
+ 61,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 130,
+ 62,
+ 0,
+ 64,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 131,
+ 63,
+ 0,
+ 65,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 132,
+ 66,
+ 0,
+ 68,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 133,
+ 67,
+ 0,
+ 69,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 134,
+ 71,
+ 0,
+ 72,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 135,
+ 73,
+ 0,
+ 74,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 136,
+ 75,
+ 0,
+ 48,
+ 0,
+ "IMAGE"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Graphics/Pattern/CR_Starburst_Lines_demo_v01.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Graphics/Pattern/CR_Starburst_Lines_demo_v01.json
new file mode 100644
index 0000000000000000000000000000000000000000..508d5de0c255c6baa31a8fd54df81e4eb1d96823
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Graphics/Pattern/CR_Starburst_Lines_demo_v01.json
@@ -0,0 +1,198 @@
+{
+ "last_node_id": 9,
+ "last_link_id": 7,
+ "nodes": [
+ {
+ "id": 2,
+ "type": "PreviewImage",
+ "pos": [
+ 1190,
+ 620
+ ],
+ "size": {
+ "0": 320,
+ "1": 250
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 5,
+ "type": "PrimitiveNode",
+ "pos": [
+ 270,
+ 620
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 6
+ ],
+ "slot_index": 0,
+ "widget": {
+ "name": "a"
+ }
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ 180.5,
+ "increment"
+ ]
+ },
+ {
+ "id": 9,
+ "type": "JWFloatMul",
+ "pos": [
+ 540,
+ 620
+ ],
+ "size": [
+ 210,
+ 70
+ ],
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "a",
+ "type": "FLOAT",
+ "link": 6,
+ "widget": {
+ "name": "a"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 7
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "JWFloatMul"
+ },
+ "widgets_values": [
+ 180.5,
+ 20
+ ]
+ },
+ {
+ "id": 1,
+ "type": "CR Starburst Lines",
+ "pos": [
+ 820,
+ 620
+ ],
+ "size": [
+ 320,
+ 340
+ ],
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "rotation",
+ "type": "FLOAT",
+ "link": 7,
+ "widget": {
+ "name": "rotation"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 1
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_help",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Starburst Lines"
+ },
+ "widgets_values": [
+ 511,
+ 512,
+ 6,
+ 2,
+ 50,
+ "blue",
+ "yellow",
+ 0,
+ 0,
+ 60,
+ "#00FF33",
+ "#0033AA"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ }
+ ],
+ "links": [
+ [
+ 1,
+ 1,
+ 0,
+ 2,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 6,
+ 5,
+ 0,
+ 9,
+ 0,
+ "FLOAT"
+ ],
+ [
+ 7,
+ 9,
+ 0,
+ 1,
+ 0,
+ "FLOAT"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Graphics/Template/CR_Simple_Meme_Template_Demo1_v01.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Graphics/Template/CR_Simple_Meme_Template_Demo1_v01.json
new file mode 100644
index 0000000000000000000000000000000000000000..2a6e64d26c2a8b3b3a50561e7382b37522890186
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Graphics/Template/CR_Simple_Meme_Template_Demo1_v01.json
@@ -0,0 +1,149 @@
+{
+ "last_node_id": 60,
+ "last_link_id": 65,
+ "nodes": [
+ {
+ "id": 9,
+ "type": "LoadImage",
+ "pos": [
+ 140,
+ 0
+ ],
+ "size": [
+ 320,
+ 310
+ ],
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 22
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "SDXL_00006_.png",
+ "image"
+ ]
+ },
+ {
+ "id": 35,
+ "type": "SaveImage",
+ "pos": [
+ 150,
+ 760
+ ],
+ "size": {
+ "0": 320,
+ "1": 270
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 37
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "CR"
+ ]
+ },
+ {
+ "id": 21,
+ "type": "CR Simple Meme Template",
+ "pos": [
+ 130,
+ 390
+ ],
+ "size": {
+ "0": 400,
+ "1": 314
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 22
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 37
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_help",
+ "type": "STRING",
+ "links": [],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Meme Template"
+ },
+ "widgets_values": [
+ "custom",
+ "One Does Not Simply",
+ "MEME IN COMFY",
+ "impact.ttf",
+ 150,
+ "white",
+ "thick",
+ "white",
+ "no bars"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ }
+ ],
+ "links": [
+ [
+ 22,
+ 9,
+ 0,
+ 21,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 37,
+ 21,
+ 0,
+ 35,
+ 0,
+ "IMAGE"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Graphics/Template/CR_Simple_Meme_Template_Demo2_v01.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Graphics/Template/CR_Simple_Meme_Template_Demo2_v01.json
new file mode 100644
index 0000000000000000000000000000000000000000..8f539d00beba16dedfabb81f7227558ff893ff0f
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Graphics/Template/CR_Simple_Meme_Template_Demo2_v01.json
@@ -0,0 +1,606 @@
+{
+ "last_node_id": 59,
+ "last_link_id": 58,
+ "nodes": [
+ {
+ "id": 35,
+ "type": "SaveImage",
+ "pos": [
+ 130,
+ 770
+ ],
+ "size": {
+ "0": 320,
+ "1": 270
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 37
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "CR"
+ ]
+ },
+ {
+ "id": 49,
+ "type": "SaveImage",
+ "pos": [
+ 580,
+ 770
+ ],
+ "size": {
+ "0": 320,
+ "1": 270
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 51
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "CR"
+ ]
+ },
+ {
+ "id": 52,
+ "type": "SaveImage",
+ "pos": [
+ 1030,
+ 770
+ ],
+ "size": {
+ "0": 320,
+ "1": 270
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 53
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "CR"
+ ]
+ },
+ {
+ "id": 47,
+ "type": "LoadImage",
+ "pos": [
+ 590,
+ 0
+ ],
+ "size": [
+ 320,
+ 310
+ ],
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 50
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "SDXL10__00108_ (1).png",
+ "image"
+ ]
+ },
+ {
+ "id": 55,
+ "type": "SaveImage",
+ "pos": [
+ 1480,
+ 770
+ ],
+ "size": {
+ "0": 320,
+ "1": 270
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 55
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "CR"
+ ]
+ },
+ {
+ "id": 53,
+ "type": "LoadImage",
+ "pos": [
+ 1490,
+ 0
+ ],
+ "size": [
+ 320,
+ 310
+ ],
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 54
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "SDXL10__00108_ (1).png",
+ "image"
+ ]
+ },
+ {
+ "id": 50,
+ "type": "LoadImage",
+ "pos": [
+ 1040,
+ 0
+ ],
+ "size": [
+ 320,
+ 310
+ ],
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 52
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "SDXL10__00108_ (1).png",
+ "image"
+ ]
+ },
+ {
+ "id": 59,
+ "type": "ShowText|pysssss",
+ "pos": [
+ 1920,
+ 410
+ ],
+ "size": {
+ "0": 430,
+ "1": 170
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 58,
+ "widget": {
+ "name": "text"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "Help:\n \n The two text entry boxes are for the top and bottom text.\n these can be added either on a color bar or as an overlay.\n Both top and bottom text are optional.\n \n Only the first two lines will be used for top and bottom text.\n If you enter more than two lines any additional lines will be ignored.\n \n If you enter both top and bottom text and select a single bar (top or bottom),\n then one of texts will be ouput as overlay text.\n \n If you enter both top and bottom text and select no bars,\n then both texts will be ouput as overlay text."
+ ]
+ },
+ {
+ "id": 9,
+ "type": "LoadImage",
+ "pos": [
+ 140,
+ 0
+ ],
+ "size": [
+ 320,
+ 310
+ ],
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 22
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "SDXL10__00108_ (1).png",
+ "image"
+ ]
+ },
+ {
+ "id": 21,
+ "type": "CR Simple Meme Template",
+ "pos": [
+ 130,
+ 390
+ ],
+ "size": {
+ "0": 400,
+ "1": 314
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 22
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 37
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_help",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Meme Template"
+ },
+ "widgets_values": [
+ "custom",
+ "Make A Meme Make A Meme Make A Meme\nMake A Meme Make A Meme Make A Meme",
+ "impact.ttf",
+ "impact.ttf",
+ 150,
+ "white",
+ "thick",
+ "black",
+ "no bars"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 48,
+ "type": "CR Simple Meme Template",
+ "pos": [
+ 580,
+ 390
+ ],
+ "size": {
+ "0": 400,
+ "1": 314
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 50
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 51
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_help",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Meme Template"
+ },
+ "widgets_values": [
+ "custom",
+ "Make A Meme",
+ "impact.ttf",
+ "impact.ttf",
+ 150,
+ "white",
+ "thick",
+ "black",
+ "top"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 51,
+ "type": "CR Simple Meme Template",
+ "pos": [
+ 1030,
+ 390
+ ],
+ "size": {
+ "0": 400,
+ "1": 314
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 52
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 53
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_help",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Meme Template"
+ },
+ "widgets_values": [
+ "custom",
+ "Make A Meme",
+ "impact.ttf",
+ "impact.ttf",
+ 150,
+ "white",
+ "thick",
+ "black",
+ "bottom"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 54,
+ "type": "CR Simple Meme Template",
+ "pos": [
+ 1480,
+ 390
+ ],
+ "size": {
+ "0": 400,
+ "1": 314
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 54
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 55
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_help",
+ "type": "STRING",
+ "links": [
+ 58
+ ],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Meme Template"
+ },
+ "widgets_values": [
+ "custom",
+ "Make A Meme",
+ "impact.ttf",
+ "impact.ttf",
+ 150,
+ "white",
+ "thick",
+ "black",
+ "top and bottom"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ }
+ ],
+ "links": [
+ [
+ 22,
+ 9,
+ 0,
+ 21,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 37,
+ 21,
+ 0,
+ 35,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 50,
+ 47,
+ 0,
+ 48,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 51,
+ 48,
+ 0,
+ 49,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 52,
+ 50,
+ 0,
+ 51,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 53,
+ 51,
+ 0,
+ 52,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 54,
+ 53,
+ 0,
+ 54,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 55,
+ 54,
+ 0,
+ 55,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 58,
+ 54,
+ 1,
+ 59,
+ 0,
+ "STRING"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Graphics/Template/CR_Simple_Meme_Template_Demo3_v01.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Graphics/Template/CR_Simple_Meme_Template_Demo3_v01.json
new file mode 100644
index 0000000000000000000000000000000000000000..1f25343283e84eb494ef7745ccfb4b1269c7930a
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Graphics/Template/CR_Simple_Meme_Template_Demo3_v01.json
@@ -0,0 +1,302 @@
+{
+ "last_node_id": 18,
+ "last_link_id": 27,
+ "nodes": [
+ {
+ "id": 16,
+ "type": "PreviewImage",
+ "pos": [
+ 790,
+ 30
+ ],
+ "size": {
+ "0": 270,
+ "1": 360
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 23
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 17,
+ "type": "CR Simple Meme Template",
+ "pos": [
+ 310,
+ 30
+ ],
+ "size": {
+ "0": 400,
+ "1": 362
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 24
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 23
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "show_help",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Simple Meme Template"
+ },
+ "widgets_values": [
+ "One Does Not Simply ... MEME IN COMFY",
+ "text_top",
+ "text_bottom",
+ "impact.ttf",
+ 150,
+ "white",
+ "none",
+ "black",
+ "top and bottom",
+ "#000000",
+ "#000000"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 18,
+ "type": "ImpactMakeImageBatch",
+ "pos": [
+ 40,
+ 30
+ ],
+ "size": [
+ 180,
+ 90
+ ],
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image1",
+ "type": "IMAGE",
+ "link": 25
+ },
+ {
+ "name": "image2",
+ "type": "IMAGE",
+ "link": 26
+ },
+ {
+ "name": "image3",
+ "type": "IMAGE",
+ "link": 27
+ },
+ {
+ "name": "image4",
+ "type": "IMAGE",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 24
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ImpactMakeImageBatch"
+ }
+ },
+ {
+ "id": 4,
+ "type": "LoadImage",
+ "pos": [
+ -750,
+ 10
+ ],
+ "size": {
+ "0": 210,
+ "1": 360
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 25
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "SDXL10__00007_.png",
+ "image"
+ ]
+ },
+ {
+ "id": 9,
+ "type": "LoadImage",
+ "pos": [
+ -500,
+ 10
+ ],
+ "size": [
+ 210,
+ 360
+ ],
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 26
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "SDXL10__00008_.png",
+ "image"
+ ]
+ },
+ {
+ "id": 13,
+ "type": "LoadImage",
+ "pos": [
+ -250,
+ 10
+ ],
+ "size": [
+ 210,
+ 360
+ ],
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 27
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "SDXL10__00009_ (1).png",
+ "image"
+ ]
+ }
+ ],
+ "links": [
+ [
+ 23,
+ 17,
+ 0,
+ 16,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 24,
+ 18,
+ 0,
+ 17,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 25,
+ 4,
+ 0,
+ 18,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 26,
+ 9,
+ 0,
+ 18,
+ 1,
+ "IMAGE"
+ ],
+ [
+ 27,
+ 13,
+ 0,
+ 18,
+ 2,
+ "IMAGE"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Model Merge/CR_SDXL_MultiModelGradientMerge_v01b.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Model Merge/CR_SDXL_MultiModelGradientMerge_v01b.json
new file mode 100644
index 0000000000000000000000000000000000000000..2cb756e80f834242825c1e40760001738b9729d1
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Model Merge/CR_SDXL_MultiModelGradientMerge_v01b.json
@@ -0,0 +1,3072 @@
+{
+ "last_node_id": 243,
+ "last_link_id": 588,
+ "nodes": [
+ {
+ "id": 125,
+ "type": "UpscaleModelLoader",
+ "pos": [
+ 2271.464720934434,
+ -42.51139164477244
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "UPSCALE_MODEL",
+ "type": "UPSCALE_MODEL",
+ "links": [
+ 363
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "UpscaleModelLoader"
+ },
+ "widgets_values": [
+ "RealESRGAN_x2.pth"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 177,
+ "type": "Note",
+ "pos": [
+ -507.1336667459445,
+ 828.9509770334472
+ ],
+ "size": {
+ "0": 315.0569152832031,
+ "1": 64.73121643066406
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "title": "Save Model",
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "To unhide this node, right click then Mode > Always\n\n"
+ ],
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 174,
+ "type": "VAELoader",
+ "pos": [
+ 68.82123096767066,
+ 238.46804881065552
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 503
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "sdxl_vae.safetensors"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 205,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ 630,
+ -50
+ ],
+ "size": {
+ "0": 458,
+ "1": 132
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 538
+ ],
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 531
+ ],
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null
+ }
+ ],
+ "title": "XL Refiner Model",
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "SDXL\\sd_xl_refiner_1.0.safetensors"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 206,
+ "type": "SeargePromptText",
+ "pos": [
+ 671.3846854124997,
+ 386.538436474219
+ ],
+ "size": {
+ "0": 400,
+ "1": 200
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "prompt",
+ "type": "STRING",
+ "links": [
+ 532,
+ 533,
+ 534
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "SeargePromptText"
+ },
+ "widgets_values": [
+ "A vibrant 20-year-old woman with a free-spirited style, strolling gracefully through a picturesque meadow adorned with a myriad of colorful flowers. She radiates the essence of summer, her flowing dress and long, sun-kissed hair adding to the natural beauty of the scene"
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 207,
+ "type": "SeargePromptText",
+ "pos": [
+ 671.3846854124997,
+ 666.5384364742179
+ ],
+ "size": {
+ "0": 400,
+ "1": 200
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "prompt",
+ "type": "STRING",
+ "links": [
+ 535,
+ 536,
+ 537
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "SeargePromptText"
+ },
+ "widgets_values": [
+ ""
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 208,
+ "type": "SeargeSamplerInputs",
+ "pos": [
+ 1641.0815801410135,
+ 62.855455210877295
+ ],
+ "size": {
+ "0": 315,
+ "1": 102
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "sampler_name",
+ "type": "SAMPLER_NAME",
+ "links": [
+ 528
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "scheduler",
+ "type": "SCHEDULER_NAME",
+ "links": [
+ 529
+ ],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "SeargeSamplerInputs"
+ },
+ "widgets_values": [
+ "dpmpp_2m",
+ "karras"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 160,
+ "type": "ModelMergeSimple",
+ "pos": [
+ -1318.4105971698255,
+ 476.88168464943556
+ ],
+ "size": {
+ "0": 230,
+ "1": 80
+ },
+ "flags": {
+ "collapsed": false
+ },
+ "order": 33,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model1",
+ "type": "MODEL",
+ "link": 575
+ },
+ {
+ "name": "model2",
+ "type": "MODEL",
+ "link": 574
+ },
+ {
+ "name": "ratio",
+ "type": "FLOAT",
+ "link": 566,
+ "widget": {
+ "name": "ratio",
+ "config": [
+ "FLOAT",
+ {
+ "default": 1,
+ "min": 0,
+ "max": 1,
+ "step": 0.01
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 464
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "Model Merge",
+ "properties": {
+ "Node name for S&R": "ModelMergeSimple"
+ },
+ "widgets_values": [
+ 0.5
+ ]
+ },
+ {
+ "id": 172,
+ "type": "CR LoRA Stack",
+ "pos": [
+ -972.1116192382817,
+ 468.55508251673456
+ ],
+ "size": {
+ "0": 315,
+ "1": 322
+ },
+ "flags": {},
+ "order": 23,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "lora_stack",
+ "type": "LORA_STACK",
+ "link": 461
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LORA_STACK",
+ "type": "LORA_STACK",
+ "links": [
+ 462
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "XL LoRA Stack 2",
+ "properties": {
+ "Node name for S&R": "CR LoRA Stack"
+ },
+ "widgets_values": [
+ "Off",
+ "None",
+ 1,
+ 1,
+ "Off",
+ "None",
+ 1,
+ 1,
+ "Off",
+ "None",
+ 1,
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 183,
+ "type": "Reroute",
+ "pos": [
+ 1430,
+ 100
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 21,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 489
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 547
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 188,
+ "type": "Reroute",
+ "pos": [
+ 510,
+ 170
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 18,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 503
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 504
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 209,
+ "type": "Reroute",
+ "pos": [
+ 1430,
+ 250
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 19,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 538
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 524
+ ]
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 95,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 530,
+ -160
+ ],
+ "size": {
+ "0": 210,
+ "1": 74
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 20,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "link": 540,
+ "widget": {
+ "name": "width",
+ "config": [
+ "INT",
+ {
+ "default": 512,
+ "min": 64,
+ "max": 8192,
+ "step": 8
+ }
+ ]
+ }
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "link": 541,
+ "widget": {
+ "name": "height",
+ "config": [
+ "INT",
+ {
+ "default": 512,
+ "min": 64,
+ "max": 8192,
+ "step": 8
+ }
+ ]
+ }
+ },
+ {
+ "name": "batch_size",
+ "type": "INT",
+ "link": 542,
+ "widget": {
+ "name": "batch_size",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": 1,
+ "max": 64
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 549
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ 1
+ ]
+ },
+ {
+ "id": 161,
+ "type": "CLIPMergeSimple",
+ "pos": [
+ -1320.2519182689223,
+ 609.9827772677869
+ ],
+ "size": {
+ "0": 230,
+ "1": 80
+ },
+ "flags": {
+ "collapsed": false
+ },
+ "order": 35,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip1",
+ "type": "CLIP",
+ "link": 555
+ },
+ {
+ "name": "clip2",
+ "type": "CLIP",
+ "link": 556
+ },
+ {
+ "name": "ratio",
+ "type": "FLOAT",
+ "link": 567,
+ "widget": {
+ "name": "ratio",
+ "config": [
+ "FLOAT",
+ {
+ "default": 1,
+ "min": 0,
+ "max": 1,
+ "step": 0.01
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 463
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "CLIP Merge",
+ "properties": {
+ "Node name for S&R": "CLIPMergeSimple"
+ },
+ "widgets_values": [
+ 0.5
+ ]
+ },
+ {
+ "id": 212,
+ "type": "Reroute",
+ "pos": [
+ 1150,
+ -190
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 26,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 549
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 550
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 211,
+ "type": "Reroute",
+ "pos": [
+ 1430,
+ 140
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 31,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 550
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 546
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 185,
+ "type": "Reroute",
+ "pos": [
+ -220,
+ 410
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 39,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 494
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 551
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 167,
+ "type": "CheckpointSave",
+ "pos": [
+ -505.1336667459445,
+ 656.9509770334475
+ ],
+ "size": {
+ "0": 315,
+ "1": 98
+ },
+ "flags": {},
+ "order": 40,
+ "mode": 2,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 468
+ },
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 467
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 498
+ }
+ ],
+ "title": "Checkpoint Save",
+ "properties": {
+ "Node name for S&R": "CheckpointSave"
+ },
+ "widgets_values": [
+ "checkpoints/MyModel"
+ ]
+ },
+ {
+ "id": 184,
+ "type": "Reroute",
+ "pos": [
+ -220,
+ 470
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 41,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 495
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 552
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 197,
+ "type": "Reroute",
+ "pos": [
+ 480,
+ 410
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 42,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 551
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 543
+ ]
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 196,
+ "type": "Reroute",
+ "pos": [
+ 480,
+ 470
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 43,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 552
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 530
+ ]
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 189,
+ "type": "Reroute",
+ "pos": [
+ 1430,
+ 210
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 44,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 543
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 521
+ ]
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 204,
+ "type": "SeargeSDXLPromptEncoder",
+ "pos": [
+ 1230,
+ 390
+ ],
+ "size": {
+ "0": 311.32244873046875,
+ "1": 415.3662414550781
+ },
+ "flags": {},
+ "order": 45,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "base_clip",
+ "type": "CLIP",
+ "link": 530
+ },
+ {
+ "name": "refiner_clip",
+ "type": "CLIP",
+ "link": 531
+ },
+ {
+ "name": "pos_g",
+ "type": "STRING",
+ "link": 532,
+ "widget": {
+ "name": "pos_g",
+ "config": [
+ "STRING",
+ {
+ "multiline": true,
+ "default": "POS_G"
+ }
+ ]
+ }
+ },
+ {
+ "name": "pos_l",
+ "type": "STRING",
+ "link": 533,
+ "widget": {
+ "name": "pos_l",
+ "config": [
+ "STRING",
+ {
+ "multiline": true,
+ "default": "POS_L"
+ }
+ ]
+ }
+ },
+ {
+ "name": "pos_r",
+ "type": "STRING",
+ "link": 534,
+ "widget": {
+ "name": "pos_r",
+ "config": [
+ "STRING",
+ {
+ "multiline": true,
+ "default": "POS_R"
+ }
+ ]
+ }
+ },
+ {
+ "name": "neg_g",
+ "type": "STRING",
+ "link": 535,
+ "widget": {
+ "name": "neg_g",
+ "config": [
+ "STRING",
+ {
+ "multiline": true,
+ "default": "NEG_G"
+ }
+ ]
+ }
+ },
+ {
+ "name": "neg_l",
+ "type": "STRING",
+ "link": 536,
+ "widget": {
+ "name": "neg_l",
+ "config": [
+ "STRING",
+ {
+ "multiline": true,
+ "default": "NEG_L"
+ }
+ ]
+ }
+ },
+ {
+ "name": "neg_r",
+ "type": "STRING",
+ "link": 537,
+ "widget": {
+ "name": "neg_r",
+ "config": [
+ "STRING",
+ {
+ "multiline": true,
+ "default": "NEG_R"
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "base_positive",
+ "type": "CONDITIONING",
+ "links": [
+ 522
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "base_negative",
+ "type": "CONDITIONING",
+ "links": [
+ 523
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "refiner_positive",
+ "type": "CONDITIONING",
+ "links": [
+ 525
+ ],
+ "shape": 3,
+ "slot_index": 2
+ },
+ {
+ "name": "refiner_negative",
+ "type": "CONDITIONING",
+ "links": [
+ 526
+ ],
+ "shape": 3,
+ "slot_index": 3
+ }
+ ],
+ "title": "XL Prompt Encoder",
+ "properties": {
+ "Node name for S&R": "SeargeSDXLPromptEncoder"
+ },
+ "widgets_values": [
+ "POS_G",
+ "POS_L",
+ "POS_R",
+ "NEG_G",
+ "NEG_L",
+ "NEG_R",
+ 4096,
+ 4096,
+ 0,
+ 0,
+ 4096,
+ 4096,
+ 6,
+ 2.5,
+ 2048,
+ 2048
+ ]
+ },
+ {
+ "id": 118,
+ "type": "VAEDecode",
+ "pos": [
+ 2030,
+ 200
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 47,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 548
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 504
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 341,
+ 364
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 123,
+ "type": "PreviewImage",
+ "pos": [
+ 2240,
+ 210
+ ],
+ "size": {
+ "0": 520,
+ "1": 800
+ },
+ "flags": {},
+ "order": 48,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 341
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 137,
+ "type": "ImageUpscaleWithModel",
+ "pos": [
+ 2271.464720934434,
+ 67.48860835522774
+ ],
+ "size": {
+ "0": 241.79998779296875,
+ "1": 46
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 49,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "upscale_model",
+ "type": "UPSCALE_MODEL",
+ "link": 363
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 364
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 423
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ImageUpscaleWithModel"
+ }
+ },
+ {
+ "id": 157,
+ "type": "Image Levels Adjustment",
+ "pos": [
+ 2621.464720934434,
+ -42.51139164477244
+ ],
+ "size": {
+ "0": 315,
+ "1": 106
+ },
+ "flags": {
+ "collapsed": false
+ },
+ "order": 50,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 423
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 424
+ ],
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Image Levels Adjustment"
+ },
+ "widgets_values": [
+ 0,
+ 127.5,
+ 255
+ ]
+ },
+ {
+ "id": 135,
+ "type": "SaveImage",
+ "pos": [
+ 2810,
+ 180
+ ],
+ "size": {
+ "0": 520,
+ "1": 830
+ },
+ "flags": {},
+ "order": 51,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 424,
+ "slot_index": 0
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "Merge/Merge"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 173,
+ "type": "CR Apply LoRA Stack",
+ "pos": [
+ -550,
+ 420
+ ],
+ "size": {
+ "0": 210,
+ "1": 66
+ },
+ "flags": {
+ "collapsed": false
+ },
+ "order": 38,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 464
+ },
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 463
+ },
+ {
+ "name": "lora_stack",
+ "type": "LORA_STACK",
+ "link": 462
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 468,
+ 494
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 467,
+ 495
+ ],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "title": "Apply LoRA Stack",
+ "properties": {
+ "Node name for S&R": "CR Apply LoRA Stack"
+ }
+ },
+ {
+ "id": 210,
+ "type": "CR SDXL Aspect Ratio",
+ "pos": [
+ 70,
+ -190
+ ],
+ "size": {
+ "0": 315,
+ "1": 238
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "links": [
+ 540
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "links": [
+ 541
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "upscale_factor",
+ "type": "FLOAT",
+ "links": [],
+ "shape": 3,
+ "slot_index": 2
+ },
+ {
+ "name": "batch_size",
+ "type": "INT",
+ "links": [
+ 542
+ ],
+ "shape": 3,
+ "slot_index": 3
+ }
+ ],
+ "title": "SDXL Aspect Ratio",
+ "properties": {
+ "Node name for S&R": "CR SDXL Aspect Ratio"
+ },
+ "widgets_values": [
+ 1024,
+ 1024,
+ "3:4 portrait 896x1152",
+ "Off",
+ 1,
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 181,
+ "type": "CR Seed",
+ "pos": [
+ 68.82123096767066,
+ 98.46804881065545
+ ],
+ "size": {
+ "0": 315,
+ "1": 82
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "seed",
+ "type": "INT",
+ "links": [
+ 489
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "Seed",
+ "properties": {
+ "Node name for S&R": "CR Seed"
+ },
+ "widgets_values": [
+ 0,
+ "fixed"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 219,
+ "type": "CR Gradient Float",
+ "pos": [
+ -1340,
+ -80
+ ],
+ "size": {
+ "0": 250,
+ "1": 154
+ },
+ "flags": {},
+ "order": 27,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 564,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 567,
+ 568
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "CLIP Gradient",
+ "properties": {
+ "Node name for S&R": "CR Gradient Float"
+ },
+ "widgets_values": [
+ 1,
+ 0,
+ 0,
+ 10,
+ 0,
+ "Lerp"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 225,
+ "type": "CR Float To String",
+ "pos": [
+ -1640,
+ 130
+ ],
+ "size": {
+ "0": 220,
+ "1": 60
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 34,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "float_",
+ "type": "FLOAT",
+ "link": 571,
+ "widget": {
+ "name": "float_",
+ "config": [
+ "FLOAT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 1000000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 570
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Float To String"
+ },
+ "widgets_values": [
+ 0
+ ]
+ },
+ {
+ "id": 224,
+ "type": "ShowText|pysssss",
+ "pos": [
+ -1650,
+ 200
+ ],
+ "size": {
+ "0": 220,
+ "1": 80
+ },
+ "flags": {},
+ "order": 37,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 570,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "forceInput": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "0.7"
+ ]
+ },
+ {
+ "id": 222,
+ "type": "CR Float To String",
+ "pos": [
+ -1330,
+ 130
+ ],
+ "size": {
+ "0": 220,
+ "1": 60
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 32,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "float_",
+ "type": "FLOAT",
+ "link": 568,
+ "widget": {
+ "name": "float_",
+ "config": [
+ "FLOAT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 1000000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 569
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Float To String"
+ },
+ "widgets_values": [
+ 0
+ ]
+ },
+ {
+ "id": 218,
+ "type": "CR Current Frame",
+ "pos": [
+ -1330,
+ -160
+ ],
+ "size": {
+ "0": 250,
+ "1": 80
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 22,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "link": 563,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "index",
+ "type": "INT",
+ "links": [
+ 564,
+ 565
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Current Frame"
+ },
+ "widgets_values": [
+ 3,
+ "Yes"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 221,
+ "type": "ShowText|pysssss",
+ "pos": [
+ -1330,
+ 200
+ ],
+ "size": {
+ "0": 220,
+ "1": 80
+ },
+ "flags": {},
+ "order": 36,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 569,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "forceInput": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": null,
+ "shape": 6
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ShowText|pysssss"
+ },
+ "widgets_values": [
+ "0.7"
+ ]
+ },
+ {
+ "id": 186,
+ "type": "VAELoader",
+ "pos": [
+ -540,
+ 260
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 498
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "sdxl_vae_fixed.safetensors"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 162,
+ "type": "CLIPSetLastLayer",
+ "pos": [
+ -1660,
+ 660
+ ],
+ "size": {
+ "0": 220,
+ "1": 60
+ },
+ "flags": {
+ "pinned": false,
+ "collapsed": false
+ },
+ "order": 30,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 577
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 556
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPSetLastLayer"
+ },
+ "widgets_values": [
+ -1
+ ]
+ },
+ {
+ "id": 2,
+ "type": "CLIPSetLastLayer",
+ "pos": [
+ -1660,
+ 550
+ ],
+ "size": {
+ "0": 220,
+ "1": 60
+ },
+ "flags": {},
+ "order": 29,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 576
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 555
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPSetLastLayer"
+ },
+ "widgets_values": [
+ -1
+ ]
+ },
+ {
+ "id": 73,
+ "type": "Note",
+ "pos": [
+ -2520,
+ -280
+ ],
+ "size": {
+ "0": 530,
+ "1": 150
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "title": "Workbook Details",
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Workflow\nhttps://civitai.com/models/123125\n\nRequires CR Animation Nodes\nhttps://civitai.com/models/137333/comfyui-cr-animation-nodes\n\nSetember 2023\nAkatsuzi\n\n\n"
+ ],
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 217,
+ "type": "PrimitiveNode",
+ "pos": [
+ -1650,
+ -230
+ ],
+ "size": {
+ "0": 210,
+ "1": 80
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 563
+ ],
+ "slot_index": 0,
+ "widget": {
+ "name": "index",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": -10000,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ 4,
+ "increment"
+ ]
+ },
+ {
+ "id": 229,
+ "type": "CR Apply Model Merge",
+ "pos": [
+ -2120,
+ 70
+ ],
+ "size": {
+ "0": 330,
+ "1": 146
+ },
+ "flags": {},
+ "order": 24,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model_stack",
+ "type": "MODEL_STACK",
+ "link": 587
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 575
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 576
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "model_mix_info",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "title": "Apply Model Merge 1",
+ "properties": {
+ "Node name for S&R": "CR Apply Model Merge"
+ },
+ "widgets_values": [
+ "Recursive",
+ "Yes",
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 171,
+ "type": "CR LoRA Stack",
+ "pos": [
+ -971.3108651981408,
+ 96.30713404293414
+ ],
+ "size": {
+ "0": 315,
+ "1": 322
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "lora_stack",
+ "type": "LORA_STACK",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LORA_STACK",
+ "type": "LORA_STACK",
+ "links": [
+ 461
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "XL LoRA Stack 1",
+ "properties": {
+ "Node name for S&R": "CR LoRA Stack"
+ },
+ "widgets_values": [
+ "Off",
+ "None",
+ 1,
+ 1,
+ "Off",
+ "None",
+ 1,
+ 1,
+ "Off",
+ "None",
+ 1,
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 226,
+ "type": "CR Apply Model Merge",
+ "pos": [
+ -2120,
+ 450
+ ],
+ "size": {
+ "0": 330,
+ "1": 146
+ },
+ "flags": {},
+ "order": 25,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model_stack",
+ "type": "MODEL_STACK",
+ "link": 588
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 574
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 577
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "model_mix_info",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "title": "Apply Model Merge 2",
+ "properties": {
+ "Node name for S&R": "CR Apply Model Merge"
+ },
+ "widgets_values": [
+ "Recursive",
+ "Yes",
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 220,
+ "type": "CR Gradient Float",
+ "pos": [
+ -1670,
+ -80
+ ],
+ "size": {
+ "0": 260,
+ "1": 154
+ },
+ "flags": {},
+ "order": 28,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "current_frame",
+ "type": "INT",
+ "link": 565,
+ "widget": {
+ "name": "current_frame",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 9999,
+ "step": 1
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 566,
+ 571
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "Model Gradient",
+ "properties": {
+ "Node name for S&R": "CR Gradient Float"
+ },
+ "widgets_values": [
+ 1,
+ 0,
+ 0,
+ 10,
+ 0,
+ "Lerp"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 242,
+ "type": "Note",
+ "pos": [
+ -1340,
+ 790
+ ],
+ "size": {
+ "0": 220,
+ "1": 70
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "title": "Merging",
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The output from the two stack merges are merged here\n"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 243,
+ "type": "Note",
+ "pos": [
+ -2010,
+ 660
+ ],
+ "size": {
+ "0": 220,
+ "1": 70
+ },
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "title": "Merging",
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The two model merge stacks are each merged here\n"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 241,
+ "type": "Note",
+ "pos": [
+ -1010,
+ -290
+ ],
+ "size": {
+ "0": 270,
+ "1": 150
+ },
+ "flags": {},
+ "order": 15,
+ "mode": 0,
+ "title": "Gradients",
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The gradients will provide 10 merges stepping between the merged models outputs from stacks 1 and 2\n\nSet the Batch Count in Queue Prompt to 10\n\n"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 239,
+ "type": "CR Model Merge Stack",
+ "pos": [
+ -2480,
+ 70
+ ],
+ "size": {
+ "0": 315,
+ "1": 322
+ },
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model_stack",
+ "type": "MODEL_STACK",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL_STACK",
+ "type": "MODEL_STACK",
+ "links": [
+ 587
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "XL Model Merge Stack 1",
+ "properties": {
+ "Node name for S&R": "CR Model Merge Stack"
+ },
+ "widgets_values": [
+ "On",
+ "SDXL\\rundiffusionXL_beta.safetensors",
+ 0.5,
+ 0.5,
+ "On",
+ "SDXL\\nijiDiffusionXlBase1_v10.safetensors",
+ 0.5,
+ 0.5,
+ "Off",
+ "None",
+ 1,
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 240,
+ "type": "CR Model Merge Stack",
+ "pos": [
+ -2480,
+ 450
+ ],
+ "size": {
+ "0": 315,
+ "1": 322
+ },
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model_stack",
+ "type": "MODEL_STACK",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL_STACK",
+ "type": "MODEL_STACK",
+ "links": [
+ 588
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "XL Model Merge Stack 2",
+ "properties": {
+ "Node name for S&R": "CR Model Merge Stack"
+ },
+ "widgets_values": [
+ "On",
+ "SDXL\\dreamshaperXL10_alpha2Xl10.safetensors",
+ 0.5,
+ 0.5,
+ "On",
+ "SDXL\\copaxRealisticXLSDXL1_v2.safetensors",
+ 0.5,
+ 0.5,
+ "Off",
+ "None",
+ 1,
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 203,
+ "type": "SeargeSDXLSampler2",
+ "pos": [
+ 1641.0815801410135,
+ 232.8554552108752
+ ],
+ "size": {
+ "0": 320,
+ "1": 620
+ },
+ "flags": {},
+ "order": 46,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "base_model",
+ "type": "MODEL",
+ "link": 521
+ },
+ {
+ "name": "base_positive",
+ "type": "CONDITIONING",
+ "link": 522
+ },
+ {
+ "name": "base_negative",
+ "type": "CONDITIONING",
+ "link": 523
+ },
+ {
+ "name": "refiner_model",
+ "type": "MODEL",
+ "link": 524
+ },
+ {
+ "name": "refiner_positive",
+ "type": "CONDITIONING",
+ "link": 525
+ },
+ {
+ "name": "refiner_negative",
+ "type": "CONDITIONING",
+ "link": 526
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 546
+ },
+ {
+ "name": "sampler_name",
+ "type": "SAMPLER_NAME",
+ "link": 528
+ },
+ {
+ "name": "scheduler",
+ "type": "SCHEDULER_NAME",
+ "link": 529
+ },
+ {
+ "name": "noise_seed",
+ "type": "INT",
+ "link": 547,
+ "widget": {
+ "name": "noise_seed",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 18446744073709552000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 548
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "SDXL Mix Sampler",
+ "properties": {
+ "Node name for S&R": "SeargeSDXLSampler2"
+ },
+ "widgets_values": [
+ 602234572132077,
+ "randomize",
+ 24,
+ 8,
+ 1,
+ 1,
+ 3,
+ 1,
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ }
+ ],
+ "links": [
+ [
+ 341,
+ 118,
+ 0,
+ 123,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 363,
+ 125,
+ 0,
+ 137,
+ 0,
+ "UPSCALE_MODEL"
+ ],
+ [
+ 364,
+ 118,
+ 0,
+ 137,
+ 1,
+ "IMAGE"
+ ],
+ [
+ 423,
+ 137,
+ 0,
+ 157,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 424,
+ 157,
+ 0,
+ 135,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 461,
+ 171,
+ 0,
+ 172,
+ 0,
+ "LORA_STACK"
+ ],
+ [
+ 462,
+ 172,
+ 0,
+ 173,
+ 2,
+ "LORA_STACK"
+ ],
+ [
+ 463,
+ 161,
+ 0,
+ 173,
+ 1,
+ "CLIP"
+ ],
+ [
+ 464,
+ 160,
+ 0,
+ 173,
+ 0,
+ "MODEL"
+ ],
+ [
+ 467,
+ 173,
+ 1,
+ 167,
+ 1,
+ "CLIP"
+ ],
+ [
+ 468,
+ 173,
+ 0,
+ 167,
+ 0,
+ "MODEL"
+ ],
+ [
+ 489,
+ 181,
+ 0,
+ 183,
+ 0,
+ "*"
+ ],
+ [
+ 494,
+ 173,
+ 0,
+ 185,
+ 0,
+ "*"
+ ],
+ [
+ 495,
+ 173,
+ 1,
+ 184,
+ 0,
+ "*"
+ ],
+ [
+ 498,
+ 186,
+ 0,
+ 167,
+ 2,
+ "VAE"
+ ],
+ [
+ 503,
+ 174,
+ 0,
+ 188,
+ 0,
+ "*"
+ ],
+ [
+ 504,
+ 188,
+ 0,
+ 118,
+ 1,
+ "VAE"
+ ],
+ [
+ 521,
+ 189,
+ 0,
+ 203,
+ 0,
+ "MODEL"
+ ],
+ [
+ 522,
+ 204,
+ 0,
+ 203,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 523,
+ 204,
+ 1,
+ 203,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 524,
+ 209,
+ 0,
+ 203,
+ 3,
+ "MODEL"
+ ],
+ [
+ 525,
+ 204,
+ 2,
+ 203,
+ 4,
+ "CONDITIONING"
+ ],
+ [
+ 526,
+ 204,
+ 3,
+ 203,
+ 5,
+ "CONDITIONING"
+ ],
+ [
+ 528,
+ 208,
+ 0,
+ 203,
+ 7,
+ "SAMPLER_NAME"
+ ],
+ [
+ 529,
+ 208,
+ 1,
+ 203,
+ 8,
+ "SCHEDULER_NAME"
+ ],
+ [
+ 530,
+ 196,
+ 0,
+ 204,
+ 0,
+ "CLIP"
+ ],
+ [
+ 531,
+ 205,
+ 1,
+ 204,
+ 1,
+ "CLIP"
+ ],
+ [
+ 532,
+ 206,
+ 0,
+ 204,
+ 2,
+ "STRING"
+ ],
+ [
+ 533,
+ 206,
+ 0,
+ 204,
+ 3,
+ "STRING"
+ ],
+ [
+ 534,
+ 206,
+ 0,
+ 204,
+ 4,
+ "STRING"
+ ],
+ [
+ 535,
+ 207,
+ 0,
+ 204,
+ 5,
+ "STRING"
+ ],
+ [
+ 536,
+ 207,
+ 0,
+ 204,
+ 6,
+ "STRING"
+ ],
+ [
+ 537,
+ 207,
+ 0,
+ 204,
+ 7,
+ "STRING"
+ ],
+ [
+ 538,
+ 205,
+ 0,
+ 209,
+ 0,
+ "*"
+ ],
+ [
+ 540,
+ 210,
+ 0,
+ 95,
+ 0,
+ "INT"
+ ],
+ [
+ 541,
+ 210,
+ 1,
+ 95,
+ 1,
+ "INT"
+ ],
+ [
+ 542,
+ 210,
+ 3,
+ 95,
+ 2,
+ "INT"
+ ],
+ [
+ 543,
+ 197,
+ 0,
+ 189,
+ 0,
+ "*"
+ ],
+ [
+ 546,
+ 211,
+ 0,
+ 203,
+ 6,
+ "LATENT"
+ ],
+ [
+ 547,
+ 183,
+ 0,
+ 203,
+ 9,
+ "INT"
+ ],
+ [
+ 548,
+ 203,
+ 0,
+ 118,
+ 0,
+ "LATENT"
+ ],
+ [
+ 549,
+ 95,
+ 0,
+ 212,
+ 0,
+ "*"
+ ],
+ [
+ 550,
+ 212,
+ 0,
+ 211,
+ 0,
+ "*"
+ ],
+ [
+ 551,
+ 185,
+ 0,
+ 197,
+ 0,
+ "*"
+ ],
+ [
+ 552,
+ 184,
+ 0,
+ 196,
+ 0,
+ "*"
+ ],
+ [
+ 555,
+ 2,
+ 0,
+ 161,
+ 0,
+ "CLIP"
+ ],
+ [
+ 556,
+ 162,
+ 0,
+ 161,
+ 1,
+ "CLIP"
+ ],
+ [
+ 563,
+ 217,
+ 0,
+ 218,
+ 0,
+ "INT"
+ ],
+ [
+ 564,
+ 218,
+ 0,
+ 219,
+ 0,
+ "INT"
+ ],
+ [
+ 565,
+ 218,
+ 0,
+ 220,
+ 0,
+ "INT"
+ ],
+ [
+ 566,
+ 220,
+ 0,
+ 160,
+ 2,
+ "FLOAT"
+ ],
+ [
+ 567,
+ 219,
+ 0,
+ 161,
+ 2,
+ "FLOAT"
+ ],
+ [
+ 568,
+ 219,
+ 0,
+ 222,
+ 0,
+ "FLOAT"
+ ],
+ [
+ 569,
+ 222,
+ 0,
+ 221,
+ 0,
+ "STRING"
+ ],
+ [
+ 570,
+ 225,
+ 0,
+ 224,
+ 0,
+ "STRING"
+ ],
+ [
+ 571,
+ 220,
+ 0,
+ 225,
+ 0,
+ "FLOAT"
+ ],
+ [
+ 574,
+ 226,
+ 0,
+ 160,
+ 1,
+ "MODEL"
+ ],
+ [
+ 575,
+ 229,
+ 0,
+ 160,
+ 0,
+ "MODEL"
+ ],
+ [
+ 576,
+ 229,
+ 1,
+ 2,
+ 0,
+ "CLIP"
+ ],
+ [
+ 577,
+ 226,
+ 1,
+ 162,
+ 0,
+ "CLIP"
+ ],
+ [
+ 587,
+ 239,
+ 0,
+ 229,
+ 0,
+ "MODEL_STACK"
+ ],
+ [
+ 588,
+ 240,
+ 0,
+ 226,
+ 0,
+ "MODEL_STACK"
+ ]
+ ],
+ "groups": [
+ {
+ "title": "Prompt",
+ "bounding": [
+ 626,
+ 264,
+ 482,
+ 678
+ ],
+ "color": "#3f789e",
+ "locked": false
+ },
+ {
+ "title": "XL Models",
+ "bounding": [
+ -2524,
+ -25,
+ 772,
+ 835
+ ],
+ "color": "#3f789e",
+ "locked": false
+ },
+ {
+ "title": "XL LoRAs",
+ "bounding": [
+ -1002,
+ -6,
+ 383,
+ 847
+ ],
+ "color": "#3f789e",
+ "locked": false
+ },
+ {
+ "title": "Sampling",
+ "bounding": [
+ 1608,
+ -32,
+ 386,
+ 968
+ ],
+ "color": "#3f789e",
+ "locked": false
+ },
+ {
+ "title": "Upscale and Levels",
+ "bounding": [
+ 2238,
+ -130,
+ 731,
+ 233
+ ],
+ "color": "#3f789e",
+ "locked": false
+ },
+ {
+ "title": "SDXL Model Gradient Merge with Model Merge Stacks",
+ "bounding": [
+ -2563,
+ -385,
+ 2447,
+ 1430
+ ],
+ "color": "#3f789e",
+ "locked": false
+ },
+ {
+ "title": "Merge Models",
+ "bounding": [
+ -1346,
+ 394,
+ 287,
+ 335
+ ],
+ "color": "#3f789e",
+ "locked": false
+ },
+ {
+ "title": "Setup",
+ "bounding": [
+ 33,
+ -282,
+ 397,
+ 629
+ ],
+ "color": "#3f789e",
+ "locked": false
+ },
+ {
+ "title": "Save Model",
+ "bounding": [
+ -535,
+ 569,
+ 376,
+ 363
+ ],
+ "color": "#3f789e",
+ "locked": false
+ },
+ {
+ "title": "Model Preview",
+ "bounding": [
+ -12,
+ -381,
+ 3380,
+ 1429
+ ],
+ "color": "#3f789e",
+ "locked": false
+ },
+ {
+ "title": "Gradients",
+ "bounding": [
+ -1693,
+ -318,
+ 635,
+ 675
+ ],
+ "color": "#3f789e",
+ "locked": false
+ }
+ ],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Model Merge/CR_SDXL_MultiModelMerge_v01b.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Model Merge/CR_SDXL_MultiModelMerge_v01b.json
new file mode 100644
index 0000000000000000000000000000000000000000..3638d84001200cbeef39e5c9e1cd1e270fd65a56
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Model Merge/CR_SDXL_MultiModelMerge_v01b.json
@@ -0,0 +1,2222 @@
+{
+ "last_node_id": 245,
+ "last_link_id": 596,
+ "nodes": [
+ {
+ "id": 125,
+ "type": "UpscaleModelLoader",
+ "pos": [
+ 2269.567273365684,
+ -98.5369286336395
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "UPSCALE_MODEL",
+ "type": "UPSCALE_MODEL",
+ "links": [
+ 363
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "UpscaleModelLoader"
+ },
+ "widgets_values": [
+ "RealESRGAN_x2.pth"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 174,
+ "type": "VAELoader",
+ "pos": [
+ 68.82123096767066,
+ 238.46804881065552
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 503
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "sdxl_vae.safetensors"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 205,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ 630,
+ -50
+ ],
+ "size": {
+ "0": 458,
+ "1": 132
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 538
+ ],
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 531
+ ],
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null
+ }
+ ],
+ "title": "XL Refiner Model",
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "SDXL\\sd_xl_refiner_1.0.safetensors"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 208,
+ "type": "SeargeSamplerInputs",
+ "pos": [
+ 1641.0815801410135,
+ 62.855455210877295
+ ],
+ "size": {
+ "0": 315,
+ "1": 102
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "sampler_name",
+ "type": "SAMPLER_NAME",
+ "links": [
+ 528
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "scheduler",
+ "type": "SCHEDULER_NAME",
+ "links": [
+ 529
+ ],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "SeargeSamplerInputs"
+ },
+ "widgets_values": [
+ "dpmpp_2m",
+ "karras"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 172,
+ "type": "CR LoRA Stack",
+ "pos": [
+ -972.1116192382817,
+ 468.55508251673456
+ ],
+ "size": {
+ "0": 315,
+ "1": 322
+ },
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "lora_stack",
+ "type": "LORA_STACK",
+ "link": 461
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LORA_STACK",
+ "type": "LORA_STACK",
+ "links": [
+ 462
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "XL LoRA Stack 2",
+ "properties": {
+ "Node name for S&R": "CR LoRA Stack"
+ },
+ "widgets_values": [
+ "Off",
+ "None",
+ 1,
+ 1,
+ "Off",
+ "None",
+ 1,
+ 1,
+ "Off",
+ "None",
+ 1,
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 183,
+ "type": "Reroute",
+ "pos": [
+ 1430,
+ 100
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 489
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 547
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 188,
+ "type": "Reroute",
+ "pos": [
+ 510,
+ 170
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 503
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 504
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 209,
+ "type": "Reroute",
+ "pos": [
+ 1430,
+ 250
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 15,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 538
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 524
+ ]
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 95,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 530,
+ -160
+ ],
+ "size": {
+ "0": 210,
+ "1": 74
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 18,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "link": 540,
+ "widget": {
+ "name": "width",
+ "config": [
+ "INT",
+ {
+ "default": 512,
+ "min": 64,
+ "max": 8192,
+ "step": 8
+ }
+ ]
+ }
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "link": 541,
+ "widget": {
+ "name": "height",
+ "config": [
+ "INT",
+ {
+ "default": 512,
+ "min": 64,
+ "max": 8192,
+ "step": 8
+ }
+ ]
+ }
+ },
+ {
+ "name": "batch_size",
+ "type": "INT",
+ "link": 542,
+ "widget": {
+ "name": "batch_size",
+ "config": [
+ "INT",
+ {
+ "default": 1,
+ "min": 1,
+ "max": 64
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 549
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ 1
+ ]
+ },
+ {
+ "id": 212,
+ "type": "Reroute",
+ "pos": [
+ 1150,
+ -190
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 20,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 549
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 550
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 211,
+ "type": "Reroute",
+ "pos": [
+ 1430,
+ 140
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 22,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 550
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 546
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 185,
+ "type": "Reroute",
+ "pos": [
+ -220,
+ 410
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 25,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 494
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 551
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 167,
+ "type": "CheckpointSave",
+ "pos": [
+ -505.1336667459445,
+ 656.9509770334475
+ ],
+ "size": {
+ "0": 315,
+ "1": 98
+ },
+ "flags": {},
+ "order": 26,
+ "mode": 2,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 468
+ },
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 467
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 498
+ }
+ ],
+ "title": "Checkpoint Save",
+ "properties": {
+ "Node name for S&R": "CheckpointSave"
+ },
+ "widgets_values": [
+ "checkpoints/MyModel"
+ ]
+ },
+ {
+ "id": 197,
+ "type": "Reroute",
+ "pos": [
+ 480,
+ 410
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 28,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 551
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 543
+ ]
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 196,
+ "type": "Reroute",
+ "pos": [
+ 480,
+ 470
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 29,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 596
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 530
+ ]
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 189,
+ "type": "Reroute",
+ "pos": [
+ 1430,
+ 210
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 30,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 543
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 521
+ ]
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 204,
+ "type": "SeargeSDXLPromptEncoder",
+ "pos": [
+ 1230,
+ 390
+ ],
+ "size": {
+ "0": 311.32244873046875,
+ "1": 415.3662414550781
+ },
+ "flags": {},
+ "order": 31,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "base_clip",
+ "type": "CLIP",
+ "link": 530
+ },
+ {
+ "name": "refiner_clip",
+ "type": "CLIP",
+ "link": 531
+ },
+ {
+ "name": "pos_g",
+ "type": "STRING",
+ "link": 532,
+ "widget": {
+ "name": "pos_g",
+ "config": [
+ "STRING",
+ {
+ "multiline": true,
+ "default": "POS_G"
+ }
+ ]
+ }
+ },
+ {
+ "name": "pos_l",
+ "type": "STRING",
+ "link": 533,
+ "widget": {
+ "name": "pos_l",
+ "config": [
+ "STRING",
+ {
+ "multiline": true,
+ "default": "POS_L"
+ }
+ ]
+ }
+ },
+ {
+ "name": "pos_r",
+ "type": "STRING",
+ "link": 534,
+ "widget": {
+ "name": "pos_r",
+ "config": [
+ "STRING",
+ {
+ "multiline": true,
+ "default": "POS_R"
+ }
+ ]
+ }
+ },
+ {
+ "name": "neg_g",
+ "type": "STRING",
+ "link": 535,
+ "widget": {
+ "name": "neg_g",
+ "config": [
+ "STRING",
+ {
+ "multiline": true,
+ "default": "NEG_G"
+ }
+ ]
+ }
+ },
+ {
+ "name": "neg_l",
+ "type": "STRING",
+ "link": 536,
+ "widget": {
+ "name": "neg_l",
+ "config": [
+ "STRING",
+ {
+ "multiline": true,
+ "default": "NEG_L"
+ }
+ ]
+ }
+ },
+ {
+ "name": "neg_r",
+ "type": "STRING",
+ "link": 537,
+ "widget": {
+ "name": "neg_r",
+ "config": [
+ "STRING",
+ {
+ "multiline": true,
+ "default": "NEG_R"
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "base_positive",
+ "type": "CONDITIONING",
+ "links": [
+ 522
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "base_negative",
+ "type": "CONDITIONING",
+ "links": [
+ 523
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "refiner_positive",
+ "type": "CONDITIONING",
+ "links": [
+ 525
+ ],
+ "shape": 3,
+ "slot_index": 2
+ },
+ {
+ "name": "refiner_negative",
+ "type": "CONDITIONING",
+ "links": [
+ 526
+ ],
+ "shape": 3,
+ "slot_index": 3
+ }
+ ],
+ "title": "XL Prompt Encoder",
+ "properties": {
+ "Node name for S&R": "SeargeSDXLPromptEncoder"
+ },
+ "widgets_values": [
+ "POS_G",
+ "POS_L",
+ "POS_R",
+ "NEG_G",
+ "NEG_L",
+ "NEG_R",
+ 4096,
+ 4096,
+ 0,
+ 0,
+ 4096,
+ 4096,
+ 6,
+ 2.5,
+ 2048,
+ 2048
+ ]
+ },
+ {
+ "id": 203,
+ "type": "SeargeSDXLSampler2",
+ "pos": [
+ 1641.0815801410135,
+ 232.8554552108752
+ ],
+ "size": {
+ "0": 320,
+ "1": 620
+ },
+ "flags": {},
+ "order": 32,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "base_model",
+ "type": "MODEL",
+ "link": 521
+ },
+ {
+ "name": "base_positive",
+ "type": "CONDITIONING",
+ "link": 522
+ },
+ {
+ "name": "base_negative",
+ "type": "CONDITIONING",
+ "link": 523
+ },
+ {
+ "name": "refiner_model",
+ "type": "MODEL",
+ "link": 524
+ },
+ {
+ "name": "refiner_positive",
+ "type": "CONDITIONING",
+ "link": 525
+ },
+ {
+ "name": "refiner_negative",
+ "type": "CONDITIONING",
+ "link": 526
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 546
+ },
+ {
+ "name": "sampler_name",
+ "type": "SAMPLER_NAME",
+ "link": 528
+ },
+ {
+ "name": "scheduler",
+ "type": "SCHEDULER_NAME",
+ "link": 529
+ },
+ {
+ "name": "noise_seed",
+ "type": "INT",
+ "link": 547,
+ "widget": {
+ "name": "noise_seed",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0,
+ "max": 18446744073709552000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 548
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "SDXL Mix Sampler",
+ "properties": {
+ "Node name for S&R": "SeargeSDXLSampler2"
+ },
+ "widgets_values": [
+ 68907621190797,
+ "randomize",
+ 20,
+ 7,
+ 0.8,
+ 1,
+ 0,
+ 1,
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 118,
+ "type": "VAEDecode",
+ "pos": [
+ 2030,
+ 200
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 33,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 548
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 504
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 364
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 137,
+ "type": "ImageUpscaleWithModel",
+ "pos": [
+ 2269.567273365684,
+ 11.463071366360666
+ ],
+ "size": {
+ "0": 241.79998779296875,
+ "1": 46
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 34,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "upscale_model",
+ "type": "UPSCALE_MODEL",
+ "link": 363
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 364
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 423
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ImageUpscaleWithModel"
+ }
+ },
+ {
+ "id": 157,
+ "type": "Image Levels Adjustment",
+ "pos": [
+ 2619.567273365684,
+ -98.5369286336395
+ ],
+ "size": {
+ "0": 315,
+ "1": 106
+ },
+ "flags": {
+ "collapsed": false
+ },
+ "order": 35,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 423
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 424
+ ],
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Image Levels Adjustment"
+ },
+ "widgets_values": [
+ 0,
+ 127.5,
+ 255
+ ]
+ },
+ {
+ "id": 173,
+ "type": "CR Apply LoRA Stack",
+ "pos": [
+ -550,
+ 420
+ ],
+ "size": {
+ "0": 210,
+ "1": 66
+ },
+ "flags": {
+ "collapsed": false
+ },
+ "order": 24,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 593
+ },
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 592
+ },
+ {
+ "name": "lora_stack",
+ "type": "LORA_STACK",
+ "link": 462
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 468,
+ 494
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 467,
+ 495
+ ],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "title": "Apply LoRA Stack",
+ "properties": {
+ "Node name for S&R": "CR Apply LoRA Stack"
+ }
+ },
+ {
+ "id": 181,
+ "type": "CR Seed",
+ "pos": [
+ 68.82123096767066,
+ 98.46804881065545
+ ],
+ "size": {
+ "0": 315,
+ "1": 82
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "seed",
+ "type": "INT",
+ "links": [
+ 489
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "Seed",
+ "properties": {
+ "Node name for S&R": "CR Seed"
+ },
+ "widgets_values": [
+ 0,
+ "fixed"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 186,
+ "type": "VAELoader",
+ "pos": [
+ -540,
+ 260
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 498
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "sdxl_vae_fixed.safetensors"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 171,
+ "type": "CR LoRA Stack",
+ "pos": [
+ -971.3108651981408,
+ 96.30713404293414
+ ],
+ "size": {
+ "0": 315,
+ "1": 322
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "lora_stack",
+ "type": "LORA_STACK",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LORA_STACK",
+ "type": "LORA_STACK",
+ "links": [
+ 461
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "XL LoRA Stack 1",
+ "properties": {
+ "Node name for S&R": "CR LoRA Stack"
+ },
+ "widgets_values": [
+ "Off",
+ "None",
+ 1,
+ 1,
+ "Off",
+ "None",
+ 1,
+ 1,
+ "Off",
+ "None",
+ 1,
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 184,
+ "type": "Reroute",
+ "pos": [
+ -220,
+ 470
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 27,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 495
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 596
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 210,
+ "type": "CR SDXL Aspect Ratio",
+ "pos": [
+ 70,
+ -190
+ ],
+ "size": {
+ "0": 315,
+ "1": 238
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "links": [
+ 540
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "links": [
+ 541
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "upscale_factor",
+ "type": "FLOAT",
+ "links": [],
+ "shape": 3,
+ "slot_index": 2
+ },
+ {
+ "name": "batch_size",
+ "type": "INT",
+ "links": [
+ 542
+ ],
+ "shape": 3,
+ "slot_index": 3
+ }
+ ],
+ "title": "SDXL Aspect Ratio",
+ "properties": {
+ "Node name for S&R": "CR SDXL Aspect Ratio"
+ },
+ "widgets_values": [
+ 1024,
+ 1024,
+ "3:4 portrait 896x1152",
+ "Off",
+ 2,
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 162,
+ "type": "CLIPSetLastLayer",
+ "pos": [
+ -1320,
+ 498.4568778542175
+ ],
+ "size": {
+ "0": 220,
+ "1": 60
+ },
+ "flags": {
+ "pinned": false,
+ "collapsed": false
+ },
+ "order": 23,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 577
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 592
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPSetLastLayer"
+ },
+ "widgets_values": [
+ -1
+ ]
+ },
+ {
+ "id": 226,
+ "type": "CR Apply Model Merge",
+ "pos": [
+ -1700,
+ 418.4568778542175
+ ],
+ "size": {
+ "0": 330,
+ "1": 146
+ },
+ "flags": {},
+ "order": 21,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model_stack",
+ "type": "MODEL_STACK",
+ "link": 594
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 593
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 577
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "model_mix_info",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "title": "Apply Model Merge",
+ "properties": {
+ "Node name for S&R": "CR Apply Model Merge"
+ },
+ "widgets_values": [
+ "Recursive",
+ "Yes",
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 243,
+ "type": "Note",
+ "pos": [
+ -1590,
+ 618.4568778542175
+ ],
+ "size": {
+ "0": 220,
+ "1": 70
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "title": "Merging",
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The model merge stacks are each merged here\n"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 73,
+ "type": "Note",
+ "pos": [
+ -2120,
+ -210
+ ],
+ "size": {
+ "0": 530,
+ "1": 150
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "title": "Workbook Details",
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Workflow\nhttps://civitai.com/models/145275\n\nSetember 2023\nAkatsuzi\n\n\n"
+ ],
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 240,
+ "type": "CR Model Merge Stack",
+ "pos": [
+ -2080,
+ 100
+ ],
+ "size": {
+ "0": 315,
+ "1": 322
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model_stack",
+ "type": "MODEL_STACK",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL_STACK",
+ "type": "MODEL_STACK",
+ "links": [
+ 595
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "XL Model Merge Stack",
+ "properties": {
+ "Node name for S&R": "CR Model Merge Stack"
+ },
+ "widgets_values": [
+ "On",
+ "SDXL\\4Guofeng4_v10Beta.safetensors",
+ 1,
+ 1,
+ "On",
+ "SDXL\\xl6HEPHAISTOSSD10XLSFW_v21BakedVAEFP16Fix.safetensors",
+ 1,
+ 1,
+ "Off",
+ "None",
+ 1,
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 245,
+ "type": "CR Model Merge Stack",
+ "pos": [
+ -2080,
+ 480
+ ],
+ "size": {
+ "0": 315,
+ "1": 322
+ },
+ "flags": {},
+ "order": 19,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model_stack",
+ "type": "MODEL_STACK",
+ "link": 595
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL_STACK",
+ "type": "MODEL_STACK",
+ "links": [
+ 594
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "XL Model Merge Stack",
+ "properties": {
+ "Node name for S&R": "CR Model Merge Stack"
+ },
+ "widgets_values": [
+ "On",
+ "SDXL\\rundiffusionXL_beta.safetensors",
+ 1,
+ 1,
+ "On",
+ "SDXL\\copaxRealisticXLSDXL1_v2.safetensors",
+ 1,
+ 1,
+ "Off",
+ "None",
+ 1,
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 177,
+ "type": "Note",
+ "pos": [
+ -507.1336667459445,
+ 828.9509770334472
+ ],
+ "size": {
+ "0": 230,
+ "1": 60
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "title": "Save Model",
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "To unhide this node, right click then Mode > Always\n\n"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 207,
+ "type": "SeargePromptText",
+ "pos": [
+ 670,
+ 670
+ ],
+ "size": {
+ "0": 400,
+ "1": 200
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "prompt",
+ "type": "STRING",
+ "links": [
+ 535,
+ 536,
+ 537
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "Prompt text input",
+ "properties": {
+ "Node name for S&R": "SeargePromptText"
+ },
+ "widgets_values": [
+ ""
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 206,
+ "type": "SeargePromptText",
+ "pos": [
+ 670,
+ 390
+ ],
+ "size": {
+ "0": 400,
+ "1": 200
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "prompt",
+ "type": "STRING",
+ "links": [
+ 532,
+ 533,
+ 534
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "Prompt text input",
+ "properties": {
+ "Node name for S&R": "SeargePromptText"
+ },
+ "widgets_values": [
+ "A beautiful young woman staring into the abyss of infinity"
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 135,
+ "type": "SaveImage",
+ "pos": [
+ 2240,
+ 140
+ ],
+ "size": {
+ "0": 720,
+ "1": 860
+ },
+ "flags": {},
+ "order": 36,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 424,
+ "slot_index": 0
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "Merge/Merge"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ }
+ ],
+ "links": [
+ [
+ 363,
+ 125,
+ 0,
+ 137,
+ 0,
+ "UPSCALE_MODEL"
+ ],
+ [
+ 364,
+ 118,
+ 0,
+ 137,
+ 1,
+ "IMAGE"
+ ],
+ [
+ 423,
+ 137,
+ 0,
+ 157,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 424,
+ 157,
+ 0,
+ 135,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 461,
+ 171,
+ 0,
+ 172,
+ 0,
+ "LORA_STACK"
+ ],
+ [
+ 462,
+ 172,
+ 0,
+ 173,
+ 2,
+ "LORA_STACK"
+ ],
+ [
+ 467,
+ 173,
+ 1,
+ 167,
+ 1,
+ "CLIP"
+ ],
+ [
+ 468,
+ 173,
+ 0,
+ 167,
+ 0,
+ "MODEL"
+ ],
+ [
+ 489,
+ 181,
+ 0,
+ 183,
+ 0,
+ "*"
+ ],
+ [
+ 494,
+ 173,
+ 0,
+ 185,
+ 0,
+ "*"
+ ],
+ [
+ 495,
+ 173,
+ 1,
+ 184,
+ 0,
+ "*"
+ ],
+ [
+ 498,
+ 186,
+ 0,
+ 167,
+ 2,
+ "VAE"
+ ],
+ [
+ 503,
+ 174,
+ 0,
+ 188,
+ 0,
+ "*"
+ ],
+ [
+ 504,
+ 188,
+ 0,
+ 118,
+ 1,
+ "VAE"
+ ],
+ [
+ 521,
+ 189,
+ 0,
+ 203,
+ 0,
+ "MODEL"
+ ],
+ [
+ 522,
+ 204,
+ 0,
+ 203,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 523,
+ 204,
+ 1,
+ 203,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 524,
+ 209,
+ 0,
+ 203,
+ 3,
+ "MODEL"
+ ],
+ [
+ 525,
+ 204,
+ 2,
+ 203,
+ 4,
+ "CONDITIONING"
+ ],
+ [
+ 526,
+ 204,
+ 3,
+ 203,
+ 5,
+ "CONDITIONING"
+ ],
+ [
+ 528,
+ 208,
+ 0,
+ 203,
+ 7,
+ "SAMPLER_NAME"
+ ],
+ [
+ 529,
+ 208,
+ 1,
+ 203,
+ 8,
+ "SCHEDULER_NAME"
+ ],
+ [
+ 530,
+ 196,
+ 0,
+ 204,
+ 0,
+ "CLIP"
+ ],
+ [
+ 531,
+ 205,
+ 1,
+ 204,
+ 1,
+ "CLIP"
+ ],
+ [
+ 532,
+ 206,
+ 0,
+ 204,
+ 2,
+ "STRING"
+ ],
+ [
+ 533,
+ 206,
+ 0,
+ 204,
+ 3,
+ "STRING"
+ ],
+ [
+ 534,
+ 206,
+ 0,
+ 204,
+ 4,
+ "STRING"
+ ],
+ [
+ 535,
+ 207,
+ 0,
+ 204,
+ 5,
+ "STRING"
+ ],
+ [
+ 536,
+ 207,
+ 0,
+ 204,
+ 6,
+ "STRING"
+ ],
+ [
+ 537,
+ 207,
+ 0,
+ 204,
+ 7,
+ "STRING"
+ ],
+ [
+ 538,
+ 205,
+ 0,
+ 209,
+ 0,
+ "*"
+ ],
+ [
+ 540,
+ 210,
+ 0,
+ 95,
+ 0,
+ "INT"
+ ],
+ [
+ 541,
+ 210,
+ 1,
+ 95,
+ 1,
+ "INT"
+ ],
+ [
+ 542,
+ 210,
+ 3,
+ 95,
+ 2,
+ "INT"
+ ],
+ [
+ 543,
+ 197,
+ 0,
+ 189,
+ 0,
+ "*"
+ ],
+ [
+ 546,
+ 211,
+ 0,
+ 203,
+ 6,
+ "LATENT"
+ ],
+ [
+ 547,
+ 183,
+ 0,
+ 203,
+ 9,
+ "INT"
+ ],
+ [
+ 548,
+ 203,
+ 0,
+ 118,
+ 0,
+ "LATENT"
+ ],
+ [
+ 549,
+ 95,
+ 0,
+ 212,
+ 0,
+ "*"
+ ],
+ [
+ 550,
+ 212,
+ 0,
+ 211,
+ 0,
+ "*"
+ ],
+ [
+ 551,
+ 185,
+ 0,
+ 197,
+ 0,
+ "*"
+ ],
+ [
+ 577,
+ 226,
+ 1,
+ 162,
+ 0,
+ "CLIP"
+ ],
+ [
+ 592,
+ 162,
+ 0,
+ 173,
+ 1,
+ "CLIP"
+ ],
+ [
+ 593,
+ 226,
+ 0,
+ 173,
+ 0,
+ "MODEL"
+ ],
+ [
+ 594,
+ 245,
+ 0,
+ 226,
+ 0,
+ "MODEL_STACK"
+ ],
+ [
+ 595,
+ 240,
+ 0,
+ 245,
+ 0,
+ "MODEL_STACK"
+ ],
+ [
+ 596,
+ 184,
+ 0,
+ 196,
+ 0,
+ "*"
+ ]
+ ],
+ "groups": [
+ {
+ "title": "Prompt",
+ "bounding": [
+ 626,
+ 264,
+ 482,
+ 678
+ ],
+ "color": "#3f789e",
+ "locked": false
+ },
+ {
+ "title": "XL Model Merge",
+ "bounding": [
+ -2125,
+ -5,
+ 1064,
+ 849
+ ],
+ "color": "#3f789e",
+ "locked": false
+ },
+ {
+ "title": "XL LoRAs",
+ "bounding": [
+ -1002,
+ -6,
+ 383,
+ 847
+ ],
+ "color": "#3f789e",
+ "locked": false
+ },
+ {
+ "title": "Sampling",
+ "bounding": [
+ 1608,
+ -32,
+ 386,
+ 968
+ ],
+ "color": "#3f789e",
+ "locked": false
+ },
+ {
+ "title": "Upscale and Levels",
+ "bounding": [
+ 2236,
+ -186,
+ 731,
+ 233
+ ],
+ "color": "#3f789e",
+ "locked": false
+ },
+ {
+ "title": "SDXL Model Merge with Model Merge Stacks",
+ "bounding": [
+ -2168,
+ -383,
+ 2061,
+ 1432
+ ],
+ "color": "#3f789e",
+ "locked": false
+ },
+ {
+ "title": "Setup",
+ "bounding": [
+ 33,
+ -282,
+ 397,
+ 629
+ ],
+ "color": "#3f789e",
+ "locked": false
+ },
+ {
+ "title": "Save Model",
+ "bounding": [
+ -535,
+ 569,
+ 376,
+ 363
+ ],
+ "color": "#3f789e",
+ "locked": false
+ },
+ {
+ "title": "Model Preview",
+ "bounding": [
+ -12,
+ -381,
+ 3033,
+ 1432
+ ],
+ "color": "#3f789e",
+ "locked": false
+ }
+ ],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Model Merge/CR_SimpleMultiModelMerge_v01a.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Model Merge/CR_SimpleMultiModelMerge_v01a.json
new file mode 100644
index 0000000000000000000000000000000000000000..4114228c4541f8a4575b51cbdf92c1a2cd7ecc4a
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Model Merge/CR_SimpleMultiModelMerge_v01a.json
@@ -0,0 +1,562 @@
+{
+ "last_node_id": 38,
+ "last_link_id": 53,
+ "nodes": [
+ {
+ "id": 10,
+ "type": "CheckpointSave",
+ "pos": [
+ 1240,
+ 720
+ ],
+ "size": {
+ "0": 310,
+ "1": 100
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 31
+ },
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 32
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 13
+ }
+ ],
+ "title": "Checkpoint Save",
+ "properties": {
+ "Node name for S&R": "CheckpointSave"
+ },
+ "widgets_values": [
+ "MergeModels/Merge"
+ ]
+ },
+ {
+ "id": 21,
+ "type": "CR Apply LoRA Stack",
+ "pos": [
+ 940,
+ 680
+ ],
+ "size": {
+ "0": 210,
+ "1": 66
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 49
+ },
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 48
+ },
+ {
+ "name": "lora_stack",
+ "type": "LORA_STACK",
+ "link": 28
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 31
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 32
+ ],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "title": "Apply LoRA Stack",
+ "properties": {
+ "Node name for S&R": "CR Apply LoRA Stack"
+ },
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 25,
+ "type": "Save Text File",
+ "pos": [
+ 1240,
+ 870
+ ],
+ "size": {
+ "0": 310,
+ "1": 130
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 51
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Save Text File"
+ },
+ "widgets_values": [
+ "./ComfyUI/output/MergeModels/[time(%Y-%m-%d)]",
+ "MergeInfo",
+ "_",
+ 4,
+ 4
+ ]
+ },
+ {
+ "id": 20,
+ "type": "CR LoRA Stack",
+ "pos": [
+ 1050,
+ 250
+ ],
+ "size": {
+ "0": 315,
+ "1": 322
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "lora_stack",
+ "type": "LORA_STACK",
+ "link": 27
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LORA_STACK",
+ "type": "LORA_STACK",
+ "links": [
+ 28
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "LoRA Stack",
+ "properties": {
+ "Node name for S&R": "CR LoRA Stack"
+ },
+ "widgets_values": [
+ "Off",
+ "None",
+ 1,
+ 1,
+ "Off",
+ "None",
+ 1,
+ 1,
+ "Off",
+ "SD1_5\\ArknightsDusk_10.safetensors",
+ 1,
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 31,
+ "type": "CR Apply Model Merge",
+ "pos": [
+ 540,
+ 680
+ ],
+ "size": {
+ "0": 330,
+ "1": 146
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model_stack",
+ "type": "MODEL_STACK",
+ "link": 52
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 49
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 48
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "model_mix_info",
+ "type": "STRING",
+ "links": [
+ 51
+ ],
+ "shape": 3,
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Apply Model Merge"
+ },
+ "widgets_values": [
+ "Recursive",
+ "Yes",
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 34,
+ "type": "Note",
+ "pos": [
+ -100,
+ 650
+ ],
+ "size": {
+ "0": 270,
+ "1": 150
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "title": "Gradients",
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Model and CLIP ratios should add up to 1.0.\n\nIf normalise_ratios is on Yes, then the total ratios may be more or less than 1. The model merge will automatically normalise the ratios.\n\n"
+ ],
+ "color": "#233",
+ "bgcolor": "#355"
+ },
+ {
+ "id": 32,
+ "type": "CR Model Merge Stack",
+ "pos": [
+ -100,
+ 260
+ ],
+ "size": {
+ "0": 315,
+ "1": 322
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model_stack",
+ "type": "MODEL_STACK",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL_STACK",
+ "type": "MODEL_STACK",
+ "links": [
+ 53
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Model Merge Stack"
+ },
+ "widgets_values": [
+ "On",
+ "SD1_5\\ComfyrollAnime_v1_fp16_pruned.safetensors",
+ 0.33,
+ 0.33,
+ "On",
+ "SD1_5\\7th_anime_v3_A.safetensors",
+ 0.33,
+ 0.33,
+ "Off",
+ "None",
+ 1,
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 33,
+ "type": "CR Model Merge Stack",
+ "pos": [
+ 250,
+ 260
+ ],
+ "size": {
+ "0": 315,
+ "1": 322
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model_stack",
+ "type": "MODEL_STACK",
+ "link": 53
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL_STACK",
+ "type": "MODEL_STACK",
+ "links": [
+ 52
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Model Merge Stack"
+ },
+ "widgets_values": [
+ "On",
+ "SD1_5\\mixProV4_v4.safetensors",
+ 0.33,
+ 0.33,
+ "Off",
+ "None",
+ 1,
+ 1,
+ "Off",
+ "SD1_5\\mixproyuki77mi_v10.safetensors",
+ 1,
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 19,
+ "type": "CR LoRA Stack",
+ "pos": [
+ 700,
+ 250
+ ],
+ "size": {
+ "0": 315,
+ "1": 322
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "lora_stack",
+ "type": "LORA_STACK",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LORA_STACK",
+ "type": "LORA_STACK",
+ "links": [
+ 27
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "LoRA Stack",
+ "properties": {
+ "Node name for S&R": "CR LoRA Stack"
+ },
+ "widgets_values": [
+ "Off",
+ "SD1_5\\add_detail.safetensors",
+ 0.2,
+ 0.2,
+ "Off",
+ "None",
+ 1,
+ 1,
+ "Off",
+ "SD1_5\\ArknightsNian_20.safetensors",
+ 1,
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 12,
+ "type": "VAELoader",
+ "pos": [
+ 730,
+ 890
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 13
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "vae-ft-mse-840000-ema-pruned.safetensors"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ }
+ ],
+ "links": [
+ [
+ 13,
+ 12,
+ 0,
+ 10,
+ 2,
+ "VAE"
+ ],
+ [
+ 27,
+ 19,
+ 0,
+ 20,
+ 0,
+ "LORA_STACK"
+ ],
+ [
+ 28,
+ 20,
+ 0,
+ 21,
+ 2,
+ "LORA_STACK"
+ ],
+ [
+ 31,
+ 21,
+ 0,
+ 10,
+ 0,
+ "MODEL"
+ ],
+ [
+ 32,
+ 21,
+ 1,
+ 10,
+ 1,
+ "CLIP"
+ ],
+ [
+ 48,
+ 31,
+ 1,
+ 21,
+ 1,
+ "CLIP"
+ ],
+ [
+ 49,
+ 31,
+ 0,
+ 21,
+ 0,
+ "MODEL"
+ ],
+ [
+ 51,
+ 31,
+ 2,
+ 25,
+ 0,
+ "STRING"
+ ],
+ [
+ 52,
+ 33,
+ 0,
+ 31,
+ 0,
+ "MODEL_STACK"
+ ],
+ [
+ 53,
+ 32,
+ 0,
+ 33,
+ 0,
+ "MODEL_STACK"
+ ]
+ ],
+ "groups": [
+ {
+ "title": "Stacked Model Merge Template (SDXL and 1.5)",
+ "bounding": [
+ -158,
+ 154,
+ 1762,
+ 883
+ ],
+ "color": "#3f789e",
+ "locked": false
+ }
+ ],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Model Merge/CR_UltraSimpleMultiModelMerge_v01b.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Model Merge/CR_UltraSimpleMultiModelMerge_v01b.json
new file mode 100644
index 0000000000000000000000000000000000000000..5b673b5b6b9b4c30a3d78f7630fce0bc1b39c521
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Model Merge/CR_UltraSimpleMultiModelMerge_v01b.json
@@ -0,0 +1,240 @@
+{
+ "last_node_id": 39,
+ "last_link_id": 55,
+ "nodes": [
+ {
+ "id": 12,
+ "type": "VAELoader",
+ "pos": [
+ 320,
+ 598.3380063476562
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 13
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "sdxl_vae.safetensors"
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 10,
+ "type": "CheckpointSave",
+ "pos": [
+ 770,
+ 478.33800634765623
+ ],
+ "size": {
+ "0": 310,
+ "1": 100
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 54
+ },
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 55
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 13
+ }
+ ],
+ "title": "Checkpoint Save",
+ "properties": {
+ "Node name for S&R": "CheckpointSave"
+ },
+ "widgets_values": [
+ "MergeModels/Merge"
+ ]
+ },
+ {
+ "id": 33,
+ "type": "CR Model Merge Stack",
+ "pos": [
+ -90,
+ 258.33800634765623
+ ],
+ "size": {
+ "0": 315,
+ "1": 322
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model_stack",
+ "type": "MODEL_STACK",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL_STACK",
+ "type": "MODEL_STACK",
+ "links": [
+ 52
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Model Merge Stack"
+ },
+ "widgets_values": [
+ "On",
+ "SDXL\\mbbxlUltimate_v10RC.safetensors",
+ 0.5,
+ 0.5,
+ "On",
+ "SDXL\\dreamshaperXL10_alpha2Xl10.safetensors",
+ 0.5,
+ 0.5,
+ "Off",
+ "None",
+ 1,
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ },
+ {
+ "id": 31,
+ "type": "CR Apply Model Merge",
+ "pos": [
+ 310,
+ 398.33800634765623
+ ],
+ "size": {
+ "0": 330,
+ "1": 146
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model_stack",
+ "type": "MODEL_STACK",
+ "link": 52
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 54
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 55
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "model_mix_info",
+ "type": "STRING",
+ "links": [],
+ "shape": 3,
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Apply Model Merge"
+ },
+ "widgets_values": [
+ "Recursive",
+ "Yes",
+ 1
+ ],
+ "color": "#222",
+ "bgcolor": "#000"
+ }
+ ],
+ "links": [
+ [
+ 13,
+ 12,
+ 0,
+ 10,
+ 2,
+ "VAE"
+ ],
+ [
+ 52,
+ 33,
+ 0,
+ 31,
+ 0,
+ "MODEL_STACK"
+ ],
+ [
+ 54,
+ 31,
+ 0,
+ 10,
+ 0,
+ "MODEL"
+ ],
+ [
+ 55,
+ 31,
+ 1,
+ 10,
+ 1,
+ "CLIP"
+ ]
+ ],
+ "groups": [
+ {
+ "title": "Ultra-Simple Model Merge Template (SDXL and 1.5)",
+ "bounding": [
+ -158,
+ 152,
+ 1271,
+ 554
+ ],
+ "color": "#3f789e",
+ "locked": false
+ }
+ ],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Upscale/Comfyroll_Upscale_Demo1_v01.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Upscale/Comfyroll_Upscale_Demo1_v01.json
new file mode 100644
index 0000000000000000000000000000000000000000..732808ab9a670c33c4e847cd463e7b54bd48b878
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Upscale/Comfyroll_Upscale_Demo1_v01.json
@@ -0,0 +1,195 @@
+{
+ "last_node_id": 25,
+ "last_link_id": 28,
+ "nodes": [
+ {
+ "id": 3,
+ "type": "PreviewImage",
+ "pos": [
+ 1330,
+ 330
+ ],
+ "size": {
+ "0": 290,
+ "1": 330
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 26
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 5,
+ "type": "CR Upscale Image",
+ "pos": [
+ 550,
+ 330
+ ],
+ "size": {
+ "0": 310,
+ "1": 202
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 28
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 25
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Upscale Image"
+ },
+ "widgets_values": [
+ "1x_PixelSharpen_v2_strong.pth",
+ "rescale",
+ 1,
+ 1024,
+ "lanczos",
+ "true",
+ 8
+ ]
+ },
+ {
+ "id": 21,
+ "type": "CR Upscale Image",
+ "pos": [
+ 940,
+ 330
+ ],
+ "size": {
+ "0": 310,
+ "1": 202
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 25
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 26
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Upscale Image"
+ },
+ "widgets_values": [
+ "4x-UltraSharp.pth",
+ "rescale",
+ 2,
+ 1024,
+ "lanczos",
+ "true",
+ 8
+ ]
+ },
+ {
+ "id": 25,
+ "type": "Load Image Batch",
+ "pos": [
+ 160,
+ 330
+ ],
+ "size": {
+ "0": 315,
+ "1": 222
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 28
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "filename_text",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Load Image Batch"
+ },
+ "widgets_values": [
+ "incremental_image",
+ 0,
+ "Batch 001",
+ "E:\\Comfy Projects\\SDXL\\17 - Metal Cat\\plastic",
+ "*",
+ "false",
+ "true"
+ ]
+ }
+ ],
+ "links": [
+ [
+ 25,
+ 5,
+ 0,
+ 21,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 26,
+ 21,
+ 0,
+ 3,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 28,
+ 25,
+ 0,
+ 5,
+ 0,
+ "IMAGE"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Upscale/Comfyroll_Upscale_Demo2_v01.json b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Upscale/Comfyroll_Upscale_Demo2_v01.json
new file mode 100644
index 0000000000000000000000000000000000000000..3a481466784047d054c9d82709026d831418d072
--- /dev/null
+++ b/custom_nodes/ComfyUI_Comfyroll_CustomNodes/workflows/Upscale/Comfyroll_Upscale_Demo2_v01.json
@@ -0,0 +1,198 @@
+{
+ "last_node_id": 18,
+ "last_link_id": 22,
+ "nodes": [
+ {
+ "id": 9,
+ "type": "CR Multi Upscale Stack",
+ "pos": [
+ -140,
+ -260
+ ],
+ "size": {
+ "0": 390,
+ "1": 250
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "upscale_stack",
+ "type": "UPSCALE_STACK",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "UPSCALE_STACK",
+ "type": "UPSCALE_STACK",
+ "links": [
+ 8
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Multi Upscale Stack"
+ },
+ "widgets_values": [
+ "Off",
+ "1x_ArtClarity.pth",
+ 1,
+ "On",
+ "1x_PixelSharpen_v2.pth",
+ 1,
+ "On",
+ "4x-UltraSharp.pth",
+ 2
+ ]
+ },
+ {
+ "id": 18,
+ "type": "Load Image Batch",
+ "pos": [
+ -130,
+ 70
+ ],
+ "size": {
+ "0": 315,
+ "1": 222
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 21
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "filename_text",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Load Image Batch"
+ },
+ "widgets_values": [
+ "incremental_image",
+ 0,
+ "Batch 001",
+ "E:\\Comfy Projects\\SDXL\\17 - Metal Cat\\plastic",
+ "*",
+ "false",
+ "true"
+ ]
+ },
+ {
+ "id": 10,
+ "type": "CR Apply Multi Upscale",
+ "pos": [
+ 320,
+ 70
+ ],
+ "size": {
+ "0": 270,
+ "1": 126
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 21
+ },
+ {
+ "name": "upscale_stack",
+ "type": "UPSCALE_STACK",
+ "link": 8
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 10
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CR Apply Multi Upscale"
+ },
+ "widgets_values": [
+ "lanczos",
+ "true",
+ 8
+ ]
+ },
+ {
+ "id": 8,
+ "type": "PreviewImage",
+ "pos": [
+ 660,
+ -270
+ ],
+ "size": [
+ 390,
+ 560
+ ],
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 10
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ }
+ ],
+ "links": [
+ [
+ 8,
+ 9,
+ 0,
+ 10,
+ 1,
+ "UPSCALE_STACK"
+ ],
+ [
+ 10,
+ 10,
+ 0,
+ 8,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 21,
+ 18,
+ 0,
+ 10,
+ 0,
+ "IMAGE"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/.gitignore b/custom_nodes/ComfyUI_IPAdapter_plus/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42b4d89400b633d0dac7e2de4d7ec88b32e6fa0d
--- /dev/null
+++ b/custom_nodes/ComfyUI_IPAdapter_plus/.gitignore
@@ -0,0 +1,3 @@
+/__pycache__/
+/models/*.bin
+/models/*.safetensors
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/IPAdapterPlus.py b/custom_nodes/ComfyUI_IPAdapter_plus/IPAdapterPlus.py
new file mode 100644
index 0000000000000000000000000000000000000000..782a4125d1494d401b20c5f21098c55147f28e84
--- /dev/null
+++ b/custom_nodes/ComfyUI_IPAdapter_plus/IPAdapterPlus.py
@@ -0,0 +1,765 @@
+import torch
+import contextlib
+import os
+import math
+
+import comfy.utils
+import comfy.model_management
+from comfy.clip_vision import clip_preprocess
+from comfy.ldm.modules.attention import optimized_attention
+import folder_paths
+
+from torch import nn
+from PIL import Image
+import torch.nn.functional as F
+import torchvision.transforms as TT
+
+from .resampler import Resampler
+
+# set the models directory backward compatible
+GLOBAL_MODELS_DIR = os.path.join(folder_paths.models_dir, "ipadapter")
+MODELS_DIR = GLOBAL_MODELS_DIR if os.path.isdir(GLOBAL_MODELS_DIR) else os.path.join(os.path.dirname(os.path.realpath(__file__)), "models")
+if "ipadapter" not in folder_paths.folder_names_and_paths:
+ folder_paths.folder_names_and_paths["ipadapter"] = ([MODELS_DIR], folder_paths.supported_pt_extensions)
+else:
+ folder_paths.folder_names_and_paths["ipadapter"][1].update(folder_paths.supported_pt_extensions)
+
+class MLPProjModel(torch.nn.Module):
+ """SD model with image prompt"""
+ def __init__(self, cross_attention_dim=1024, clip_embeddings_dim=1024):
+ super().__init__()
+
+ self.proj = torch.nn.Sequential(
+ torch.nn.Linear(clip_embeddings_dim, clip_embeddings_dim),
+ torch.nn.GELU(),
+ torch.nn.Linear(clip_embeddings_dim, cross_attention_dim),
+ torch.nn.LayerNorm(cross_attention_dim)
+ )
+
+ def forward(self, image_embeds):
+ clip_extra_context_tokens = self.proj(image_embeds)
+ return clip_extra_context_tokens
+
+class ImageProjModel(nn.Module):
+ def __init__(self, cross_attention_dim=1024, clip_embeddings_dim=1024, clip_extra_context_tokens=4):
+ super().__init__()
+
+ self.cross_attention_dim = cross_attention_dim
+ self.clip_extra_context_tokens = clip_extra_context_tokens
+ self.proj = nn.Linear(clip_embeddings_dim, self.clip_extra_context_tokens * cross_attention_dim)
+ self.norm = nn.LayerNorm(cross_attention_dim)
+
+ def forward(self, image_embeds):
+ embeds = image_embeds
+ clip_extra_context_tokens = self.proj(embeds).reshape(-1, self.clip_extra_context_tokens, self.cross_attention_dim)
+ clip_extra_context_tokens = self.norm(clip_extra_context_tokens)
+ return clip_extra_context_tokens
+
+class To_KV(nn.Module):
+ def __init__(self, state_dict):
+ super().__init__()
+
+ self.to_kvs = nn.ModuleDict()
+ for key, value in state_dict.items():
+ self.to_kvs[key.replace(".weight", "").replace(".", "_")] = nn.Linear(value.shape[1], value.shape[0], bias=False)
+ self.to_kvs[key.replace(".weight", "").replace(".", "_")].weight.data = value
+
+def set_model_patch_replace(model, patch_kwargs, key):
+ to = model.model_options["transformer_options"]
+ if "patches_replace" not in to:
+ to["patches_replace"] = {}
+ if "attn2" not in to["patches_replace"]:
+ to["patches_replace"]["attn2"] = {}
+ if key not in to["patches_replace"]["attn2"]:
+ patch = CrossAttentionPatch(**patch_kwargs)
+ to["patches_replace"]["attn2"][key] = patch
+ else:
+ to["patches_replace"]["attn2"][key].set_new_condition(**patch_kwargs)
+
+def image_add_noise(image, noise):
+ image = image.permute([0,3,1,2])
+ torch.manual_seed(0) # use a fixed random for reproducible results
+ transforms = TT.Compose([
+ TT.CenterCrop(min(image.shape[2], image.shape[3])),
+ TT.Resize((224, 224), interpolation=TT.InterpolationMode.BICUBIC, antialias=True),
+ TT.ElasticTransform(alpha=75.0, sigma=noise*3.5), # shuffle the image
+ TT.RandomVerticalFlip(p=1.0), # flip the image to change the geometry even more
+ TT.RandomHorizontalFlip(p=1.0),
+ ])
+ image = transforms(image.cpu())
+ image = image.permute([0,2,3,1])
+ image = image + ((0.25*(1-noise)+0.05) * torch.randn_like(image) ) # add further random noise
+ return image
+
+def zeroed_hidden_states(clip_vision, batch_size):
+ image = torch.zeros([batch_size, 224, 224, 3])
+ comfy.model_management.load_model_gpu(clip_vision.patcher)
+ pixel_values = clip_preprocess(image.to(clip_vision.load_device))
+
+ if clip_vision.dtype != torch.float32:
+ precision_scope = torch.autocast
+ else:
+ precision_scope = lambda a, b: contextlib.nullcontext(a)
+
+ with precision_scope(comfy.model_management.get_autocast_device(clip_vision.load_device), torch.float32):
+ outputs = clip_vision.model(pixel_values, intermediate_output=-2)
+
+ # we only need the penultimate hidden states
+ outputs = outputs[1].to(comfy.model_management.intermediate_device())
+
+ return outputs
+
+def min_(tensor_list):
+ # return the element-wise min of the tensor list.
+ x = torch.stack(tensor_list)
+ mn = x.min(axis=0)[0]
+ return torch.clamp(mn, min=0)
+
+def max_(tensor_list):
+ # return the element-wise max of the tensor list.
+ x = torch.stack(tensor_list)
+ mx = x.max(axis=0)[0]
+ return torch.clamp(mx, max=1)
+
+# From https://github.com/Jamy-L/Pytorch-Contrast-Adaptive-Sharpening/
+def contrast_adaptive_sharpening(image, amount):
+ img = F.pad(image, pad=(1, 1, 1, 1)).cpu()
+
+ a = img[..., :-2, :-2]
+ b = img[..., :-2, 1:-1]
+ c = img[..., :-2, 2:]
+ d = img[..., 1:-1, :-2]
+ e = img[..., 1:-1, 1:-1]
+ f = img[..., 1:-1, 2:]
+ g = img[..., 2:, :-2]
+ h = img[..., 2:, 1:-1]
+ i = img[..., 2:, 2:]
+
+ # Computing contrast
+ cross = (b, d, e, f, h)
+ mn = min_(cross)
+ mx = max_(cross)
+
+ diag = (a, c, g, i)
+ mn2 = min_(diag)
+ mx2 = max_(diag)
+ mx = mx + mx2
+ mn = mn + mn2
+
+ # Computing local weight
+ inv_mx = torch.reciprocal(mx)
+ amp = inv_mx * torch.minimum(mn, (2 - mx))
+
+ # scaling
+ amp = torch.sqrt(amp)
+ w = - amp * (amount * (1/5 - 1/8) + 1/8)
+ div = torch.reciprocal(1 + 4*w)
+
+ output = ((b + d + f + h)*w + e) * div
+ output = output.clamp(0, 1)
+ output = torch.nan_to_num(output)
+
+ return (output)
+
+class IPAdapter(nn.Module):
+ def __init__(self, ipadapter_model, cross_attention_dim=1024, output_cross_attention_dim=1024, clip_embeddings_dim=1024, clip_extra_context_tokens=4, is_sdxl=False, is_plus=False, is_full=False):
+ super().__init__()
+
+ self.clip_embeddings_dim = clip_embeddings_dim
+ self.cross_attention_dim = cross_attention_dim
+ self.output_cross_attention_dim = output_cross_attention_dim
+ self.clip_extra_context_tokens = clip_extra_context_tokens
+ self.is_sdxl = is_sdxl
+ self.is_full = is_full
+
+ self.image_proj_model = self.init_proj() if not is_plus else self.init_proj_plus()
+ self.image_proj_model.load_state_dict(ipadapter_model["image_proj"])
+ self.ip_layers = To_KV(ipadapter_model["ip_adapter"])
+
+ def init_proj(self):
+ image_proj_model = ImageProjModel(
+ cross_attention_dim=self.cross_attention_dim,
+ clip_embeddings_dim=self.clip_embeddings_dim,
+ clip_extra_context_tokens=self.clip_extra_context_tokens
+ )
+ return image_proj_model
+
+ def init_proj_plus(self):
+ if self.is_full:
+ image_proj_model = MLPProjModel(
+ cross_attention_dim=self.cross_attention_dim,
+ clip_embeddings_dim=self.clip_embeddings_dim
+ )
+ else:
+ image_proj_model = Resampler(
+ dim=self.cross_attention_dim,
+ depth=4,
+ dim_head=64,
+ heads=20 if self.is_sdxl else 12,
+ num_queries=self.clip_extra_context_tokens,
+ embedding_dim=self.clip_embeddings_dim,
+ output_dim=self.output_cross_attention_dim,
+ ff_mult=4
+ )
+ return image_proj_model
+
+ @torch.inference_mode()
+ def get_image_embeds(self, clip_embed, clip_embed_zeroed):
+ image_prompt_embeds = self.image_proj_model(clip_embed)
+ uncond_image_prompt_embeds = self.image_proj_model(clip_embed_zeroed)
+ return image_prompt_embeds, uncond_image_prompt_embeds
+
+class CrossAttentionPatch:
+ # forward for patching
+ def __init__(self, weight, ipadapter, device, dtype, number, cond, uncond, weight_type, mask=None, sigma_start=0.0, sigma_end=1.0, unfold_batch=False):
+ self.weights = [weight]
+ self.ipadapters = [ipadapter]
+ self.conds = [cond]
+ self.unconds = [uncond]
+ self.device = 'cuda' if 'cuda' in device.type else 'cpu'
+ self.dtype = dtype if 'cuda' in self.device else torch.bfloat16
+ self.number = number
+ self.weight_type = [weight_type]
+ self.masks = [mask]
+ self.sigma_start = [sigma_start]
+ self.sigma_end = [sigma_end]
+ self.unfold_batch = [unfold_batch]
+
+ self.k_key = str(self.number*2+1) + "_to_k_ip"
+ self.v_key = str(self.number*2+1) + "_to_v_ip"
+
+ def set_new_condition(self, weight, ipadapter, device, dtype, number, cond, uncond, weight_type, mask=None, sigma_start=0.0, sigma_end=1.0, unfold_batch=False):
+ self.weights.append(weight)
+ self.ipadapters.append(ipadapter)
+ self.conds.append(cond)
+ self.unconds.append(uncond)
+ self.masks.append(mask)
+ self.device = 'cuda' if 'cuda' in device.type else 'cpu'
+ self.dtype = dtype if 'cuda' in self.device else torch.bfloat16
+ self.weight_type.append(weight_type)
+ self.sigma_start.append(sigma_start)
+ self.sigma_end.append(sigma_end)
+ self.unfold_batch.append(unfold_batch)
+
+ def __call__(self, n, context_attn2, value_attn2, extra_options):
+ org_dtype = n.dtype
+ cond_or_uncond = extra_options["cond_or_uncond"]
+ sigma = extra_options["sigmas"][0].item() if 'sigmas' in extra_options else 999999999.9
+
+ # extra options for AnimateDiff
+ ad_params = extra_options['ad_params'] if "ad_params" in extra_options else None
+
+ with torch.autocast(device_type=self.device, dtype=self.dtype):
+ q = n
+ k = context_attn2
+ v = value_attn2
+ b = q.shape[0]
+ qs = q.shape[1]
+ batch_prompt = b // len(cond_or_uncond)
+ out = optimized_attention(q, k, v, extra_options["n_heads"])
+ _, _, lh, lw = extra_options["original_shape"]
+
+ for weight, cond, uncond, ipadapter, mask, weight_type, sigma_start, sigma_end, unfold_batch in zip(self.weights, self.conds, self.unconds, self.ipadapters, self.masks, self.weight_type, self.sigma_start, self.sigma_end, self.unfold_batch):
+ if sigma > sigma_start or sigma < sigma_end:
+ continue
+
+ if unfold_batch and cond.shape[0] > 1:
+ # Check AnimateDiff context window
+ if ad_params is not None and ad_params["sub_idxs"] is not None:
+ # if images length matches or exceeds full_length get sub_idx images
+ if cond.shape[0] >= ad_params["full_length"]:
+ cond = torch.Tensor(cond[ad_params["sub_idxs"]])
+ uncond = torch.Tensor(uncond[ad_params["sub_idxs"]])
+ # otherwise, need to do more to get proper sub_idxs masks
+ else:
+ # check if images length matches full_length - if not, make it match
+ if cond.shape[0] < ad_params["full_length"]:
+ cond = torch.cat((cond, cond[-1:].repeat((ad_params["full_length"]-cond.shape[0], 1, 1))), dim=0)
+ uncond = torch.cat((uncond, uncond[-1:].repeat((ad_params["full_length"]-uncond.shape[0], 1, 1))), dim=0)
+ # if we have too many remove the excess (should not happen, but just in case)
+ if cond.shape[0] > ad_params["full_length"]:
+ cond = cond[:ad_params["full_length"]]
+ uncond = uncond[:ad_params["full_length"]]
+ cond = cond[ad_params["sub_idxs"]]
+ uncond = uncond[ad_params["sub_idxs"]]
+
+ # if we don't have enough reference images repeat the last one until we reach the right size
+ if cond.shape[0] < batch_prompt:
+ cond = torch.cat((cond, cond[-1:].repeat((batch_prompt-cond.shape[0], 1, 1))), dim=0)
+ uncond = torch.cat((uncond, uncond[-1:].repeat((batch_prompt-uncond.shape[0], 1, 1))), dim=0)
+ # if we have too many remove the exceeding
+ elif cond.shape[0] > batch_prompt:
+ cond = cond[:batch_prompt]
+ uncond = uncond[:batch_prompt]
+
+ k_cond = ipadapter.ip_layers.to_kvs[self.k_key](cond)
+ k_uncond = ipadapter.ip_layers.to_kvs[self.k_key](uncond)
+ v_cond = ipadapter.ip_layers.to_kvs[self.v_key](cond)
+ v_uncond = ipadapter.ip_layers.to_kvs[self.v_key](uncond)
+ else:
+ k_cond = ipadapter.ip_layers.to_kvs[self.k_key](cond).repeat(batch_prompt, 1, 1)
+ k_uncond = ipadapter.ip_layers.to_kvs[self.k_key](uncond).repeat(batch_prompt, 1, 1)
+ v_cond = ipadapter.ip_layers.to_kvs[self.v_key](cond).repeat(batch_prompt, 1, 1)
+ v_uncond = ipadapter.ip_layers.to_kvs[self.v_key](uncond).repeat(batch_prompt, 1, 1)
+
+ if weight_type.startswith("linear"):
+ ip_k = torch.cat([(k_cond, k_uncond)[i] for i in cond_or_uncond], dim=0) * weight
+ ip_v = torch.cat([(v_cond, v_uncond)[i] for i in cond_or_uncond], dim=0) * weight
+ else:
+ ip_k = torch.cat([(k_cond, k_uncond)[i] for i in cond_or_uncond], dim=0)
+ ip_v = torch.cat([(v_cond, v_uncond)[i] for i in cond_or_uncond], dim=0)
+
+ if weight_type.startswith("channel"):
+ # code by Lvmin Zhang at Stanford University as also seen on Fooocus IPAdapter implementation
+ # please read licensing notes https://github.com/lllyasviel/Fooocus/blob/main/fooocus_extras/ip_adapter.py#L225
+ ip_v_mean = torch.mean(ip_v, dim=1, keepdim=True)
+ ip_v_offset = ip_v - ip_v_mean
+ _, _, C = ip_k.shape
+ channel_penalty = float(C) / 1280.0
+ W = weight * channel_penalty
+ ip_k = ip_k * W
+ ip_v = ip_v_offset + ip_v_mean * W
+
+ out_ip = optimized_attention(q, ip_k, ip_v, extra_options["n_heads"])
+ if weight_type.startswith("original"):
+ out_ip = out_ip * weight
+
+ if mask is not None:
+ # TODO: needs checking
+ mask_h = max(1, round(lh / math.sqrt(lh * lw / qs)))
+ mask_w = qs // mask_h
+
+ # check if using AnimateDiff and sliding context window
+ if (mask.shape[0] > 1 and ad_params is not None and ad_params["sub_idxs"] is not None):
+ # if mask length matches or exceeds full_length, just get sub_idx masks, resize, and continue
+ if mask.shape[0] >= ad_params["full_length"]:
+ mask_downsample = torch.Tensor(mask[ad_params["sub_idxs"]])
+ mask_downsample = F.interpolate(mask_downsample.unsqueeze(1), size=(mask_h, mask_w), mode="bicubic").squeeze(1)
+ # otherwise, need to do more to get proper sub_idxs masks
+ else:
+ # resize to needed attention size (to save on memory)
+ mask_downsample = F.interpolate(mask.unsqueeze(1), size=(mask_h, mask_w), mode="bicubic").squeeze(1)
+ # check if mask length matches full_length - if not, make it match
+ if mask_downsample.shape[0] < ad_params["full_length"]:
+ mask_downsample = torch.cat((mask_downsample, mask_downsample[-1:].repeat((ad_params["full_length"]-mask_downsample.shape[0], 1, 1))), dim=0)
+ # if we have too many remove the excess (should not happen, but just in case)
+ if mask_downsample.shape[0] > ad_params["full_length"]:
+ mask_downsample = mask_downsample[:ad_params["full_length"]]
+ # now, select sub_idxs masks
+ mask_downsample = mask_downsample[ad_params["sub_idxs"]]
+ # otherwise, perform usual mask interpolation
+ else:
+ mask_downsample = F.interpolate(mask.unsqueeze(1), size=(mask_h, mask_w), mode="bicubic").squeeze(1)
+
+ # if we don't have enough masks repeat the last one until we reach the right size
+ if mask_downsample.shape[0] < batch_prompt:
+ mask_downsample = torch.cat((mask_downsample, mask_downsample[-1:, :, :].repeat((batch_prompt-mask_downsample.shape[0], 1, 1))), dim=0)
+ # if we have too many remove the exceeding
+ elif mask_downsample.shape[0] > batch_prompt:
+ mask_downsample = mask_downsample[:batch_prompt, :, :]
+
+ # repeat the masks
+ mask_downsample = mask_downsample.repeat(len(cond_or_uncond), 1, 1)
+ mask_downsample = mask_downsample.view(mask_downsample.shape[0], -1, 1).repeat(1, 1, out.shape[2])
+
+ out_ip = out_ip * mask_downsample
+
+ out = out + out_ip
+
+ return out.to(dtype=org_dtype)
+
+class IPAdapterModelLoader:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": { "ipadapter_file": (folder_paths.get_filename_list("ipadapter"), )}}
+
+ RETURN_TYPES = ("IPADAPTER",)
+ FUNCTION = "load_ipadapter_model"
+
+ CATEGORY = "ipadapter"
+
+ def load_ipadapter_model(self, ipadapter_file):
+ ckpt_path = folder_paths.get_full_path("ipadapter", ipadapter_file)
+
+ model = comfy.utils.load_torch_file(ckpt_path, safe_load=True)
+
+ if ckpt_path.lower().endswith(".safetensors"):
+ st_model = {"image_proj": {}, "ip_adapter": {}}
+ for key in model.keys():
+ if key.startswith("image_proj."):
+ st_model["image_proj"][key.replace("image_proj.", "")] = model[key]
+ elif key.startswith("ip_adapter."):
+ st_model["ip_adapter"][key.replace("ip_adapter.", "")] = model[key]
+ model = st_model
+
+ if not "ip_adapter" in model.keys() or not model["ip_adapter"]:
+ raise Exception("invalid IPAdapter model {}".format(ckpt_path))
+
+ return (model,)
+
+class IPAdapterApply:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "ipadapter": ("IPADAPTER", ),
+ "clip_vision": ("CLIP_VISION",),
+ "image": ("IMAGE",),
+ "model": ("MODEL", ),
+ "weight": ("FLOAT", { "default": 1.0, "min": -1, "max": 3, "step": 0.05 }),
+ "noise": ("FLOAT", { "default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01 }),
+ "weight_type": (["original", "linear", "channel penalty"], ),
+ "start_at": ("FLOAT", { "default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001 }),
+ "end_at": ("FLOAT", { "default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001 }),
+ "unfold_batch": ("BOOLEAN", { "default": False }),
+ },
+ "optional": {
+ "attn_mask": ("MASK",),
+ }
+ }
+
+ RETURN_TYPES = ("MODEL",)
+ FUNCTION = "apply_ipadapter"
+ CATEGORY = "ipadapter"
+
+ def apply_ipadapter(self, ipadapter, model, weight, clip_vision=None, image=None, weight_type="original", noise=None, embeds=None, attn_mask=None, start_at=0.0, end_at=1.0, unfold_batch=False):
+ self.dtype = model.model.diffusion_model.dtype
+ self.device = comfy.model_management.get_torch_device()
+ self.weight = weight
+ self.is_full = "proj.0.weight" in ipadapter["image_proj"]
+ self.is_plus = self.is_full or "latents" in ipadapter["image_proj"]
+
+ output_cross_attention_dim = ipadapter["ip_adapter"]["1.to_k_ip.weight"].shape[1]
+ self.is_sdxl = output_cross_attention_dim == 2048
+ cross_attention_dim = 1280 if self.is_plus and self.is_sdxl else output_cross_attention_dim
+ clip_extra_context_tokens = 16 if self.is_plus else 4
+
+ if embeds is not None:
+ embeds = torch.unbind(embeds)
+ clip_embed = embeds[0].cpu()
+ clip_embed_zeroed = embeds[1].cpu()
+ else:
+ if image.shape[1] != image.shape[2]:
+ print("\033[33mINFO: the IPAdapter reference image is not a square, CLIPImageProcessor will resize and crop it at the center. If the main focus of the picture is not in the middle the result might not be what you are expecting.\033[0m")
+
+ clip_embed = clip_vision.encode_image(image)
+ neg_image = image_add_noise(image, noise) if noise > 0 else None
+
+ if self.is_plus:
+ clip_embed = clip_embed.penultimate_hidden_states
+ if noise > 0:
+ clip_embed_zeroed = clip_vision.encode_image(neg_image).penultimate_hidden_states
+ else:
+ clip_embed_zeroed = zeroed_hidden_states(clip_vision, image.shape[0])
+ else:
+ clip_embed = clip_embed.image_embeds
+ if noise > 0:
+ clip_embed_zeroed = clip_vision.encode_image(neg_image).image_embeds
+ else:
+ clip_embed_zeroed = torch.zeros_like(clip_embed)
+
+ clip_embeddings_dim = clip_embed.shape[-1]
+
+ self.ipadapter = IPAdapter(
+ ipadapter,
+ cross_attention_dim=cross_attention_dim,
+ output_cross_attention_dim=output_cross_attention_dim,
+ clip_embeddings_dim=clip_embeddings_dim,
+ clip_extra_context_tokens=clip_extra_context_tokens,
+ is_sdxl=self.is_sdxl,
+ is_plus=self.is_plus,
+ is_full=self.is_full,
+ )
+
+ self.ipadapter.to(self.device, dtype=self.dtype)
+
+ image_prompt_embeds, uncond_image_prompt_embeds = self.ipadapter.get_image_embeds(clip_embed.to(self.device, self.dtype), clip_embed_zeroed.to(self.device, self.dtype))
+ image_prompt_embeds = image_prompt_embeds.to(self.device, dtype=self.dtype)
+ uncond_image_prompt_embeds = uncond_image_prompt_embeds.to(self.device, dtype=self.dtype)
+
+ work_model = model.clone()
+
+ if attn_mask is not None:
+ attn_mask = attn_mask.to(self.device)
+
+ sigma_start = model.model.model_sampling.percent_to_sigma(start_at)
+ sigma_end = model.model.model_sampling.percent_to_sigma(end_at)
+
+ patch_kwargs = {
+ "number": 0,
+ "weight": self.weight,
+ "ipadapter": self.ipadapter,
+ "device": self.device,
+ "dtype": self.dtype,
+ "cond": image_prompt_embeds,
+ "uncond": uncond_image_prompt_embeds,
+ "weight_type": weight_type,
+ "mask": attn_mask,
+ "sigma_start": sigma_start,
+ "sigma_end": sigma_end,
+ "unfold_batch": unfold_batch,
+ }
+
+ if not self.is_sdxl:
+ for id in [1,2,4,5,7,8]: # id of input_blocks that have cross attention
+ set_model_patch_replace(work_model, patch_kwargs, ("input", id))
+ patch_kwargs["number"] += 1
+ for id in [3,4,5,6,7,8,9,10,11]: # id of output_blocks that have cross attention
+ set_model_patch_replace(work_model, patch_kwargs, ("output", id))
+ patch_kwargs["number"] += 1
+ set_model_patch_replace(work_model, patch_kwargs, ("middle", 0))
+ else:
+ for id in [4,5,7,8]: # id of input_blocks that have cross attention
+ block_indices = range(2) if id in [4, 5] else range(10) # transformer_depth
+ for index in block_indices:
+ set_model_patch_replace(work_model, patch_kwargs, ("input", id, index))
+ patch_kwargs["number"] += 1
+ for id in range(6): # id of output_blocks that have cross attention
+ block_indices = range(2) if id in [3, 4, 5] else range(10) # transformer_depth
+ for index in block_indices:
+ set_model_patch_replace(work_model, patch_kwargs, ("output", id, index))
+ patch_kwargs["number"] += 1
+ for index in range(10):
+ set_model_patch_replace(work_model, patch_kwargs, ("middle", 0, index))
+ patch_kwargs["number"] += 1
+
+ return (work_model, )
+
+class PrepImageForClipVision:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {
+ "image": ("IMAGE",),
+ "interpolation": (["LANCZOS", "BICUBIC", "HAMMING", "BILINEAR", "BOX", "NEAREST"],),
+ "crop_position": (["top", "bottom", "left", "right", "center", "pad"],),
+ "sharpening": ("FLOAT", {"default": 0.0, "min": 0, "max": 1, "step": 0.05}),
+ },
+ }
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "prep_image"
+
+ CATEGORY = "ipadapter"
+
+ def prep_image(self, image, interpolation="LANCZOS", crop_position="center", sharpening=0.0):
+ _, oh, ow, _ = image.shape
+ output = image.permute([0,3,1,2])
+
+ if "pad" in crop_position:
+ target_length = max(oh, ow)
+ pad_l = (target_length - ow) // 2
+ pad_r = (target_length - ow) - pad_l
+ pad_t = (target_length - oh) // 2
+ pad_b = (target_length - oh) - pad_t
+ output = F.pad(output, (pad_l, pad_r, pad_t, pad_b), value=0, mode="constant")
+ else:
+ crop_size = min(oh, ow)
+ x = (ow-crop_size) // 2
+ y = (oh-crop_size) // 2
+ if "top" in crop_position:
+ y = 0
+ elif "bottom" in crop_position:
+ y = oh-crop_size
+ elif "left" in crop_position:
+ x = 0
+ elif "right" in crop_position:
+ x = ow-crop_size
+
+ x2 = x+crop_size
+ y2 = y+crop_size
+
+ # crop
+ output = output[:, :, y:y2, x:x2]
+
+ # resize (apparently PIL resize is better than tourchvision interpolate)
+ imgs = []
+ for i in range(output.shape[0]):
+ img = TT.ToPILImage()(output[i])
+ img = img.resize((224,224), resample=Image.Resampling[interpolation])
+ imgs.append(TT.ToTensor()(img))
+ output = torch.stack(imgs, dim=0)
+
+ if sharpening > 0:
+ output = contrast_adaptive_sharpening(output, sharpening)
+
+ output = output.permute([0,2,3,1])
+
+ return (output,)
+
+class IPAdapterEncoder:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {
+ "clip_vision": ("CLIP_VISION",),
+ "image_1": ("IMAGE",),
+ "ipadapter_plus": ("BOOLEAN", { "default": False }),
+ "noise": ("FLOAT", { "default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01 }),
+ "weight_1": ("FLOAT", { "default": 1.0, "min": 0, "max": 1.0, "step": 0.01 }),
+ },
+ "optional": {
+ "image_2": ("IMAGE",),
+ "image_3": ("IMAGE",),
+ "image_4": ("IMAGE",),
+ "weight_2": ("FLOAT", { "default": 1.0, "min": 0, "max": 1.0, "step": 0.01 }),
+ "weight_3": ("FLOAT", { "default": 1.0, "min": 0, "max": 1.0, "step": 0.01 }),
+ "weight_4": ("FLOAT", { "default": 1.0, "min": 0, "max": 1.0, "step": 0.01 }),
+ }
+ }
+
+ RETURN_TYPES = ("EMBEDS",)
+ FUNCTION = "preprocess"
+ CATEGORY = "ipadapter"
+
+ def preprocess(self, clip_vision, image_1, ipadapter_plus, noise, weight_1, image_2=None, image_3=None, image_4=None, weight_2=1.0, weight_3=1.0, weight_4=1.0):
+ weight_1 *= (0.1 + (weight_1 - 0.1))
+ weight_1 = 1.19e-05 if weight_1 <= 1.19e-05 else weight_1
+ weight_2 *= (0.1 + (weight_2 - 0.1))
+ weight_2 = 1.19e-05 if weight_2 <= 1.19e-05 else weight_2
+ weight_3 *= (0.1 + (weight_3 - 0.1))
+ weight_3 = 1.19e-05 if weight_3 <= 1.19e-05 else weight_3
+ weight_4 *= (0.1 + (weight_4 - 0.1))
+ weight_5 = 1.19e-05 if weight_4 <= 1.19e-05 else weight_4
+
+ image = image_1
+ weight = [weight_1]*image_1.shape[0]
+
+ if image_2 is not None:
+ if image_1.shape[1:] != image_2.shape[1:]:
+ image_2 = comfy.utils.common_upscale(image_2.movedim(-1,1), image.shape[2], image.shape[1], "bilinear", "center").movedim(1,-1)
+ image = torch.cat((image, image_2), dim=0)
+ weight += [weight_2]*image_2.shape[0]
+ if image_3 is not None:
+ if image.shape[1:] != image_3.shape[1:]:
+ image_3 = comfy.utils.common_upscale(image_3.movedim(-1,1), image.shape[2], image.shape[1], "bilinear", "center").movedim(1,-1)
+ image = torch.cat((image, image_3), dim=0)
+ weight += [weight_3]*image_3.shape[0]
+ if image_4 is not None:
+ if image.shape[1:] != image_4.shape[1:]:
+ image_4 = comfy.utils.common_upscale(image_4.movedim(-1,1), image.shape[2], image.shape[1], "bilinear", "center").movedim(1,-1)
+ image = torch.cat((image, image_4), dim=0)
+ weight += [weight_4]*image_4.shape[0]
+
+ clip_embed = clip_vision.encode_image(image)
+ neg_image = image_add_noise(image, noise) if noise > 0 else None
+
+ if ipadapter_plus:
+ clip_embed = clip_embed.penultimate_hidden_states
+ if noise > 0:
+ clip_embed_zeroed = clip_vision.encode_image(neg_image).penultimate_hidden_states
+ else:
+ clip_embed_zeroed = zeroed_hidden_states(clip_vision, image.shape[0])
+ else:
+ clip_embed = clip_embed.image_embeds
+ if noise > 0:
+ clip_embed_zeroed = clip_vision.encode_image(neg_image).image_embeds
+ else:
+ clip_embed_zeroed = torch.zeros_like(clip_embed)
+
+ if any(e != 1.0 for e in weight):
+ weight = torch.tensor(weight).unsqueeze(-1) if not ipadapter_plus else torch.tensor(weight).unsqueeze(-1).unsqueeze(-1)
+ clip_embed = clip_embed * weight
+
+ output = torch.stack((clip_embed, clip_embed_zeroed))
+
+ return( output, )
+
+class IPAdapterApplyEncoded(IPAdapterApply):
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "ipadapter": ("IPADAPTER", ),
+ "embeds": ("EMBEDS",),
+ "model": ("MODEL", ),
+ "weight": ("FLOAT", { "default": 1.0, "min": -1, "max": 3, "step": 0.05 }),
+ "weight_type": (["original", "linear", "channel penalty"], ),
+ "start_at": ("FLOAT", { "default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001 }),
+ "end_at": ("FLOAT", { "default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001 }),
+ "unfold_batch": ("BOOLEAN", { "default": False }),
+ },
+ "optional": {
+ "attn_mask": ("MASK",),
+ }
+ }
+
+class IPAdapterSaveEmbeds:
+ def __init__(self):
+ self.output_dir = folder_paths.get_output_directory()
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {
+ "embeds": ("EMBEDS",),
+ "filename_prefix": ("STRING", {"default": "embeds/IPAdapter"})
+ },
+ }
+
+ RETURN_TYPES = ()
+ FUNCTION = "save"
+ OUTPUT_NODE = True
+ CATEGORY = "ipadapter"
+
+ def save(self, embeds, filename_prefix):
+ full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir)
+ file = f"{filename}_{counter:05}_.ipadpt"
+ file = os.path.join(full_output_folder, file)
+
+ torch.save(embeds, file)
+ return (None, )
+
+
+class IPAdapterLoadEmbeds:
+ @classmethod
+ def INPUT_TYPES(s):
+ input_dir = folder_paths.get_input_directory()
+ files = [os.path.relpath(os.path.join(root, file), input_dir) for root, dirs, files in os.walk(input_dir) for file in files if file.endswith('.ipadpt')]
+ return {"required": {"embeds": [sorted(files), ]}, }
+
+ RETURN_TYPES = ("EMBEDS", )
+ FUNCTION = "load"
+ CATEGORY = "ipadapter"
+
+ def load(self, embeds):
+ path = folder_paths.get_annotated_filepath(embeds)
+ output = torch.load(path).cpu()
+
+ return (output, )
+
+
+class IPAdapterBatchEmbeds:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {
+ "embed1": ("EMBEDS",),
+ "embed2": ("EMBEDS",),
+ }}
+
+ RETURN_TYPES = ("EMBEDS",)
+ FUNCTION = "batch"
+ CATEGORY = "ipadapter"
+
+ def batch(self, embed1, embed2):
+ output = torch.cat((embed1, embed2), dim=1)
+ return (output, )
+
+NODE_CLASS_MAPPINGS = {
+ "IPAdapterModelLoader": IPAdapterModelLoader,
+ "IPAdapterApply": IPAdapterApply,
+ "IPAdapterApplyEncoded": IPAdapterApplyEncoded,
+ "PrepImageForClipVision": PrepImageForClipVision,
+ "IPAdapterEncoder": IPAdapterEncoder,
+ "IPAdapterSaveEmbeds": IPAdapterSaveEmbeds,
+ "IPAdapterLoadEmbeds": IPAdapterLoadEmbeds,
+ "IPAdapterBatchEmbeds": IPAdapterBatchEmbeds,
+}
+
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "IPAdapterModelLoader": "Load IPAdapter Model",
+ "IPAdapterApply": "Apply IPAdapter",
+ "IPAdapterApplyEncoded": "Apply IPAdapter from Encoded",
+ "PrepImageForClipVision": "Prepare Image For Clip Vision",
+ "IPAdapterEncoder": "Encode IPAdapter Image",
+ "IPAdapterSaveEmbeds": "Save IPAdapter Embeds",
+ "IPAdapterLoadEmbeds": "Load IPAdapter Embeds",
+ "IPAdapterBatchEmbeds": "IPAdapter Batch Embeds",
+}
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/IPAdapter_workflow.json b/custom_nodes/ComfyUI_IPAdapter_plus/IPAdapter_workflow.json
new file mode 100644
index 0000000000000000000000000000000000000000..6f66f283f84f51fa820ea5b732dbe68b4371f704
--- /dev/null
+++ b/custom_nodes/ComfyUI_IPAdapter_plus/IPAdapter_workflow.json
@@ -0,0 +1,606 @@
+{
+ "last_node_id": 12,
+ "last_link_id": 13,
+ "nodes": [
+ {
+ "id": 10,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 650,
+ 590
+ ],
+ "size": {
+ "0": 210,
+ "1": 110
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 10
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ 1
+ ]
+ },
+ {
+ "id": 8,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 650,
+ 420
+ ],
+ "size": {
+ "0": 210,
+ "1": 120
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 6
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 9
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "blurry, horror"
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 7,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 650,
+ 250
+ ],
+ "size": {
+ "0": 210,
+ "1": 120
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 5
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 8
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "beautiful renaissance girl, detailed"
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 11,
+ "type": "VAEDecode",
+ "pos": [
+ 1300,
+ 170
+ ],
+ "size": {
+ "0": 140,
+ "1": 50
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 11
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 12
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 13
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 2,
+ "type": "VAELoader",
+ "pos": [
+ 940,
+ 480
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 12
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "vae-ft-mse-840000-ema-pruned.safetensors"
+ ]
+ },
+ {
+ "id": 12,
+ "type": "SaveImage",
+ "pos": [
+ 1300,
+ 270
+ ],
+ "size": {
+ "0": 400,
+ "1": 450
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 13
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "IPAdapter"
+ ]
+ },
+ {
+ "id": 6,
+ "type": "LoadImage",
+ "pos": [
+ 40,
+ 60
+ ],
+ "size": {
+ "0": 220,
+ "1": 320
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 3
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "venere.jpg",
+ "image"
+ ]
+ },
+ {
+ "id": 9,
+ "type": "KSampler",
+ "pos": [
+ 930,
+ 170
+ ],
+ "size": {
+ "0": 315,
+ "1": 262
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 7
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 8
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 9
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 10
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 11
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 0,
+ "fixed",
+ 25,
+ 6,
+ "ddim",
+ "ddim_uniform",
+ 1
+ ]
+ },
+ {
+ "id": 4,
+ "type": "CLIPVisionLoader",
+ "pos": [
+ 290,
+ 170
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "CLIP_VISION",
+ "type": "CLIP_VISION",
+ "links": [
+ 2
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPVisionLoader"
+ },
+ "widgets_values": [
+ "IPAdapter_image_encoder_sd15.safetensors"
+ ]
+ },
+ {
+ "id": 1,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ 290,
+ 280
+ ],
+ "size": {
+ "0": 300,
+ "1": 100
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 4
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 5,
+ 6
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "v1-5-pruned-emaonly.safetensors"
+ ]
+ },
+ {
+ "id": 5,
+ "type": "IPAdapterApply",
+ "pos": [
+ 651,
+ -57
+ ],
+ "size": {
+ "0": 210,
+ "1": 258
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "ipadapter",
+ "type": "IPADAPTER",
+ "link": 1
+ },
+ {
+ "name": "clip_vision",
+ "type": "CLIP_VISION",
+ "link": 2
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 3
+ },
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 4
+ },
+ {
+ "name": "attn_mask",
+ "type": "MASK",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 7
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapterApply"
+ },
+ "widgets_values": [
+ 1,
+ 0,
+ "original",
+ 0,
+ 1,
+ false
+ ]
+ },
+ {
+ "id": 3,
+ "type": "IPAdapterModelLoader",
+ "pos": [
+ 290,
+ 60
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IPADAPTER",
+ "type": "IPADAPTER",
+ "links": [
+ 1
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapterModelLoader"
+ },
+ "widgets_values": [
+ "ip-adapter_sd15.safetensors"
+ ]
+ }
+ ],
+ "links": [
+ [
+ 1,
+ 3,
+ 0,
+ 5,
+ 0,
+ "IPADAPTER"
+ ],
+ [
+ 2,
+ 4,
+ 0,
+ 5,
+ 1,
+ "CLIP_VISION"
+ ],
+ [
+ 3,
+ 6,
+ 0,
+ 5,
+ 2,
+ "IMAGE"
+ ],
+ [
+ 4,
+ 1,
+ 0,
+ 5,
+ 3,
+ "MODEL"
+ ],
+ [
+ 5,
+ 1,
+ 1,
+ 7,
+ 0,
+ "CLIP"
+ ],
+ [
+ 6,
+ 1,
+ 1,
+ 8,
+ 0,
+ "CLIP"
+ ],
+ [
+ 7,
+ 5,
+ 0,
+ 9,
+ 0,
+ "MODEL"
+ ],
+ [
+ 8,
+ 7,
+ 0,
+ 9,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 9,
+ 8,
+ 0,
+ 9,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 10,
+ 10,
+ 0,
+ 9,
+ 3,
+ "LATENT"
+ ],
+ [
+ 11,
+ 9,
+ 0,
+ 11,
+ 0,
+ "LATENT"
+ ],
+ [
+ 12,
+ 2,
+ 0,
+ 11,
+ 1,
+ "VAE"
+ ],
+ [
+ 13,
+ 11,
+ 0,
+ 12,
+ 0,
+ "IMAGE"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/LICENSE b/custom_nodes/ComfyUI_IPAdapter_plus/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..f288702d2fa16d3cdf0035b15a9fcbc552cd88e7
--- /dev/null
+++ b/custom_nodes/ComfyUI_IPAdapter_plus/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/README.md b/custom_nodes/ComfyUI_IPAdapter_plus/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..864171cf7e12d08ca32faf4dd423c4bcff951d3a
--- /dev/null
+++ b/custom_nodes/ComfyUI_IPAdapter_plus/README.md
@@ -0,0 +1,209 @@
+# ComfyUI IPAdapter plus
+[ComfyUI](https://github.com/comfyanonymous/ComfyUI) reference implementation for [IPAdapter](https://github.com/tencent-ailab/IP-Adapter/) models.
+
+IPAdapter implementation that follows the ComfyUI way of doing things. The code is memory efficient, fast, and shouldn't break with Comfy updates.
+
+## Important updates
+
+**2023/12/05**: Added `batch embeds` node. This lets you encode images in batches and merge them together into an `IPAdapter Apply Encoded` node. Useful mostly for animations because the clip vision encoder takes a lot of VRAM. My suggestion is to split the animation in batches of about 120 frames.
+
+**2023/11/29**: Added `unfold_batch` option to send the reference images sequentially to a latent batch. Useful for animations.
+
+**2023/11/26**: Added [timestepping](#timestepping). You may need to delete the old nodes and recreate them. **Important:** For this to work you need to update ComfyUI to the latest version.
+
+**2023/11/24**: Support for multiple attention masks.
+
+**2023/11/23**: Small but important update: the new default location for the IPAdapter models is `ComfyUI/models/ipadapter`. **No panic**: the legacy `ComfyUI/custom_nodes/ComfyUI_IPAdapter_plus/models` location still works and nothing will break.
+
+**2023/11/08**: Added [attention masking](#attention-masking).
+
+**2023/11/07**: Added three ways to apply the weight. [See below](#weight-types) for more info. **This might break things!** Please let me know if you are having issues. When loading an old workflow try to reload the page a couple of times or delete the `IPAdapter Apply` node and insert a new one.
+
+**2023/11/02**: Added compatibility with the new models in safetensors format (available on [huggingface](https://huggingface.co/h94/IP-Adapter)).
+
+**2023/10/12**: Added image weighting in the `IPAdapterEncoder` node. This update is somewhat breaking; if you use `IPAdapterEncoder` and `PrepImageForClipVision` nodes you need to remove them from your workflow, refresh and recreate them. In the examples you'll find a [workflow](examples/IPAdapter_weighted.json) for weighted images.
+
+*(previous updates removed for better readability)*
+
+## What is it?
+
+The IPAdapter are very powerful models for image-to-image conditioning. Given a reference image you can do variations augmented by text prompt, controlnets and masks. Think of it as a 1-image lora.
+
+## Example workflow
+
+
+
+## Video Introduction
+
+
+
+
+
+**:nerd_face: [Basic usage video](https://youtu.be/7m9ZZFU3HWo)**
+
+**:rocket: [Advanced features video](https://www.youtube.com/watch?v=mJQ62ly7jrg)**
+
+**:japanese_goblin: [Attention Masking video](https://www.youtube.com/watch?v=vqG1VXKteQg)**
+
+**:movie_camera: [Animation Features video](https://www.youtube.com/watch?v=ddYbhv3WgWw)**
+
+## Installation
+
+Download or git clone this repository inside `ComfyUI/custom_nodes/` directory.
+
+The pre-trained models are available on [huggingface](https://huggingface.co/h94/IP-Adapter), download and place them in the `ComfyUI/models/ipadapter` directory (create it if not present). You can also use any custom location setting an `ipadapter` entry in the `extra_model_paths.yaml` file.
+
+Note: the legacy `ComfyUI/custom_nodes/ComfyUI_IPAdapter_plus/models` is still supported and it will be ignored only if the global directory is present.
+
+For SD1.5 you need:
+
+- [ip-adapter_sd15.bin](https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter_sd15.bin)
+- [ip-adapter_sd15_light.bin](https://huggingface.co/h94/IP-Adapter/blob/main/models/ip-adapter_sd15_light.safetensors), use this when text prompt is more important than reference images
+- [ip-adapter-plus_sd15.bin](https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter-plus_sd15.bin)
+- [ip-adapter-plus-face_sd15.bin](https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter-plus-face_sd15.bin)
+- [ip-adapter-full-face_sd15.bin](https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter-full-face_sd15.bin)
+- [ip-adapter_sd15_vit-G.bin](https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter_sd15_vit-G.bin), this model requires the vit-bigG image encoder (the SDXL one below)
+
+For SDXL you need:
+- [ip-adapter_sdxl.bin](https://huggingface.co/h94/IP-Adapter/resolve/main/sdxl_models/ip-adapter_sdxl.bin)
+- [ip-adapter_sdxl_vit-h.bin](https://huggingface.co/h94/IP-Adapter/resolve/main/sdxl_models/ip-adapter_sdxl_vit-h.bin) **This model requires the use of the SD1.5 encoder despite being for SDXL checkpoints**
+- [ip-adapter-plus_sdxl_vit-h.bin](https://huggingface.co/h94/IP-Adapter/resolve/main/sdxl_models/ip-adapter-plus_sdxl_vit-h.bin) Same as above, use the SD1.5 encoder
+- [ip-adapter-plus-face_sdxl_vit-h.bin](https://huggingface.co/h94/IP-Adapter/resolve/main/sdxl_models/ip-adapter-plus-face_sdxl_vit-h.bin) As always, use the SD1.5 encoder
+
+Please note that now the models are also available in safetensors format, you can find them on [huggingface](https://huggingface.co/h94/IP-Adapter).
+
+Additionally you need the image encoders to be placed in the `ComfyUI/models/clip_vision/` directory:
+
+- [SD 1.5 model](https://huggingface.co/h94/IP-Adapter/resolve/main/models/image_encoder/model.safetensors) (use this also for all models ending with **_vit-h**)
+- [SDXL model](https://huggingface.co/h94/IP-Adapter/resolve/main/sdxl_models/image_encoder/model.safetensors)
+
+You can rename them to something easier to remember or put them into a sub-directory.
+
+**Note:** the image encoders are actually [ViT-H](https://huggingface.co/laion/CLIP-ViT-H-14-laion2B-s32B-b79K) and [ViT-bigG](https://huggingface.co/laion/CLIP-ViT-bigG-14-laion2B-39B-b160k) (used only for one SDXL model). You probably already have them.
+
+## How to
+
+There's a basic workflow included in this repo and a few examples in the [examples](./examples/) directory. Usually it's a good idea to lower the `weight` to at least `0.8`.
+
+The `noise` parameter is an experimental exploitation of the IPAdapter models. You can set it as low as `0.01` for an arguably better result.
+
+
+More info about the noise option
+
+
+
+Basically the IPAdapter sends two pictures for the conditioning, one is the reference the other --that you don't see-- is an empty image that could be considered like a negative conditioning.
+
+What I'm doing is to send a very noisy image instead of an empty one. The `noise` parameter determines the amount of noise that is added. A value of `0.01` adds a lot of noise (more noise == less impact becaue the model doesn't get it); a value of `1.0` removes most of noise so the generated image gets conditioned more.
+
+
+### Preparing the reference image
+
+The reference image needs to be encoded by the CLIP vision model. The encoder resizes the image to 224×224 **and crops it to the center!**. It's not an IPAdapter thing, it's how the clip vision works. This means that if you use a portrait or landscape image and the main attention (eg: the face of a character) is not in the middle you'll likely get undesired results. Use square pictures as reference for more predictable results.
+
+I've added a `PrepImageForClipVision` node that does all the required operations for you. You just have to select the crop position (top/left/center/etc...) and a sharpening amount if you want.
+
+In the image below you can see the difference between prepped and not prepped images.
+
+
+
+### KSampler configuration suggestions
+
+The IPAdapter generally requires a few more `steps` than usual, if the result is underwhelming try to add 10+ steps. The model tends to burn the images a little. If needed lower the CFG scale.
+
+The `noise` option generally grants better results, experiment with it.
+
+### IPAdapter + ControlNet
+
+The model is very effective when paired with a ControlNet. In the example below I experimented with Canny. [The workflow](./examples/IPAdapter_Canny.json) is in the examples directory.
+
+
+
+### IPAdapter Face
+
+IPAdapter offers an interesting model for a kind of "face swap" effect. [The workflow is provided](./examples/IPAdapter_face.json). Set a close up face as reference image and then input your text prompt as always. The generated character should have the face of the reference. It also works with img2img given a high denoise.
+
+
+
+**Note:** there's a new `full-face` model available that's arguably better.
+
+### Masking (Inpainting)
+
+The most effective way to apply the IPAdapter to a region is by an [inpainting workflow](./examples/IPAdapter_inpaint.json). Remeber to use a specific checkpoint for inpainting otherwise it won't work. Even if you are inpainting a face I find that the *IPAdapter-Plus* (not the *face* one), works best.
+
+
+
+### Image Batches
+
+It is possible to pass multiple images for the conditioning with the `Batch Images` node. An [example workflow](./examples/IPAdapter_batch_images.json) is provided; in the picture below you can see the result of one and two images conditioning.
+
+
+
+It seems to be effective with 2-3 images, beyond that it tends to *blur* the information too much.
+
+### Image Weighting
+
+When sending multiple images you can increase/decrease the weight of each image by using the `IPAdapterEncoder` node. The workflow ([included in the examples](examples/IPAdapter_weighted.json)) looks like this:
+
+
+
+The node accepts 4 images, but remember that you can send batches of images to each slot.
+
+### Weight types
+
+You can choose how the IPAdapter weight is applied to the image embeds. Options are:
+
+- **original**: The weight is applied to the aggregated tensors. The weight works predictably for values greater and lower than 1.
+- **linear**: The weight is applied to the individual tensors before aggretating them. Compared to `original` the influence is weaker when weight is <1 and stronger when >1. **Note:** at weight `1` the two methods are equivalent.
+- **channel penalty**: This method is a modified version of Lvmin Zhang's (Fooocus). Results are sometimes sharper. It works very well also when weight is >1. Still experimental, may change in the future.
+
+The image below shows the difference (zoom in).
+
+
+
+In the examples directory you can find [a workflow](examples/IPAdapter_weight_types.json) that lets you easily compare the three methods.
+
+**Note:** I'm not still sure whether all methods will stay. `Linear` seems the most sensible but I wanted to keep the `original` for backward compatibility. `channel penalty` has a weird non-commercial clause but it's still part of a GNU GPLv3 software (ie: there's a licensing clash) so I'm trying to understand how to deal with that.
+
+### Attention masking
+
+It's possible to add a mask to define the area where the IPAdapter will be applied to. Everything outside the mask will ignore the reference images and will only listen to the text prompt.
+
+It is suggested to use a mask of the same size of the final generated image.
+
+In the picture below I use two reference images masked one on the left and the other on the right. The image is generated only with IPAdapter and one ksampler (without in/outpainting or area conditioning).
+
+
+
+It is also possible to send a batch of masks that will be applied to a batch of latents, one per frame. The size should be the same but if needed some normalization will be performed to avoid errors. This feature also supports (experimentally) AnimateDiff including context sliding.
+
+In the examples directory you'll find a couple of masking workflows: [simple](examples/IPAdapter_mask.json) and [two masks](examples/IPAdapter_2_masks.json).
+
+### Timestepping
+
+In the `Apply IPAdapter` node you can set a start and an end point. The IPAdapter will be applied exclusively in that timeframe of the generation. This is a very powerful tool to modulate the intesity of IPAdapter models.
+
+
+
+## Troubleshooting
+
+Please check the [troubleshooting](https://github.com/cubiq/ComfyUI_IPAdapter_plus/issues/108) before posting a new issue.
+
+## Diffusers version
+
+If you are interested I've also implemented the same features for [Huggingface Diffusers](https://github.com/cubiq/Diffusers_IPAdapter).
+
+## Credits
+
+- [IPAdapter](https://github.com/tencent-ailab/IP-Adapter/)
+- [ComfyUI](https://github.com/comfyanonymous/ComfyUI)
+- [laksjdjf](https://github.com/laksjdjf/IPAdapter-ComfyUI/)
+- [fooocus](https://github.com/lllyasviel/Fooocus/blob/main/fooocus_extras/ip_adapter.py)
+
+## IPAdapter in the wild
+
+Let me know if you spot the IPAdapter in the wild or tag @latentvision in the video description!
+
+- For German speakers you can find interesting YouTube tutorials on [A Latent Place](https://www.youtube.com/watch?v=rAWn_0YOBU0).
+- In Chinese [Introversify](https://www.youtube.com/watch?v=xl8f3oxZgY8)
+- [Scott Detweiler](https://www.youtube.com/watch?v=xzGdynQDzsM) covered this extension.
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/__init__.py b/custom_nodes/ComfyUI_IPAdapter_plus/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..cdd2511fd1d90cf6d3048b631ffb0d080fb11e4a
--- /dev/null
+++ b/custom_nodes/ComfyUI_IPAdapter_plus/__init__.py
@@ -0,0 +1,3 @@
+from .IPAdapterPlus import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
+
+__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/__pycache__/IPAdapterPlus.cpython-311.pyc b/custom_nodes/ComfyUI_IPAdapter_plus/__pycache__/IPAdapterPlus.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0cf67f825edd99c482fa8c99ed04f810155c02ad
Binary files /dev/null and b/custom_nodes/ComfyUI_IPAdapter_plus/__pycache__/IPAdapterPlus.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/__pycache__/__init__.cpython-311.pyc b/custom_nodes/ComfyUI_IPAdapter_plus/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ea82c51f9f2550020f723984998cd46ba50e9d72
Binary files /dev/null and b/custom_nodes/ComfyUI_IPAdapter_plus/__pycache__/__init__.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/__pycache__/resampler.cpython-311.pyc b/custom_nodes/ComfyUI_IPAdapter_plus/__pycache__/resampler.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..442bf5363ad187fe1c2b09266ef632c08d780afc
Binary files /dev/null and b/custom_nodes/ComfyUI_IPAdapter_plus/__pycache__/resampler.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_2_masks.json b/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_2_masks.json
new file mode 100644
index 0000000000000000000000000000000000000000..1e8105ff5bacb2f0fe54db19ea94081d8669fb01
--- /dev/null
+++ b/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_2_masks.json
@@ -0,0 +1,1202 @@
+{
+ "last_node_id": 52,
+ "last_link_id": 92,
+ "nodes": [
+ {
+ "id": 5,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 550,
+ 510
+ ],
+ "size": {
+ "0": 400,
+ "1": 160
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 6
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 3
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "blurry, malformed, illustration, video game, rendering, naked, cleavage, horror, zombie"
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 6,
+ "type": "VAEDecode",
+ "pos": [
+ 1430,
+ 300
+ ],
+ "size": {
+ "0": 140,
+ "1": 50
+ },
+ "flags": {},
+ "order": 21,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 7
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 8
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 9
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 7,
+ "type": "VAELoader",
+ "pos": [
+ 1160,
+ 600
+ ],
+ "size": {
+ "0": 240,
+ "1": 60
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 8
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "vae-ft-mse-840000-ema-pruned.safetensors"
+ ]
+ },
+ {
+ "id": 2,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ 210,
+ 410
+ ],
+ "size": {
+ "0": 290,
+ "1": 100
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 88
+ ],
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 5,
+ 6
+ ],
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [],
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "sd15/dreamshaper_8.safetensors"
+ ]
+ },
+ {
+ "id": 3,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 740,
+ 710
+ ],
+ "size": {
+ "0": 210,
+ "1": 110
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 4
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 768,
+ 512,
+ 1
+ ]
+ },
+ {
+ "id": 34,
+ "type": "SolidMask",
+ "pos": [
+ -570,
+ -50
+ ],
+ "size": {
+ "0": 210,
+ "1": 106
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": [
+ 51,
+ 57
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "SolidMask"
+ },
+ "widgets_values": [
+ 0,
+ 768,
+ 512
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 32,
+ "type": "SolidMask",
+ "pos": [
+ -570,
+ 100
+ ],
+ "size": {
+ "0": 210,
+ "1": 106
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": [
+ 71
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "SolidMask"
+ },
+ "widgets_values": [
+ 1,
+ 384,
+ 512
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 4,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 550,
+ 310
+ ],
+ "size": {
+ "0": 400,
+ "1": 160
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 5
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 2
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "two girl friends laughing"
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 8,
+ "type": "PreviewImage",
+ "pos": [
+ 1450,
+ 400
+ ],
+ "size": {
+ "0": 675.9465942382812,
+ "1": 480.1444091796875
+ },
+ "flags": {},
+ "order": 22,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 9
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 50,
+ "type": "IPAdapterApply",
+ "pos": [
+ 1030,
+ -220
+ ],
+ "size": {
+ "0": 210,
+ "1": 190
+ },
+ "flags": {},
+ "order": 19,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "ipadapter",
+ "type": "IPADAPTER",
+ "link": 79
+ },
+ {
+ "name": "clip_vision",
+ "type": "CLIP_VISION",
+ "link": 80
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 92
+ },
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 90
+ },
+ {
+ "name": "attn_mask",
+ "type": "MASK",
+ "link": 83
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 84
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapterApply"
+ },
+ "widgets_values": [
+ 0.7000000000000001,
+ 0.5,
+ "channel penalty"
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 52,
+ "type": "PrepImageForClipVision",
+ "pos": [
+ 760,
+ -380
+ ],
+ "size": {
+ "0": 243.60000610351562,
+ "1": 110
+ },
+ "flags": {},
+ "order": 15,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 91
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 92
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PrepImageForClipVision"
+ },
+ "widgets_values": [
+ "LANCZOS",
+ "center",
+ 0
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 11,
+ "type": "CLIPVisionLoader",
+ "pos": [
+ 50,
+ -350
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "CLIP_VISION",
+ "type": "CLIP_VISION",
+ "links": [
+ 80,
+ 86
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPVisionLoader"
+ },
+ "widgets_values": [
+ "IPAdapter_image_encoder_sd15.safetensors"
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 12,
+ "type": "LoadImage",
+ "pos": [
+ -270,
+ -420
+ ],
+ "size": {
+ "0": 230,
+ "1": 320
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 32
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": [],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "woman.png",
+ "image"
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 13,
+ "type": "PrepImageForClipVision",
+ "pos": [
+ 20,
+ -200
+ ],
+ "size": {
+ "0": 243.60000610351562,
+ "1": 110
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 32
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 87
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PrepImageForClipVision"
+ },
+ "widgets_values": [
+ "LANCZOS",
+ "center",
+ 0
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 47,
+ "type": "FeatherMask",
+ "pos": [
+ -310,
+ 100
+ ],
+ "size": {
+ "0": 210,
+ "1": 130
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "mask",
+ "type": "MASK",
+ "link": 71
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": [
+ 72
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "FeatherMask"
+ },
+ "widgets_values": [
+ 0,
+ 0,
+ 150,
+ 0
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 33,
+ "type": "MaskComposite",
+ "pos": [
+ -50,
+ 40
+ ],
+ "size": {
+ "0": 210,
+ "1": 130
+ },
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "destination",
+ "type": "MASK",
+ "link": 51
+ },
+ {
+ "name": "source",
+ "type": "MASK",
+ "link": 72
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": [
+ 89
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "MaskComposite"
+ },
+ "widgets_values": [
+ 0,
+ 0,
+ "add"
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 38,
+ "type": "MaskComposite",
+ "pos": [
+ 780,
+ -70
+ ],
+ "size": {
+ "0": 210,
+ "1": 126
+ },
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "destination",
+ "type": "MASK",
+ "link": 57
+ },
+ {
+ "name": "source",
+ "type": "MASK",
+ "link": 76
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": [
+ 83
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "MaskComposite"
+ },
+ "widgets_values": [
+ 384,
+ 0,
+ "add"
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 37,
+ "type": "SolidMask",
+ "pos": [
+ 280,
+ 100
+ ],
+ "size": {
+ "0": 210,
+ "1": 106
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": [
+ 75
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "SolidMask"
+ },
+ "widgets_values": [
+ 1,
+ 384,
+ 512
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 48,
+ "type": "FeatherMask",
+ "pos": [
+ 530,
+ 90
+ ],
+ "size": {
+ "0": 210,
+ "1": 130
+ },
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "mask",
+ "type": "MASK",
+ "link": 75
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": [
+ 76
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "FeatherMask"
+ },
+ "widgets_values": [
+ 150,
+ 0,
+ 0,
+ 0
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 51,
+ "type": "IPAdapterApply",
+ "pos": [
+ 468,
+ -169
+ ],
+ "size": {
+ "0": 210,
+ "1": 190
+ },
+ "flags": {},
+ "order": 18,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "ipadapter",
+ "type": "IPADAPTER",
+ "link": 85
+ },
+ {
+ "name": "clip_vision",
+ "type": "CLIP_VISION",
+ "link": 86
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 87
+ },
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 88
+ },
+ {
+ "name": "attn_mask",
+ "type": "MASK",
+ "link": 89
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 90
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapterApply"
+ },
+ "widgets_values": [
+ 0.7000000000000001,
+ 0.5,
+ "channel penalty"
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 27,
+ "type": "LoadImage",
+ "pos": [
+ 480,
+ -600
+ ],
+ "size": {
+ "0": 240,
+ "1": 330
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 91
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": [],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "venere.jpg",
+ "image"
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 10,
+ "type": "IPAdapterModelLoader",
+ "pos": [
+ 50,
+ -460
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IPADAPTER",
+ "type": "IPADAPTER",
+ "links": [
+ 79,
+ 85
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapterModelLoader"
+ },
+ "widgets_values": [
+ "ip-adapter-plus_sd15.safetensors"
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 1,
+ "type": "KSampler",
+ "pos": [
+ 1160,
+ 300
+ ],
+ "size": {
+ "0": 240,
+ "1": 262
+ },
+ "flags": {},
+ "order": 20,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 84
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 2
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 3
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 4
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 7
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 37,
+ "fixed",
+ 30,
+ 5,
+ "dpmpp_2m",
+ "karras",
+ 1
+ ]
+ }
+ ],
+ "links": [
+ [
+ 2,
+ 4,
+ 0,
+ 1,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 3,
+ 5,
+ 0,
+ 1,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 4,
+ 3,
+ 0,
+ 1,
+ 3,
+ "LATENT"
+ ],
+ [
+ 5,
+ 2,
+ 1,
+ 4,
+ 0,
+ "CLIP"
+ ],
+ [
+ 6,
+ 2,
+ 1,
+ 5,
+ 0,
+ "CLIP"
+ ],
+ [
+ 7,
+ 1,
+ 0,
+ 6,
+ 0,
+ "LATENT"
+ ],
+ [
+ 8,
+ 7,
+ 0,
+ 6,
+ 1,
+ "VAE"
+ ],
+ [
+ 9,
+ 6,
+ 0,
+ 8,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 32,
+ 12,
+ 0,
+ 13,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 51,
+ 34,
+ 0,
+ 33,
+ 0,
+ "MASK"
+ ],
+ [
+ 57,
+ 34,
+ 0,
+ 38,
+ 0,
+ "MASK"
+ ],
+ [
+ 71,
+ 32,
+ 0,
+ 47,
+ 0,
+ "MASK"
+ ],
+ [
+ 72,
+ 47,
+ 0,
+ 33,
+ 1,
+ "MASK"
+ ],
+ [
+ 75,
+ 37,
+ 0,
+ 48,
+ 0,
+ "MASK"
+ ],
+ [
+ 76,
+ 48,
+ 0,
+ 38,
+ 1,
+ "MASK"
+ ],
+ [
+ 79,
+ 10,
+ 0,
+ 50,
+ 0,
+ "IPADAPTER"
+ ],
+ [
+ 80,
+ 11,
+ 0,
+ 50,
+ 1,
+ "CLIP_VISION"
+ ],
+ [
+ 83,
+ 38,
+ 0,
+ 50,
+ 4,
+ "MASK"
+ ],
+ [
+ 84,
+ 50,
+ 0,
+ 1,
+ 0,
+ "MODEL"
+ ],
+ [
+ 85,
+ 10,
+ 0,
+ 51,
+ 0,
+ "IPADAPTER"
+ ],
+ [
+ 86,
+ 11,
+ 0,
+ 51,
+ 1,
+ "CLIP_VISION"
+ ],
+ [
+ 87,
+ 13,
+ 0,
+ 51,
+ 2,
+ "IMAGE"
+ ],
+ [
+ 88,
+ 2,
+ 0,
+ 51,
+ 3,
+ "MODEL"
+ ],
+ [
+ 89,
+ 33,
+ 0,
+ 51,
+ 4,
+ "MASK"
+ ],
+ [
+ 90,
+ 51,
+ 0,
+ 50,
+ 3,
+ "MODEL"
+ ],
+ [
+ 91,
+ 27,
+ 0,
+ 52,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 92,
+ 52,
+ 0,
+ 50,
+ 2,
+ "IMAGE"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_Canny.json b/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_Canny.json
new file mode 100644
index 0000000000000000000000000000000000000000..d91bd2b9d0880687b7cc620de79c31959f97a9c7
--- /dev/null
+++ b/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_Canny.json
@@ -0,0 +1,832 @@
+{
+ "last_node_id": 17,
+ "last_link_id": 19,
+ "nodes": [
+ {
+ "id": 10,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 650,
+ 590
+ ],
+ "size": {
+ "0": 210,
+ "1": 110
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 10
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ 1
+ ]
+ },
+ {
+ "id": 8,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 650,
+ 420
+ ],
+ "size": {
+ "0": 210,
+ "1": 120
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 6
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 9
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "blurry, horror"
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 7,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 650,
+ 250
+ ],
+ "size": {
+ "0": 210,
+ "1": 120
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 5
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 16
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "beautiful renaissance girl with a necklace, detailed"
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 11,
+ "type": "VAEDecode",
+ "pos": [
+ 1300,
+ 170
+ ],
+ "size": {
+ "0": 140,
+ "1": 50
+ },
+ "flags": {},
+ "order": 15,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 11
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 12
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 13
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 2,
+ "type": "VAELoader",
+ "pos": [
+ 940,
+ 480
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 12
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "vae-ft-mse-840000-ema-pruned.safetensors"
+ ]
+ },
+ {
+ "id": 12,
+ "type": "SaveImage",
+ "pos": [
+ 1300,
+ 270
+ ],
+ "size": {
+ "0": 400,
+ "1": 450
+ },
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 13
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "IPAdapter"
+ ]
+ },
+ {
+ "id": 6,
+ "type": "LoadImage",
+ "pos": [
+ 40,
+ 60
+ ],
+ "size": {
+ "0": 220,
+ "1": 320
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 3
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "venere.jpg",
+ "image"
+ ]
+ },
+ {
+ "id": 14,
+ "type": "LoadImage",
+ "pos": [
+ 50,
+ 860
+ ],
+ "size": {
+ "0": 220,
+ "1": 320
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 14
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "portrait.jpg",
+ "image"
+ ]
+ },
+ {
+ "id": 15,
+ "type": "ControlNetLoader",
+ "pos": [
+ 190,
+ 750
+ ],
+ "size": {
+ "0": 310,
+ "1": 60
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "CONTROL_NET",
+ "type": "CONTROL_NET",
+ "links": [
+ 15
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ControlNetLoader"
+ },
+ "widgets_values": [
+ "control_v11p_sd15_canny_fp16.safetensors"
+ ]
+ },
+ {
+ "id": 17,
+ "type": "PreviewImage",
+ "pos": [
+ 579.412728881836,
+ 903.3208389282227
+ ],
+ "size": {
+ "0": 210,
+ "1": 246
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 19
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 13,
+ "type": "Canny",
+ "pos": [
+ 290,
+ 860
+ ],
+ "size": {
+ "0": 210,
+ "1": 82
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 14
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 18,
+ 19
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Canny"
+ },
+ "widgets_values": [
+ 0.1,
+ 0.5
+ ]
+ },
+ {
+ "id": 16,
+ "type": "ControlNetApply",
+ "pos": [
+ 540,
+ 750
+ ],
+ "size": {
+ "0": 317.4000244140625,
+ "1": 98
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "conditioning",
+ "type": "CONDITIONING",
+ "link": 16
+ },
+ {
+ "name": "control_net",
+ "type": "CONTROL_NET",
+ "link": 15
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 18
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 17
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ControlNetApply"
+ },
+ "widgets_values": [
+ 0.8
+ ]
+ },
+ {
+ "id": 9,
+ "type": "KSampler",
+ "pos": [
+ 930,
+ 170
+ ],
+ "size": {
+ "0": 315,
+ "1": 262
+ },
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 7
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 17
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 9
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 10
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 11
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 27,
+ "fixed",
+ 30,
+ 6,
+ "ddim",
+ "ddim_uniform",
+ 1
+ ]
+ },
+ {
+ "id": 4,
+ "type": "CLIPVisionLoader",
+ "pos": [
+ 290,
+ 170
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "CLIP_VISION",
+ "type": "CLIP_VISION",
+ "links": [
+ 2
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPVisionLoader"
+ },
+ "widgets_values": [
+ "IPAdapter_image_encoder_sd15.safetensors"
+ ]
+ },
+ {
+ "id": 3,
+ "type": "IPAdapterModelLoader",
+ "pos": [
+ 290,
+ 60
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IPADAPTER",
+ "type": "IPADAPTER",
+ "links": [
+ 1
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapterModelLoader"
+ },
+ "widgets_values": [
+ "ip-adapter-plus_sd15.safetensors"
+ ]
+ },
+ {
+ "id": 1,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ 290,
+ 280
+ ],
+ "size": {
+ "0": 300,
+ "1": 100
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 4
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 5,
+ 6
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "sd15/realisticVisionV51_v51VAE.safetensors"
+ ]
+ },
+ {
+ "id": 5,
+ "type": "IPAdapterApply",
+ "pos": [
+ 656,
+ -51
+ ],
+ "size": {
+ "0": 210,
+ "1": 258
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "ipadapter",
+ "type": "IPADAPTER",
+ "link": 1
+ },
+ {
+ "name": "clip_vision",
+ "type": "CLIP_VISION",
+ "link": 2
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 3
+ },
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 4
+ },
+ {
+ "name": "attn_mask",
+ "type": "MASK",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 7
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapterApply"
+ },
+ "widgets_values": [
+ 0.8,
+ 0,
+ "original",
+ 0,
+ 1,
+ false
+ ]
+ }
+ ],
+ "links": [
+ [
+ 1,
+ 3,
+ 0,
+ 5,
+ 0,
+ "IPADAPTER"
+ ],
+ [
+ 2,
+ 4,
+ 0,
+ 5,
+ 1,
+ "CLIP_VISION"
+ ],
+ [
+ 3,
+ 6,
+ 0,
+ 5,
+ 2,
+ "IMAGE"
+ ],
+ [
+ 4,
+ 1,
+ 0,
+ 5,
+ 3,
+ "MODEL"
+ ],
+ [
+ 5,
+ 1,
+ 1,
+ 7,
+ 0,
+ "CLIP"
+ ],
+ [
+ 6,
+ 1,
+ 1,
+ 8,
+ 0,
+ "CLIP"
+ ],
+ [
+ 7,
+ 5,
+ 0,
+ 9,
+ 0,
+ "MODEL"
+ ],
+ [
+ 9,
+ 8,
+ 0,
+ 9,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 10,
+ 10,
+ 0,
+ 9,
+ 3,
+ "LATENT"
+ ],
+ [
+ 11,
+ 9,
+ 0,
+ 11,
+ 0,
+ "LATENT"
+ ],
+ [
+ 12,
+ 2,
+ 0,
+ 11,
+ 1,
+ "VAE"
+ ],
+ [
+ 13,
+ 11,
+ 0,
+ 12,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 14,
+ 14,
+ 0,
+ 13,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 15,
+ 15,
+ 0,
+ 16,
+ 1,
+ "CONTROL_NET"
+ ],
+ [
+ 16,
+ 7,
+ 0,
+ 16,
+ 0,
+ "CONDITIONING"
+ ],
+ [
+ 17,
+ 16,
+ 0,
+ 9,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 18,
+ 13,
+ 0,
+ 16,
+ 2,
+ "IMAGE"
+ ],
+ [
+ 19,
+ 13,
+ 0,
+ 17,
+ 0,
+ "IMAGE"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_batch_images.json b/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_batch_images.json
new file mode 100644
index 0000000000000000000000000000000000000000..16bd704a38665ba7f52f03e3e4497ed4629b09a9
--- /dev/null
+++ b/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_batch_images.json
@@ -0,0 +1,693 @@
+{
+ "last_node_id": 14,
+ "last_link_id": 23,
+ "nodes": [
+ {
+ "id": 10,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 650,
+ 590
+ ],
+ "size": {
+ "0": 210,
+ "1": 110
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 10
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ 1
+ ]
+ },
+ {
+ "id": 3,
+ "type": "IPAdapterModelLoader",
+ "pos": [
+ 290,
+ 60
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IPADAPTER",
+ "type": "IPADAPTER",
+ "links": [
+ 1
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapterModelLoader"
+ },
+ "widgets_values": [
+ "ip-adapter_sd15.bin"
+ ]
+ },
+ {
+ "id": 8,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 650,
+ 420
+ ],
+ "size": {
+ "0": 210,
+ "1": 120
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 6
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 9
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "blurry, horror"
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 7,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 650,
+ 250
+ ],
+ "size": {
+ "0": 210,
+ "1": 120
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 5
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 8
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "beautiful renaissance girl, detailed"
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 11,
+ "type": "VAEDecode",
+ "pos": [
+ 1300,
+ 170
+ ],
+ "size": {
+ "0": 140,
+ "1": 50
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 11
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 12
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 13
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 2,
+ "type": "VAELoader",
+ "pos": [
+ 940,
+ 480
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 12
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "vae-ft-mse-840000-ema-pruned.safetensors"
+ ]
+ },
+ {
+ "id": 12,
+ "type": "SaveImage",
+ "pos": [
+ 1300,
+ 270
+ ],
+ "size": {
+ "0": 400,
+ "1": 450
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 13
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "IPAdapter"
+ ]
+ },
+ {
+ "id": 5,
+ "type": "IPAdapterApply",
+ "pos": [
+ 650,
+ 60
+ ],
+ "size": {
+ "0": 210,
+ "1": 142
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "ipadapter",
+ "type": "IPADAPTER",
+ "link": 1
+ },
+ {
+ "name": "clip_vision",
+ "type": "CLIP_VISION",
+ "link": 2
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 23
+ },
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 4
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 7
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapterApply"
+ },
+ "widgets_values": [
+ 1,
+ 0
+ ]
+ },
+ {
+ "id": 9,
+ "type": "KSampler",
+ "pos": [
+ 930,
+ 170
+ ],
+ "size": {
+ "0": 315,
+ "1": 262
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 7
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 8
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 9
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 10
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 11
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 0,
+ "fixed",
+ 25,
+ 6,
+ "ddim",
+ "ddim_uniform",
+ 1
+ ]
+ },
+ {
+ "id": 4,
+ "type": "CLIPVisionLoader",
+ "pos": [
+ 290,
+ 170
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "CLIP_VISION",
+ "type": "CLIP_VISION",
+ "links": [
+ 2
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPVisionLoader"
+ },
+ "widgets_values": [
+ "IPAdapter_image_encoder_sd15.safetensors"
+ ]
+ },
+ {
+ "id": 6,
+ "type": "LoadImage",
+ "pos": [
+ 40,
+ 60
+ ],
+ "size": {
+ "0": 220,
+ "1": 320
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 21
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "venere.jpg",
+ "image"
+ ]
+ },
+ {
+ "id": 13,
+ "type": "LoadImage",
+ "pos": [
+ 40,
+ 420
+ ],
+ "size": {
+ "0": 220,
+ "1": 320
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 14
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "portrait.jpg",
+ "image"
+ ]
+ },
+ {
+ "id": 14,
+ "type": "ImageBatch",
+ "pos": [
+ 290,
+ 430
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image1",
+ "type": "IMAGE",
+ "link": 21
+ },
+ {
+ "name": "image2",
+ "type": "IMAGE",
+ "link": 14
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 23
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ImageBatch"
+ }
+ },
+ {
+ "id": 1,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ 290,
+ 280
+ ],
+ "size": {
+ "0": 300,
+ "1": 100
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 4
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 5,
+ 6
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "sd15/absolutereality_v181.safetensors"
+ ]
+ }
+ ],
+ "links": [
+ [
+ 1,
+ 3,
+ 0,
+ 5,
+ 0,
+ "IPADAPTER"
+ ],
+ [
+ 2,
+ 4,
+ 0,
+ 5,
+ 1,
+ "CLIP_VISION"
+ ],
+ [
+ 4,
+ 1,
+ 0,
+ 5,
+ 3,
+ "MODEL"
+ ],
+ [
+ 5,
+ 1,
+ 1,
+ 7,
+ 0,
+ "CLIP"
+ ],
+ [
+ 6,
+ 1,
+ 1,
+ 8,
+ 0,
+ "CLIP"
+ ],
+ [
+ 7,
+ 5,
+ 0,
+ 9,
+ 0,
+ "MODEL"
+ ],
+ [
+ 8,
+ 7,
+ 0,
+ 9,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 9,
+ 8,
+ 0,
+ 9,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 10,
+ 10,
+ 0,
+ 9,
+ 3,
+ "LATENT"
+ ],
+ [
+ 11,
+ 9,
+ 0,
+ 11,
+ 0,
+ "LATENT"
+ ],
+ [
+ 12,
+ 2,
+ 0,
+ 11,
+ 1,
+ "VAE"
+ ],
+ [
+ 13,
+ 11,
+ 0,
+ 12,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 14,
+ 13,
+ 0,
+ 14,
+ 1,
+ "IMAGE"
+ ],
+ [
+ 21,
+ 6,
+ 0,
+ 14,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 23,
+ 14,
+ 0,
+ 5,
+ 2,
+ "IMAGE"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_face.json b/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_face.json
new file mode 100644
index 0000000000000000000000000000000000000000..54731fbffef3d99509ad966e05b84e626852f3aa
--- /dev/null
+++ b/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_face.json
@@ -0,0 +1,597 @@
+{
+ "last_node_id": 18,
+ "last_link_id": 28,
+ "nodes": [
+ {
+ "id": 8,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 650,
+ 420
+ ],
+ "size": {
+ "0": 210,
+ "1": 120
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 6
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 9
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "blurry, horror"
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 11,
+ "type": "VAEDecode",
+ "pos": [
+ 1300,
+ 170
+ ],
+ "size": {
+ "0": 140,
+ "1": 50
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 11
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 12
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 13
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 12,
+ "type": "SaveImage",
+ "pos": [
+ 1300,
+ 270
+ ],
+ "size": {
+ "0": 400,
+ "1": 450
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 13
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "IPAdapter"
+ ]
+ },
+ {
+ "id": 1,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ 290,
+ 280
+ ],
+ "size": {
+ "0": 300,
+ "1": 100
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 4
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 5,
+ 6
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "realisticVisionV51_v51VAE.safetensors"
+ ]
+ },
+ {
+ "id": 3,
+ "type": "IPAdapterModelLoader",
+ "pos": [
+ 290,
+ 60
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IPADAPTER",
+ "type": "IPADAPTER",
+ "links": [
+ 1
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapterModelLoader"
+ },
+ "widgets_values": [
+ "ip-adapter-plus-face_sd15.bin"
+ ]
+ },
+ {
+ "id": 2,
+ "type": "VAELoader",
+ "pos": [
+ 940,
+ 480
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 12
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "vae-ft-mse-840000-ema-pruned.safetensors"
+ ]
+ },
+ {
+ "id": 7,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 650,
+ 250
+ ],
+ "size": {
+ "0": 210,
+ "1": 120
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 5
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 25
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "closeup photo of a guy flexing muscles at the gym"
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 10,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 650,
+ 590
+ ],
+ "size": {
+ "0": 210,
+ "1": 110
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 26
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ 1
+ ]
+ },
+ {
+ "id": 14,
+ "type": "LoadImage",
+ "pos": [
+ 40,
+ 60
+ ],
+ "size": {
+ "0": 220,
+ "1": 320
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 28
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "einstein.jpg",
+ "image"
+ ]
+ },
+ {
+ "id": 4,
+ "type": "CLIPVisionLoader",
+ "pos": [
+ 290,
+ 170
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "CLIP_VISION",
+ "type": "CLIP_VISION",
+ "links": [
+ 2
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPVisionLoader"
+ },
+ "widgets_values": [
+ "IPAdapter_image_encoder_sd15.safetensors"
+ ]
+ },
+ {
+ "id": 5,
+ "type": "IPAdapterApply",
+ "pos": [
+ 650,
+ 60
+ ],
+ "size": {
+ "0": 210,
+ "1": 142
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "ipadapter",
+ "type": "IPADAPTER",
+ "link": 1
+ },
+ {
+ "name": "clip_vision",
+ "type": "CLIP_VISION",
+ "link": 2
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 28
+ },
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 4
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 7
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapterApply"
+ },
+ "widgets_values": [
+ 0.6000000000000001,
+ 0.3
+ ]
+ },
+ {
+ "id": 9,
+ "type": "KSampler",
+ "pos": [
+ 930,
+ 170
+ ],
+ "size": {
+ "0": 315,
+ "1": 262
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 7
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 25
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 9
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 26
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 11
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 32,
+ "fixed",
+ 35,
+ 6,
+ "ddim",
+ "ddim_uniform",
+ 1
+ ]
+ }
+ ],
+ "links": [
+ [
+ 1,
+ 3,
+ 0,
+ 5,
+ 0,
+ "IPADAPTER"
+ ],
+ [
+ 2,
+ 4,
+ 0,
+ 5,
+ 1,
+ "CLIP_VISION"
+ ],
+ [
+ 4,
+ 1,
+ 0,
+ 5,
+ 3,
+ "MODEL"
+ ],
+ [
+ 5,
+ 1,
+ 1,
+ 7,
+ 0,
+ "CLIP"
+ ],
+ [
+ 6,
+ 1,
+ 1,
+ 8,
+ 0,
+ "CLIP"
+ ],
+ [
+ 7,
+ 5,
+ 0,
+ 9,
+ 0,
+ "MODEL"
+ ],
+ [
+ 9,
+ 8,
+ 0,
+ 9,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 11,
+ 9,
+ 0,
+ 11,
+ 0,
+ "LATENT"
+ ],
+ [
+ 12,
+ 2,
+ 0,
+ 11,
+ 1,
+ "VAE"
+ ],
+ [
+ 13,
+ 11,
+ 0,
+ 12,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 25,
+ 7,
+ 0,
+ 9,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 26,
+ 10,
+ 0,
+ 9,
+ 3,
+ "LATENT"
+ ],
+ [
+ 28,
+ 14,
+ 0,
+ 5,
+ 2,
+ "IMAGE"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_inpaint.json b/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_inpaint.json
new file mode 100644
index 0000000000000000000000000000000000000000..0e41cc6e0381c77fc6d8419f79c7da9450633cfe
--- /dev/null
+++ b/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_inpaint.json
@@ -0,0 +1,679 @@
+{
+ "last_node_id": 23,
+ "last_link_id": 37,
+ "nodes": [
+ {
+ "id": 8,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 650,
+ 420
+ ],
+ "size": {
+ "0": 210,
+ "1": 120
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 6
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 9
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "blurry, horror"
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 11,
+ "type": "VAEDecode",
+ "pos": [
+ 1300,
+ 170
+ ],
+ "size": {
+ "0": 140,
+ "1": 50
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 11
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 12
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 13
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 12,
+ "type": "SaveImage",
+ "pos": [
+ 1300,
+ 270
+ ],
+ "size": {
+ "0": 400,
+ "1": 450
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 13
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "IPAdapter"
+ ]
+ },
+ {
+ "id": 7,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 650,
+ 250
+ ],
+ "size": {
+ "0": 210,
+ "1": 120
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 5
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 25
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "closeup photo of a renaissance astronaut "
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 14,
+ "type": "LoadImage",
+ "pos": [
+ 40,
+ 60
+ ],
+ "size": {
+ "0": 220,
+ "1": 320
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 28
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "venere.jpg",
+ "image"
+ ]
+ },
+ {
+ "id": 5,
+ "type": "IPAdapterApply",
+ "pos": [
+ 650,
+ 60
+ ],
+ "size": {
+ "0": 210,
+ "1": 142
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "ipadapter",
+ "type": "IPADAPTER",
+ "link": 1
+ },
+ {
+ "name": "clip_vision",
+ "type": "CLIP_VISION",
+ "link": 2
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 28
+ },
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 4
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 7
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapterApply"
+ },
+ "widgets_values": [
+ 0.8,
+ 0
+ ]
+ },
+ {
+ "id": 2,
+ "type": "VAELoader",
+ "pos": [
+ 290,
+ 430
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 12,
+ 30
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "vae-ft-mse-840000-ema-pruned.safetensors"
+ ]
+ },
+ {
+ "id": 9,
+ "type": "KSampler",
+ "pos": [
+ 930,
+ 170
+ ],
+ "size": {
+ "0": 315,
+ "1": 262
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 7
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 25
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 9
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 32
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 11
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 34,
+ "fixed",
+ 35,
+ 6,
+ "ddim",
+ "ddim_uniform",
+ 1
+ ]
+ },
+ {
+ "id": 3,
+ "type": "IPAdapterModelLoader",
+ "pos": [
+ 290,
+ 60
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IPADAPTER",
+ "type": "IPADAPTER",
+ "links": [
+ 1
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapterModelLoader"
+ },
+ "widgets_values": [
+ "ip-adapter-plus_sd15.bin"
+ ]
+ },
+ {
+ "id": 20,
+ "type": "VAEEncodeForInpaint",
+ "pos": [
+ 650,
+ 590
+ ],
+ "size": {
+ "0": 230,
+ "1": 100
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "pixels",
+ "type": "IMAGE",
+ "link": 29
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 30
+ },
+ {
+ "name": "mask",
+ "type": "MASK",
+ "link": 37
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 32
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEEncodeForInpaint"
+ },
+ "widgets_values": [
+ 12
+ ]
+ },
+ {
+ "id": 19,
+ "type": "LoadImage",
+ "pos": [
+ 370,
+ 600
+ ],
+ "size": {
+ "0": 220,
+ "1": 320
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 29
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": [
+ 37
+ ],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "clipspace/clipspace-mask-8127362.png [input]",
+ "image"
+ ]
+ },
+ {
+ "id": 4,
+ "type": "CLIPVisionLoader",
+ "pos": [
+ 290,
+ 170
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "CLIP_VISION",
+ "type": "CLIP_VISION",
+ "links": [
+ 2
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPVisionLoader"
+ },
+ "widgets_values": [
+ "IPAdapter_image_encoder_sd15.safetensors"
+ ]
+ },
+ {
+ "id": 1,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ 290,
+ 280
+ ],
+ "size": {
+ "0": 300,
+ "1": 100
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 4
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 5,
+ 6
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "sd15/absolutereality_v181INPAINTING.safetensors"
+ ]
+ }
+ ],
+ "links": [
+ [
+ 1,
+ 3,
+ 0,
+ 5,
+ 0,
+ "IPADAPTER"
+ ],
+ [
+ 2,
+ 4,
+ 0,
+ 5,
+ 1,
+ "CLIP_VISION"
+ ],
+ [
+ 4,
+ 1,
+ 0,
+ 5,
+ 3,
+ "MODEL"
+ ],
+ [
+ 5,
+ 1,
+ 1,
+ 7,
+ 0,
+ "CLIP"
+ ],
+ [
+ 6,
+ 1,
+ 1,
+ 8,
+ 0,
+ "CLIP"
+ ],
+ [
+ 7,
+ 5,
+ 0,
+ 9,
+ 0,
+ "MODEL"
+ ],
+ [
+ 9,
+ 8,
+ 0,
+ 9,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 11,
+ 9,
+ 0,
+ 11,
+ 0,
+ "LATENT"
+ ],
+ [
+ 12,
+ 2,
+ 0,
+ 11,
+ 1,
+ "VAE"
+ ],
+ [
+ 13,
+ 11,
+ 0,
+ 12,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 25,
+ 7,
+ 0,
+ 9,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 28,
+ 14,
+ 0,
+ 5,
+ 2,
+ "IMAGE"
+ ],
+ [
+ 29,
+ 19,
+ 0,
+ 20,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 30,
+ 2,
+ 0,
+ 20,
+ 1,
+ "VAE"
+ ],
+ [
+ 32,
+ 20,
+ 0,
+ 9,
+ 3,
+ "LATENT"
+ ],
+ [
+ 37,
+ 19,
+ 1,
+ 20,
+ 2,
+ "MASK"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_mask.json b/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_mask.json
new file mode 100644
index 0000000000000000000000000000000000000000..b82d79be4dbe0058aa577dca87bd57d74290aad7
--- /dev/null
+++ b/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_mask.json
@@ -0,0 +1,665 @@
+{
+ "last_node_id": 51,
+ "last_link_id": 96,
+ "nodes": [
+ {
+ "id": 5,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 550,
+ 510
+ ],
+ "size": {
+ "0": 400,
+ "1": 160
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 6
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 3
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "blurry, malformed, video game, rendering, naked, cleavage, horror, zombie, text, watermark"
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 6,
+ "type": "VAEDecode",
+ "pos": [
+ 1360,
+ 270
+ ],
+ "size": {
+ "0": 140,
+ "1": 50
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 7
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 8
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 9
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 7,
+ "type": "VAELoader",
+ "pos": [
+ 1090,
+ 570
+ ],
+ "size": {
+ "0": 240,
+ "1": 60
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 8
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "vae-ft-mse-840000-ema-pruned.safetensors"
+ ]
+ },
+ {
+ "id": 2,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ 210,
+ 410
+ ],
+ "size": {
+ "0": 290,
+ "1": 100
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 88
+ ],
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 5,
+ 6
+ ],
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [],
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "sd15/dreamshaper_8.safetensors"
+ ]
+ },
+ {
+ "id": 11,
+ "type": "CLIPVisionLoader",
+ "pos": [
+ 490,
+ 20
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "CLIP_VISION",
+ "type": "CLIP_VISION",
+ "links": [
+ 86
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPVisionLoader"
+ },
+ "widgets_values": [
+ "IPAdapter_image_encoder_sd15.safetensors"
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 13,
+ "type": "PrepImageForClipVision",
+ "pos": [
+ 540,
+ 130
+ ],
+ "size": {
+ "0": 243.60000610351562,
+ "1": 110
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 32
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 87
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PrepImageForClipVision"
+ },
+ "widgets_values": [
+ "LANCZOS",
+ "center",
+ 0
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 4,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 550,
+ 310
+ ],
+ "size": {
+ "0": 400,
+ "1": 160
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 5
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 2
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "photography of a warrior woman in a cherry blossom forest, high quality, highly detailed"
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 3,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 740,
+ 720
+ ],
+ "size": {
+ "0": 210,
+ "1": 110
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 4
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 768,
+ 512,
+ 1
+ ]
+ },
+ {
+ "id": 1,
+ "type": "KSampler",
+ "pos": [
+ 1090,
+ 270
+ ],
+ "size": {
+ "0": 240,
+ "1": 262
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 93
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 2
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 3
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 4
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 7
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 40,
+ "fixed",
+ 30,
+ 5,
+ "ddim",
+ "ddim_uniform",
+ 1
+ ]
+ },
+ {
+ "id": 10,
+ "type": "IPAdapterModelLoader",
+ "pos": [
+ 490,
+ -80
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IPADAPTER",
+ "type": "IPADAPTER",
+ "links": [
+ 85
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapterModelLoader"
+ },
+ "widgets_values": [
+ "ip-adapter-plus_sd15.bin"
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 12,
+ "type": "LoadImage",
+ "pos": [
+ 230,
+ -80
+ ],
+ "size": [
+ 230,
+ 320
+ ],
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 32
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": [
+ 96
+ ],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "clipspace/clipspace-mask-790059.png [input]",
+ "image"
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 51,
+ "type": "IPAdapterApply",
+ "pos": [
+ 830,
+ 40
+ ],
+ "size": [
+ 210,
+ 190
+ ],
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "ipadapter",
+ "type": "IPADAPTER",
+ "link": 85
+ },
+ {
+ "name": "clip_vision",
+ "type": "CLIP_VISION",
+ "link": 86
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 87
+ },
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 88
+ },
+ {
+ "name": "attn_mask",
+ "type": "MASK",
+ "link": 96
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 93
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapterApply"
+ },
+ "widgets_values": [
+ 0.7000000000000001,
+ 0,
+ "linear"
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 8,
+ "type": "PreviewImage",
+ "pos": [
+ 1365,
+ 369
+ ],
+ "size": [
+ 675.9466064453127,
+ 480.1444152832032
+ ],
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 9
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ }
+ ],
+ "links": [
+ [
+ 2,
+ 4,
+ 0,
+ 1,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 3,
+ 5,
+ 0,
+ 1,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 4,
+ 3,
+ 0,
+ 1,
+ 3,
+ "LATENT"
+ ],
+ [
+ 5,
+ 2,
+ 1,
+ 4,
+ 0,
+ "CLIP"
+ ],
+ [
+ 6,
+ 2,
+ 1,
+ 5,
+ 0,
+ "CLIP"
+ ],
+ [
+ 7,
+ 1,
+ 0,
+ 6,
+ 0,
+ "LATENT"
+ ],
+ [
+ 8,
+ 7,
+ 0,
+ 6,
+ 1,
+ "VAE"
+ ],
+ [
+ 9,
+ 6,
+ 0,
+ 8,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 32,
+ 12,
+ 0,
+ 13,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 85,
+ 10,
+ 0,
+ 51,
+ 0,
+ "IPADAPTER"
+ ],
+ [
+ 86,
+ 11,
+ 0,
+ 51,
+ 1,
+ "CLIP_VISION"
+ ],
+ [
+ 87,
+ 13,
+ 0,
+ 51,
+ 2,
+ "IMAGE"
+ ],
+ [
+ 88,
+ 2,
+ 0,
+ 51,
+ 3,
+ "MODEL"
+ ],
+ [
+ 93,
+ 51,
+ 0,
+ 1,
+ 0,
+ "MODEL"
+ ],
+ [
+ 96,
+ 12,
+ 1,
+ 51,
+ 4,
+ "MASK"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_prepped.json b/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_prepped.json
new file mode 100644
index 0000000000000000000000000000000000000000..85d5ad7fef628c2de4f286befce661c285cb5101
--- /dev/null
+++ b/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_prepped.json
@@ -0,0 +1,791 @@
+{
+ "last_node_id": 29,
+ "last_link_id": 64,
+ "nodes": [
+ {
+ "id": 8,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 650,
+ 420
+ ],
+ "size": {
+ "0": 210,
+ "1": 120
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 6
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 9
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "blurry, horror"
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 7,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 650,
+ 250
+ ],
+ "size": {
+ "0": 210,
+ "1": 120
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 5
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 8
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ ""
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 11,
+ "type": "VAEDecode",
+ "pos": [
+ 1300,
+ 170
+ ],
+ "size": {
+ "0": 140,
+ "1": 50
+ },
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 11
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 12
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 13
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 12,
+ "type": "SaveImage",
+ "pos": [
+ 1300,
+ 270
+ ],
+ "size": {
+ "0": 400,
+ "1": 450
+ },
+ "flags": {},
+ "order": 15,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 13
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "IPAdapter"
+ ]
+ },
+ {
+ "id": 4,
+ "type": "CLIPVisionLoader",
+ "pos": [
+ 290,
+ 170
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "CLIP_VISION",
+ "type": "CLIP_VISION",
+ "links": [
+ 2
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPVisionLoader"
+ },
+ "widgets_values": [
+ "IPAdapter_image_encoder_sd15.safetensors"
+ ]
+ },
+ {
+ "id": 9,
+ "type": "KSampler",
+ "pos": [
+ 930,
+ 170
+ ],
+ "size": {
+ "0": 315,
+ "1": 262
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 7
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 8
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 9
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 10
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 11
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 0,
+ "fixed",
+ 30,
+ 5,
+ "ddim",
+ "ddim_uniform",
+ 1
+ ]
+ },
+ {
+ "id": 5,
+ "type": "IPAdapterApply",
+ "pos": [
+ 650,
+ 60
+ ],
+ "size": {
+ "0": 210,
+ "1": 142
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "ipadapter",
+ "type": "IPADAPTER",
+ "link": 1
+ },
+ {
+ "name": "clip_vision",
+ "type": "CLIP_VISION",
+ "link": 2
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 45
+ },
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 4
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 7
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapterApply"
+ },
+ "widgets_values": [
+ 1,
+ 0
+ ]
+ },
+ {
+ "id": 3,
+ "type": "IPAdapterModelLoader",
+ "pos": [
+ 290,
+ 60
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IPADAPTER",
+ "type": "IPADAPTER",
+ "links": [
+ 1
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapterModelLoader"
+ },
+ "widgets_values": [
+ "ip-adapter_sdxl_vit-h.bin"
+ ]
+ },
+ {
+ "id": 1,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ 290,
+ 280
+ ],
+ "size": {
+ "0": 300,
+ "1": 100
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 4
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 5,
+ 6
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "sdxl/sd_xl_base_1.0.safetensors"
+ ]
+ },
+ {
+ "id": 10,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 650,
+ 590
+ ],
+ "size": {
+ "0": 210,
+ "1": 110
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 10
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 1024,
+ 1024,
+ 1
+ ]
+ },
+ {
+ "id": 2,
+ "type": "VAELoader",
+ "pos": [
+ 940,
+ 480
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 12
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "sdxl_vae.safetensors"
+ ]
+ },
+ {
+ "id": 21,
+ "type": "LoadImage",
+ "pos": [
+ -10,
+ 440
+ ],
+ "size": {
+ "0": 220,
+ "1": 320
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 61
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "woman.png",
+ "image"
+ ]
+ },
+ {
+ "id": 6,
+ "type": "LoadImage",
+ "pos": [
+ -10,
+ 70
+ ],
+ "size": {
+ "0": 220,
+ "1": 320
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 63
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "girl_sitting.png",
+ "image"
+ ]
+ },
+ {
+ "id": 24,
+ "type": "ImageBatch",
+ "pos": [
+ 380,
+ 420
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image1",
+ "type": "IMAGE",
+ "link": 64
+ },
+ {
+ "name": "image2",
+ "type": "IMAGE",
+ "link": 62
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 45
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ImageBatch"
+ }
+ },
+ {
+ "id": 28,
+ "type": "PrepImageForClipVision",
+ "pos": [
+ 350,
+ 510
+ ],
+ "size": [
+ 240,
+ 110
+ ],
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 61
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 62
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PrepImageForClipVision"
+ },
+ "widgets_values": [
+ "LANCZOS",
+ "top",
+ 0
+ ]
+ },
+ {
+ "id": 29,
+ "type": "PrepImageForClipVision",
+ "pos": [
+ 350,
+ 660
+ ],
+ "size": [
+ 240,
+ 110
+ ],
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 63
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 64
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PrepImageForClipVision"
+ },
+ "widgets_values": [
+ "LANCZOS",
+ "top",
+ 0
+ ]
+ }
+ ],
+ "links": [
+ [
+ 1,
+ 3,
+ 0,
+ 5,
+ 0,
+ "IPADAPTER"
+ ],
+ [
+ 2,
+ 4,
+ 0,
+ 5,
+ 1,
+ "CLIP_VISION"
+ ],
+ [
+ 4,
+ 1,
+ 0,
+ 5,
+ 3,
+ "MODEL"
+ ],
+ [
+ 5,
+ 1,
+ 1,
+ 7,
+ 0,
+ "CLIP"
+ ],
+ [
+ 6,
+ 1,
+ 1,
+ 8,
+ 0,
+ "CLIP"
+ ],
+ [
+ 7,
+ 5,
+ 0,
+ 9,
+ 0,
+ "MODEL"
+ ],
+ [
+ 8,
+ 7,
+ 0,
+ 9,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 9,
+ 8,
+ 0,
+ 9,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 10,
+ 10,
+ 0,
+ 9,
+ 3,
+ "LATENT"
+ ],
+ [
+ 11,
+ 9,
+ 0,
+ 11,
+ 0,
+ "LATENT"
+ ],
+ [
+ 12,
+ 2,
+ 0,
+ 11,
+ 1,
+ "VAE"
+ ],
+ [
+ 13,
+ 11,
+ 0,
+ 12,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 45,
+ 24,
+ 0,
+ 5,
+ 2,
+ "IMAGE"
+ ],
+ [
+ 61,
+ 21,
+ 0,
+ 28,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 62,
+ 28,
+ 0,
+ 24,
+ 1,
+ "IMAGE"
+ ],
+ [
+ 63,
+ 6,
+ 0,
+ 29,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 64,
+ 29,
+ 0,
+ 24,
+ 0,
+ "IMAGE"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_sdxl_vit-h.json b/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_sdxl_vit-h.json
new file mode 100644
index 0000000000000000000000000000000000000000..dc20bf47c7d746d6687ca0faa9a4ff94c68d78f5
--- /dev/null
+++ b/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_sdxl_vit-h.json
@@ -0,0 +1,606 @@
+{
+ "last_node_id": 12,
+ "last_link_id": 13,
+ "nodes": [
+ {
+ "id": 8,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 650,
+ 420
+ ],
+ "size": {
+ "0": 210,
+ "1": 120
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 6
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 9
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "blurry, horror"
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 7,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 650,
+ 250
+ ],
+ "size": {
+ "0": 210,
+ "1": 120
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 5
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 8
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "beautiful renaissance girl, detailed"
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 11,
+ "type": "VAEDecode",
+ "pos": [
+ 1300,
+ 170
+ ],
+ "size": {
+ "0": 140,
+ "1": 50
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 11
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 12
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 13
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 12,
+ "type": "SaveImage",
+ "pos": [
+ 1300,
+ 270
+ ],
+ "size": {
+ "0": 400,
+ "1": 450
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 13
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "IPAdapter"
+ ]
+ },
+ {
+ "id": 6,
+ "type": "LoadImage",
+ "pos": [
+ 40,
+ 60
+ ],
+ "size": {
+ "0": 220,
+ "1": 320
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 3
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "venere.jpg",
+ "image"
+ ]
+ },
+ {
+ "id": 2,
+ "type": "VAELoader",
+ "pos": [
+ 940,
+ 480
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 12
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "sdxl_vae.safetensors"
+ ]
+ },
+ {
+ "id": 10,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 650,
+ 590
+ ],
+ "size": {
+ "0": 210,
+ "1": 110
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 10
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 1024,
+ 1024,
+ 1
+ ]
+ },
+ {
+ "id": 9,
+ "type": "KSampler",
+ "pos": [
+ 930,
+ 170
+ ],
+ "size": {
+ "0": 315,
+ "1": 262
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 7
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 8
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 9
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 10
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 11
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 0,
+ "fixed",
+ 25,
+ 5,
+ "ddim",
+ "ddim_uniform",
+ 1
+ ]
+ },
+ {
+ "id": 4,
+ "type": "CLIPVisionLoader",
+ "pos": [
+ 290,
+ 170
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "CLIP_VISION",
+ "type": "CLIP_VISION",
+ "links": [
+ 2
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPVisionLoader"
+ },
+ "widgets_values": [
+ "IPAdapter_image_encoder_sd15.safetensors"
+ ]
+ },
+ {
+ "id": 5,
+ "type": "IPAdapterApply",
+ "pos": [
+ 652,
+ -55
+ ],
+ "size": {
+ "0": 210,
+ "1": 258
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "ipadapter",
+ "type": "IPADAPTER",
+ "link": 1
+ },
+ {
+ "name": "clip_vision",
+ "type": "CLIP_VISION",
+ "link": 2
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 3
+ },
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 4
+ },
+ {
+ "name": "attn_mask",
+ "type": "MASK",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 7
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapterApply"
+ },
+ "widgets_values": [
+ 0.8,
+ 0,
+ "original",
+ 0,
+ 1,
+ false
+ ]
+ },
+ {
+ "id": 3,
+ "type": "IPAdapterModelLoader",
+ "pos": [
+ 290,
+ 60
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IPADAPTER",
+ "type": "IPADAPTER",
+ "links": [
+ 1
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapterModelLoader"
+ },
+ "widgets_values": [
+ "ip-adapter_sdxl_vit-h.safetensors"
+ ]
+ },
+ {
+ "id": 1,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ 290,
+ 280
+ ],
+ "size": {
+ "0": 300,
+ "1": 100
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 4
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 5,
+ 6
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "sdxl/sd_xl_base_1.0_0.9vae.safetensors"
+ ]
+ }
+ ],
+ "links": [
+ [
+ 1,
+ 3,
+ 0,
+ 5,
+ 0,
+ "IPADAPTER"
+ ],
+ [
+ 2,
+ 4,
+ 0,
+ 5,
+ 1,
+ "CLIP_VISION"
+ ],
+ [
+ 3,
+ 6,
+ 0,
+ 5,
+ 2,
+ "IMAGE"
+ ],
+ [
+ 4,
+ 1,
+ 0,
+ 5,
+ 3,
+ "MODEL"
+ ],
+ [
+ 5,
+ 1,
+ 1,
+ 7,
+ 0,
+ "CLIP"
+ ],
+ [
+ 6,
+ 1,
+ 1,
+ 8,
+ 0,
+ "CLIP"
+ ],
+ [
+ 7,
+ 5,
+ 0,
+ 9,
+ 0,
+ "MODEL"
+ ],
+ [
+ 8,
+ 7,
+ 0,
+ 9,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 9,
+ 8,
+ 0,
+ 9,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 10,
+ 10,
+ 0,
+ 9,
+ 3,
+ "LATENT"
+ ],
+ [
+ 11,
+ 9,
+ 0,
+ 11,
+ 0,
+ "LATENT"
+ ],
+ [
+ 12,
+ 2,
+ 0,
+ 11,
+ 1,
+ "VAE"
+ ],
+ [
+ 13,
+ 11,
+ 0,
+ 12,
+ 0,
+ "IMAGE"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_weight_types.json b/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_weight_types.json
new file mode 100644
index 0000000000000000000000000000000000000000..5b9d099f718d59389e34c9fb8060ec3cdb2b4a36
--- /dev/null
+++ b/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_weight_types.json
@@ -0,0 +1,1380 @@
+{
+ "last_node_id": 57,
+ "last_link_id": 154,
+ "nodes": [
+ {
+ "id": 18,
+ "type": "CLIPVisionLoader",
+ "pos": [
+ 260,
+ -470
+ ],
+ "size": {
+ "0": 310,
+ "1": 60
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "CLIP_VISION",
+ "type": "CLIP_VISION",
+ "links": [
+ 89,
+ 94,
+ 98
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPVisionLoader"
+ },
+ "widgets_values": [
+ "IPAdapter_image_encoder_sd15.safetensors"
+ ]
+ },
+ {
+ "id": 7,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 360,
+ 120
+ ],
+ "size": {
+ "0": 380,
+ "1": 160
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 72
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 6,
+ 102,
+ 111,
+ 129
+ ],
+ "slot_index": 0
+ }
+ ],
+ "title": "CLIP Text Encode (Negative)",
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "blurry, distorted, malformed, gore, naked, bare skin, tattoo"
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 11,
+ "type": "VAELoader",
+ "pos": [
+ 440,
+ 480
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 12,
+ 104,
+ 106,
+ 133
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "vae-ft-mse-840000-ema-pruned.safetensors"
+ ]
+ },
+ {
+ "id": 4,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ -184,
+ 113
+ ],
+ "size": {
+ "0": 340,
+ "1": 100
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 91,
+ 96,
+ 100,
+ 131
+ ],
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 71,
+ 72
+ ],
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [],
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "sd15/deliberate_v3.safetensors"
+ ]
+ },
+ {
+ "id": 5,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 520,
+ 330
+ ],
+ "size": {
+ "0": 220,
+ "1": 106
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 2,
+ 103,
+ 112,
+ 130
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ 1
+ ]
+ },
+ {
+ "id": 17,
+ "type": "IPAdapterModelLoader",
+ "pos": [
+ 260,
+ -570
+ ],
+ "size": {
+ "0": 310,
+ "1": 60
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IPADAPTER",
+ "type": "IPADAPTER",
+ "links": [
+ 88,
+ 93,
+ 97
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapterModelLoader"
+ },
+ "widgets_values": [
+ "ip-adapter-plus_sd15.bin"
+ ]
+ },
+ {
+ "id": 51,
+ "type": "PreviewImage",
+ "pos": [
+ 960,
+ 10
+ ],
+ "size": {
+ "0": 527.208984375,
+ "1": 576.05859375
+ },
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 134
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 42,
+ "type": "PreviewImage",
+ "pos": [
+ 2730,
+ 10
+ ],
+ "size": {
+ "0": 532.08154296875,
+ "1": 578.00732421875
+ },
+ "flags": {},
+ "order": 23,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 107
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 43,
+ "type": "KSampler",
+ "pos": [
+ 2970,
+ -290
+ ],
+ "size": {
+ "0": 290,
+ "1": 262
+ },
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 115
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 151
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 111
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 112
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 117
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 292,
+ "fixed",
+ 40,
+ 5.5,
+ "dpmpp_2m",
+ "karras",
+ 1
+ ]
+ },
+ {
+ "id": 41,
+ "type": "VAEDecode",
+ "pos": [
+ 2810,
+ -90
+ ],
+ "size": {
+ "0": 140,
+ "1": 60
+ },
+ "flags": {},
+ "order": 20,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 117
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 106
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 107
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 3,
+ "type": "KSampler",
+ "pos": [
+ 2370,
+ -300
+ ],
+ "size": {
+ "0": 300,
+ "1": 262
+ },
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 92
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 149
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 6
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 2
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 7
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 292,
+ "fixed",
+ 40,
+ 5.5,
+ "dpmpp_2m",
+ "karras",
+ 1
+ ]
+ },
+ {
+ "id": 8,
+ "type": "VAEDecode",
+ "pos": [
+ 2210,
+ -100
+ ],
+ "size": {
+ "0": 140,
+ "1": 60
+ },
+ "flags": {},
+ "order": 18,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 7
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 12
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 56
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 38,
+ "type": "KSampler",
+ "pos": [
+ 1770,
+ -310
+ ],
+ "size": {
+ "0": 290,
+ "1": 262
+ },
+ "flags": {},
+ "order": 15,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 108
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 150
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 102
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 103
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 116
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 292,
+ "fixed",
+ 40,
+ 5.5,
+ "dpmpp_2m",
+ "karras",
+ 1
+ ]
+ },
+ {
+ "id": 39,
+ "type": "VAEDecode",
+ "pos": [
+ 1610,
+ -110
+ ],
+ "size": {
+ "0": 140,
+ "1": 60
+ },
+ "flags": {},
+ "order": 19,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 116
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 104
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 105
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 50,
+ "type": "VAEDecode",
+ "pos": [
+ 1000,
+ -110
+ ],
+ "size": {
+ "0": 140,
+ "1": 60
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 135
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 133
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 134
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 6,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 360,
+ -90
+ ],
+ "size": {
+ "0": 380,
+ "1": 170
+ },
+ "flags": {
+ "collapsed": false
+ },
+ "order": 6,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 71
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 149,
+ 150,
+ 151,
+ 152
+ ],
+ "slot_index": 0
+ }
+ ],
+ "title": "CLIP Text Encode (Positive)",
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "portrait illustration of a warrior woman in full armor, in a dungeon\n\nhighly detailed, dramatic lighting, 4k"
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 20,
+ "type": "PrepImageForClipVision",
+ "pos": [
+ 320,
+ -350
+ ],
+ "size": {
+ "0": 243.60000610351562,
+ "1": 110
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 148
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 144,
+ 146,
+ 147
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PrepImageForClipVision"
+ },
+ "widgets_values": [
+ "LANCZOS",
+ "center",
+ 0
+ ]
+ },
+ {
+ "id": 49,
+ "type": "KSampler",
+ "pos": [
+ 1160,
+ -310
+ ],
+ "size": {
+ "0": 300,
+ "1": 262
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 131
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 152
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 129
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 130
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 135
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 292,
+ "fixed",
+ 46,
+ 5.5,
+ "dpmpp_2m",
+ "karras",
+ 1
+ ]
+ },
+ {
+ "id": 40,
+ "type": "PreviewImage",
+ "pos": [
+ 1540,
+ 10
+ ],
+ "size": {
+ "0": 520,
+ "1": 570
+ },
+ "flags": {},
+ "order": 22,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 105
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 29,
+ "type": "PreviewImage",
+ "pos": [
+ 2130,
+ 10
+ ],
+ "size": {
+ "0": 520,
+ "1": 570
+ },
+ "flags": {},
+ "order": 21,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 56
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 36,
+ "type": "IPAdapterApply",
+ "pos": [
+ 1520,
+ -320
+ ],
+ "size": {
+ "0": 234.41876220703125,
+ "1": 166
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "ipadapter",
+ "type": "IPADAPTER",
+ "link": 93
+ },
+ {
+ "name": "clip_vision",
+ "type": "CLIP_VISION",
+ "link": 94
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 146
+ },
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 96
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 108
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapterApply"
+ },
+ "widgets_values": [
+ 0.25,
+ 0.3,
+ "linear"
+ ]
+ },
+ {
+ "id": 35,
+ "type": "IPAdapterApply",
+ "pos": [
+ 2100,
+ -310
+ ],
+ "size": {
+ "0": 246.50965881347656,
+ "1": 166
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "ipadapter",
+ "type": "IPADAPTER",
+ "link": 88
+ },
+ {
+ "name": "clip_vision",
+ "type": "CLIP_VISION",
+ "link": 89
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 144
+ },
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 91
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 92
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapterApply"
+ },
+ "widgets_values": [
+ 0.25,
+ 0.3,
+ "original"
+ ]
+ },
+ {
+ "id": 37,
+ "type": "IPAdapterApply",
+ "pos": [
+ 2710,
+ -300
+ ],
+ "size": {
+ "0": 239.32774353027344,
+ "1": 166
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "ipadapter",
+ "type": "IPADAPTER",
+ "link": 97
+ },
+ {
+ "name": "clip_vision",
+ "type": "CLIP_VISION",
+ "link": 98
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 147
+ },
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 100
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 115
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapterApply"
+ },
+ "widgets_values": [
+ 0.25,
+ 0.3,
+ "channel penalty"
+ ]
+ },
+ {
+ "id": 19,
+ "type": "LoadImage",
+ "pos": [
+ 78,
+ -351
+ ],
+ "size": {
+ "0": 210,
+ "1": 280
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 148
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "woman.png",
+ "image"
+ ]
+ }
+ ],
+ "links": [
+ [
+ 2,
+ 5,
+ 0,
+ 3,
+ 3,
+ "LATENT"
+ ],
+ [
+ 6,
+ 7,
+ 0,
+ 3,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 7,
+ 3,
+ 0,
+ 8,
+ 0,
+ "LATENT"
+ ],
+ [
+ 12,
+ 11,
+ 0,
+ 8,
+ 1,
+ "VAE"
+ ],
+ [
+ 56,
+ 8,
+ 0,
+ 29,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 71,
+ 4,
+ 1,
+ 6,
+ 0,
+ "CLIP"
+ ],
+ [
+ 72,
+ 4,
+ 1,
+ 7,
+ 0,
+ "CLIP"
+ ],
+ [
+ 88,
+ 17,
+ 0,
+ 35,
+ 0,
+ "IPADAPTER"
+ ],
+ [
+ 89,
+ 18,
+ 0,
+ 35,
+ 1,
+ "CLIP_VISION"
+ ],
+ [
+ 91,
+ 4,
+ 0,
+ 35,
+ 3,
+ "MODEL"
+ ],
+ [
+ 92,
+ 35,
+ 0,
+ 3,
+ 0,
+ "MODEL"
+ ],
+ [
+ 93,
+ 17,
+ 0,
+ 36,
+ 0,
+ "IPADAPTER"
+ ],
+ [
+ 94,
+ 18,
+ 0,
+ 36,
+ 1,
+ "CLIP_VISION"
+ ],
+ [
+ 96,
+ 4,
+ 0,
+ 36,
+ 3,
+ "MODEL"
+ ],
+ [
+ 97,
+ 17,
+ 0,
+ 37,
+ 0,
+ "IPADAPTER"
+ ],
+ [
+ 98,
+ 18,
+ 0,
+ 37,
+ 1,
+ "CLIP_VISION"
+ ],
+ [
+ 100,
+ 4,
+ 0,
+ 37,
+ 3,
+ "MODEL"
+ ],
+ [
+ 102,
+ 7,
+ 0,
+ 38,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 103,
+ 5,
+ 0,
+ 38,
+ 3,
+ "LATENT"
+ ],
+ [
+ 104,
+ 11,
+ 0,
+ 39,
+ 1,
+ "VAE"
+ ],
+ [
+ 105,
+ 39,
+ 0,
+ 40,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 106,
+ 11,
+ 0,
+ 41,
+ 1,
+ "VAE"
+ ],
+ [
+ 107,
+ 41,
+ 0,
+ 42,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 108,
+ 36,
+ 0,
+ 38,
+ 0,
+ "MODEL"
+ ],
+ [
+ 111,
+ 7,
+ 0,
+ 43,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 112,
+ 5,
+ 0,
+ 43,
+ 3,
+ "LATENT"
+ ],
+ [
+ 115,
+ 37,
+ 0,
+ 43,
+ 0,
+ "MODEL"
+ ],
+ [
+ 116,
+ 38,
+ 0,
+ 39,
+ 0,
+ "LATENT"
+ ],
+ [
+ 117,
+ 43,
+ 0,
+ 41,
+ 0,
+ "LATENT"
+ ],
+ [
+ 129,
+ 7,
+ 0,
+ 49,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 130,
+ 5,
+ 0,
+ 49,
+ 3,
+ "LATENT"
+ ],
+ [
+ 131,
+ 4,
+ 0,
+ 49,
+ 0,
+ "MODEL"
+ ],
+ [
+ 133,
+ 11,
+ 0,
+ 50,
+ 1,
+ "VAE"
+ ],
+ [
+ 134,
+ 50,
+ 0,
+ 51,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 135,
+ 49,
+ 0,
+ 50,
+ 0,
+ "LATENT"
+ ],
+ [
+ 144,
+ 20,
+ 0,
+ 35,
+ 2,
+ "IMAGE"
+ ],
+ [
+ 146,
+ 20,
+ 0,
+ 36,
+ 2,
+ "IMAGE"
+ ],
+ [
+ 147,
+ 20,
+ 0,
+ 37,
+ 2,
+ "IMAGE"
+ ],
+ [
+ 148,
+ 19,
+ 0,
+ 20,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 149,
+ 6,
+ 0,
+ 3,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 150,
+ 6,
+ 0,
+ 38,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 151,
+ 6,
+ 0,
+ 43,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 152,
+ 6,
+ 0,
+ 49,
+ 1,
+ "CONDITIONING"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_weighted.json b/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_weighted.json
new file mode 100644
index 0000000000000000000000000000000000000000..b55ba3e1d464daa57577f88e31dd16ab5980cdf5
--- /dev/null
+++ b/custom_nodes/ComfyUI_IPAdapter_plus/examples/IPAdapter_weighted.json
@@ -0,0 +1,710 @@
+{
+ "last_node_id": 15,
+ "last_link_id": 22,
+ "nodes": [
+ {
+ "id": 10,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 820,
+ 520
+ ],
+ "size": {
+ "0": 210,
+ "1": 110
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 10
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ 1
+ ]
+ },
+ {
+ "id": 8,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 820,
+ 350
+ ],
+ "size": {
+ "0": 210,
+ "1": 120
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 6
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 9
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "blurry, horror"
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 7,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 820,
+ 180
+ ],
+ "size": {
+ "0": 210,
+ "1": 120
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 5
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 8
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "beautiful renaissance girl, detailed"
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 11,
+ "type": "VAEDecode",
+ "pos": [
+ 1470,
+ 120
+ ],
+ "size": {
+ "0": 140,
+ "1": 50
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 11
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 12
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 13
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 12,
+ "type": "SaveImage",
+ "pos": [
+ 1470,
+ 220
+ ],
+ "size": {
+ "0": 400,
+ "1": 450
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 13
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "IPAdapter"
+ ]
+ },
+ {
+ "id": 4,
+ "type": "CLIPVisionLoader",
+ "pos": [
+ 120,
+ -280
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "CLIP_VISION",
+ "type": "CLIP_VISION",
+ "links": [
+ 16
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPVisionLoader"
+ },
+ "widgets_values": [
+ "IPAdapter_image_encoder_sd15.safetensors"
+ ]
+ },
+ {
+ "id": 15,
+ "type": "LoadImage",
+ "pos": [
+ -40,
+ -170
+ ],
+ "size": {
+ "0": 220,
+ "1": 320
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 21
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "cwf_portrait.jpg",
+ "image"
+ ]
+ },
+ {
+ "id": 1,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ 410,
+ 210
+ ],
+ "size": {
+ "0": 300,
+ "1": 100
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 19
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 5,
+ 6
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "v1-5-pruned-emaonly.safetensors"
+ ]
+ },
+ {
+ "id": 6,
+ "type": "LoadImage",
+ "pos": [
+ 200,
+ -170
+ ],
+ "size": {
+ "0": 220,
+ "1": 320
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 20
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "venere.jpg",
+ "image"
+ ]
+ },
+ {
+ "id": 3,
+ "type": "IPAdapterModelLoader",
+ "pos": [
+ 450,
+ -210
+ ],
+ "size": [
+ 260,
+ 60
+ ],
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IPADAPTER",
+ "type": "IPADAPTER",
+ "links": [
+ 17
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapterModelLoader"
+ },
+ "widgets_values": [
+ "ip-adapter_sd15.bin"
+ ]
+ },
+ {
+ "id": 13,
+ "type": "IPAdapterApplyEncoded",
+ "pos": [
+ 790,
+ 30
+ ],
+ "size": [
+ 240,
+ 100
+ ],
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "ipadapter",
+ "type": "IPADAPTER",
+ "link": 17
+ },
+ {
+ "name": "embeds",
+ "type": "EMBEDS",
+ "link": 18
+ },
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 19
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 22
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapterApplyEncoded"
+ },
+ "widgets_values": [
+ 1
+ ]
+ },
+ {
+ "id": 2,
+ "type": "VAELoader",
+ "pos": [
+ 1110,
+ 460
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 12
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAELoader"
+ },
+ "widgets_values": [
+ "vae-ft-mse-840000-ema-pruned.safetensors"
+ ]
+ },
+ {
+ "id": 14,
+ "type": "IPAdapterEncoder",
+ "pos": [
+ 500,
+ -100
+ ],
+ "size": [
+ 210,
+ 260
+ ],
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip_vision",
+ "type": "CLIP_VISION",
+ "link": 16
+ },
+ {
+ "name": "image_1",
+ "type": "IMAGE",
+ "link": 21
+ },
+ {
+ "name": "image_2",
+ "type": "IMAGE",
+ "link": 20
+ },
+ {
+ "name": "image_3",
+ "type": "IMAGE",
+ "link": null
+ },
+ {
+ "name": "image_4",
+ "type": "IMAGE",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "EMBEDS",
+ "type": "EMBEDS",
+ "links": [
+ 18
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapterEncoder"
+ },
+ "widgets_values": [
+ false,
+ 0.31,
+ 0.38,
+ 1,
+ 1,
+ 1
+ ]
+ },
+ {
+ "id": 9,
+ "type": "KSampler",
+ "pos": [
+ 1100,
+ 150
+ ],
+ "size": {
+ "0": 315,
+ "1": 262
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 22
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 8
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 9
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 10
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 11
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 0,
+ "fixed",
+ 35,
+ 5,
+ "ddim",
+ "ddim_uniform",
+ 1
+ ]
+ }
+ ],
+ "links": [
+ [
+ 5,
+ 1,
+ 1,
+ 7,
+ 0,
+ "CLIP"
+ ],
+ [
+ 6,
+ 1,
+ 1,
+ 8,
+ 0,
+ "CLIP"
+ ],
+ [
+ 8,
+ 7,
+ 0,
+ 9,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 9,
+ 8,
+ 0,
+ 9,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 10,
+ 10,
+ 0,
+ 9,
+ 3,
+ "LATENT"
+ ],
+ [
+ 11,
+ 9,
+ 0,
+ 11,
+ 0,
+ "LATENT"
+ ],
+ [
+ 12,
+ 2,
+ 0,
+ 11,
+ 1,
+ "VAE"
+ ],
+ [
+ 13,
+ 11,
+ 0,
+ 12,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 16,
+ 4,
+ 0,
+ 14,
+ 0,
+ "CLIP_VISION"
+ ],
+ [
+ 17,
+ 3,
+ 0,
+ 13,
+ 0,
+ "IPADAPTER"
+ ],
+ [
+ 18,
+ 14,
+ 0,
+ 13,
+ 1,
+ "EMBEDS"
+ ],
+ [
+ 19,
+ 1,
+ 0,
+ 13,
+ 2,
+ "MODEL"
+ ],
+ [
+ 20,
+ 6,
+ 0,
+ 14,
+ 2,
+ "IMAGE"
+ ],
+ [
+ 21,
+ 15,
+ 0,
+ 14,
+ 1,
+ "IMAGE"
+ ],
+ [
+ 22,
+ 13,
+ 0,
+ 9,
+ 0,
+ "MODEL"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/examples/batch_images.jpg b/custom_nodes/ComfyUI_IPAdapter_plus/examples/batch_images.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..fffae3e09d2a62c916968154b6649277a9c0d8c5
Binary files /dev/null and b/custom_nodes/ComfyUI_IPAdapter_plus/examples/batch_images.jpg differ
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/examples/canny_controlnet.jpg b/custom_nodes/ComfyUI_IPAdapter_plus/examples/canny_controlnet.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8fd1ef5c82e1b1577e4e5498e756e901fdc100cd
Binary files /dev/null and b/custom_nodes/ComfyUI_IPAdapter_plus/examples/canny_controlnet.jpg differ
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/examples/face_swap.jpg b/custom_nodes/ComfyUI_IPAdapter_plus/examples/face_swap.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d638483f1ceaffe561183cc543a38bfc4edc89aa
Binary files /dev/null and b/custom_nodes/ComfyUI_IPAdapter_plus/examples/face_swap.jpg differ
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/examples/image_weighting.jpg b/custom_nodes/ComfyUI_IPAdapter_plus/examples/image_weighting.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..94c27ec111a4ab59f4fe7f8b8ec995a7b7f2358c
Binary files /dev/null and b/custom_nodes/ComfyUI_IPAdapter_plus/examples/image_weighting.jpg differ
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/examples/inpainting.jpg b/custom_nodes/ComfyUI_IPAdapter_plus/examples/inpainting.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d8ab43725aaa0318c1ad957013d2e57419b47f0b
Binary files /dev/null and b/custom_nodes/ComfyUI_IPAdapter_plus/examples/inpainting.jpg differ
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/examples/masking.jpg b/custom_nodes/ComfyUI_IPAdapter_plus/examples/masking.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..dd45bae2537a5cd5254c59693ba9cf75e7270ca0
Binary files /dev/null and b/custom_nodes/ComfyUI_IPAdapter_plus/examples/masking.jpg differ
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/examples/noise_example.jpg b/custom_nodes/ComfyUI_IPAdapter_plus/examples/noise_example.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c73dc93f8128cbc418040c49d66102cd22ae637a
Binary files /dev/null and b/custom_nodes/ComfyUI_IPAdapter_plus/examples/noise_example.jpg differ
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/examples/prep_images.jpg b/custom_nodes/ComfyUI_IPAdapter_plus/examples/prep_images.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8aed2821cbd38b3976b37b356f22baaa86104f4f
Binary files /dev/null and b/custom_nodes/ComfyUI_IPAdapter_plus/examples/prep_images.jpg differ
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/examples/timestepping.jpg b/custom_nodes/ComfyUI_IPAdapter_plus/examples/timestepping.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3878778b9d77527a57b068dd117dbfa223e55766
Binary files /dev/null and b/custom_nodes/ComfyUI_IPAdapter_plus/examples/timestepping.jpg differ
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/examples/weight_types.jpg b/custom_nodes/ComfyUI_IPAdapter_plus/examples/weight_types.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..228cd7b596e482587a4cf397b8f43df05d203e5a
Binary files /dev/null and b/custom_nodes/ComfyUI_IPAdapter_plus/examples/weight_types.jpg differ
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/ipadapter_workflow.png b/custom_nodes/ComfyUI_IPAdapter_plus/ipadapter_workflow.png
new file mode 100644
index 0000000000000000000000000000000000000000..f061ca1a72f94664ef6ced3e354a1c557fabb2bc
Binary files /dev/null and b/custom_nodes/ComfyUI_IPAdapter_plus/ipadapter_workflow.png differ
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/models/ip-adapter_sd15_plus.pth b/custom_nodes/ComfyUI_IPAdapter_plus/models/ip-adapter_sd15_plus.pth
new file mode 100644
index 0000000000000000000000000000000000000000..b4fcc4637147d978238cfa837de3478c533882cb
--- /dev/null
+++ b/custom_nodes/ComfyUI_IPAdapter_plus/models/ip-adapter_sd15_plus.pth
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a0db87557825ac2f8888ef06ec64c85f175e8ba5467baa2722ade3b4a9feb9f5
+size 158030471
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/models/put_ipadapter_models_here.txt b/custom_nodes/ComfyUI_IPAdapter_plus/models/put_ipadapter_models_here.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/custom_nodes/ComfyUI_IPAdapter_plus/resampler.py b/custom_nodes/ComfyUI_IPAdapter_plus/resampler.py
new file mode 100644
index 0000000000000000000000000000000000000000..4521c8c3e6f17caf4547c3dd84118da760e5179f
--- /dev/null
+++ b/custom_nodes/ComfyUI_IPAdapter_plus/resampler.py
@@ -0,0 +1,121 @@
+# modified from https://github.com/mlfoundations/open_flamingo/blob/main/open_flamingo/src/helpers.py
+import math
+
+import torch
+import torch.nn as nn
+
+
+# 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):
+ """
+ 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)
+
+ 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,
+ ):
+ 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)
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Noise/LICENSE b/custom_nodes/ComfyUI_Noise/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..f288702d2fa16d3cdf0035b15a9fcbc552cd88e7
--- /dev/null
+++ b/custom_nodes/ComfyUI_Noise/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/custom_nodes/ComfyUI_Noise/README.md b/custom_nodes/ComfyUI_Noise/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..7402929546b9cc995e7346f96f5ee5724b5ab9e6
--- /dev/null
+++ b/custom_nodes/ComfyUI_Noise/README.md
@@ -0,0 +1,88 @@
+# ComfyUI Noise
+
+This repo contains 6 nodes for [ComfyUI](https://github.com/comfyanonymous/ComfyUI) that allows for more control and flexibility over the noise. This allows e.g. for workflows with small variations to generations or finding the accompanying noise to some input image and prompt.
+
+## Nodes
+
+### Noisy Latent Image:
+This node lets you generate noise, you can find this node under `latent>noise` and it the following settings:
+- **source**: where to generate the noise, currently supports GPU and CPU.
+- **seed**: the noise seed.
+- **width**: image width.
+- **height**: image height.
+- **batch_size**: batch size.
+
+### Duplicate Batch Index:
+The functionality of this node has been moved to core, please use: `Latent>Batch>Repeat Latent Batch` and `Latent>Batch>Latent From Batch` instead.
+
+This node lets you duplicate a certain sample in the batch, this can be used to duplicate e.g. encoded images but also noise generated from the node listed above. You can find this node under `latent` and it has the following settings:
+- **latents**: the latents.
+- **batch_index**: which sample in the latents to duplicate.
+- **batch_size**: the new batch size, (i.e. how many times to duplicate the sample).
+
+### Slerp Latents:
+This node lets you mix two latents together. Both of the input latents must share the same dimensions or the node will ignore the mix factor and instead output the top slot. When it comes to other things attached to the latents such as e.g. masks, only those of the top slot are passed on. You can find this node under `latent` and it comes with the following inputs:
+- **latents1**: first batch of latents.
+- **latents2**: second batch of latents. This input is optional.
+- **mask**: determines where in the latents to slerp. This input is optional
+- **factor**: how much of the second batch of latents should be slerped into the first.
+
+### Get Sigma:
+This node can be used to calculate the amount of noise a sampler expects when it starts denoising. You can find this node under `latent>noise` and it comes with the following inputs and settings:
+- **model**: The model for which to calculate the sigma.
+- **sampler_name**: the name of the sampler for which to calculate the sigma.
+- **scheduler**: the type of schedule used in the sampler
+- **steps**: the total number of steps in the schedule
+- **start_at_step**: the start step of the sampler, i.e. how much noise it expects in the input image
+- **end_at_step**: the current end step of the previous sampler, i.e. how much noise already is in the image.
+
+Most of the time you'd simply want to keep `start_at_step` at zero, and `end_at_step` at `steps`, but if you'd want to re-inject some noise in between two samplers, e.g. one sampler that denoises from 0 to 15, and a second that denoises from 10 to 20, you'd want to use a `start_at_step` 10 and an `end_at_step` of 15. So that the image we get, which is at step 15, can be noised back down to step 10, so the second sampler can bring it to 20. Take note that the Advanced Ksampler has a settings for `add_noise` and `return_with_leftover_noise` which when working with these nodes we both want to have disabled.
+
+### Inject Noise:
+This node lets you actually inject the noise into an image latent, you can find this node under `latent>noise` and it comes with the following inputs:
+- **latents**: The latents to inject the noise into.
+- **noise**: The noise. This input is optional
+- **mask**: determines where to inject noise. This input is optional
+- **strength**: The strength of the noise. Note that we can use the node above to calculate for us an appropriate strength value.
+
+### Unsampler:
+This node does the reverse of a sampler. It calculates the noise that would generate the image given the model and the prompt. You can find this node under `sampling` and it takes the following inputs and settings:
+- **model**: The model to target.
+- **steps**: number of steps to noise.
+- **end_step**: to what step to travel back to.
+- **cfg**: classifier free guidance scale.
+- **sampler_name**: The name of the sampling technique to use.
+- **scheduler**: The type of schedule to use.
+- **normalize**: whether to normalize the noise before output. Useful when passing it on to an Inject Noise node which expects normalizes noise.
+- **positive**: Positive prompt.
+- **negative**: Negative prompt.
+- **latent_image**: The image to renoise.
+
+When trying to reconstruct the target image as faithful as possible this works best if both the unsampler and sampler use a cfg scale close to 1.0 and similar number of steps. But it is fun and worth it to play around with these settings to get a better intuition of the results. This node let's you do similar things the A1111 [img2img alternative](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#img2img-alternative-test) script does
+
+## Examples
+
+here are some examples that show how to use the nodes above. Workflows to these examples can be found in the `example_workflow` folder.
+
+
+
+generating variations
+
+
+
+
+To create small variations to a given generation we can do the following: We generate the noise of the seed that we're interested using a `Noisy Latent Image` node, we then create an entire batch of these with a `Duplicate Batch Index` node. Note that if we were doing this for img2img we can use this same node to duplicate the image latents. Next we generate some more noise, but this time we generate a batch of noise rather than a single sample. We then Slerp this newly created noise into the other one with a `Slerp Latents` node. To figure out the required strength for injecting this noise we use a `Get Sigma` node. And finally we inject the slerped noise into a batch of empty latents with a `Inject Noise` node. Take note that we use an advanced Ksampler with the `add_noise` setting disabled
+
+
+
+
+
+"unsampling"
+
+
+
+
+To get the noise that recreates a certain image, we first load an image. Then we use the `Unsampler` node with a low cfg value. To check if this is working we then take the resulting noise and feed it back into an advanced ksampler with the `add_noise` setting disabled, and a cfg of 1.0.
+
+
+
diff --git a/custom_nodes/ComfyUI_Noise/__init__.py b/custom_nodes/ComfyUI_Noise/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d721463be66961a2f388b3a756760d167ea5d510
--- /dev/null
+++ b/custom_nodes/ComfyUI_Noise/__init__.py
@@ -0,0 +1,3 @@
+from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
+
+__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Noise/__pycache__/__init__.cpython-311.pyc b/custom_nodes/ComfyUI_Noise/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..47f6c9adc1aa6c245862d8a98a173ca00e8df104
Binary files /dev/null and b/custom_nodes/ComfyUI_Noise/__pycache__/__init__.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Noise/__pycache__/nodes.cpython-311.pyc b/custom_nodes/ComfyUI_Noise/__pycache__/nodes.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c3b441cb996a749b8b2ad95d685d3916e2e07ada
Binary files /dev/null and b/custom_nodes/ComfyUI_Noise/__pycache__/nodes.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_Noise/example_workflows/unsample_example.json b/custom_nodes/ComfyUI_Noise/example_workflows/unsample_example.json
new file mode 100644
index 0000000000000000000000000000000000000000..86ebae968a66c3450636d45465de50d9a628e6ce
--- /dev/null
+++ b/custom_nodes/ComfyUI_Noise/example_workflows/unsample_example.json
@@ -0,0 +1,698 @@
+{
+ "last_node_id": 27,
+ "last_link_id": 66,
+ "nodes": [
+ {
+ "id": 23,
+ "type": "Reroute",
+ "pos": [
+ 228,
+ 840
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 50
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "VAE",
+ "links": [
+ 51,
+ 52
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": false,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 24,
+ "type": "Reroute",
+ "pos": [
+ 400,
+ 740
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 53
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "MODEL",
+ "links": [
+ 54
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": false,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 8,
+ "type": "VAEDecode",
+ "pos": [
+ 970,
+ 640
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 44
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 52
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 9
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 9,
+ "type": "SaveImage",
+ "pos": [
+ 1280,
+ 681
+ ],
+ "size": {
+ "0": 367.50909423828125,
+ "1": 383.8414306640625
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 9
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "ComfyUI"
+ ]
+ },
+ {
+ "id": 7,
+ "type": "CLIPTextEncode",
+ "pos": [
+ -64,
+ 642
+ ],
+ "size": {
+ "0": 425.27801513671875,
+ "1": 180.6060791015625
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 5
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 56
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "text, watermark"
+ ]
+ },
+ {
+ "id": 6,
+ "type": "CLIPTextEncode",
+ "pos": [
+ -68,
+ 432
+ ],
+ "size": {
+ "0": 422.84503173828125,
+ "1": 164.31304931640625
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 3
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 59
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
+ ]
+ },
+ {
+ "id": 19,
+ "type": "LoadImage",
+ "pos": [
+ -124,
+ 906
+ ],
+ "size": {
+ "0": 434.40911865234375,
+ "1": 440.44140625
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 34
+ ],
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "example.png",
+ "image"
+ ]
+ },
+ {
+ "id": 12,
+ "type": "KSamplerAdvanced",
+ "pos": [
+ 950,
+ 740
+ ],
+ "size": {
+ "0": 315,
+ "1": 334
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 54
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 61
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 58
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 66
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 44
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSamplerAdvanced"
+ },
+ "widgets_values": [
+ "disable",
+ 0,
+ "fixed",
+ 25,
+ 1,
+ "dpmpp_2m",
+ "karras",
+ 0,
+ 25,
+ "disable"
+ ]
+ },
+ {
+ "id": 26,
+ "type": "Reroute",
+ "pos": [
+ 450,
+ 670
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 59
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "CONDITIONING",
+ "links": [
+ 61,
+ 62
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": false,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 25,
+ "type": "Reroute",
+ "pos": [
+ 430,
+ 700
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 56
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "CONDITIONING",
+ "links": [
+ 58,
+ 63
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": false,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 20,
+ "type": "VAEEncode",
+ "pos": [
+ 354,
+ 894
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "pixels",
+ "type": "IMAGE",
+ "link": 34
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 51
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 64
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEEncode"
+ }
+ },
+ {
+ "id": 4,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ -635,
+ 661
+ ],
+ "size": {
+ "0": 315,
+ "1": 98
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 53,
+ 65
+ ],
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 3,
+ 5
+ ],
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 50
+ ],
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "v1-5-pruned-emaonly.safetensors"
+ ]
+ },
+ {
+ "id": 27,
+ "type": "BNK_Unsampler",
+ "pos": [
+ 608,
+ 857
+ ],
+ "size": {
+ "0": 315,
+ "1": 214
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 65
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 62
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 63
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 64
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 66
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "BNK_Unsampler"
+ },
+ "widgets_values": [
+ 25,
+ 0,
+ 1,
+ "dpmpp_2m",
+ "karras"
+ ]
+ }
+ ],
+ "links": [
+ [
+ 3,
+ 4,
+ 1,
+ 6,
+ 0,
+ "CLIP"
+ ],
+ [
+ 5,
+ 4,
+ 1,
+ 7,
+ 0,
+ "CLIP"
+ ],
+ [
+ 9,
+ 8,
+ 0,
+ 9,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 34,
+ 19,
+ 0,
+ 20,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 44,
+ 12,
+ 0,
+ 8,
+ 0,
+ "LATENT"
+ ],
+ [
+ 50,
+ 4,
+ 2,
+ 23,
+ 0,
+ "*"
+ ],
+ [
+ 51,
+ 23,
+ 0,
+ 20,
+ 1,
+ "VAE"
+ ],
+ [
+ 52,
+ 23,
+ 0,
+ 8,
+ 1,
+ "VAE"
+ ],
+ [
+ 53,
+ 4,
+ 0,
+ 24,
+ 0,
+ "*"
+ ],
+ [
+ 54,
+ 24,
+ 0,
+ 12,
+ 0,
+ "MODEL"
+ ],
+ [
+ 56,
+ 7,
+ 0,
+ 25,
+ 0,
+ "*"
+ ],
+ [
+ 58,
+ 25,
+ 0,
+ 12,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 59,
+ 6,
+ 0,
+ 26,
+ 0,
+ "*"
+ ],
+ [
+ 61,
+ 26,
+ 0,
+ 12,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 62,
+ 26,
+ 0,
+ 27,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 63,
+ 25,
+ 0,
+ 27,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 64,
+ 20,
+ 0,
+ 27,
+ 3,
+ "LATENT"
+ ],
+ [
+ 65,
+ 4,
+ 0,
+ 27,
+ 0,
+ "MODEL"
+ ],
+ [
+ 66,
+ 27,
+ 0,
+ 12,
+ 3,
+ "LATENT"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Noise/example_workflows/variations_example.json b/custom_nodes/ComfyUI_Noise/example_workflows/variations_example.json
new file mode 100644
index 0000000000000000000000000000000000000000..a9a75e41d34ecaeeb3a2d7f19f1c117a9ff4103d
--- /dev/null
+++ b/custom_nodes/ComfyUI_Noise/example_workflows/variations_example.json
@@ -0,0 +1,868 @@
+{
+ "last_node_id": 39,
+ "last_link_id": 84,
+ "nodes": [
+ {
+ "id": 26,
+ "type": "Reroute",
+ "pos": [
+ 450,
+ 670
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 59
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "CONDITIONING",
+ "links": [
+ 61
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": false,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 25,
+ "type": "Reroute",
+ "pos": [
+ 430,
+ 700
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 56
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "CONDITIONING",
+ "links": [
+ 58
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": false,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 24,
+ "type": "Reroute",
+ "pos": [
+ 400,
+ 740
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 53
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "MODEL",
+ "links": [
+ 54
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": false,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 7,
+ "type": "CLIPTextEncode",
+ "pos": [
+ -64,
+ 642
+ ],
+ "size": {
+ "0": 425.27801513671875,
+ "1": 180.6060791015625
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 5
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 56
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "text, watermark"
+ ]
+ },
+ {
+ "id": 6,
+ "type": "CLIPTextEncode",
+ "pos": [
+ -68,
+ 432
+ ],
+ "size": {
+ "0": 422.84503173828125,
+ "1": 164.31304931640625
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 3
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 59
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
+ ]
+ },
+ {
+ "id": 12,
+ "type": "KSamplerAdvanced",
+ "pos": [
+ 835,
+ 887
+ ],
+ "size": {
+ "0": 315,
+ "1": 334
+ },
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 54
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 61
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 58
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 84
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 44
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSamplerAdvanced"
+ },
+ "widgets_values": [
+ "disable",
+ 0,
+ "fixed",
+ 25,
+ 8,
+ "dpmpp_2m",
+ "karras",
+ 0,
+ 25,
+ "disable"
+ ]
+ },
+ {
+ "id": 23,
+ "type": "Reroute",
+ "pos": [
+ -230,
+ 1632
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 50
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "VAE",
+ "links": [
+ 52
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": false,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 8,
+ "type": "VAEDecode",
+ "pos": [
+ 1183,
+ 1133
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 15,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 44
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 52
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 9
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 9,
+ "type": "SaveImage",
+ "pos": [
+ 771,
+ 1259
+ ],
+ "size": {
+ "0": 494.55535888671875,
+ "1": 524.3897705078125
+ },
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 9
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "ComfyUI"
+ ]
+ },
+ {
+ "id": 4,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ -635,
+ 661
+ ],
+ "size": {
+ "0": 315,
+ "1": 98
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 53,
+ 74
+ ],
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 3,
+ 5
+ ],
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 50
+ ],
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "v1-5-pruned-emaonly.safetensors"
+ ]
+ },
+ {
+ "id": 34,
+ "type": "BNK_NoisyLatentImage",
+ "pos": [
+ -216,
+ 980
+ ],
+ "size": {
+ "0": 315,
+ "1": 178
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 75
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "BNK_NoisyLatentImage"
+ },
+ "widgets_values": [
+ "CPU",
+ 0,
+ "fixed",
+ 512,
+ 512,
+ 1
+ ]
+ },
+ {
+ "id": 35,
+ "type": "BNK_NoisyLatentImage",
+ "pos": [
+ -217,
+ 1197
+ ],
+ "size": {
+ "0": 315,
+ "1": 178
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 77
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "BNK_NoisyLatentImage"
+ },
+ "widgets_values": [
+ "CPU",
+ 1,
+ "fixed",
+ 512,
+ 512,
+ 4
+ ]
+ },
+ {
+ "id": 37,
+ "type": "BNK_DuplicateBatchIndex",
+ "pos": [
+ 134,
+ 1012
+ ],
+ "size": {
+ "0": 315,
+ "1": 82
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "latents",
+ "type": "LATENT",
+ "link": 75
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 76
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "BNK_DuplicateBatchIndex"
+ },
+ "widgets_values": [
+ 0,
+ 4
+ ]
+ },
+ {
+ "id": 38,
+ "type": "BNK_SlerpLatent",
+ "pos": [
+ 137,
+ 1144
+ ],
+ "size": {
+ "0": 315,
+ "1": 98
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "latents1",
+ "type": "LATENT",
+ "link": 76
+ },
+ {
+ "name": "latents2",
+ "type": "LATENT",
+ "link": 77
+ },
+ {
+ "name": "mask",
+ "type": "MASK",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 81
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "BNK_SlerpLatent"
+ },
+ "widgets_values": [
+ 0.05
+ ]
+ },
+ {
+ "id": 39,
+ "type": "BNK_InjectNoise",
+ "pos": [
+ 476,
+ 1131
+ ],
+ "size": [
+ 315,
+ 98
+ ],
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "latents",
+ "type": "LATENT",
+ "link": 82
+ },
+ {
+ "name": "noise",
+ "type": "LATENT",
+ "link": 81
+ },
+ {
+ "name": "mask",
+ "type": "MASK",
+ "link": null
+ },
+ {
+ "name": "strength",
+ "type": "FLOAT",
+ "link": 80,
+ "widget": {
+ "name": "strength",
+ "config": [
+ "FLOAT",
+ {
+ "default": 1,
+ "min": 0,
+ "max": 20,
+ "step": 0.01
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 84
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "BNK_InjectNoise"
+ },
+ "widgets_values": [
+ 1
+ ]
+ },
+ {
+ "id": 33,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 474,
+ 985
+ ],
+ "size": {
+ "0": 315,
+ "1": 106
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 82
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ 4
+ ]
+ },
+ {
+ "id": 36,
+ "type": "BNK_GetSigma",
+ "pos": [
+ -221,
+ 1420
+ ],
+ "size": {
+ "0": 315,
+ "1": 154
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 74
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 80
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "BNK_GetSigma"
+ },
+ "widgets_values": [
+ "dpmpp_2m",
+ "karras",
+ 25,
+ 0,
+ 25
+ ]
+ }
+ ],
+ "links": [
+ [
+ 3,
+ 4,
+ 1,
+ 6,
+ 0,
+ "CLIP"
+ ],
+ [
+ 5,
+ 4,
+ 1,
+ 7,
+ 0,
+ "CLIP"
+ ],
+ [
+ 9,
+ 8,
+ 0,
+ 9,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 44,
+ 12,
+ 0,
+ 8,
+ 0,
+ "LATENT"
+ ],
+ [
+ 50,
+ 4,
+ 2,
+ 23,
+ 0,
+ "*"
+ ],
+ [
+ 52,
+ 23,
+ 0,
+ 8,
+ 1,
+ "VAE"
+ ],
+ [
+ 53,
+ 4,
+ 0,
+ 24,
+ 0,
+ "*"
+ ],
+ [
+ 54,
+ 24,
+ 0,
+ 12,
+ 0,
+ "MODEL"
+ ],
+ [
+ 56,
+ 7,
+ 0,
+ 25,
+ 0,
+ "*"
+ ],
+ [
+ 58,
+ 25,
+ 0,
+ 12,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 59,
+ 6,
+ 0,
+ 26,
+ 0,
+ "*"
+ ],
+ [
+ 61,
+ 26,
+ 0,
+ 12,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 74,
+ 4,
+ 0,
+ 36,
+ 0,
+ "MODEL"
+ ],
+ [
+ 75,
+ 34,
+ 0,
+ 37,
+ 0,
+ "LATENT"
+ ],
+ [
+ 76,
+ 37,
+ 0,
+ 38,
+ 0,
+ "LATENT"
+ ],
+ [
+ 77,
+ 35,
+ 0,
+ 38,
+ 1,
+ "LATENT"
+ ],
+ [
+ 80,
+ 36,
+ 0,
+ 39,
+ 3,
+ "FLOAT"
+ ],
+ [
+ 81,
+ 38,
+ 0,
+ 39,
+ 1,
+ "LATENT"
+ ],
+ [
+ 82,
+ 33,
+ 0,
+ 39,
+ 0,
+ "LATENT"
+ ],
+ [
+ 84,
+ 39,
+ 0,
+ 12,
+ 3,
+ "LATENT"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_Noise/examples/example_unsample.png b/custom_nodes/ComfyUI_Noise/examples/example_unsample.png
new file mode 100644
index 0000000000000000000000000000000000000000..6296c1d5490484cb7d183ca4974689d23b2bd695
Binary files /dev/null and b/custom_nodes/ComfyUI_Noise/examples/example_unsample.png differ
diff --git a/custom_nodes/ComfyUI_Noise/examples/example_variation.png b/custom_nodes/ComfyUI_Noise/examples/example_variation.png
new file mode 100644
index 0000000000000000000000000000000000000000..44d9a3f5424d9d8db31c090ba031385058cff69b
Binary files /dev/null and b/custom_nodes/ComfyUI_Noise/examples/example_variation.png differ
diff --git a/custom_nodes/ComfyUI_Noise/nodes.py b/custom_nodes/ComfyUI_Noise/nodes.py
new file mode 100644
index 0000000000000000000000000000000000000000..7a2314df4daeebba82dd00d58c5a694cf4548c58
--- /dev/null
+++ b/custom_nodes/ComfyUI_Noise/nodes.py
@@ -0,0 +1,265 @@
+import torch
+
+import os
+import sys
+
+sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), "comfy"))
+
+import comfy.model_management
+import comfy.sample
+
+MAX_RESOLUTION=8192
+
+def prepare_mask(mask, shape):
+ mask = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), size=(shape[2], shape[3]), mode="bilinear")
+ mask = mask.expand((-1,shape[1],-1,-1))
+ if mask.shape[0] < shape[0]:
+ mask = mask.repeat((shape[0] -1) // mask.shape[0] + 1, 1, 1, 1)[:shape[0]]
+ return mask
+
+class NoisyLatentImage:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {
+ "source":(["CPU", "GPU"], ),
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
+ "width": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 8}),
+ "height": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 8}),
+ "batch_size": ("INT", {"default": 1, "min": 1, "max": 64}),
+ }}
+ RETURN_TYPES = ("LATENT",)
+ FUNCTION = "create_noisy_latents"
+
+ CATEGORY = "latent/noise"
+
+ def create_noisy_latents(self, source, seed, width, height, batch_size):
+ torch.manual_seed(seed)
+ if source == "CPU":
+ device = "cpu"
+ else:
+ device = comfy.model_management.get_torch_device()
+ noise = torch.randn((batch_size, 4, height // 8, width // 8), dtype=torch.float32, device=device).cpu()
+ return ({"samples":noise}, )
+
+class DuplicateBatchIndex:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {
+ "latents":("LATENT",),
+ "batch_index": ("INT", {"default": 0, "min": 0, "max": 63}),
+ "batch_size": ("INT", {"default": 1, "min": 1, "max": 64}),
+ }}
+
+ RETURN_TYPES = ("LATENT",)
+ FUNCTION = "duplicate_index"
+
+ CATEGORY = "latent"
+
+ def duplicate_index(self, latents, batch_index, batch_size):
+ s = latents.copy()
+ batch_index = min(s["samples"].shape[0] - 1, batch_index)
+ target = s["samples"][batch_index:batch_index + 1].clone()
+ target = target.repeat((batch_size,1,1,1))
+ s["samples"] = target
+ return (s,)
+
+# from https://discuss.pytorch.org/t/help-regarding-slerp-function-for-generative-model-sampling/32475
+def slerp(val, low, high):
+ dims = low.shape
+
+ #flatten to batches
+ low = low.reshape(dims[0], -1)
+ high = high.reshape(dims[0], -1)
+
+ low_norm = low/torch.norm(low, dim=1, keepdim=True)
+ high_norm = high/torch.norm(high, dim=1, keepdim=True)
+
+ # in case we divide by zero
+ low_norm[low_norm != low_norm] = 0.0
+ high_norm[high_norm != high_norm] = 0.0
+
+ omega = torch.acos((low_norm*high_norm).sum(1))
+ so = torch.sin(omega)
+ res = (torch.sin((1.0-val)*omega)/so).unsqueeze(1)*low + (torch.sin(val*omega)/so).unsqueeze(1) * high
+ return res.reshape(dims)
+
+class LatentSlerp:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "latents1":("LATENT",),
+ "factor": ("FLOAT", {"default": .5, "min": 0.0, "max": 1.0, "step": 0.01}),
+ },
+ "optional" :{
+ "latents2":("LATENT",),
+ "mask": ("MASK", ),
+ }}
+
+ RETURN_TYPES = ("LATENT",)
+ FUNCTION = "slerp_latents"
+
+ CATEGORY = "latent"
+
+ def slerp_latents(self, latents1, factor, latents2=None, mask=None):
+ s = latents1.copy()
+ if latents2 is None:
+ return (s,)
+ if latents1["samples"].shape != latents2["samples"].shape:
+ print("warning, shapes in LatentSlerp not the same, ignoring")
+ return (s,)
+ slerped = slerp(factor, latents1["samples"].clone(), latents2["samples"].clone())
+ if mask is not None:
+ mask = prepare_mask(mask, slerped.shape)
+ slerped = mask * slerped + (1-mask) * latents1["samples"]
+ s["samples"] = slerped
+ return (s,)
+
+class GetSigma:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {
+ "model": ("MODEL",),
+ "sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
+ "scheduler": (comfy.samplers.KSampler.SCHEDULERS, ),
+ "steps": ("INT", {"default": 10000, "min": 0, "max": 10000}),
+ "start_at_step": ("INT", {"default": 0, "min": 0, "max": 10000}),
+ "end_at_step": ("INT", {"default": 10000, "min": 1, "max": 10000}),
+ }}
+
+ RETURN_TYPES = ("FLOAT",)
+ FUNCTION = "calc_sigma"
+
+ CATEGORY = "latent/noise"
+
+ def calc_sigma(self, model, sampler_name, scheduler, steps, start_at_step, end_at_step):
+ device = comfy.model_management.get_torch_device()
+ end_at_step = min(steps, end_at_step)
+ start_at_step = min(start_at_step, end_at_step)
+ real_model = None
+ comfy.model_management.load_model_gpu(model)
+ real_model = model.model
+ sampler = comfy.samplers.KSampler(real_model, steps=steps, device=device, sampler=sampler_name, scheduler=scheduler, denoise=1.0, model_options=model.model_options)
+ sigmas = sampler.sigmas
+ sigma = sigmas[start_at_step] - sigmas[end_at_step]
+ sigma /= model.model.latent_format.scale_factor
+ return (sigma.cpu().numpy(),)
+
+class InjectNoise:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {
+ "latents":("LATENT",),
+
+ "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 200.0, "step": 0.01}),
+ },
+ "optional":{
+ "noise": ("LATENT",),
+ "mask": ("MASK", ),
+ }}
+
+ RETURN_TYPES = ("LATENT",)
+ FUNCTION = "inject_noise"
+
+ CATEGORY = "latent/noise"
+
+ def inject_noise(self, latents, strength, noise=None, mask=None):
+ s = latents.copy()
+ if noise is None:
+ return (s,)
+ if latents["samples"].shape != noise["samples"].shape:
+ print("warning, shapes in InjectNoise not the same, ignoring")
+ return (s,)
+ noised = s["samples"].clone() + noise["samples"].clone() * strength
+ if mask is not None:
+ mask = prepare_mask(mask, noised.shape)
+ noised = mask * noised + (1-mask) * latents["samples"]
+ s["samples"] = noised
+ return (s,)
+
+class Unsampler:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required":
+ {"model": ("MODEL",),
+ "steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
+ "end_at_step": ("INT", {"default": 0, "min": 0, "max": 10000}),
+ "cfg": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0}),
+ "sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
+ "scheduler": (comfy.samplers.KSampler.SCHEDULERS, ),
+ "normalize": (["disable", "enable"], ),
+ "positive": ("CONDITIONING", ),
+ "negative": ("CONDITIONING", ),
+ "latent_image": ("LATENT", ),
+ }}
+
+ RETURN_TYPES = ("LATENT",)
+ FUNCTION = "unsampler"
+
+ CATEGORY = "sampling"
+
+ def unsampler(self, model, cfg, sampler_name, steps, end_at_step, scheduler, normalize, positive, negative, latent_image):
+ normalize = normalize == "enable"
+ device = comfy.model_management.get_torch_device()
+ latent = latent_image
+ latent_image = latent["samples"]
+
+ end_at_step = min(end_at_step, steps-1)
+ end_at_step = steps - end_at_step
+
+ noise = torch.zeros(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout, device="cpu")
+ noise_mask = None
+ if "noise_mask" in latent:
+ noise_mask = comfy.sample.prepare_mask(latent["noise_mask"], noise, device)
+
+ real_model = None
+ real_model = model.model
+
+ noise = noise.to(device)
+ latent_image = latent_image.to(device)
+
+ positive = comfy.sample.convert_cond(positive)
+ negative = comfy.sample.convert_cond(negative)
+
+ models, inference_memory = comfy.sample.get_additional_models(positive, negative, model.model_dtype())
+
+ comfy.model_management.load_models_gpu([model] + models, model.memory_required(noise.shape) + inference_memory)
+
+ sampler = comfy.samplers.KSampler(real_model, steps=steps, device=device, sampler=sampler_name, scheduler=scheduler, denoise=1.0, model_options=model.model_options)
+
+ sigmas = sigmas = sampler.sigmas.flip(0) + 0.0001
+
+ pbar = comfy.utils.ProgressBar(steps)
+ def callback(step, x0, x, total_steps):
+ pbar.update_absolute(step + 1, total_steps)
+
+ samples = sampler.sample(noise, positive, negative, cfg=cfg, latent_image=latent_image, force_full_denoise=False, denoise_mask=noise_mask, sigmas=sigmas, start_step=0, last_step=end_at_step, callback=callback)
+ if normalize:
+ #technically doesn't normalize because unsampling is not guaranteed to end at a std given by the schedule
+ samples -= samples.mean()
+ samples /= samples.std()
+ samples = samples.cpu()
+
+ comfy.sample.cleanup_additional_models(models)
+
+ out = latent.copy()
+ out["samples"] = samples
+ return (out, )
+
+NODE_CLASS_MAPPINGS = {
+ "BNK_NoisyLatentImage": NoisyLatentImage,
+ #"BNK_DuplicateBatchIndex": DuplicateBatchIndex,
+ "BNK_SlerpLatent": LatentSlerp,
+ "BNK_GetSigma": GetSigma,
+ "BNK_InjectNoise": InjectNoise,
+ "BNK_Unsampler": Unsampler,
+}
+
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "BNK_NoisyLatentImage": "Noisy Latent Image",
+ #"BNK_DuplicateBatchIndex": "Duplicate Batch Index",
+ "BNK_SlerpLatent": "Slerp Latents",
+ "BNK_GetSigma": "Get Sigma",
+ "BNK_InjectNoise": "Inject Noise",
+ "BNK_Unsampler": "Unsampler",
+}
diff --git a/custom_nodes/ComfyUI_TiledKSampler/LICENSE b/custom_nodes/ComfyUI_TiledKSampler/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..f288702d2fa16d3cdf0035b15a9fcbc552cd88e7
--- /dev/null
+++ b/custom_nodes/ComfyUI_TiledKSampler/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/custom_nodes/ComfyUI_TiledKSampler/README.md b/custom_nodes/ComfyUI_TiledKSampler/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..a1553387b497d39a1f7ee8f0b8cf45bd2f375352
--- /dev/null
+++ b/custom_nodes/ComfyUI_TiledKSampler/README.md
@@ -0,0 +1,66 @@
+# Tiled sampling for ComfyUI
+
+
+
+this repo contains a tiled sampler for [ComfyUI](https://github.com/comfyanonymous/ComfyUI). It allows for denoising larger images by splitting it up into smaller tiles and denoising these. It tries to minimize any seams for showing up in the end result by gradually denoising all tiles one step at the time and randomizing tile positions for every step.
+
+### settings
+
+The tiled samplers comes with some additional settings to further control it's behavior:
+
+- **tile_width**: the width of the tiles.
+- **tile_height**: the height of the tiles.
+- **tiling_strategy**: how to do the tiling
+
+## Tiling strategies
+
+### random:
+The random tiling strategy aims to reduce the presence of seams as much as possible by slowly denoising the entire image step by step, randomizing the tile positions for each step. It does this by alternating between horizontal and vertical brick patterns, randomly offsetting the pattern each time. As the number of steps grows to infinity the strength of seams shrinks to zero. Although this random offset eliminates seams, it comes at the cost of additional overhead per step and makes this strategy incompatible with uni samplers.
+
+
+
+visual explanation
+
+
+
+
+
+
+
+example seamless image
+
+
+This tiling strategy is exceptionally good in hiding seams, even when starting off from complete noise, repetitions are visible but seams are not.
+
+
+
+
+### random strict:
+
+One downside of random is that it can unfavorably crop border tiles, random strict uses masking to ensure no border tiles have to be cropped. This tiling strategy does not play nice with the SDE sampler.
+
+### padded:
+
+The padded tiling strategy tries to reduce seams by giving each tile more context of its surroundings through padding. It does this by further dividing each tile into 9 smaller tiles, which are denoised in such a way that a tile is always surrounded by static contex during denoising. This strategy is more prone to seams but because the location of the tiles is static, this strategy is compatible with uni samplers and has no overhead between steps. However the padding makes it so that up to 4 times as many tiles have to be denoised.
+
+
+
+visual explanation
+
+
+
+
+
+### simple
+
+The simple tiling strategy divides the image into a static grid of tiles and denoises these one by one.
+
+### roadmap:
+
+ - [x] latent masks
+ - [x] image wide control nets
+ - [x] T2I adaptors
+ - [ ] tile wide control nets and T2I adaptors (e.g. style models)
+ - [x] area conditioning
+ - [x] area mask conditioning
+ - [x] GLIGEN
diff --git a/custom_nodes/ComfyUI_TiledKSampler/__init__.py b/custom_nodes/ComfyUI_TiledKSampler/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d721463be66961a2f388b3a756760d167ea5d510
--- /dev/null
+++ b/custom_nodes/ComfyUI_TiledKSampler/__init__.py
@@ -0,0 +1,3 @@
+from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
+
+__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_TiledKSampler/__pycache__/__init__.cpython-310.pyc b/custom_nodes/ComfyUI_TiledKSampler/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..dd66c24fc47dc02fdd1d21df917a983a86ba5091
Binary files /dev/null and b/custom_nodes/ComfyUI_TiledKSampler/__pycache__/__init__.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI_TiledKSampler/__pycache__/__init__.cpython-311.pyc b/custom_nodes/ComfyUI_TiledKSampler/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..97de7c835d1c20cd190dbebc3d6b9c96a297b206
Binary files /dev/null and b/custom_nodes/ComfyUI_TiledKSampler/__pycache__/__init__.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_TiledKSampler/__pycache__/nodes.cpython-310.pyc b/custom_nodes/ComfyUI_TiledKSampler/__pycache__/nodes.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a2149ce089478522f194d7de30fb183a656a036a
Binary files /dev/null and b/custom_nodes/ComfyUI_TiledKSampler/__pycache__/nodes.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI_TiledKSampler/__pycache__/nodes.cpython-311.pyc b/custom_nodes/ComfyUI_TiledKSampler/__pycache__/nodes.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5292bbfa63a3452aea581fb35dd1434fdb826772
Binary files /dev/null and b/custom_nodes/ComfyUI_TiledKSampler/__pycache__/nodes.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_TiledKSampler/__pycache__/tiling.cpython-310.pyc b/custom_nodes/ComfyUI_TiledKSampler/__pycache__/tiling.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..6091a1b7be9051bcb3d91a182f5a869d089e369c
Binary files /dev/null and b/custom_nodes/ComfyUI_TiledKSampler/__pycache__/tiling.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI_TiledKSampler/__pycache__/tiling.cpython-311.pyc b/custom_nodes/ComfyUI_TiledKSampler/__pycache__/tiling.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ebe7bd48f71c748d5659eeb30b8a15fc64adf451
Binary files /dev/null and b/custom_nodes/ComfyUI_TiledKSampler/__pycache__/tiling.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_TiledKSampler/examples/ComfyUI_02006_.png b/custom_nodes/ComfyUI_TiledKSampler/examples/ComfyUI_02006_.png
new file mode 100644
index 0000000000000000000000000000000000000000..19c28f15e9cbd65d7d1abfee69362b9bad3a5375
--- /dev/null
+++ b/custom_nodes/ComfyUI_TiledKSampler/examples/ComfyUI_02006_.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:343e7b747e6af0b766ce2b40fdb1d8191fe70be150ab11ac1a0ab0bcb65f10a7
+size 7490127
diff --git a/custom_nodes/ComfyUI_TiledKSampler/examples/ComfyUI_02010_.png b/custom_nodes/ComfyUI_TiledKSampler/examples/ComfyUI_02010_.png
new file mode 100644
index 0000000000000000000000000000000000000000..bb3843f89c5777d4b5c6be4d249c8fc8a37c4e47
--- /dev/null
+++ b/custom_nodes/ComfyUI_TiledKSampler/examples/ComfyUI_02010_.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:0a845e0a9fd437ec675b1249a8858d14c63e768e174f705d3c64af04553b28ba
+size 4286483
diff --git a/custom_nodes/ComfyUI_TiledKSampler/examples/tiled_padding.gif b/custom_nodes/ComfyUI_TiledKSampler/examples/tiled_padding.gif
new file mode 100644
index 0000000000000000000000000000000000000000..5aa15a4be21f29f17cf901227f6f1aec44804208
Binary files /dev/null and b/custom_nodes/ComfyUI_TiledKSampler/examples/tiled_padding.gif differ
diff --git a/custom_nodes/ComfyUI_TiledKSampler/examples/tiled_random.gif b/custom_nodes/ComfyUI_TiledKSampler/examples/tiled_random.gif
new file mode 100644
index 0000000000000000000000000000000000000000..c370287efd396991fdf036694ec3d520a6444e83
Binary files /dev/null and b/custom_nodes/ComfyUI_TiledKSampler/examples/tiled_random.gif differ
diff --git a/custom_nodes/ComfyUI_TiledKSampler/nodes.py b/custom_nodes/ComfyUI_TiledKSampler/nodes.py
new file mode 100644
index 0000000000000000000000000000000000000000..0c13a81101b1b69e4a0789a58d7a4252919dc1e9
--- /dev/null
+++ b/custom_nodes/ComfyUI_TiledKSampler/nodes.py
@@ -0,0 +1,359 @@
+import sys
+import os
+import itertools
+import numpy as np
+
+from tqdm.auto import tqdm
+
+import torch
+
+sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), "comfy"))
+import comfy.sd
+import comfy.controlnet
+import comfy.model_management
+import comfy.sample
+from . import tiling
+import latent_preview
+
+MAX_RESOLUTION=8192
+
+def recursion_to_list(obj, attr):
+ current = obj
+ yield current
+ while True:
+ current = getattr(current, attr, None)
+ if current is not None:
+ yield current
+ else:
+ return
+
+def copy_cond(cond):
+ return [[c1,c2.copy()] for c1,c2 in cond]
+
+def slice_cond(tile_h, tile_h_len, tile_w, tile_w_len, cond, area):
+ tile_h_end = tile_h + tile_h_len
+ tile_w_end = tile_w + tile_w_len
+ coords = area[0] #h_len, w_len, h, w,
+ mask = area[1]
+ if coords is not None:
+ h_len, w_len, h, w = coords
+ h_end = h + h_len
+ w_end = w + w_len
+ if h < tile_h_end and h_end > tile_h and w < tile_w_end and w_end > tile_w:
+ new_h = max(0, h - tile_h)
+ new_w = max(0, w - tile_w)
+ new_h_end = min(tile_h_end, h_end - tile_h)
+ new_w_end = min(tile_w_end, w_end - tile_w)
+ cond[1]['area'] = (new_h_end - new_h, new_w_end - new_w, new_h, new_w)
+ else:
+ return (cond, True)
+ if mask is not None:
+ new_mask = tiling.get_slice(mask, tile_h,tile_h_len,tile_w,tile_w_len)
+ if new_mask.sum().cpu() == 0.0 and 'mask' in cond[1]:
+ return (cond, True)
+ else:
+ cond[1]['mask'] = new_mask
+ return (cond, False)
+
+def slice_gligen(tile_h, tile_h_len, tile_w, tile_w_len, cond, gligen):
+ tile_h_end = tile_h + tile_h_len
+ tile_w_end = tile_w + tile_w_len
+ if gligen is None:
+ return
+ gligen_type = gligen[0]
+ gligen_model = gligen[1]
+ gligen_areas = gligen[2]
+
+ gligen_areas_new = []
+ for emb, h_len, w_len, h, w in gligen_areas:
+ h_end = h + h_len
+ w_end = w + w_len
+ if h < tile_h_end and h_end > tile_h and w < tile_w_end and w_end > tile_w:
+ new_h = max(0, h - tile_h)
+ new_w = max(0, w - tile_w)
+ new_h_end = min(tile_h_end, h_end - tile_h)
+ new_w_end = min(tile_w_end, w_end - tile_w)
+ gligen_areas_new.append((emb, new_h_end - new_h, new_w_end - new_w, new_h, new_w))
+
+ if len(gligen_areas_new) == 0:
+ del cond['gligen']
+ else:
+ cond['gligen'] = (gligen_type, gligen_model, gligen_areas_new)
+
+def slice_cnet(h, h_len, w, w_len, model:comfy.controlnet.ControlBase, img):
+ if img is None:
+ img = model.cond_hint_original
+ model.cond_hint = tiling.get_slice(img, h*8, h_len*8, w*8, w_len*8).to(model.control_model.dtype).to(model.device)
+
+def slices_T2I(h, h_len, w, w_len, model:comfy.controlnet.ControlBase, img):
+ model.control_input = None
+ if img is None:
+ img = model.cond_hint_original
+ model.cond_hint = tiling.get_slice(img, h*8, h_len*8, w*8, w_len*8).float().to(model.device)
+
+# TODO: refactor some of the mess
+
+from PIL import Image
+
+def sample_common(model, add_noise, noise_seed, tile_width, tile_height, tiling_strategy, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, start_at_step, end_at_step, return_with_leftover_noise, denoise=1.0, preview=False):
+ end_at_step = min(end_at_step, steps)
+ device = comfy.model_management.get_torch_device()
+ samples = latent_image["samples"]
+ noise_mask = latent_image["noise_mask"] if "noise_mask" in latent_image else None
+ force_full_denoise = return_with_leftover_noise == "enable"
+ if add_noise == "disable":
+ noise = torch.zeros(samples.size(), dtype=samples.dtype, layout=samples.layout, device="cpu")
+ else:
+ skip = latent_image["batch_index"] if "batch_index" in latent_image else None
+ noise = comfy.sample.prepare_noise(samples, noise_seed, skip)
+
+ if noise_mask is not None:
+ noise_mask = comfy.sample.prepare_mask(noise_mask, noise.shape, device='cpu')
+
+ shape = samples.shape
+ samples = samples.clone()
+
+ tile_width = min(shape[-1] * 8, tile_width)
+ tile_height = min(shape[2] * 8, tile_height)
+
+ real_model = None
+ positive_copy = comfy.sample.convert_cond(positive)
+ negative_copy = comfy.sample.convert_cond(negative)
+ modelPatches, inference_memory = comfy.sample.get_additional_models(positive_copy, negative_copy, model.model_dtype())
+
+ comfy.model_management.load_models_gpu([model] + modelPatches, model.memory_required(noise.shape) + inference_memory)
+ real_model = model.model
+
+ sampler = comfy.samplers.KSampler(real_model, steps=steps, device=device, sampler=sampler_name, scheduler=scheduler, denoise=denoise, model_options=model.model_options)
+
+ if tiling_strategy != 'padded':
+ if noise_mask is not None:
+ samples += sampler.sigmas[start_at_step].cpu() * noise_mask * model.model.process_latent_out(noise)
+ else:
+ samples += sampler.sigmas[start_at_step].cpu() * model.model.process_latent_out(noise)
+
+ # cnets
+ cnets = [c['control'] for (_, c) in positive + negative if 'control' in c]
+ # unroll recursion
+ cnets = list(set([x for m in cnets for x in recursion_to_list(m, "previous_controlnet")]))
+ # filter down to only cnets
+ cnets = [x for x in cnets if isinstance(x, comfy.controlnet.ControlNet)]
+ cnet_imgs = [
+ torch.nn.functional.interpolate(m.cond_hint_original, (shape[-2] * 8, shape[-1] * 8), mode='nearest-exact').to('cpu')
+ if m.cond_hint_original.shape[-2] != shape[-2] * 8 or m.cond_hint_original.shape[-1] != shape[-1] * 8 else None
+ for m in cnets]
+
+ # T2I
+ T2Is = [c['control'] for (_, c) in positive + negative if 'control' in c]
+ # unroll recursion
+ T2Is = [x for m in T2Is for x in recursion_to_list(m, "previous_controlnet")]
+ # filter down to only T2I
+ T2Is = [x for x in T2Is if isinstance(x, comfy.controlnet.T2IAdapter)]
+ T2I_imgs = [
+ torch.nn.functional.interpolate(m.cond_hint_original, (shape[-2] * 8, shape[-1] * 8), mode='nearest-exact').to('cpu')
+ if m.cond_hint_original.shape[-2] != shape[-2] * 8 or m.cond_hint_original.shape[-1] != shape[-1] * 8 or (m.channels_in == 1 and m.cond_hint_original.shape[1] != 1) else None
+ for m in T2Is
+ ]
+ T2I_imgs = [
+ torch.mean(img, 1, keepdim=True) if img is not None and m.channels_in == 1 and m.cond_hint_original.shape[1] else img
+ for m, img in zip(T2Is, T2I_imgs)
+ ]
+
+ #cond area and mask
+ spatial_conds_pos = [
+ (c[1]['area'] if 'area' in c[1] else None,
+ comfy.sample.prepare_mask(c[1]['mask'], shape, device) if 'mask' in c[1] else None)
+ for c in positive
+ ]
+ spatial_conds_neg = [
+ (c[1]['area'] if 'area' in c[1] else None,
+ comfy.sample.prepare_mask(c[1]['mask'], shape, device) if 'mask' in c[1] else None)
+ for c in negative
+ ]
+
+ #gligen
+ gligen_pos = [
+ c[1]['gligen'] if 'gligen' in c[1] else None
+ for c in positive
+ ]
+ gligen_neg = [
+ c[1]['gligen'] if 'gligen' in c[1] else None
+ for c in negative
+ ]
+
+ gen = torch.manual_seed(noise_seed)
+ if tiling_strategy == 'random' or tiling_strategy == 'random strict':
+ tiles = tiling.get_tiles_and_masks_rgrid(end_at_step - start_at_step, samples.shape, tile_height, tile_width, gen)
+ elif tiling_strategy == 'padded':
+ tiles = tiling.get_tiles_and_masks_padded(end_at_step - start_at_step, samples.shape, tile_height, tile_width)
+ else:
+ tiles = tiling.get_tiles_and_masks_simple(end_at_step - start_at_step, samples.shape, tile_height, tile_width)
+
+ total_steps = sum([num_steps for img_pass in tiles for steps_list in img_pass for _,_,_,_,num_steps,_ in steps_list])
+ current_step = [0]
+
+ preview_format = "JPEG"
+ if preview_format not in ["JPEG", "PNG"]:
+ preview_format = "JPEG"
+ previewer = None
+ if preview:
+ previewer = latent_preview.get_previewer(device, model.model.latent_format)
+
+
+ with tqdm(total=total_steps) as pbar_tqdm:
+ pbar = comfy.utils.ProgressBar(total_steps)
+
+ def callback(step, x0, x, total_steps):
+ current_step[0] += 1
+ preview_bytes = None
+ if previewer:
+ preview_bytes = previewer.decode_latent_to_preview_image(preview_format, x0)
+ pbar.update_absolute(current_step[0], preview=preview_bytes)
+ pbar_tqdm.update(1)
+
+ if tiling_strategy == "random strict":
+ samples_next = samples.clone()
+ for img_pass in tiles:
+ for i in range(len(img_pass)):
+ for tile_h, tile_h_len, tile_w, tile_w_len, tile_steps, tile_mask in img_pass[i]:
+ tiled_mask = None
+ if noise_mask is not None:
+ tiled_mask = tiling.get_slice(noise_mask, tile_h, tile_h_len, tile_w, tile_w_len).to(device)
+ if tile_mask is not None:
+ if tiled_mask is not None:
+ tiled_mask *= tile_mask.to(device)
+ else:
+ tiled_mask = tile_mask.to(device)
+
+ if tiling_strategy == 'padded' or tiling_strategy == 'random strict':
+ tile_h, tile_h_len, tile_w, tile_w_len, tiled_mask = tiling.mask_at_boundary( tile_h, tile_h_len, tile_w, tile_w_len,
+ tile_height, tile_width, samples.shape[-2], samples.shape[-1],
+ tiled_mask, device)
+
+
+ if tiled_mask is not None and tiled_mask.sum().cpu() == 0.0:
+ continue
+
+ tiled_latent = tiling.get_slice(samples, tile_h, tile_h_len, tile_w, tile_w_len).to(device)
+
+ if tiling_strategy == 'padded':
+ tiled_noise = tiling.get_slice(noise, tile_h, tile_h_len, tile_w, tile_w_len).to(device)
+ else:
+ if tiled_mask is None or noise_mask is None:
+ tiled_noise = torch.zeros_like(tiled_latent)
+ else:
+ tiled_noise = tiling.get_slice(noise, tile_h, tile_h_len, tile_w, tile_w_len).to(device) * (1 - tiled_mask)
+
+ #TODO: all other condition based stuff like area sets and GLIGEN should also happen here
+
+ #cnets
+ for m, img in zip(cnets, cnet_imgs):
+ slice_cnet(tile_h, tile_h_len, tile_w, tile_w_len, m, img)
+
+ #T2I
+ for m, img in zip(T2Is, T2I_imgs):
+ slices_T2I(tile_h, tile_h_len, tile_w, tile_w_len, m, img)
+
+ pos = [c.copy() for c in positive_copy]#copy_cond(positive_copy)
+ neg = [c.copy() for c in negative_copy]#copy_cond(negative_copy)
+
+ #cond areas
+ pos = [slice_cond(tile_h, tile_h_len, tile_w, tile_w_len, c, area) for c, area in zip(pos, spatial_conds_pos)]
+ pos = [c for c, ignore in pos if not ignore]
+ neg = [slice_cond(tile_h, tile_h_len, tile_w, tile_w_len, c, area) for c, area in zip(neg, spatial_conds_neg)]
+ neg = [c for c, ignore in neg if not ignore]
+
+ #gligen
+ for cond, gligen in zip(pos, gligen_pos):
+ slice_gligen(tile_h, tile_h_len, tile_w, tile_w_len, cond, gligen)
+ for cond, gligen in zip(neg, gligen_neg):
+ slice_gligen(tile_h, tile_h_len, tile_w, tile_w_len, cond, gligen)
+
+ tile_result = sampler.sample(tiled_noise, pos, neg, cfg=cfg, latent_image=tiled_latent, start_step=start_at_step + i * tile_steps, last_step=start_at_step + i*tile_steps + tile_steps, force_full_denoise=force_full_denoise and i+1 == end_at_step - start_at_step, denoise_mask=tiled_mask, callback=callback, disable_pbar=True, seed=noise_seed)
+ tile_result = tile_result.cpu()
+ if tiled_mask is not None:
+ tiled_mask = tiled_mask.cpu()
+ if tiling_strategy == "random strict":
+ tiling.set_slice(samples_next, tile_result, tile_h, tile_h_len, tile_w, tile_w_len, tiled_mask)
+ else:
+ tiling.set_slice(samples, tile_result, tile_h, tile_h_len, tile_w, tile_w_len, tiled_mask)
+ if tiling_strategy == "random strict":
+ samples = samples_next.clone()
+
+
+ comfy.sample.cleanup_additional_models(modelPatches)
+
+ out = latent_image.copy()
+ out["samples"] = samples.cpu()
+ return (out, )
+
+class TiledKSampler:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required":
+ {"model": ("MODEL",),
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
+ "tile_width": ("INT", {"default": 512, "min": 256, "max": MAX_RESOLUTION, "step": 64}),
+ "tile_height": ("INT", {"default": 512, "min": 256, "max": MAX_RESOLUTION, "step": 64}),
+ "tiling_strategy": (["random", "random strict", "padded", 'simple'], ),
+ "steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
+ "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
+ "sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
+ "scheduler": (comfy.samplers.KSampler.SCHEDULERS, ),
+ "positive": ("CONDITIONING", ),
+ "negative": ("CONDITIONING", ),
+ "latent_image": ("LATENT", ),
+ "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
+ }}
+
+ RETURN_TYPES = ("LATENT",)
+ FUNCTION = "sample"
+
+ CATEGORY = "sampling"
+
+ def sample(self, model, seed, tile_width, tile_height, tiling_strategy, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise):
+ steps_total = int(steps / denoise)
+ return sample_common(model, 'enable', seed, tile_width, tile_height, tiling_strategy, steps_total, cfg, sampler_name, scheduler, positive, negative, latent_image, steps_total-steps, steps_total, 'disable', denoise=1.0, preview=True)
+
+class TiledKSamplerAdvanced:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required":
+ {"model": ("MODEL",),
+ "add_noise": (["enable", "disable"], ),
+ "noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
+ "tile_width": ("INT", {"default": 512, "min": 256, "max": MAX_RESOLUTION, "step": 64}),
+ "tile_height": ("INT", {"default": 512, "min": 256, "max": MAX_RESOLUTION, "step": 64}),
+ "tiling_strategy": (["random", "random strict", "padded", 'simple'], ),
+ "steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
+ "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
+ "sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
+ "scheduler": (comfy.samplers.KSampler.SCHEDULERS, ),
+ "positive": ("CONDITIONING", ),
+ "negative": ("CONDITIONING", ),
+ "latent_image": ("LATENT", ),
+ "start_at_step": ("INT", {"default": 0, "min": 0, "max": 10000}),
+ "end_at_step": ("INT", {"default": 10000, "min": 0, "max": 10000}),
+ "return_with_leftover_noise": (["disable", "enable"], ),
+ "preview": (["disable", "enable"], ),
+ }}
+
+ RETURN_TYPES = ("LATENT",)
+ FUNCTION = "sample"
+
+ CATEGORY = "sampling"
+
+ def sample(self, model, add_noise, noise_seed, tile_width, tile_height, tiling_strategy, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, start_at_step, end_at_step, return_with_leftover_noise, preview, denoise=1.0):
+ return sample_common(model, add_noise, noise_seed, tile_width, tile_height, tiling_strategy, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, start_at_step, end_at_step, return_with_leftover_noise, denoise=1.0, preview= preview == 'enable')
+
+
+
+NODE_CLASS_MAPPINGS = {
+ "BNK_TiledKSamplerAdvanced": TiledKSamplerAdvanced,
+ "BNK_TiledKSampler": TiledKSampler,
+}
+
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "BNK_TiledKSamplerAdvanced": "TiledK Sampler (Advanced)",
+ "BNK_TiledKSampler": "Tiled KSampler",
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_TiledKSampler/tiling.py b/custom_nodes/ComfyUI_TiledKSampler/tiling.py
new file mode 100644
index 0000000000000000000000000000000000000000..096f8baa1a1a639e3ed251e415a95aa14bcf8753
--- /dev/null
+++ b/custom_nodes/ComfyUI_TiledKSampler/tiling.py
@@ -0,0 +1,175 @@
+import torch
+import itertools
+import numpy as np
+
+def grouper(n, iterable):
+ it = iter(iterable)
+ while True:
+ chunk = list(itertools.islice(it, n))
+ if not chunk:
+ return
+ yield chunk
+
+def create_batches(n, iterable):
+ groups = itertools.groupby(iterable, key= lambda x: (x[1], x[3]))
+ for _, x in groups:
+ for y in grouper(n, x):
+ yield y
+
+
+def get_slice(tensor, h, h_len, w, w_len):
+ t = tensor.narrow(-2, h, h_len)
+ t = t.narrow(-1, w, w_len)
+ return t
+
+def set_slice(tensor1,tensor2, h, h_len, w, w_len, mask=None):
+ if mask is not None:
+ tensor1[:,:,h:h+h_len,w:w+w_len] = tensor1[:,:,h:h+h_len,w:w+w_len] * (1 - mask) + tensor2 * mask
+ else:
+ tensor1[:,:,h:h+h_len,w:w+w_len] = tensor2
+
+def get_tiles_and_masks_simple(steps, latent_shape, tile_height, tile_width):
+ latent_size_h = latent_shape[-2]
+ latent_size_w = latent_shape[-1]
+ tile_size_h = int(tile_height // 8)
+ tile_size_w = int(tile_width // 8)
+
+ h = np.arange(0,latent_size_h, tile_size_h)
+ w = np.arange(0,latent_size_w, tile_size_w)
+
+ def create_tile(hs, ws, i, j):
+ h = int(hs[i])
+ w = int(ws[j])
+ h_len = min(tile_size_h, latent_size_h - h)
+ w_len = min(tile_size_w, latent_size_w - w)
+ return (h, h_len, w, w_len, steps, None)
+
+ passes = [
+ [[create_tile(h, w, i, j) for i in range(len(h)) for j in range(len(w))]],
+ ]
+ return passes
+
+def get_tiles_and_masks_padded(steps, latent_shape, tile_height, tile_width):
+ batch_size = latent_shape[0]
+ latent_size_h = latent_shape[-2]
+ latent_size_w = latent_shape[-1]
+
+ tile_size_h = int(tile_height // 8)
+ tile_size_h = int((tile_size_h // 4) * 4)
+ tile_size_w = int(tile_width // 8)
+ tile_size_w = int((tile_size_w // 4) * 4)
+
+ #masks
+ mask_h = [0,tile_size_h // 4, tile_size_h - tile_size_h // 4, tile_size_h]
+ mask_w = [0,tile_size_w // 4, tile_size_w - tile_size_w // 4, tile_size_w]
+ masks = [[] for _ in range(3)]
+ for i in range(3):
+ for j in range(3):
+ mask = torch.zeros((batch_size,1,tile_size_h, tile_size_w), dtype=torch.float32, device='cpu')
+ mask[:,:,mask_h[i]:mask_h[i+1],mask_w[j]:mask_w[j+1]] = 1.0
+ masks[i].append(mask)
+
+ def create_mask(h_ind, w_ind, h_ind_max, w_ind_max, mask_h, mask_w, h_len, w_len):
+ mask = masks[1][1]
+ if not (h_ind == 0 or h_ind == h_ind_max or w_ind == 0 or w_ind == w_ind_max):
+ return get_slice(mask, 0, h_len, 0, w_len)
+ mask = mask.clone()
+ if h_ind == 0 and mask_h:
+ mask += masks[0][1]
+ if h_ind == h_ind_max and mask_h:
+ mask += masks[2][1]
+ if w_ind == 0 and mask_w:
+ mask += masks[1][0]
+ if w_ind == w_ind_max and mask_w:
+ mask += masks[1][2]
+ if h_ind == 0 and w_ind == 0 and mask_h and mask_w:
+ mask += masks[0][0]
+ if h_ind == 0 and w_ind == w_ind_max and mask_h and mask_w:
+ mask += masks[0][2]
+ if h_ind == h_ind_max and w_ind == 0 and mask_h and mask_w:
+ mask += masks[2][0]
+ if h_ind == h_ind_max and w_ind == w_ind_max and mask_h and mask_w:
+ mask += masks[2][2]
+ return get_slice(mask, 0, h_len, 0, w_len)
+
+ h = np.arange(0,latent_size_h, tile_size_h)
+ h_shift = np.arange(tile_size_h // 2, latent_size_h - tile_size_h // 2, tile_size_h)
+ w = np.arange(0,latent_size_w, tile_size_w)
+ w_shift = np.arange(tile_size_w // 2, latent_size_w - tile_size_h // 2, tile_size_w)
+
+
+ def create_tile(hs, ws, mask_h, mask_w, i, j):
+ h = int(hs[i])
+ w = int(ws[j])
+ h_len = min(tile_size_h, latent_size_h - h)
+ w_len = min(tile_size_w, latent_size_w - w)
+ mask = create_mask(i,j,len(hs)-1, len(ws)-1, mask_h, mask_w, h_len, w_len)
+ return (h, h_len, w, w_len, steps, mask)
+
+ passes = [
+ [[create_tile(h, w, True, True, i, j) for i in range(len(h)) for j in range(len(w))]],
+ [[create_tile(h_shift, w, False, True, i, j) for i in range(len(h_shift)) for j in range(len(w))]],
+ [[create_tile(h, w_shift, True, False, i, j) for i in range(len(h)) for j in range(len(w_shift))]],
+ [[create_tile(h_shift, w_shift, False, False, i,j) for i in range(len(h_shift)) for j in range(len(w_shift))]],
+ ]
+
+ return passes
+
+def mask_at_boundary(h, h_len, w, w_len, tile_size_h, tile_size_w, latent_size_h, latent_size_w, mask, device='cpu'):
+ tile_size_h = int(tile_size_h // 8)
+ tile_size_w = int(tile_size_w // 8)
+
+ if (h_len == tile_size_h or h_len == latent_size_h) and (w_len == tile_size_w or w_len == latent_size_w):
+ return h, h_len, w, w_len, mask
+ h_offset = min(0, latent_size_h - (h + tile_size_h))
+ w_offset = min(0, latent_size_w - (w + tile_size_w))
+ new_mask = torch.zeros((1,1,tile_size_h, tile_size_w), dtype=torch.float32, device=device)
+ new_mask[:,:,-h_offset:h_len if h_offset == 0 else tile_size_h, -w_offset:w_len if w_offset == 0 else tile_size_w] = 1.0 if mask is None else mask
+ return h + h_offset, tile_size_h, w + w_offset, tile_size_w, new_mask
+
+def get_tiles_and_masks_rgrid(steps, latent_shape, tile_height, tile_width, generator):
+
+ def calc_coords(latent_size, tile_size, jitter):
+ tile_coords = int((latent_size + jitter - 1) // tile_size + 1)
+ tile_coords = [np.clip(tile_size * c - jitter, 0, latent_size) for c in range(tile_coords + 1)]
+ tile_coords = [(c1, c2-c1) for c1, c2 in zip(tile_coords, tile_coords[1:])]
+ return tile_coords
+
+ #calc stuff
+ batch_size = latent_shape[0]
+ latent_size_h = latent_shape[-2]
+ latent_size_w = latent_shape[-1]
+ tile_size_h = int(tile_height // 8)
+ tile_size_w = int(tile_width // 8)
+
+ tiles_all = []
+
+ for s in range(steps):
+ rands = torch.rand((2,), dtype=torch.float32, generator=generator, device='cpu').numpy()
+
+ jitter_w1 = int(rands[0] * tile_size_w)
+ jitter_w2 = int(((rands[0] + .5) % 1.0) * tile_size_w)
+ jitter_h1 = int(rands[1] * tile_size_h)
+ jitter_h2 = int(((rands[1] + .5) % 1.0) * tile_size_h)
+
+ #calc number of tiles
+ tiles_h = [
+ calc_coords(latent_size_h, tile_size_h, jitter_h1),
+ calc_coords(latent_size_h, tile_size_h, jitter_h2)
+ ]
+ tiles_w = [
+ calc_coords(latent_size_w, tile_size_w, jitter_w1),
+ calc_coords(latent_size_w, tile_size_w, jitter_w2)
+ ]
+
+ tiles = []
+ if s % 2 == 0:
+ for i, h in enumerate(tiles_h[0]):
+ for w in tiles_w[i%2]:
+ tiles.append((int(h[0]), int(h[1]), int(w[0]), int(w[1]), 1, None))
+ else:
+ for i, w in enumerate(tiles_w[0]):
+ for h in tiles_h[i%2]:
+ tiles.append((int(h[0]), int(h[1]), int(w[0]), int(w[1]), 1, None))
+ tiles_all.append(tiles)
+ return [tiles_all]
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_experiments/LICENSE b/custom_nodes/ComfyUI_experiments/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..f288702d2fa16d3cdf0035b15a9fcbc552cd88e7
--- /dev/null
+++ b/custom_nodes/ComfyUI_experiments/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/custom_nodes/ComfyUI_experiments/README.md b/custom_nodes/ComfyUI_experiments/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..accace076c26b68b78b625ced597db595281b379
--- /dev/null
+++ b/custom_nodes/ComfyUI_experiments/README.md
@@ -0,0 +1,23 @@
+## Some experimental custom nodes for [ComfyUI](https://github.com/comfyanonymous/ComfyUI)
+
+Copy the .py files to your custom_nodes directory to use them.
+
+They will show up in: custom_node_experiments/
+
+### sampler_tonemap.py
+contains ModelSamplerTonemapNoiseTest a node that makes the sampler use a simple tonemapping algorithm to tonemap the noise. It will let you use higher CFG without breaking the image. To using higher CFG lower the multiplier value.
+
+### sampler_rescalecfg.py
+contains an implementation of the Rescale Classifier-Free Guidance from: https://arxiv.org/pdf/2305.08891.pdf
+
+### advanced_model_merging.py
+
+Node for merging models by block.
+
+### sdxl_model_merging.py
+
+Node for merging SDXL base models.
+
+### reference_only.py
+
+Contains a node that implements the "reference only controlnet". An example workflow can be found in the workflows folder.
diff --git a/custom_nodes/ComfyUI_experiments/__init__.py b/custom_nodes/ComfyUI_experiments/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..c6273d12a0f26a8e03127fcd9f72ff626f3c4226
--- /dev/null
+++ b/custom_nodes/ComfyUI_experiments/__init__.py
@@ -0,0 +1,23 @@
+import importlib
+import os
+
+node_list = [ #Add list of .py files containing nodes here
+ "advanced_model_merging",
+ "reference_only",
+ "sampler_rescalecfg",
+ "sampler_tonemap",
+ "sampler_tonemap_rescalecfg",
+ "sdxl_model_merging"
+]
+
+NODE_CLASS_MAPPINGS = {}
+NODE_DISPLAY_NAME_MAPPINGS = {}
+
+for module_name in node_list:
+ imported_module = importlib.import_module(".{}".format(module_name), __name__)
+
+ NODE_CLASS_MAPPINGS = {**NODE_CLASS_MAPPINGS, **imported_module.NODE_CLASS_MAPPINGS}
+ if hasattr(imported_module, "NODE_DISPLAY_NAME_MAPPINGS"):
+ NODE_DISPLAY_NAME_MAPPINGS = {**NODE_DISPLAY_NAME_MAPPINGS, **imported_module.NODE_DISPLAY_NAME_MAPPINGS}
+
+__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
diff --git a/custom_nodes/ComfyUI_experiments/__pycache__/__init__.cpython-310.pyc b/custom_nodes/ComfyUI_experiments/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..173fdfe00ea278fe9899fc7c7f260bb3d4c0c3b5
Binary files /dev/null and b/custom_nodes/ComfyUI_experiments/__pycache__/__init__.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI_experiments/__pycache__/__init__.cpython-311.pyc b/custom_nodes/ComfyUI_experiments/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..9db01aa97823253d77d6f727851460b3f2f25f4d
Binary files /dev/null and b/custom_nodes/ComfyUI_experiments/__pycache__/__init__.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_experiments/__pycache__/advanced_model_merging.cpython-310.pyc b/custom_nodes/ComfyUI_experiments/__pycache__/advanced_model_merging.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..bb301ffa959a7bd7136279fa17324f72664aae2a
Binary files /dev/null and b/custom_nodes/ComfyUI_experiments/__pycache__/advanced_model_merging.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI_experiments/__pycache__/advanced_model_merging.cpython-311.pyc b/custom_nodes/ComfyUI_experiments/__pycache__/advanced_model_merging.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..fc512addc5db0e272d2ae9472c8ba7ac72f77d08
Binary files /dev/null and b/custom_nodes/ComfyUI_experiments/__pycache__/advanced_model_merging.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_experiments/__pycache__/reference_only.cpython-310.pyc b/custom_nodes/ComfyUI_experiments/__pycache__/reference_only.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..510156161c8bcf0a1120aadc2011e21012223389
Binary files /dev/null and b/custom_nodes/ComfyUI_experiments/__pycache__/reference_only.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI_experiments/__pycache__/reference_only.cpython-311.pyc b/custom_nodes/ComfyUI_experiments/__pycache__/reference_only.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a6330e5c0a3c4b2bf4194bb8d8c0b4b273f9ec67
Binary files /dev/null and b/custom_nodes/ComfyUI_experiments/__pycache__/reference_only.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_experiments/__pycache__/sampler_rescalecfg.cpython-310.pyc b/custom_nodes/ComfyUI_experiments/__pycache__/sampler_rescalecfg.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..686246fa9a88a4d2abe654b14a8afe740185fe60
Binary files /dev/null and b/custom_nodes/ComfyUI_experiments/__pycache__/sampler_rescalecfg.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI_experiments/__pycache__/sampler_rescalecfg.cpython-311.pyc b/custom_nodes/ComfyUI_experiments/__pycache__/sampler_rescalecfg.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..174ecfa49ce84a769756f0ad12e7580991bf310e
Binary files /dev/null and b/custom_nodes/ComfyUI_experiments/__pycache__/sampler_rescalecfg.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_experiments/__pycache__/sampler_tonemap.cpython-310.pyc b/custom_nodes/ComfyUI_experiments/__pycache__/sampler_tonemap.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8c838567ab4827362eb628d77c19522eb447726d
Binary files /dev/null and b/custom_nodes/ComfyUI_experiments/__pycache__/sampler_tonemap.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI_experiments/__pycache__/sampler_tonemap.cpython-311.pyc b/custom_nodes/ComfyUI_experiments/__pycache__/sampler_tonemap.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a0dc33661ee0dcaad7b6b833d03c750ed72a57c7
Binary files /dev/null and b/custom_nodes/ComfyUI_experiments/__pycache__/sampler_tonemap.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_experiments/__pycache__/sampler_tonemap_rescalecfg.cpython-310.pyc b/custom_nodes/ComfyUI_experiments/__pycache__/sampler_tonemap_rescalecfg.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8dde2579c050a5ae9fafe189306fef8501e3241b
Binary files /dev/null and b/custom_nodes/ComfyUI_experiments/__pycache__/sampler_tonemap_rescalecfg.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI_experiments/__pycache__/sampler_tonemap_rescalecfg.cpython-311.pyc b/custom_nodes/ComfyUI_experiments/__pycache__/sampler_tonemap_rescalecfg.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4caf1b20f545bb4abd9a9f4cd7fb5653a6c33c98
Binary files /dev/null and b/custom_nodes/ComfyUI_experiments/__pycache__/sampler_tonemap_rescalecfg.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_experiments/__pycache__/sdxl_model_merging.cpython-310.pyc b/custom_nodes/ComfyUI_experiments/__pycache__/sdxl_model_merging.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b0623b8a569e3e47e19fa926662dacf9c457776f
Binary files /dev/null and b/custom_nodes/ComfyUI_experiments/__pycache__/sdxl_model_merging.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI_experiments/__pycache__/sdxl_model_merging.cpython-311.pyc b/custom_nodes/ComfyUI_experiments/__pycache__/sdxl_model_merging.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..58db7fb43940b2e8d4ba2615f2c69cd54a8cea8a
Binary files /dev/null and b/custom_nodes/ComfyUI_experiments/__pycache__/sdxl_model_merging.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_experiments/advanced_model_merging.py b/custom_nodes/ComfyUI_experiments/advanced_model_merging.py
new file mode 100644
index 0000000000000000000000000000000000000000..f1d068676fd4185b684a3643a0377e981d3c7397
--- /dev/null
+++ b/custom_nodes/ComfyUI_experiments/advanced_model_merging.py
@@ -0,0 +1,30 @@
+import comfy_extras.nodes_model_merging
+
+class ModelMergeBlockNumber(comfy_extras.nodes_model_merging.ModelMergeBlocks):
+ @classmethod
+ def INPUT_TYPES(s):
+ arg_dict = { "model1": ("MODEL",),
+ "model2": ("MODEL",)}
+
+ argument = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01})
+
+ arg_dict["time_embed."] = argument
+ arg_dict["label_emb."] = argument
+
+ for i in range(12):
+ arg_dict["input_blocks.{}.".format(i)] = argument
+
+ for i in range(3):
+ arg_dict["middle_block.{}.".format(i)] = argument
+
+ for i in range(12):
+ arg_dict["output_blocks.{}.".format(i)] = argument
+
+ arg_dict["out."] = argument
+
+ return {"required": arg_dict}
+
+
+NODE_CLASS_MAPPINGS = {
+ "ModelMergeBlockNumber": ModelMergeBlockNumber,
+}
diff --git a/custom_nodes/ComfyUI_experiments/reference_only.py b/custom_nodes/ComfyUI_experiments/reference_only.py
new file mode 100644
index 0000000000000000000000000000000000000000..ae824a4142169d6e55b3d7b6ca497ca4b0ea1d2e
--- /dev/null
+++ b/custom_nodes/ComfyUI_experiments/reference_only.py
@@ -0,0 +1,54 @@
+import torch
+
+class ReferenceOnlySimple:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": { "model": ("MODEL",),
+ "reference": ("LATENT",),
+ "batch_size": ("INT", {"default": 1, "min": 1, "max": 64})
+ }}
+
+ RETURN_TYPES = ("MODEL", "LATENT")
+ FUNCTION = "reference_only"
+
+ CATEGORY = "custom_node_experiments"
+
+ def reference_only(self, model, reference, batch_size):
+ model_reference = model.clone()
+ size_latent = list(reference["samples"].shape)
+ size_latent[0] = batch_size
+ latent = {}
+ latent["samples"] = torch.zeros(size_latent)
+
+ batch = latent["samples"].shape[0] + reference["samples"].shape[0]
+ def reference_apply(q, k, v, extra_options):
+ k = k.clone().repeat(1, 2, 1)
+ offset = 0
+ if q.shape[0] > batch:
+ offset = batch
+
+ for o in range(0, q.shape[0], batch):
+ for x in range(1, batch):
+ k[x + o, q.shape[1]:] = q[o,:]
+
+ return q, k, k
+
+ model_reference.set_model_attn1_patch(reference_apply)
+ out_latent = torch.cat((reference["samples"], latent["samples"]))
+ if "noise_mask" in latent:
+ mask = latent["noise_mask"]
+ else:
+ mask = torch.ones((64,64), dtype=torch.float32, device="cpu")
+
+ if len(mask.shape) < 3:
+ mask = mask.unsqueeze(0)
+ if mask.shape[0] < latent["samples"].shape[0]:
+ print(latent["samples"].shape, mask.shape)
+ mask = mask.repeat(latent["samples"].shape[0], 1, 1)
+
+ out_mask = torch.zeros((1,mask.shape[1],mask.shape[2]), dtype=torch.float32, device="cpu")
+ return (model_reference, {"samples": out_latent, "noise_mask": torch.cat((out_mask, mask))})
+
+NODE_CLASS_MAPPINGS = {
+ "ReferenceOnlySimple": ReferenceOnlySimple,
+}
diff --git a/custom_nodes/ComfyUI_experiments/sampler_rescalecfg.py b/custom_nodes/ComfyUI_experiments/sampler_rescalecfg.py
new file mode 100644
index 0000000000000000000000000000000000000000..d99959d6a8131c0faed9a6ea33974fa55728bafb
--- /dev/null
+++ b/custom_nodes/ComfyUI_experiments/sampler_rescalecfg.py
@@ -0,0 +1,38 @@
+import torch
+
+
+class RescaleClassifierFreeGuidance:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": { "model": ("MODEL",),
+ "multiplier": ("FLOAT", {"default": 0.7, "min": 0.0, "max": 1.0, "step": 0.01}),
+ }}
+ RETURN_TYPES = ("MODEL",)
+ FUNCTION = "patch"
+
+ CATEGORY = "custom_node_experiments"
+
+ def patch(self, model, multiplier):
+
+ def rescale_cfg(args):
+ cond = args["cond"]
+ uncond = args["uncond"]
+ cond_scale = args["cond_scale"]
+
+ x_cfg = uncond + cond_scale * (cond - uncond)
+ ro_pos = torch.std(cond, dim=(1,2,3), keepdim=True)
+ ro_cfg = torch.std(x_cfg, dim=(1,2,3), keepdim=True)
+
+ x_rescaled = x_cfg * (ro_pos / ro_cfg)
+ x_final = multiplier * x_rescaled + (1.0 - multiplier) * x_cfg
+
+ return x_final
+
+ m = model.clone()
+ m.set_model_sampler_cfg_function(rescale_cfg)
+ return (m, )
+
+
+NODE_CLASS_MAPPINGS = {
+ "RescaleClassifierFreeGuidanceTest": RescaleClassifierFreeGuidance,
+}
diff --git a/custom_nodes/ComfyUI_experiments/sampler_tonemap.py b/custom_nodes/ComfyUI_experiments/sampler_tonemap.py
new file mode 100644
index 0000000000000000000000000000000000000000..14f732e2529d608c42da3250ddb7a24893a8abf8
--- /dev/null
+++ b/custom_nodes/ComfyUI_experiments/sampler_tonemap.py
@@ -0,0 +1,44 @@
+import torch
+
+
+class ModelSamplerTonemapNoiseTest:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": { "model": ("MODEL",),
+ "multiplier": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.01}),
+ }}
+ RETURN_TYPES = ("MODEL",)
+ FUNCTION = "patch"
+
+ CATEGORY = "custom_node_experiments"
+
+ def patch(self, model, multiplier):
+
+ def sampler_tonemap_reinhard(args):
+ cond = args["cond"]
+ uncond = args["uncond"]
+ cond_scale = args["cond_scale"]
+ noise_pred = (cond - uncond)
+ noise_pred_vector_magnitude = (torch.linalg.vector_norm(noise_pred, dim=(1)) + 0.0000000001)[:,None]
+ noise_pred /= noise_pred_vector_magnitude
+
+ mean = torch.mean(noise_pred_vector_magnitude, dim=(1,2,3), keepdim=True)
+ std = torch.std(noise_pred_vector_magnitude, dim=(1,2,3), keepdim=True)
+
+ top = (std * 3 + mean) * multiplier
+
+ #reinhard
+ noise_pred_vector_magnitude *= (1.0 / top)
+ new_magnitude = noise_pred_vector_magnitude / (noise_pred_vector_magnitude + 1.0)
+ new_magnitude *= top
+
+ return uncond + noise_pred * new_magnitude * cond_scale
+
+ m = model.clone()
+ m.set_model_sampler_cfg_function(sampler_tonemap_reinhard)
+ return (m, )
+
+
+NODE_CLASS_MAPPINGS = {
+ "ModelSamplerTonemapNoiseTest": ModelSamplerTonemapNoiseTest,
+}
diff --git a/custom_nodes/ComfyUI_experiments/sampler_tonemap_rescalecfg.py b/custom_nodes/ComfyUI_experiments/sampler_tonemap_rescalecfg.py
new file mode 100644
index 0000000000000000000000000000000000000000..3d3bf051a4155bbf0df1af814853a3c54502b8be
--- /dev/null
+++ b/custom_nodes/ComfyUI_experiments/sampler_tonemap_rescalecfg.py
@@ -0,0 +1,55 @@
+import torch
+
+
+class TonemapNoiseWithRescaleCFG:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {"model": ("MODEL",),
+ "tonemap_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.01}),
+ "rescale_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
+ }}
+ RETURN_TYPES = ("MODEL",)
+ FUNCTION = "patch"
+
+ CATEGORY = "custom_node_experiments"
+
+ def patch(self, model, tonemap_multiplier, rescale_multiplier):
+
+ def tonemap_noise_rescale_cfg(args):
+ cond = args["cond"]
+ uncond = args["uncond"]
+ cond_scale = args["cond_scale"]
+
+ # Tonemap
+ noise_pred = (cond - uncond)
+ noise_pred_vector_magnitude = (torch.linalg.vector_norm(noise_pred, dim=(1)) + 0.0000000001)[:, None]
+ noise_pred /= noise_pred_vector_magnitude
+
+ mean = torch.mean(noise_pred_vector_magnitude, dim=(1, 2, 3), keepdim=True)
+ std = torch.std(noise_pred_vector_magnitude, dim=(1, 2, 3), keepdim=True)
+
+ top = (std * 3 + mean) * tonemap_multiplier
+
+ # Reinhard
+ noise_pred_vector_magnitude *= (1.0 / top)
+ new_magnitude = noise_pred_vector_magnitude / (noise_pred_vector_magnitude + 1.0)
+ new_magnitude *= top
+
+ # Rescale CFG
+ x_cfg = uncond + (noise_pred * new_magnitude * cond_scale)
+ ro_pos = torch.std(cond, dim=(1, 2, 3), keepdim=True)
+ ro_cfg = torch.std(x_cfg, dim=(1, 2, 3), keepdim=True)
+
+ x_rescaled = x_cfg * (ro_pos / ro_cfg)
+ x_final = rescale_multiplier * x_rescaled + (1.0 - rescale_multiplier) * x_cfg
+
+ return x_final
+
+ m = model.clone()
+ m.set_model_sampler_cfg_function(tonemap_noise_rescale_cfg)
+ return (m, )
+
+
+NODE_CLASS_MAPPINGS = {
+ "TonemapNoiseWithRescaleCFG": TonemapNoiseWithRescaleCFG,
+}
diff --git a/custom_nodes/ComfyUI_experiments/sdxl_model_merging.py b/custom_nodes/ComfyUI_experiments/sdxl_model_merging.py
new file mode 100644
index 0000000000000000000000000000000000000000..e288f3cb4963379fbb02b08d5907988bd3810356
--- /dev/null
+++ b/custom_nodes/ComfyUI_experiments/sdxl_model_merging.py
@@ -0,0 +1,114 @@
+import comfy_extras.nodes_model_merging
+
+class ModelMergeSDXL(comfy_extras.nodes_model_merging.ModelMergeBlocks):
+ @classmethod
+ def INPUT_TYPES(s):
+ arg_dict = { "model1": ("MODEL",),
+ "model2": ("MODEL",)}
+
+ argument = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01})
+
+ arg_dict["time_embed."] = argument
+ arg_dict["label_emb."] = argument
+
+ for i in range(9):
+ arg_dict["input_blocks.{}".format(i)] = argument
+
+ for i in range(3):
+ arg_dict["middle_block.{}".format(i)] = argument
+
+ for i in range(9):
+ arg_dict["output_blocks.{}".format(i)] = argument
+
+ arg_dict["out."] = argument
+
+ return {"required": arg_dict}
+
+
+class ModelMergeSDXLTransformers(comfy_extras.nodes_model_merging.ModelMergeBlocks):
+ @classmethod
+ def INPUT_TYPES(s):
+ arg_dict = { "model1": ("MODEL",),
+ "model2": ("MODEL",)}
+
+ argument = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01})
+
+ arg_dict["time_embed."] = argument
+ arg_dict["label_emb."] = argument
+
+ transformers = {4: 2, 5:2, 7:10, 8:10}
+
+ for i in range(9):
+ arg_dict["input_blocks.{}.0.".format(i)] = argument
+ if i in transformers:
+ arg_dict["input_blocks.{}.1.".format(i)] = argument
+ for j in range(transformers[i]):
+ arg_dict["input_blocks.{}.1.transformer_blocks.{}.".format(i, j)] = argument
+
+ for i in range(3):
+ arg_dict["middle_block.{}.".format(i)] = argument
+ if i == 1:
+ for j in range(10):
+ arg_dict["middle_block.{}.transformer_blocks.{}.".format(i, j)] = argument
+
+ transformers = {3:2, 4: 2, 5:2, 6:10, 7:10, 8:10}
+ for i in range(9):
+ arg_dict["output_blocks.{}.0.".format(i)] = argument
+ t = 8 - i
+ if t in transformers:
+ arg_dict["output_blocks.{}.1.".format(i)] = argument
+ for j in range(transformers[t]):
+ arg_dict["output_blocks.{}.1.transformer_blocks.{}.".format(i, j)] = argument
+
+ arg_dict["out."] = argument
+
+ return {"required": arg_dict}
+
+class ModelMergeSDXLDetailedTransformers(comfy_extras.nodes_model_merging.ModelMergeBlocks):
+ @classmethod
+ def INPUT_TYPES(s):
+ arg_dict = { "model1": ("MODEL",),
+ "model2": ("MODEL",)}
+
+ argument = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01})
+
+ arg_dict["time_embed."] = argument
+ arg_dict["label_emb."] = argument
+
+ transformers = {4: 2, 5:2, 7:10, 8:10}
+ transformers_args = ["norm1", "attn1.to_q", "attn1.to_k", "attn1.to_v", "attn1.to_out", "ff.net", "norm2", "attn2.to_q", "attn2.to_k", "attn2.to_v", "attn2.to_out", "norm3"]
+
+ for i in range(9):
+ arg_dict["input_blocks.{}.0.".format(i)] = argument
+ if i in transformers:
+ arg_dict["input_blocks.{}.1.".format(i)] = argument
+ for j in range(transformers[i]):
+ for x in transformers_args:
+ arg_dict["input_blocks.{}.1.transformer_blocks.{}.{}".format(i, j, x)] = argument
+
+ for i in range(3):
+ arg_dict["middle_block.{}.".format(i)] = argument
+ if i == 1:
+ for j in range(10):
+ for x in transformers_args:
+ arg_dict["middle_block.{}.transformer_blocks.{}.{}".format(i, j, x)] = argument
+
+ transformers = {3:2, 4: 2, 5:2, 6:10, 7:10, 8:10}
+ for i in range(9):
+ arg_dict["output_blocks.{}.0.".format(i)] = argument
+ t = 8 - i
+ if t in transformers:
+ arg_dict["output_blocks.{}.1.".format(i)] = argument
+ for j in range(transformers[t]):
+ for x in transformers_args:
+ arg_dict["output_blocks.{}.1.transformer_blocks.{}.{}".format(i, j, x)] = argument
+
+ arg_dict["out."] = argument
+
+ return {"required": arg_dict}
+
+NODE_CLASS_MAPPINGS = {
+ "ModelMergeSDXL": ModelMergeSDXL,
+ "ModelMergeSDXLTransformers": ModelMergeSDXLTransformers,
+ "ModelMergeSDXLDetailedTransformers": ModelMergeSDXLDetailedTransformers,
+}
diff --git a/custom_nodes/ComfyUI_experiments/workflows/reference_only_simple.json b/custom_nodes/ComfyUI_experiments/workflows/reference_only_simple.json
new file mode 100644
index 0000000000000000000000000000000000000000..952ce762fda365cc9d98b8204f4872dc22797244
--- /dev/null
+++ b/custom_nodes/ComfyUI_experiments/workflows/reference_only_simple.json
@@ -0,0 +1,552 @@
+{
+ "last_node_id": 15,
+ "last_link_id": 37,
+ "nodes": [
+ {
+ "id": 8,
+ "type": "VAEDecode",
+ "pos": [
+ 1209,
+ 188
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 7
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 8
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 9
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 6,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 233,
+ 117
+ ],
+ "size": {
+ "0": 422.84503173828125,
+ "1": 164.31304931640625
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 3
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 4
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "crude drawing of girl"
+ ]
+ },
+ {
+ "id": 7,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 237,
+ 370
+ ],
+ "size": {
+ "0": 425.27801513671875,
+ "1": 180.6060791015625
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 5
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 6
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "text, watermark"
+ ]
+ },
+ {
+ "id": 3,
+ "type": "KSampler",
+ "pos": [
+ 863,
+ 186
+ ],
+ "size": {
+ "0": 315,
+ "1": 262
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 37
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 4
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 6
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 34
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 7
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 719286772344905,
+ "fixed",
+ 20,
+ 8,
+ "euler",
+ "normal",
+ 1
+ ]
+ },
+ {
+ "id": 9,
+ "type": "SaveImage",
+ "pos": [
+ 1548,
+ 180
+ ],
+ "size": [
+ 1454.6668601568254,
+ 548.2885143635223
+ ],
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 9
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "refer/ComfyUI"
+ ]
+ },
+ {
+ "id": 4,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ -563,
+ 510
+ ],
+ "size": {
+ "0": 315,
+ "1": 98
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 32
+ ],
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 3,
+ 5
+ ],
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 8,
+ 20
+ ],
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "sd_xl_1.0.safetensors"
+ ]
+ },
+ {
+ "id": 14,
+ "type": "ImageScale",
+ "pos": [
+ -129,
+ 763
+ ],
+ "size": {
+ "0": 315,
+ "1": 130
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 19
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 18
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ImageScale"
+ },
+ "widgets_values": [
+ "nearest-exact",
+ 768,
+ 768,
+ "center"
+ ]
+ },
+ {
+ "id": 13,
+ "type": "LoadImage",
+ "pos": [
+ -483,
+ 777
+ ],
+ "size": {
+ "0": 315,
+ "1": 314
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 19
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "example.png",
+ "image"
+ ]
+ },
+ {
+ "id": 15,
+ "type": "ReferenceOnlySimple",
+ "pos": [
+ 515,
+ 675
+ ],
+ "size": {
+ "0": 315,
+ "1": 78
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 32,
+ "slot_index": 0
+ },
+ {
+ "name": "reference",
+ "type": "LATENT",
+ "link": 35
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 37
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 34
+ ],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ReferenceOnlySimple"
+ },
+ "widgets_values": [
+ 2
+ ]
+ },
+ {
+ "id": 12,
+ "type": "VAEEncode",
+ "pos": [
+ 248,
+ 732
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "pixels",
+ "type": "IMAGE",
+ "link": 18,
+ "slot_index": 0
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 20,
+ "slot_index": 1
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 35
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEEncode"
+ }
+ }
+ ],
+ "links": [
+ [
+ 3,
+ 4,
+ 1,
+ 6,
+ 0,
+ "CLIP"
+ ],
+ [
+ 4,
+ 6,
+ 0,
+ 3,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 5,
+ 4,
+ 1,
+ 7,
+ 0,
+ "CLIP"
+ ],
+ [
+ 6,
+ 7,
+ 0,
+ 3,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 7,
+ 3,
+ 0,
+ 8,
+ 0,
+ "LATENT"
+ ],
+ [
+ 8,
+ 4,
+ 2,
+ 8,
+ 1,
+ "VAE"
+ ],
+ [
+ 9,
+ 8,
+ 0,
+ 9,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 18,
+ 14,
+ 0,
+ 12,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 19,
+ 13,
+ 0,
+ 14,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 20,
+ 4,
+ 2,
+ 12,
+ 1,
+ "VAE"
+ ],
+ [
+ 32,
+ 4,
+ 0,
+ 15,
+ 0,
+ "MODEL"
+ ],
+ [
+ 34,
+ 15,
+ 1,
+ 3,
+ 3,
+ "LATENT"
+ ],
+ [
+ 35,
+ 12,
+ 0,
+ 15,
+ 1,
+ "LATENT"
+ ],
+ [
+ 37,
+ 15,
+ 0,
+ 3,
+ 0,
+ "MODEL"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
diff --git a/custom_nodes/ComfyUI_node_Lilly/.gitattributes b/custom_nodes/ComfyUI_node_Lilly/.gitattributes
new file mode 100644
index 0000000000000000000000000000000000000000..dfe0770424b2a19faf507a501ebfc23be8f54e7b
--- /dev/null
+++ b/custom_nodes/ComfyUI_node_Lilly/.gitattributes
@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text=auto
diff --git a/custom_nodes/ComfyUI_node_Lilly/.gitignore b/custom_nodes/ComfyUI_node_Lilly/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..439474c5a0e05c38347464e690e94c07e7567387
--- /dev/null
+++ b/custom_nodes/ComfyUI_node_Lilly/.gitignore
@@ -0,0 +1,3 @@
+
+*.pyc
+**/nppBackup/
diff --git a/custom_nodes/ComfyUI_node_Lilly/CLIPTextEncodeWildcards.py b/custom_nodes/ComfyUI_node_Lilly/CLIPTextEncodeWildcards.py
new file mode 100644
index 0000000000000000000000000000000000000000..0616381deea5dd0eb97e7c7bbfc8f7c84c595180
--- /dev/null
+++ b/custom_nodes/ComfyUI_node_Lilly/CLIPTextEncodeWildcards.py
@@ -0,0 +1,88 @@
+import os, glob, sys
+import random
+import re
+import os
+if __name__ == os.path.splitext(os.path.basename(__file__))[0] or __name__ =='__main__':
+ from ConsoleColor import print, console
+ from wildcards import wildcards
+else:
+ from .ConsoleColor import print, console
+ from .wildcards import wildcards
+#print(__file__)
+#print(os.path.basename(__file__))
+
+#print("wildcards_ComfyUI")
+#print(os.getcwd())
+#Wprint(f"CLIPTextEncodeWildcards __name__ {__name__}")
+
+class CLIPTextEncodeWildcards:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "text": ("STRING", {"multiline": True}), "clip": ("CLIP", )
+ }
+ }
+ RETURN_TYPES = ("CONDITIONING",)
+ FUNCTION = "encode"
+
+ CATEGORY = "conditioning"
+
+ def encode(self, clip, text):
+ print(f"[green]text : [/green]",text)
+ r=wildcards.run(text)
+ print(f"[green]result : [/green]",r)
+ return ([[clip.encode(r), {}]], )
+
+class CLIPTextEncodeWildcards2:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "text": ("STRING", {"multiline": True}), "clip": ("CLIP", ),
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
+
+ }
+ }
+ RETURN_TYPES = ("CONDITIONING",)
+ FUNCTION = "encode"
+
+ CATEGORY = "conditioning"
+
+ def encode(self, seed, clip, text):
+ random.seed(seed)
+ print(f"[green]text : [/green]",text)
+ r=wildcards.run(text)
+ print(f"[green]result : [/green]",r)
+ return ([[clip.encode(r), {}]], )
+
+
+
+class CLIPTextEncodeWildcards3:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "clip": ("CLIP", ),
+ "positive": ("STRING", {"multiline": True}),
+ "negative": ("STRING", {"multiline": True}),
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
+
+ }
+ }
+ RETURN_TYPES = ("CONDITIONING","CONDITIONING")
+ FUNCTION = "encode"
+
+ CATEGORY = "conditioning"
+
+ def encode(self, seed, clip, positive, negative):
+ random.seed(seed)
+ print(f"[green]positive : [/green]",positive)
+ positive=wildcards.run(positive)
+ print(f"[green]result : [/green]",positive)
+ print(f"[green]negative : [/green]",negative)
+ negative=wildcards.run(negative)
+ print(f"[green]result : [/green]",negative)
+ return ([[clip.encode(positive), {}]], [[clip.encode(negative), {}]], )
+
+
diff --git a/custom_nodes/ComfyUI_node_Lilly/CheckpointLoaderRandom.py b/custom_nodes/ComfyUI_node_Lilly/CheckpointLoaderRandom.py
new file mode 100644
index 0000000000000000000000000000000000000000..ce4c5a009affbf42dc6b2554ae510fb98c396597
--- /dev/null
+++ b/custom_nodes/ComfyUI_node_Lilly/CheckpointLoaderRandom.py
@@ -0,0 +1,49 @@
+import os
+import comfy.sd
+from nodes import *
+import random
+import folder_paths
+
+cnt=0
+ckpt_name=""
+ckpt_path=""
+
+class CheckpointLoaderRandom:
+ models_dir = os.path.join(os.getcwd(),"ComfyUI", "models")
+ ckpt_dir = os.path.join(models_dir, "checkpoints")
+ cnt=0
+ ckpt_name=""
+ ckpt_path=""
+
+ def __init__(self):
+ print(f"CheckpointLoaderRandom __init__")
+ pass
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
+ "max": ("INT", {"default": 10, "min": 0, "max": 0xffffffffffffffff}),
+ #"ckpt_name": (filter_files_extensions(recursive_search(s.ckpt_dir), supported_ckpt_extensions), ),
+ }
+ }
+ RETURN_TYPES = ("MODEL", "CLIP", "VAE")
+ FUNCTION = "load_checkpoint"
+
+ CATEGORY = "loaders"
+
+ def load_checkpoint(self, seed, max, output_vae=True, output_clip=True):
+ global cnt, ckpt_name, ckpt_path
+ print(f"cnt : { cnt}")
+ if ckpt_name=="" or cnt>=max :
+ cnt=0
+ ckpt_names= folder_paths.get_filename_list("checkpoints")
+ #print(f"ckpt_names : { ckpt_names}")
+ ckpt_name=random.choice(ckpt_names)
+ print(f"ckpt_name : { ckpt_name}")
+ ckpt_path = folder_paths.get_full_path("checkpoints", ckpt_name)
+ out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, embedding_directory=folder_paths.get_folder_paths("embeddings"))
+ cnt+=1
+ return out
+
diff --git a/custom_nodes/ComfyUI_node_Lilly/CheckpointLoaderSimpleText.py b/custom_nodes/ComfyUI_node_Lilly/CheckpointLoaderSimpleText.py
new file mode 100644
index 0000000000000000000000000000000000000000..3bdf13e441eaa6a4e7eb9290498497e23b59b183
--- /dev/null
+++ b/custom_nodes/ComfyUI_node_Lilly/CheckpointLoaderSimpleText.py
@@ -0,0 +1,53 @@
+import os
+import comfy.sd
+from nodes import *
+from folder_paths import *
+import random
+import os
+if __name__ == os.path.splitext(os.path.basename(__file__))[0] :
+ from ConsoleColor import print, console, ccolor
+ from mypath import *
+else:
+ from .ConsoleColor import print, console, ccolor
+ from .mypath import *
+
+#print(__file__)
+#print(os.path.basename(__file__))
+
+class CheckpointLoaderSimpleText:
+ @classmethod
+ def INPUT_TYPES(s):
+ t_checkpoints=folder_paths.get_filename_list("checkpoints")
+ #print(f"checkpoints count : {len(t_checkpoints)}", Colors.BGREEN)
+ return {
+ "required": {
+ "ckpt_name": (
+ "STRING", {
+ "multiline": False,
+ "default": random.choice(t_checkpoints)
+ }
+ ),
+ }
+ }
+ RETURN_TYPES = ("MODEL", "CLIP", "VAE")
+ FUNCTION = "load_checkpoint"
+
+ CATEGORY = "loaders"
+
+ def load_checkpoint(self, ckpt_name, output_vae=True, output_clip=True):
+ print(f"[{ccolor}]ckpt_name : [/{ccolor}]", ckpt_name)
+ ckpt_path=folder_paths.get_full_path("checkpoints", ckpt_name)
+ if ckpt_path is None:
+ ckpt_path=getFullPath(ckpt_name,"checkpoints")
+ print(f"[{ccolor}]ckpt_path : [/{ccolor}]", ckpt_path)
+ try:
+ out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, embedding_directory=folder_paths.get_folder_paths("embeddings"))
+ return out
+ except Exception as e:
+ console.print_exception()
+ return
+
+
+#NODE_CLASS_MAPPINGS = {
+# "CheckpointLoaderSimpleText": CheckpointLoaderSimpleText,
+#}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_node_Lilly/ConsoleColor.py b/custom_nodes/ComfyUI_node_Lilly/ConsoleColor.py
new file mode 100644
index 0000000000000000000000000000000000000000..cce625d3d7d76bb058ddada39f4fda90776899d3
--- /dev/null
+++ b/custom_nodes/ComfyUI_node_Lilly/ConsoleColor.py
@@ -0,0 +1,61 @@
+import os, sys
+import sys
+import subprocess
+import pkg_resources
+
+required = {'rich'}
+installed = {pkg.key for pkg in pkg_resources.working_set}
+missing = required - installed
+
+if missing:
+ python = sys.executable
+ subprocess.check_call([python, '-m', 'pip', 'install', *missing], stdout=subprocess.DEVNULL)
+
+from rich.console import Console
+from rich.theme import Theme
+#console=Console(style="reset")
+custom_theme = Theme({
+ "repr.path": "bright_blue",
+ "progress.percentage": "bright_blue",
+ "markdown.block_quote": "bright_blue",
+ "iso8601.time": "bright_blue"
+})
+console = Console(theme=custom_theme)
+print=console.log
+ccolor="bright_yellow"
+"""
+print("test", style="bold white on blue")
+print("test", style="bold green")
+print("test", style="bold CYAN")
+"""
+"""
+import os
+if __name__ == os.path.splitext(os.path.basename(__file__))[0] or __name__ =='__main__':
+ from ConsoleColor import print, console
+else:
+ from .ConsoleColor import print, console
+print(__file__)
+print(os.path.basename(__file__))
+"""
+
+"""
+
+print(
+ {
+ 'test1':'tset',
+ 'test2':'tset',
+ }
+)
+print("test", style="bold white on blue")
+"""
+
+"""
+print(__file__)
+print(os.path.basename(__file__))
+try:
+ Exception_test()
+except Exception:
+ #console.print_exception(show_locals=True)
+ console.print_exception()
+
+"""
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_node_Lilly/LoraLoaderText.py b/custom_nodes/ComfyUI_node_Lilly/LoraLoaderText.py
new file mode 100644
index 0000000000000000000000000000000000000000..1e6851208d6ac5ebd174d640bee820cc3e7f9b8a
--- /dev/null
+++ b/custom_nodes/ComfyUI_node_Lilly/LoraLoaderText.py
@@ -0,0 +1,78 @@
+import os
+import comfy.sd
+from nodes import *
+import folder_paths
+
+if __name__ == os.path.splitext(os.path.basename(__file__))[0] :
+ from ConsoleColor import print, console, ccolor
+ from mypath import *
+else:
+ from .ConsoleColor import print, console, ccolor
+ from .mypath import *
+
+class LoraLoaderText:
+ def __init__(self):
+ self.loaded_lora = None
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "model": ("MODEL",),
+ "clip": ("CLIP", ),
+ "lora_name": ("STRING", {
+ "multiline": False, #True if you want the field to look like the one on the ClipTextEncode node
+ "default": (folder_paths.get_filename_list("loras"), )
+ }),
+ #"lora_name": (folder_paths.get_filename_list("loras"), ),
+ "strength_model": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
+ "strength_clip": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
+ }
+ }
+ RETURN_TYPES = ("MODEL", "CLIP")
+ FUNCTION = "load_lora"
+
+ CATEGORY = "loaders"
+
+ def load_lora(self, model, clip, lora_name, strength_model, strength_clip):
+
+ print(f"[{ccolor}]lora_name : [/{ccolor}]", lora_name)
+ if strength_model == 0 and strength_clip == 0:
+ print("[red]strength_model,strength_clip 0[/red] : ", lora_name)
+ return (model, clip)
+
+ if lora_name is None or lora_name =="" :
+ print("[red]No lora_name[/red] : ", lora_name)
+ return (model, clip)
+
+ lora_path = folder_paths.get_full_path("loras", lora_name)
+ if lora_path is None:
+ print("[yellow]No lora_path of lora_name [/yellow] : ", lora_name)
+ lora_path=getFullPath(lora_name,"loras")
+ if lora_path is None:
+ print("[red]No lora_path of lora_name [/red] : ", lora_name)
+ return (model, clip)
+
+ lora = None
+ if self.loaded_lora is not None:
+ if self.loaded_lora[0] == lora_path:
+ lora = self.loaded_lora[1]
+ else:
+ del self.loaded_lora
+
+ if lora is None:
+ lora = comfy.utils.load_torch_file(lora_path, safe_load=True)
+ self.loaded_lora = (lora_path, lora)
+
+ # =========================================
+
+ try:
+ model_lora, clip_lora = comfy.sd.load_lora_for_models(model, clip, lora, strength_model, strength_clip)
+ return (model_lora, clip_lora)
+ except Exception as e:
+ console.print_exception()
+ return (model, clip)
+
+#NODE_CLASS_MAPPINGS = {
+# "LoraLoaderText": LoraLoaderText,
+#}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_node_Lilly/LoraLoaderTextRandom.py b/custom_nodes/ComfyUI_node_Lilly/LoraLoaderTextRandom.py
new file mode 100644
index 0000000000000000000000000000000000000000..1f72df3378c4cb72c93d26c469a8a0f0077a6cd9
--- /dev/null
+++ b/custom_nodes/ComfyUI_node_Lilly/LoraLoaderTextRandom.py
@@ -0,0 +1,90 @@
+import os
+import comfy.sd
+from nodes import *
+import folder_paths
+import random
+if __name__ == os.path.splitext(os.path.basename(__file__))[0] :
+ from ConsoleColor import print, console
+ from mypath import *
+else:
+ from .ConsoleColor import print, console
+ from .mypath import *
+
+class LoraLoaderTextRandom:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "model": ("MODEL",),
+ "clip": ("CLIP", ),
+ "lora_name": ("STRING", {
+ "multiline": False, #True if you want the field to look like the one on the ClipTextEncode node
+ "default": (folder_paths.get_filename_list("loras"), )
+ }),
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
+ #"lora_name": (folder_paths.get_filename_list("loras"), ),
+ "strength_model_min": ("FLOAT", {"default": 0.50, "min": 0.0, "max": 10.0, "step": 0.01}),
+ "strength_model_max": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
+ "strength_clip_min": ("FLOAT", {"default": 0.50, "min": 0.0, "max": 10.0, "step": 0.01}),
+ "strength_clip_max": ("FLOAT", {"default": 1.50, "min": 0.0, "max": 10.0, "step": 0.01}),
+ }
+ }
+ RETURN_TYPES = ("MODEL", "CLIP")
+ FUNCTION = "load_lora"
+
+ CATEGORY = "loaders"
+
+ def load_lora(self,
+ model,
+ clip,
+ lora_name,
+ seed,
+ strength_model_min,
+ strength_model_max,
+ strength_clip_min,
+ strength_clip_max
+ ):
+
+ strength_model=random.uniform(min(strength_model_min,strength_model_max),max(strength_model_min,strength_model_max))
+ strength_clip=random.uniform(min(strength_clip_min,strength_clip_max),max(strength_clip_min,strength_clip_max))
+
+ print(f"[{ccolor}]lora_name : [/{ccolor}]", lora_name)
+ if strength_model == 0 and strength_clip == 0:
+ print("[red]strength_model,strength_clip 0[/red] : ", lora_name)
+ return (model, clip)
+
+ if lora_name is None or lora_name =="" :
+ print("[red]No lora_name[/red] : ", lora_name)
+ return (model, clip)
+
+ lora_path = folder_paths.get_full_path("loras", lora_name)
+ if lora_path is None:
+ #print("[red]No lora_path of lora_name [/red] : ", lora_name)
+ lora_path=getFullPath(lora_name,"loras")
+ if lora_path is None:
+ print("[red]No lora_path of lora_name [/red] : ", lora_name)
+ return (model, clip)
+
+ lora = None
+ if self.loaded_lora is not None:
+ if self.loaded_lora[0] == lora_path:
+ lora = self.loaded_lora[1]
+ else:
+ del self.loaded_lora
+
+ if lora is None:
+ lora = comfy.utils.load_torch_file(lora_path, safe_load=True)
+ self.loaded_lora = (lora_path, lora)
+
+ # =========================================
+
+ try:
+ model_lora, clip_lora = comfy.sd.load_lora_for_models(model, clip, lora_path, strength_model, strength_clip)
+ return (model_lora, clip_lora)
+ except Exception as e:
+ console.print_exception()
+ return (model, clip)
+
+#NODE_CLASS_MAPPINGS = {
+# "LoraLoaderTextRandom": LoraLoaderTextRandom,
+#}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_node_Lilly/README.md b/custom_nodes/ComfyUI_node_Lilly/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..111f764c4b60095e4949589540aeb3ccb3df7c1d
--- /dev/null
+++ b/custom_nodes/ComfyUI_node_Lilly/README.md
@@ -0,0 +1,149 @@
+# ComfyUI-node-Lilly
+
+## install
+
+Go to ./custom_nodes and clone git repo:
+
+```
+cd ./custom_nodes
+git clone https://github.com/kuriot/ComfyUI_node_Lilly.git
+```
+or https://github.com/lilly1987/ComfyUI_node_Lilly/archive/refs/heads/main.zip install this like
+
+
+
+
+## wildcards
+
+### ex - wildcard
+
+- form :
+a{__b__|{c|}|{__d__|e|}|f|}g____ __my__
+
+- to :
+aeg __quality_my__, __breasts__, { |__character_dress__|__dress_my__}, __shoulder__, {high heels,| } {choker,| } {,| } NSFW, __NSFW_my__, { |__style_my__,}
+
+```
+ex : {3$$a1|{b2|c3|}|d4|{-$$|f|g}|{-2$$h||i}|{1-$$j|k|}}/{$$l|m|}/{0$$n|}
+{1|2|3} -> 1 or 2 or 3
+{2$$a|b|c} -> a,b or b,c or c,a or bb or ....
+{9$$a|b|c} -> {3$$a|b|c} auto fix max count
+{1-2$$a|b|c} -> 1~2 random choise
+{-2$$a|b|c} -> {0-2$$a|b|c} 0-2
+{1-$$a|b|c} -> {0-3$$a|b|c} 1-max
+{-$$a|b|c} -> {0-3$$a|b|c} 0-max
+```
+
+### ex - wildcard text file use
+
+- ~/a/b.txt
+```
+1
+```
+- ~/b.txt
+```
+2
+```
+
+- __b__ to 1 or 2
+- __/b__ to 2
+- __/a/b__ to 1
+- __?b__ to 2
+- __*__ to 1 or 2
+
+### filename pattern matching
+- \* is matches everything
+- ? is matches any single character
+- \[seq\] is matches any character in seq
+- \[!seq\] is matches any character not in seq
+- reference https://docs.python.org/3/library/fnmatch.html
+
+### run sample
+
+```
+python wildcards.py
+```
+
+### python sample
+
+```
+import wildcards as w
+
+# 가져올 파일 목록. get wildcards file
+w.card_path=os.path.dirname(__file__)+"\\wildcards\\**\\*.txt"
+
+# 실행 run
+print(w.run("a{__b__|{c|}|{__d__|e|}|f|}g____ __my__"))
+```
+
+
+
+### txt file (supports .txt files in different encodings.)
+from
+```
+# 주석 comment
+a,b
+{b|c|__anotherfile__}
+__anotherfile__
+```
+result
+```
+a,b
+b
+c
+__anotherfile__
+```
+
+### reload card
+
+call wildcards.card_load()
+or
+wildcards.run("{9$$-$$a|b|c}",True)
+
+## for ComfyUI
+
+
+
+### CLIPTextEncodeWildcards
+
+- CLIPTextEncodeWildcards : no seed
+- CLIPTextEncodeWildcards : seed
+
+
+
+
+### SimpleSampler+modelVAE
+
+- include wildcards
+
+
+
+
+### SimpleSampler
+
+- include wildcards
+
+
+
+
+### SimpleSamplerVAE
+
+- include wildcards
+
+
+
+### VAELoaderText , LoraLoaderText , CheckpointLoaderSimpleText
+
+- support file name Wildcard(?*)
+
+
+
+
+
+### random_sampler_node.py
+
+
+
+### VAELoaderDecode.py
+
+
diff --git a/custom_nodes/ComfyUI_node_Lilly/Random_Sampler.py b/custom_nodes/ComfyUI_node_Lilly/Random_Sampler.py
new file mode 100644
index 0000000000000000000000000000000000000000..2f6d1c35f25f18879c9b6e17bb6307a288f9c287
--- /dev/null
+++ b/custom_nodes/ComfyUI_node_Lilly/Random_Sampler.py
@@ -0,0 +1,104 @@
+import os
+import nodes
+import comfy.samplers
+import random
+from nodes import common_ksampler
+
+#wd = os.getcwd()
+#print("working directory is ", wd)
+#
+#filePath = __file__
+#print("This script file path is ", filePath)
+#
+#absFilePath = os.path.abspath(__file__)
+#print("This script absolute path is ", absFilePath)
+#
+#path, filename = os.path.split(absFilePath)
+#print("Script file path is {}, filename is {}".format(path, filename))
+
+
+class Random_Sampler:
+ def __init__(self):
+ print(f"Random_Sampler __init__")
+ pass
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "model": ("MODEL",),
+ "positive": ("CONDITIONING", ),
+ "negative": ("CONDITIONING", ),
+ "LATENT": ("LATENT", ),
+ "sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
+ "scheduler": (comfy.samplers.KSampler.SCHEDULERS, ),
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
+ #"Random": (["enable", "disable"],),
+ "steps_min": ("INT", {"default": 20, "min": 1,"max": 10000, "step": 1 }),
+ "steps_max": ("INT", {"default": 30, "min": 1,"max": 10000, "step": 1 }),
+ "cfg_min": ("FLOAT", {"default": 5.0, "min": 0.0, "max": 100.0, "step": 0.5}),
+ "cfg_max": ("FLOAT", {"default": 9.0, "min": 0.0, "max": 100.0, "step": 0.5}),
+ "denoise_min": ("FLOAT", {"default": 0.50, "min": 0.01, "max": 1.0, "step": 0.01}),
+ "denoise_max": ("FLOAT", {"default": 1.00, "min": 0.01, "max": 1.0, "step": 0.01}),
+ },
+ }
+
+ RETURN_TYPES = ("LATENT",)
+ FUNCTION = "test"
+
+ OUTPUT_NODE = False
+
+ CATEGORY = "sampling"
+
+ def test(self,
+ model,
+ positive,
+ negative,
+ LATENT,
+ sampler_name,
+ scheduler,
+ seed,
+ #Random,
+ steps_min,
+ steps_max,
+ cfg_min,
+ cfg_max,
+ denoise_min,
+ denoise_max,
+ ):
+ print(f"""
+ model : {model} ;
+ positive : {positive} ;
+ negative : {negative} ;
+ LATENT: {LATENT} ;
+ sampler_name : {sampler_name} ;
+ scheduler: {scheduler} ;
+ {seed} ;
+
+ {steps_min} ;
+ {steps_max} ;
+ {cfg_min} ;
+ {cfg_max} ;
+ {denoise_min} ;
+ {denoise_max} ;
+ """)
+ #if Random == "enable":
+ # print(f"Random enable")
+ # return common_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise=denoise)
+ return common_ksampler(
+ model,
+ seed,
+ random.randint( min(steps_min,steps_max), max(steps_min,steps_max) ),
+ random.randint( int(cfg_min*2) , int(cfg_max*2) ) / 2 ,
+ sampler_name,
+ scheduler,
+ positive,
+ negative,
+ LATENT,
+ denoise=random.uniform(min(denoise_min,denoise_max),max(denoise_min,denoise_max))
+ )
+ #return (LATENT,)
+
+#NODE_CLASS_MAPPINGS = {
+# "Random_Sampler": Random_Sampler
+#}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_node_Lilly/SaveImageSimple.py b/custom_nodes/ComfyUI_node_Lilly/SaveImageSimple.py
new file mode 100644
index 0000000000000000000000000000000000000000..c8cecf661fdb2a5a6b924635ec2b0a3ac6d8e4c7
--- /dev/null
+++ b/custom_nodes/ComfyUI_node_Lilly/SaveImageSimple.py
@@ -0,0 +1,75 @@
+from PIL import Image
+from PIL.PngImagePlugin import PngInfo
+import numpy as np
+import json
+import re
+
+import time
+
+import os
+if __name__ == os.path.splitext(os.path.basename(__file__))[0] :
+ from ConsoleColor import print, console
+ from mypath import *
+else:
+ from .ConsoleColor import print, console
+ from .mypath import *
+#print(__file__)
+#print(os.path.basename(__file__))
+
+
+class SaveImageSimple:
+ def __init__(self):
+ self.type = "output"
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "images": ("IMAGE", ),
+ "filename_prefix": ("STRING", {"default": ""})
+ },
+ "hidden": {
+ "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"
+ },
+ }
+ RETURN_TYPES = ()
+ FUNCTION = "save_images"
+
+ OUTPUT_NODE = True
+
+ CATEGORY = "image"
+
+ def save_images(self, images, filename_prefix="", prompt=None, extra_pnginfo=None):
+
+ outputdir=os.path.join(mainfolder, "output")
+ #print("outputdir : " + outputdir , Colors.CYAN)
+
+ #print("len(images) : " + str(len(images)) , Colors.CYAN)
+ filename_prefix=re.sub(r"[*]", "",filename_prefix)
+ filename_prefix+=time.strftime('_%Y%m%d_%H%M%S')
+ results = list()
+ cnt=1
+ for image in images :
+ i = 255. * image.cpu().numpy()
+ img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
+ metadata = PngInfo()
+ if prompt is not None:
+ metadata.add_text("prompt", json.dumps(prompt))
+ if extra_pnginfo is not None:
+ #print("extra_pnginfo : " + json.dumps(extra_pnginfo) , Colors.CYAN)
+ for x in extra_pnginfo:
+ metadata.add_text(x, json.dumps(extra_pnginfo[x]))
+ if not os.path.exists(outputdir):
+ print("makedirs : " + outputdir )
+ os.makedirs(outputdir)
+ filename=filename_prefix+f"_{cnt:05}_.png"
+ filename=os.path.join(outputdir, filename)
+ img.save(filename, pnginfo=metadata, optimize=True)
+ results.append({
+ "filename": filename,
+ "subfolder": subfolder,
+ "type": self.type
+ });
+ cnt+=1
+
+ return { "ui": { "images": results } }
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_node_Lilly/SimpleSampler.py b/custom_nodes/ComfyUI_node_Lilly/SimpleSampler.py
new file mode 100644
index 0000000000000000000000000000000000000000..54fd1758a4183f215a54e3895d9e8c2a231d5549
--- /dev/null
+++ b/custom_nodes/ComfyUI_node_Lilly/SimpleSampler.py
@@ -0,0 +1,187 @@
+import comfy.samplers
+import comfy.sd
+import comfy.utils
+
+#import comfy_extras.clip_vision
+
+import model_management
+import importlib
+
+import folder_paths
+import torch
+
+import os
+import sys
+import json
+import hashlib
+import copy
+import traceback
+
+
+from PIL import Image
+from nodes import common_ksampler
+from PIL.PngImagePlugin import PngInfo
+import numpy as np
+
+#print(f"SimpleSampler __name__ {__name__}")
+#print(f"SimpleSampler __file__ {os.path.splitext(os.path.basename(__file__))[0]}")
+
+import os
+
+
+if __name__ == os.path.splitext(os.path.basename(__file__))[0] or __name__ =='__main__':
+ from ConsoleColor import print, console
+ from wildcards import wildcards
+else:
+ from .ConsoleColor import print, console
+ from .wildcards import wildcards
+#print(__file__)
+#print(os.path.basename(__file__))
+
+#----------------------------
+wildcardsOn=True
+# wildcards support check
+#wildcardsOn=False
+#try:
+# wildcardsOn=True
+# #wildcards.card_path=os.path.dirname(__file__)+"\\..\\wildcards\\**\\*.txt"
+# print(f"import wildcards succ", style="bold GREEN" )
+#except:
+# print(f"import wildcards fail", style="bold RED")
+# wildcardsOn=False
+# err_msg = traceback.format_exc()
+# print(err_msg)
+
+
+def encode(clip, text):
+ if wildcardsOn:
+ text=wildcards.run(text)
+ return [[clip.encode(text), {}]]
+
+def generate(width, height, batch_size=1):
+ latent = torch.zeros([batch_size, 4, height // 8, width // 8])
+ return {"samples":latent}
+ # RETURN_TYPES = ("LATENT",)
+
+def decode(vae, samples):
+ return vae.decode(samples["samples"])
+ # RETURN_TYPES = ("IMAGE",)
+
+def sample(
+ model, seed, steps, cfg, sampler_name, scheduler,
+ clip,
+ vae,
+ positive, negative,
+ #latent_image,
+ width, height, denoise=1.0, batch_size=1
+ ):
+
+ samples=common_ksampler(
+ model, seed, steps, cfg, sampler_name, scheduler,
+ #positive,
+ encode(clip, positive),
+ #negative,
+ encode(clip, negative),
+ #latent_image,
+ generate( width, height, batch_size=1),
+ denoise=denoise)[0]
+
+ return (decode(vae,samples),)
+
+def load_vae(vae_name):
+ vae_path = folder_paths.get_full_path("vae", vae_name)
+ vae = comfy.sd.VAE(ckpt_path=vae_path)
+ return vae
+#----------------------------
+class SimpleSampler:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required":
+ {
+ "model": ("MODEL",),
+ #"positive": ("CONDITIONING", ),
+ "clip": ("CLIP", ),
+ "vae": ("VAE", ),
+ "positive": ("STRING", {"multiline": True}),
+ #"negative": ("CONDITIONING", ),
+ "negative": ("STRING", {"multiline": True}),
+ "width": ("INT", {"default": 512, "min": 64, "max": 4096, "step": 64}),
+ "height": ("INT", {"default": 512, "min": 64, "max": 4096, "step": 64}),
+ "batch_size": ("INT", {"default": 1, "min": 1, "max": 64}),
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
+ "steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
+ "cfg": ("FLOAT", {"default": 3.0, "min": 0.0, "max": 100.0}),
+ "sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
+ "scheduler": (comfy.samplers.KSampler.SCHEDULERS, ),
+ #"latent_image": ("LATENT", ),
+ "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
+ }}
+ RETURN_TYPES = ("IMAGE",)
+ #RETURN_TYPES = ("LATENT",)
+ FUNCTION = "simple"
+
+ CATEGORY = "sampling"
+
+ def simple(self,
+ model, seed, steps, cfg, sampler_name, scheduler,
+ clip,
+ vae,
+ positive, negative,
+ width, height, denoise=1.0, batch_size=1
+ ):
+
+ return sample(
+ model, seed, steps, cfg, sampler_name, scheduler,
+ clip,
+ vae,
+ positive, negative,
+ width, height, denoise, batch_size
+ )
+
+#----------------------------
+class SimpleSamplerVAE:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required":
+ {
+ "model": ("MODEL",),
+ #"positive": ("CONDITIONING", ),
+ "clip": ("CLIP", ),
+ #"vae": ("VAE", ),
+ "vae_name": (folder_paths.get_filename_list("vae"), ),
+ "positive": ("STRING", {"multiline": True}),
+ #"negative": ("CONDITIONING", ),
+ "negative": ("STRING", {"multiline": True}),
+ "width": ("INT", {"default": 512, "min": 64, "max": 4096, "step": 64}),
+ "height": ("INT", {"default": 512, "min": 64, "max": 4096, "step": 64}),
+ "batch_size": ("INT", {"default": 1, "min": 1, "max": 64}),
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
+ "steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
+ "cfg": ("FLOAT", {"default": 3.0, "min": 0.0, "max": 100.0}),
+ "sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
+ "scheduler": (comfy.samplers.KSampler.SCHEDULERS, ),
+ #"latent_image": ("LATENT", ),
+ "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
+ }}
+ RETURN_TYPES = ("IMAGE",)
+ #RETURN_TYPES = ("LATENT",)
+ FUNCTION = "simple"
+
+ CATEGORY = "sampling"
+
+ def simple(self,
+ model, seed, steps, cfg, sampler_name, scheduler,
+ clip,
+ vae_name,
+ positive, negative,
+ width, height, denoise=1.0, batch_size=1
+ ):
+
+ return sample(
+ model, seed, steps, cfg, sampler_name, scheduler,
+ clip,
+ load_vae(vae_name),
+ positive, negative,
+ width, height, denoise, batch_size
+ )
+
diff --git a/custom_nodes/ComfyUI_node_Lilly/TextWildcards.py b/custom_nodes/ComfyUI_node_Lilly/TextWildcards.py
new file mode 100644
index 0000000000000000000000000000000000000000..1b153c293a40b9ac1159812a091a060afb81873f
--- /dev/null
+++ b/custom_nodes/ComfyUI_node_Lilly/TextWildcards.py
@@ -0,0 +1,33 @@
+import os, glob, sys
+import random
+import re
+import os
+if __name__ == os.path.splitext(os.path.basename(__file__))[0] or __name__ =='__main__':
+ from ConsoleColor import print, console
+ from wildcards import wildcards
+else:
+ from .ConsoleColor import print, console
+ from .wildcards import wildcards
+
+
+class TextWildcards:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "text": ("STRING", {"multiline": True}),
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
+
+ }
+ }
+ RETURN_TYPES = ("STRING","ASCII")
+ FUNCTION = "encode"
+
+ CATEGORY = "utils"
+
+ def encode(self, seed, text):
+ random.seed(seed)
+ print(f"[green]text : [/green]",text)
+ r=wildcards.run(text)
+ print(f"[green]result : [/green]",r)
+ return (r, r)
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_node_Lilly/VAELoaderDecode.py b/custom_nodes/ComfyUI_node_Lilly/VAELoaderDecode.py
new file mode 100644
index 0000000000000000000000000000000000000000..736b7844609b72bdfbd8074da645e632e3e3b291
--- /dev/null
+++ b/custom_nodes/ComfyUI_node_Lilly/VAELoaderDecode.py
@@ -0,0 +1,75 @@
+import os
+import comfy.sd
+from nodes import *
+import folder_paths
+
+
+# VAEDecode
+# VAELoader
+# VAELoaderDecode
+
+#wd = os.getcwd()
+#print("working directory is ", wd)
+#
+#filePath = __file__
+#print("This script file path is ", filePath)
+#
+#absFilePath = os.path.abspath(__file__)
+#print("This script absolute path is ", absFilePath)
+#
+#realFilePath = os.path.realpath(__file__)
+#print("This script real path is ", realFilePath)
+#
+#path, filename = os.path.split(absFilePath)
+#print("Script file path is {}, filename is {}".format(path, filename))
+
+class VAELoaderDecode:
+
+ def __init__(self, device="cpu"):
+ self.device = device
+
+ #@classmethod
+ #def INPUT_TYPES(s):
+ # return {"required": { "samples": ("LATENT", ), "vae": ("VAE", )}}
+ #
+ #@classmethod
+ #def INPUT_TYPES(s):
+ # return {"required": { "vae_name": (filter_files_extensions(recursive_search(s.vae_dir), supported_pt_extensions), )}}
+
+ @classmethod
+ def INPUT_TYPES(s):
+
+ return {
+ "required": {
+ "samples": ("LATENT", ),
+ "vae_name": (folder_paths.get_filename_list("vae"), )
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE",)
+
+ FUNCTION = "test"
+
+ CATEGORY = "latent"
+
+ #TODO: scale factor?
+ #def load_vae(self, vae_name):
+ # vae_path = os.path.join(self.vae_dir, vae_name)
+ # vae = comfy.sd.VAE(ckpt_path=vae_path)
+ # return (vae,)
+ #
+ #def decode(self, vae, samples):
+ # return (vae.decode(samples["samples"]), )
+
+ def test(self, vae_name, samples):
+
+ t=folder_paths.get_filename_list("vae")
+ print(f"VAELoaderDecode : {t}")
+ vae_path = folder_paths.get_full_path("vae", vae_name)
+ print(f"VAELoaderDecode : {vae_path}")
+ vae = comfy.sd.VAE(ckpt_path=vae_path)
+ return (vae.decode(samples["samples"]), )
+
+#NODE_CLASS_MAPPINGS = {
+# "VAELoaderDecode": VAELoaderDecode,
+#}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_node_Lilly/VAELoaderText.py b/custom_nodes/ComfyUI_node_Lilly/VAELoaderText.py
new file mode 100644
index 0000000000000000000000000000000000000000..144f23e7996e877900ed2b30c51b3df750246fc9
--- /dev/null
+++ b/custom_nodes/ComfyUI_node_Lilly/VAELoaderText.py
@@ -0,0 +1,38 @@
+import folder_paths
+import comfy.sd
+import os
+from folder_paths import *
+if __name__ == os.path.splitext(os.path.basename(__file__))[0] :
+ from ConsoleColor import print, console, ccolor
+ from mypath import *
+else:
+ from .ConsoleColor import print, console, ccolor
+ from .mypath import *
+
+class VAELoaderText:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {
+ "vae_name": ("STRING", {
+ "multiline": False, #True if you want the field to look like the one on the ClipTextEncode node
+ "default": random.choice(folder_paths.get_filename_list("vae"))
+ }),
+ }}
+ RETURN_TYPES = ("VAE",)
+ FUNCTION = "load_vae"
+
+ CATEGORY = "loaders"
+
+ #TODO: scale factor?
+ def load_vae(self, vae_name):
+ print(f"[{ccolor}]vae_name : [/{ccolor}]", vae_name)
+ vae_path=getFullPath(vae_name,"vae")
+
+
+ try:
+ sd = comfy.utils.load_torch_file(vae_path)
+ vae = comfy.sd.VAE(sd=sd)
+ return (vae,)
+ except Exception as e:
+ console.print_exception()
+ return
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_node_Lilly/__init__.py b/custom_nodes/ComfyUI_node_Lilly/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1c3108808ec430a6a247c7ee6d15923286c82a89
--- /dev/null
+++ b/custom_nodes/ComfyUI_node_Lilly/__init__.py
@@ -0,0 +1,102 @@
+import sys
+import os
+
+
+print(f"__name__ : {__name__}")
+print(f"__file__ : {__file__}")
+print(f"os.path.basename(__file__) : {os.path.basename(__file__)}")
+print(f"os.path.splitext(os.path.basename(__file__))[0] : {os.path.splitext(os.path.basename(__file__))[0]}")
+#print(f"os.path.basename(__file__) : {os.path.basename('/ComfyUI_windows_portable/ComfyUI/custom_nodes/ComfyUI_node_Lilly/__init__.py')}")
+#print(f"os.path.splitext(os.path.basename(__file__))[0] : {os.path.splitext(os.path.basename('/ComfyUI_windows_portable/ComfyUI/custom_nodes/ComfyUI_node_Lilly/__init__.py'))[0]}")
+
+wd = os.getcwd()
+print("working directory : ", wd)
+
+if __name__ == os.path.splitext(os.path.basename(__file__))[0] or __name__ =='__main__':
+ from ConsoleColor import print, console
+ #md="custom_nodes.ComfyUI_node_Lilly."
+else:
+ from .ConsoleColor import print, console
+ #md="custom_nodes.ComfyUI_node_Lilly."
+md="custom_nodes.ComfyUI_node_Lilly."
+#print(__file__)
+#print(os.path.basename(__file__))
+
+#print(f"sys.modules : {sys.modules}")
+
+#filePath = __file__
+#print("This script file path is ", filePath)
+#
+#absFilePath = os.path.abspath(__file__)
+#print("This script absolute path is ", absFilePath)
+#
+#realFilePath = os.path.realpath(__file__)
+#print("This script real path is ", realFilePath)
+#
+#path, filename = os.path.split(absFilePath)
+#print("Script file path is {}, filename is {}".format(path, filename))
+
+
+#
+#nm=os.path.abspath(__name__)
+#nm=os.path.abspath(__name__)
+#print("abspath __name__ : ", nm)
+#print("abspath __name__ : ", nm)
+#
+#md=nm.replace(wd+"\\","")
+#print("import name", md)
+"""
+"""
+#if md in sys.modules:
+# print(f"{md!r} already in sys.modules")
+#else:
+# print(f"{md!r} not in sys.modules")
+
+#import importlib
+#import ComfyUI_node_Lilly
+#from custom_nodes.ComfyUI_node_Lilly import eval(f"{name}")
+#print(dir(ComfyUI_node_Lilly))
+#print(dir(ComfyUI_node_Lilly.ComfyUI_node_Lilly))
+
+#print(__name__ == md)
+#print(__name__ != md)
+#print(__name__ == "ComfyUI_node_Lilly")
+#print(__name__ != "ComfyUI_node_Lilly")
+if __name__ == "ComfyUI_node_Lilly" :
+ NODE_CLASS_MAPPINGS = {
+ }
+
+ def add(name,clist=None):
+ #print(f"Load : {name}")
+ try:
+ #pkg = importlib.import_module(f"{md}{name}")
+ #eval(f"{md}{name}")
+ exec(f"import {md}{name}")
+ if clist is None:
+ NODE_CLASS_MAPPINGS[name]=eval(f"{md}{name}.{name}")
+ elif type(clist) is str:
+ NODE_CLASS_MAPPINGS[clist]=eval(f"{md}{name}.{clist}")
+ elif type(clist) is list:
+ for c in clist:
+ NODE_CLASS_MAPPINGS[c]=eval(f"{md}{name}.{c}")
+
+ print(f"Load ok : {name}", style="bold green")
+ except Exception:
+ console.print_exception()
+
+ console.rule(f" init start ", style="bold green")
+
+ add("CheckpointLoaderRandom")
+ add("CheckpointLoaderSimpleText")
+ add("CLIPTextEncodeWildcards",["CLIPTextEncodeWildcards","CLIPTextEncodeWildcards2","CLIPTextEncodeWildcards3"])
+ add("LoraLoaderText")
+ add("LoraLoaderTextRandom")
+ add("Random_Sampler")
+ add("VAELoaderDecode")
+ add("VAELoaderText")
+ add("SimpleSampler",["SimpleSampler","SimpleSamplerVAE"])
+ add("SaveImageSimple")
+ add("TextWildcards")
+ #add("test")
+
+ console.rule(" init end ", style="bold green")
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/CLIPTextEncodeWildcards.cpython-310.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/CLIPTextEncodeWildcards.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e04ccc22a11f52dde250dca953a869469e7affa7
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/CLIPTextEncodeWildcards.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/CLIPTextEncodeWildcards.cpython-311.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/CLIPTextEncodeWildcards.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..54386bec542f9115fdf3cf3a0d2ed71969c2def8
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/CLIPTextEncodeWildcards.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/CheckpointLoaderRandom.cpython-310.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/CheckpointLoaderRandom.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..226d6efc81a28ff4cf60d57639301856e84fd187
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/CheckpointLoaderRandom.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/CheckpointLoaderRandom.cpython-311.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/CheckpointLoaderRandom.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..fe2ba89c3d9209d848af7c44c68726e68e4d49e7
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/CheckpointLoaderRandom.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/CheckpointLoaderSimpleText.cpython-310.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/CheckpointLoaderSimpleText.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7bb7261ca6301593f04f1be079aa029027453a74
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/CheckpointLoaderSimpleText.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/CheckpointLoaderSimpleText.cpython-311.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/CheckpointLoaderSimpleText.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e72933f4defa8a53d9a4321fa21a7e14e18e4ac2
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/CheckpointLoaderSimpleText.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/ConsoleColor.cpython-310.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/ConsoleColor.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5a124577066fc7ec2d4792e38bd15c5cc065ea99
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/ConsoleColor.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/ConsoleColor.cpython-311.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/ConsoleColor.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f935b78e4e2050592525b26d6deeec3f6bb39e32
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/ConsoleColor.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/LoraLoaderText.cpython-310.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/LoraLoaderText.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2a56dcb8176fa338e79e25934729d3d1b290b6c4
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/LoraLoaderText.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/LoraLoaderText.cpython-311.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/LoraLoaderText.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..70e210615392f45d7b8e055c035c5f796d9b3088
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/LoraLoaderText.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/LoraLoaderTextRandom.cpython-310.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/LoraLoaderTextRandom.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..20a9c4cef36d0294cc8f8ebf78421ab18c73c406
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/LoraLoaderTextRandom.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/LoraLoaderTextRandom.cpython-311.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/LoraLoaderTextRandom.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d7d4134d1774e5e3a41938306b712f8075290af1
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/LoraLoaderTextRandom.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/Random_Sampler.cpython-310.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/Random_Sampler.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..17892a4a78e8d6f4714ceee5eb6616ec6577fc72
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/Random_Sampler.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/Random_Sampler.cpython-311.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/Random_Sampler.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..cff518b962edea21d1265f7d26861b9438c90aa6
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/Random_Sampler.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/SaveImageSimple.cpython-310.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/SaveImageSimple.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c50517248801c2ec49b251ebe53d1593024b320c
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/SaveImageSimple.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/SaveImageSimple.cpython-311.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/SaveImageSimple.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ae81a1007882bc4a5a24237684d559db42d9273a
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/SaveImageSimple.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/SimpleSampler.cpython-310.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/SimpleSampler.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c14fcad17d93fbb4678591b2de5a8e9cb3e6d281
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/SimpleSampler.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/SimpleSampler.cpython-311.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/SimpleSampler.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..98399ac85f145541d86138f72db15a0963fe70d9
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/SimpleSampler.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/TextWildcards.cpython-310.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/TextWildcards.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7e410b5528dde8e42907c9f2f7af90e78aa51b2c
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/TextWildcards.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/TextWildcards.cpython-311.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/TextWildcards.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..575b5186342305a5f5866493148b381d56177292
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/TextWildcards.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/VAELoaderDecode.cpython-310.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/VAELoaderDecode.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a5b288d65e2d7e2cbc4814595271a0269becf162
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/VAELoaderDecode.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/VAELoaderDecode.cpython-311.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/VAELoaderDecode.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c1790485587727ee171f41249eafb226fe1d369a
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/VAELoaderDecode.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/VAELoaderText.cpython-310.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/VAELoaderText.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ef8695fa90e3d9bbbec6377593cb6c9193dce04b
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/VAELoaderText.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/VAELoaderText.cpython-311.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/VAELoaderText.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4c2ffe2213e45a9239d8a0799cc0bcdf6a249ca0
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/VAELoaderText.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/__init__.cpython-310.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..85308036df68f3b5b544a4c8797ade99019eb523
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/__init__.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/__init__.cpython-311.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8c6275c1b63f6f97ec759de661459a0cc4526178
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/__init__.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/mypath.cpython-310.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/mypath.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a6ac614df83661d7681e696970d437a332fd8052
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/mypath.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/mypath.cpython-311.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/mypath.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..da264e6b5f5c3822a1392695096e46d26442ddb9
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/mypath.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/wildcards.cpython-310.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/wildcards.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..918016f3d14158bcd072c50230e728f26e652443
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/wildcards.cpython-310.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/__pycache__/wildcards.cpython-311.pyc b/custom_nodes/ComfyUI_node_Lilly/__pycache__/wildcards.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..663f24c43f07c15d92fdb7b86e4741fbdb5e00c1
Binary files /dev/null and b/custom_nodes/ComfyUI_node_Lilly/__pycache__/wildcards.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_node_Lilly/mypath.py b/custom_nodes/ComfyUI_node_Lilly/mypath.py
new file mode 100644
index 0000000000000000000000000000000000000000..354ba8aab1e9604e3e185e54347f50c73be08ed7
--- /dev/null
+++ b/custom_nodes/ComfyUI_node_Lilly/mypath.py
@@ -0,0 +1,112 @@
+import sys
+import json
+import ast
+import os, glob
+import random
+from folder_paths import *
+if __name__ == os.path.splitext(os.path.basename(__file__))[0] :
+ from ConsoleColor import print, console
+else:
+ from .ConsoleColor import print, console
+
+"""
+import psutil
+for proc in psutil.process_iter():
+ ps_name = proc.name()
+ if ps_name == 'python3':
+ cmdline = proc.cmdline()
+ print(cmdline)
+"""
+
+"""
+print()
+for key, value in os.environ.items():
+ print('{}: {}'.format(key, value))
+print()
+"""
+
+py_name=os.path.basename(__file__)
+#print("os.path.basename(__file__) : ",py_name, style="bold CYAN")
+
+absFilePath = os.path.abspath(__file__)
+#print("os.path.abspath(__file__) : " , absFilePath , style="bold CYAN")
+
+realFilePath = os.path.realpath(__file__)
+#print("os.path.abspath(__file__) : " + realFilePath , style="bold CYAN")
+
+normpath=os.path.normpath(__file__)
+#print("os.path.normpath(__file__) : " + normpath , style="bold CYAN")
+
+subfolder = os.path.dirname(normpath)
+#print("os.path.dirname(normpath) : " + subfolder , style="bold CYAN")
+
+filename = os.path.basename(normpath)
+#print("os.path.basename(normpath) : " + filename , style="bold CYAN")
+
+mainFile = os.path.abspath(sys.modules['__main__'].__file__)
+#print("os.path.abspath(sys.modules\['__main__'].__file__) : " + mainFile ,style="bold CYAN")
+mainfolder = os.path.dirname(mainFile)
+#print("os.path.dirname(mainFile) : " + mainfolder , style="bold CYAN")
+
+def check_name(kind,name,supported_extensions):
+ for ext in supported_extensions:
+ if name.lower().endswith(ext):
+ path = folder_paths.get_full_path(kind, name)
+ if path is not None:
+ return path
+
+ for ext in supported_extensions:
+ path = folder_paths.get_full_path(kind, name+ext)
+ if path is not None:
+ return path
+
+def check_name_ckpt(name):
+ return check_name("checkpoints",name,supported_ckpt_extensions)
+
+def check_name_pt(kind,name):
+ return check_name(kind,name,supported_pt_extensions)
+
+def name_split_choice(name):
+ return random.choice(name.split('|'))
+
+#----------------------
+
+def filenameget(v_path):
+ t_path=os.path.join(os.path.dirname(__file__),v_path)
+ print(t_path)
+ fullpaths=glob.glob(t_path, recursive=True)
+ print(fullpaths)
+ fullpath=random.choice(fullpaths)
+ name=os.path.basename(fullpath)
+ #r_path=[os.path.basename(fullpath) for fullpath in fullpaths]
+ return (name,fullpath)
+
+# "test","vae",["pt","safetensors"]
+def getFullPath(p,k,el=["safetensors","ckpt","pt"]):
+ if os.path.isabs(p):
+ path=p
+ else:
+ path=os.path.join(models_dir,k,"**",p)
+ #print(f"path : ", path)
+ t=False
+ for e in el:
+ if p.endswith('.'+e):
+ t=True
+ break
+ if t:
+ files=glob.glob(path, recursive=True)
+ else:
+ for e in el:
+ t=path+"."+e
+ #print(f"t : ", t)
+ files=glob.glob(t, recursive=True)
+ if len(files):
+ break
+ result=None
+ #print(f"files : ", files)
+ if len(files):
+ result=random.choice(files)
+ print(f"result : ", result)
+ else:
+ print("[red]No file in path[/red] : ", path)
+ return result
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_node_Lilly/wildcards.py b/custom_nodes/ComfyUI_node_Lilly/wildcards.py
new file mode 100644
index 0000000000000000000000000000000000000000..00ab845f30bae21616d3b5ce39092335ae405fc9
--- /dev/null
+++ b/custom_nodes/ComfyUI_node_Lilly/wildcards.py
@@ -0,0 +1,223 @@
+import os
+import glob
+import sys
+import random
+import re
+import fnmatch
+
+if __name__ == os.path.splitext(os.path.basename(__file__))[0] or __name__ =='__main__':
+ from ConsoleColor import print, console, ccolor
+else:
+ from .ConsoleColor import print, console , ccolor
+#print(__file__)
+#print(os.path.basename(__file__))
+#print(os.getcwd())
+
+import subprocess
+import pkg_resources
+
+required = {'chardet'}
+installed = {pkg.key for pkg in pkg_resources.working_set}
+missing = required - installed
+
+if missing:
+ python = sys.executable
+ subprocess.check_call([python, '-m', 'pip', 'install', *missing], stdout=subprocess.DEVNULL)
+
+import chardet
+
+
+# ============================================================
+class wildcards:
+
+ # 가져올 파일 목록
+ card_path = os.path.join(os.path.dirname(__file__), "..", "..", "wildcards", "**", "*.txt")
+ #card_path=f"{os.getcwd()}\\wildcards\\**\\*.txt"
+ print(f"wildcards card_path : ", card_path , style="bold CYAN")
+
+ # 정규식
+ #resub = re.compile(r"(\{)([^\{\}]*)(\})")
+ #resub = re.compile(r"(\{)(((\d+)|(\d+)?-(\d+)?)?\$\$((.*)?\$\$)?)?([^\{\}]*)(\})")
+ resub = re.compile(r"(\{)(((\d+)|(\d+)?-(\d+)?)?\$\$(([^\{\}]*?)\$\$)?)?([^\{\}]*)(\})")
+ recard = re.compile(r"(__)(.*?)(__)")
+
+ # 카드 목록
+ is_card_Load = False
+ cards = {}
+ seperator=", "
+ loop_max=50
+
+ # | 로 입력된것중 하나 가져오기
+ def sub(match):
+ #print(f"sub : {(match.groups())}")
+ try:
+ #m=match.group(2)
+ seperator=wildcards.seperator
+ s=match.group(3)
+ m=match.group(9).split("|")
+ p=match.group(8)
+ if p:
+ seperator=p
+
+ if s is None:
+ return random.choice(m)
+ c=len(m)
+ n=int(match.group(4)) if match.group(4) else None
+ if n:
+
+ r=seperator.join(random.sample(m,min(n,c)))
+ #print(f"n : {n} ; {r}")
+ return r
+
+ n1=match.group(5)
+ n2=match.group(6)
+
+ if n1 or n2:
+ a=min(int(n1 if n1 else c), int(n2 if n2 else c),c)
+ b=min(max(int(n1 if n1 else 0), int(n2 if n2 else 0)),c)
+ #print(f"ab : {a} ; {b}")
+ r=seperator.join(
+ random.sample(
+ m,
+ random.randint(
+ a,b
+ )
+ )
+ )
+ #n1=int(match.group(5)) if not match.group(5) is None
+ #n2=int(match.group(6)) if not match.group(6) is None
+ else:
+ r=seperator.join(
+ random.sample(
+ m,
+ random.randint(
+ 0,c
+ )
+ )
+ )
+ #print(f"12 : {r}")
+ return r
+
+
+ except Exception as e:
+ console.print_exception()
+ return ""
+
+
+
+ # | 로 입력된것중 하나 가져오기 반복
+ def sub_loop(text):
+ bak=text
+ for i in range(1, wildcards.loop_max):
+ tmp=wildcards.resub.sub(wildcards.sub, bak)
+ #print(f"tmp : {tmp}")
+ if bak==tmp :
+ return tmp
+ bak=tmp
+ return bak
+
+ # 카드 중에서 가져오기
+ def card(match):
+ #print(f"card in : {match.group(2)}")
+ lst=fnmatch.filter(wildcards.cards, match.group(2))
+ if len(lst)>0:
+ #print(f"card lst : {lst}")
+ cd=random.choice(lst)
+ #print(f"card get : {cd}")
+ r=random.choice(wildcards.cards[cd])
+ else :
+ r= match.group(2)
+ #print(f"card out : {r}")
+ return r
+
+
+ # 카드 중에서 가져오기 반복. | 의 것도 처리
+ def card_loop(text):
+ bak=text
+ for i in range(1, wildcards.loop_max):
+ tmp=wildcards.recard.sub(wildcards.card, bak)
+ #print(f"card l : {bak}")
+ if bak==tmp :
+ tmp=wildcards.sub_loop(tmp)
+
+ if bak==tmp :
+ #print(f"card le : {bak}")
+ return tmp
+ bak=tmp
+ #print(f"card le : {bak}")
+ return bak
+
+ # 카드 파일 읽기
+ def card_load():
+ #cards=wildcards.cards
+ card_path=wildcards.card_path
+ cards = {}
+ #print(f"path : {path}")
+ files=glob.glob(card_path, recursive=True)
+ #print(f"files : {files}")
+
+ for file in files:
+ basenameAll = os.path.basename(file)
+ basename = os.path.relpath(file, os.path.dirname(__file__)).replace("\\", "/").replace("../../wildcards/", "")
+ #print(f"basenameAll : {basenameAll}")
+ #print(f"basename : {basename}")
+ file_nameAll = os.path.splitext(basenameAll)[0]
+ file_name = "/"+os.path.splitext(basename)[0]
+ #print(f"file_nameAll : {file_nameAll}")
+ #print(f"file_name : {file_name}")
+ if not file_nameAll in cards:
+ cards[file_nameAll]=[]
+ if not file_name in cards:
+ cards[file_name]=[]
+ #print(f"file_name : {file_name}")
+ with open(file, "rb") as f:
+ raw_data = f.read()
+ encoding = chardet.detect(raw_data)["encoding"]
+ with open(file, "r", encoding=encoding) as f:
+ lines = f.readlines()
+ for line in lines:
+ line=line.strip()
+ # 주석 빈줄 제외
+ if line.startswith("#") or len(line)==0:
+ continue
+ cards[file_nameAll]+=[line]
+ cards[file_name]+=[line]
+ #print(f"line : {line}")
+ wildcards.cards=cards
+ print(f"[cyan]cards file count : [/cyan]", len(wildcards.cards))
+ #print(f"cards : {cards.keys()}")
+ wildcards.is_card_Load=True
+
+ # 실행기
+ def run(text,load=False):
+ if text is None or not isinstance(text, str):
+ print("[red]text is not str : [/red]",text)
+ return None
+ if not wildcards.is_card_Load or load:
+ wildcards.card_load()
+
+ #print(f"text : {text}")
+ result=wildcards.card_loop(text)
+ #print(f"result : {result}")
+ return result
+
+ # ============================================================
+
+#m = p.sub(sub, test)
+#print(m)
+#print(__name__)
+#if __name__ == '__main__' :
+ # 테스트용
+#test="{3$$a1|{b2|c3|}|d4|{-$$|f|g}|{-2$$h||i}|{1-$$j|k|}}/{$$l|m|}/{0$$n|}/{9$$-$$a|b|c}/{9$$ {and|or} $$a|b|c}"
+#print("[green]wildcards test : [/green]",wildcards.run(test),style="reset")
+#print("wildcards test : "+wildcards.run("{9$$a|b}"))
+#print("[green]wildcards test : [/green]",wildcards.run("__my__"))
+#print("wildcards test : "+wildcards.run("{9$$-$$a|b|c}"))
+#print("wildcards test : "+wildcards.run("{9$$ {and|or} $$a|b|c}"))
+#print("wildcards test : "+wildcards.run("{{slender,|} {nature,|} {curvy,|} {thin,|} {narrow,|} {slim,|} {mini,|} {little,|}| {|very }{-$$ $$thin|slender|narrow|slim|little|skinny|mini} body, }"))
+print("wildcards test : "+wildcards.run("__aest__"))
+print("wildcards test : "+wildcards.run("__*test__"))
+print("wildcards test : "+wildcards.run("__?est__"))
+print("wildcards test : "+wildcards.run("__test__"))
+print("wildcards test : "+wildcards.run("__/test__"))
+print("wildcards test : "+wildcards.run("__/0/test__"))
diff --git a/custom_nodes/ComfyUI_smZNodes/.gitignore b/custom_nodes/ComfyUI_smZNodes/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..c295ed41d7fc8bcc7c27b67c767927af4565e459
--- /dev/null
+++ b/custom_nodes/ComfyUI_smZNodes/.gitignore
@@ -0,0 +1,165 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/#use-with-ide
+.pdm.toml
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
+
+backup*
+**/.DS_Store
+**/.venv
+**/.vscode
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_smZNodes/LICENSE b/custom_nodes/ComfyUI_smZNodes/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..f288702d2fa16d3cdf0035b15a9fcbc552cd88e7
--- /dev/null
+++ b/custom_nodes/ComfyUI_smZNodes/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/custom_nodes/ComfyUI_smZNodes/README.md b/custom_nodes/ComfyUI_smZNodes/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..3ad3500c9a2539ea112c9fd4f9d58dd272ac63b3
--- /dev/null
+++ b/custom_nodes/ComfyUI_smZNodes/README.md
@@ -0,0 +1,147 @@
+
+# smZNodes
+A selection of custom nodes for [ComfyUI](https://github.com/comfyanonymous/ComfyUI).
+
+1. [CLIP Text Encode++](#clip-text-encode)
+2. [Settings](#settings)
+
+## CLIP Text Encode++
+
+
+
+
+
+
+
+
+
+
+
+
+CLIP Text Encode++ can generate identical embeddings from [stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui) for [ComfyUI](https://github.com/comfyanonymous/ComfyUI).
+
+
+This means you can reproduce the same images generated from `stable-diffusion-webui` on `ComfyUI`.
+
+Simple prompts generate _identical_ images. More complex prompts with complex attention/emphasis/weighting may generate images with slight differences due to how `ComfyUI` denoises images. In that case, you can enable the option to use another denoiser with the Settings node.
+
+
+### Features
+
+- [Prompt editing](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#prompt-editing)
+ - [Alternating words](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#alternating-words)
+- Weight normalization
+- Usage of `BREAK` and `AND` keywords
+- Optional `embedding:` identifier
+
+### Installation
+
+Three methods are available for installation:
+
+1. Load via [ComfyUI Manager](https://github.com/ltdrdata/ComfyUI-Manager)
+2. Clone the repository directly into the extensions directory.
+3. Download the project manually.
+
+
+#### Load via ComfyUI Manager
+
+
+
+
+
Install via ComfyUI Manager
+
+
+#### Clone Repository
+
+```shell
+cd path/to/your/ComfyUI/custom_nodes
+git clone https://github.com/shiimizu/ComfyUI_smZNodes.git
+```
+
+#### Download Manually
+
+1. Download the project archive from [here](https://github.com/shiimizu/ComfyUI_smZNodes/archive/refs/heads/main.zip).
+2. Extract the downloaded zip file.
+3. Move the extracted files to `path/to/your/ComfyUI/custom_nodes`.
+4. Restart ComfyUI
+
+The folder structure should resemble: `path/to/your/ComfyUI/custom_nodes/ComfyUI_smZNodes`.
+
+
+### Update
+
+To update the extension, update via [ComfyUI Manager](https://github.com/ltdrdata/ComfyUI-Manager) or pull the latest changes from the repository:
+
+```shell
+cd path/to/your/ComfyUI/custom_nodes/ComfyUI_smZNodes
+git pull
+```
+
+### Comparisons
+These images can be dragged into ComfyUI to load their workflows. Each image is done using the [Silicon29](https://huggingface.co/Xynon/SD-Silicon) (in SD v1.5) checkpoint with 18 steps using the Heun sampler.
+
+|stable-diffusion-webui|A1111 parser|Comfy parser|
+|:---:|:---:|:---:|
+|  |  |  |
+|  |  |  |
+
+Image slider links:
+- https://imgsli.com/MTkxMjE0
+- https://imgsli.com/MTkxMjEy
+
+### Options
+
+|Name|Description|
+| --- | --- |
+| `parser` | The parser selected to parse prompts into tokens and then transformed (encoded) into embeddings. Taken from [`automatic`](https://github.com/vladmandic/automatic/discussions/99#discussioncomment-5931014). |
+| `mean_normalization` | Whether to take the mean of your prompt weights. It's `true` by default on `stable-diffusion-webui`. This is implemented according to `stable-diffusion-webui`. (They say that it's probably not the correct way to take the mean.) |
+| `multi_conditioning` | This is usually set to `true` for your positive prompt and `false` for your negative prompt.
For each prompt, the list is obtained by splitting the prompt using the `AND` separator. See [Compositional Visual Generation with Composable Diffusion Models](https://energy-based-model.github.io/Compositional-Visual-Generation-with-Composable-Diffusion-Models/)
|
+|`use_old_emphasis_implementation`|
Use old emphasis implementation. Can be useful to reproduce old seeds.
|
+
+> [!IMPORTANT]
+> You can right click the node to show/hide some of the widgets. E.g. the `with_SDXL` option.
+
+
+
+| Parser | Description |
+| ----------------- | -------------------------------------------------------------------------------- |
+| `comfy` | The default way `ComfyUI` handles everything |
+| `comfy++` | Uses `ComfyUI`'s parser but encodes tokens the way `stable-diffusion-webui` does, allowing to take the mean as they do. |
+| `A1111` | The default parser used in `stable-diffusion-webui` |
+| `full` | Same as `A1111` but whitespaces and newlines are stripped |
+| `compel` | Uses [`compel`](https://github.com/damian0815/compel) |
+| `fixed attention` | Prompt is untampered with |
+
+> **Note**
+> Every `parser` except `comfy` uses `stable-diffusion-webui`'s encoding pipeline.
+
+> **Warning**
+> LoRA syntax (``) is not suppprted.
+
+## Settings
+
+
+
+
Settings node workflow
+
+
+
+The Settings node can be used to finetune results from CLIP Text Encode++. Some settings apply globally, or just during tokenization, or just for CFGDenoiser. The `RNG` setting applies globally.
+
+This node can change whenever it is updated, so you may have to recreate the node to prevent issues. Hook it up before CLIP Text Encode++ nodes to apply any changes. Settings can be overridden by using another Settings node somewhere past a previous one. Right click the node for the `Hide/show all descriptions` menu option.
+
+
+## Tips to get reproducible results on both UIs
+- Use the same seed, sampler settings, RNG (CPU or GPU), clip skip (CLIP Set Last Layer), etc.
+- Ancestral samplers may not be deterministic.
+- If you're using `DDIM` as your sampler, use the `ddim_uniform` scheduler.
+- There are different `unipc` configurations. Adjust accordingly on both UIs.
+
+### FAQs
+- How does this differ from [`ComfyUI_ADV_CLIP_emb`](https://github.com/BlenderNeko/ComfyUI_ADV_CLIP_emb)?
+ - In regards to `stable-diffusion-webui`:
+ - Mine parses prompts using their parser.
+ - Mine takes the mean exactly as they do. `ComfyUI_ADV_CLIP_emb` probably takes the correct mean but hey, this is for the purpose of reproducible images.
+- Where can I learn more about how ComfyUI interprets weights?
+ - https://comfyanonymous.github.io/ComfyUI_examples/faq/
+ - https://blenderneko.github.io/ComfyUI-docs/Interface/Textprompts/
diff --git a/custom_nodes/ComfyUI_smZNodes/__init__.py b/custom_nodes/ComfyUI_smZNodes/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..ffbaec5466557f412d94c87bbac926e91729e47b
--- /dev/null
+++ b/custom_nodes/ComfyUI_smZNodes/__init__.py
@@ -0,0 +1,170 @@
+from pathlib import Path
+import os
+import shutil
+import subprocess
+import importlib
+from functools import partial
+
+def install(module):
+ import sys
+ try:
+ print(f"\033[92m[smZNodes] \033[0;31m{module} is not installed. Attempting to install...\033[0m")
+ subprocess.check_call([sys.executable, "-m", "pip", "install", module])
+ reload()
+ print(f"\033[92m[smZNodes] {module} Installed!\033[0m")
+ except:
+ print(f"\033[92m[smZNodes] \033[0;31mFailed to install {module}.\033[0m")
+
+# Reload modules after installation
+PRELOADED_MODULES = set()
+def init() :
+ # local imports to keep things neat
+ from sys import modules
+ import importlib
+ global PRELOADED_MODULES
+ # sys and importlib are ignored here too
+ PRELOADED_MODULES = set(modules.values())
+def reload() :
+ from sys import modules
+ import importlib
+ for module in set(modules.values()) - PRELOADED_MODULES :
+ try :
+ importlib.reload(module)
+ except :
+ pass
+init()
+
+# compel =================
+if importlib.util.find_spec('compel') is None:
+ install("compel")
+
+# lark =================
+if importlib.util.find_spec('lark') is None:
+ install("lark")
+# ============================
+
+WEB_DIRECTORY = "./web"
+
+from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
+__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
+
+# ==== web ======
+cwd_path = Path(__file__).parent
+comfy_path = cwd_path.parent.parent
+
+def setup_web_extension():
+ import nodes
+ web_extension_path = os.path.join(comfy_path, "web", "extensions", "smZNodes")
+
+ if os.path.exists(web_extension_path):
+ shutil.rmtree(web_extension_path)
+ if not hasattr(nodes, "EXTENSION_WEB_DIRS"):
+ # print(f"[smZNodes]: Your ComfyUI version is outdated. Please update to the latest version.")
+ # setup js
+ if not os.path.exists(web_extension_path):
+ os.makedirs(web_extension_path)
+
+ js_src_path = os.path.join(cwd_path, "web/js", "smZdynamicWidgets.js")
+ shutil.copy(js_src_path, web_extension_path)
+
+setup_web_extension()
+
+# ==============
+
+# add_sample_dpmpp_2m_alt, inject_code, opts as smZ_opts
+from .smZNodes import add_sample_dpmpp_2m_alt, inject_code, CFGNoisePredictor
+
+add_sample_dpmpp_2m_alt()
+
+# ==============
+# Hijack sampling
+
+payload = [{
+ "target_line": 'extra_args["denoise_mask"] = denoise_mask',
+ "code_to_insert": """
+ if (any([_p[1].get('from_smZ', False) for _p in positive]) or
+ any([_p[1].get('from_smZ', False) for _p in negative])):
+ from ComfyUI_smZNodes.modules.shared import opts as smZ_opts
+ if not smZ_opts.sgm_noise_multiplier: max_denoise = False
+"""
+},
+{
+ "target_line": 'positive = positive[:]',
+ "code_to_insert": """
+ if hasattr(self, 'model_denoise'): self.model_denoise.step = start_step if start_step != None else 0
+"""
+},
+]
+
+import comfy
+if not hasattr(comfy.samplers, 'Sampler'):
+ print(f"[smZNodes]: Your ComfyUI version is outdated. Please update to the latest version.")
+ comfy.samplers.KSampler.sample = inject_code(comfy.samplers.KSampler.sample, payload)
+else:
+ _KSampler_sample = comfy.samplers.KSampler.sample
+ _Sampler = comfy.samplers.Sampler
+ _max_denoise = comfy.samplers.Sampler.max_denoise
+ _sample = comfy.samplers.sample
+ _wrap_model = comfy.samplers.wrap_model
+
+ def get_value_from_args(args, kwargs, key_to_lookup, fn, idx=None):
+ arg_names = fn.__code__.co_varnames[:fn.__code__.co_argcount]
+ value = None
+ if key_to_lookup in kwargs:
+ value = kwargs[key_to_lookup]
+ else:
+ try:
+ # Get its position in the formal parameters list and retrieve from args
+ index = arg_names.index(key_to_lookup)
+ value = args[index] if index < len(args) else None
+ except Exception as err:
+ if idx is not None and idx < len(args):
+ value = args[idx]
+ return value
+
+ def KSampler_sample(*args, **kwargs):
+ start_step = get_value_from_args(args, kwargs, 'start_step', _KSampler_sample)
+ if start_step is not None:
+ args[0].model.start_step = start_step
+ return _KSampler_sample(*args, **kwargs)
+
+ def sample(*args, **kwargs):
+ model = get_value_from_args(args, kwargs, 'model', _sample, 0)
+ positive = get_value_from_args(args, kwargs, 'positive', _sample, 2)
+ negative = get_value_from_args(args, kwargs, 'negative', _sample, 3)
+ get_p1 = lambda x: x[1] if type(x) is list else x
+ model.from_smZ = (any([get_p1(_p).get('from_smZ', False) for _p in positive]) or
+ any([get_p1(_p).get('from_smZ', False) for _p in negative]))
+ return _sample(*args, **kwargs)
+
+ class Sampler(_Sampler):
+ def max_denoise(self, model_wrap, sigmas):
+ model = model_wrap.inner_model
+ if hasattr(model, 'inner_model'):
+ model = model.inner_model
+ if getattr(model, 'start_step', None) is not None:
+ model_wrap.inner_model.step = int(model.start_step)
+ del model.start_step
+ if model.from_smZ:
+ from .modules.shared import opts
+ if opts.sgm_noise_multiplier:
+ return _max_denoise(self, model_wrap, sigmas)
+ else:
+ return False
+ else:
+ return _max_denoise(self, model_wrap, sigmas)
+
+ comfy.samplers.Sampler.max_denoise = Sampler.max_denoise
+ comfy.samplers.KSampler.sample = KSampler_sample
+ comfy.samplers.sample = sample
+comfy.samplers.CFGNoisePredictor = CFGNoisePredictor
+
+if hasattr(comfy.model_management, 'unet_dtype'):
+ comfy.model_management.unet_dtype_orig = comfy.model_management.unet_dtype
+ from .modules import devices
+ def unet_dtype(device=None, model_params=0):
+ dtype = comfy.model_management.unet_dtype_orig(device=device, model_params=model_params)
+ if model_params != 0:
+ devices.dtype_unet = dtype
+ return dtype
+ comfy.model_management.unet_dtype = unet_dtype
diff --git a/custom_nodes/ComfyUI_smZNodes/__pycache__/__init__.cpython-311.pyc b/custom_nodes/ComfyUI_smZNodes/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..60c6a2e691f97897a9e09dd25d89dbc151b1f8ec
Binary files /dev/null and b/custom_nodes/ComfyUI_smZNodes/__pycache__/__init__.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_smZNodes/__pycache__/nodes.cpython-311.pyc b/custom_nodes/ComfyUI_smZNodes/__pycache__/nodes.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d0ee45b6a1eb710244441448b373cb482a769abf
Binary files /dev/null and b/custom_nodes/ComfyUI_smZNodes/__pycache__/nodes.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_smZNodes/__pycache__/smZNodes.cpython-311.pyc b/custom_nodes/ComfyUI_smZNodes/__pycache__/smZNodes.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..fb3e82addc6485eb87d094017ab93bb3113406b9
Binary files /dev/null and b/custom_nodes/ComfyUI_smZNodes/__pycache__/smZNodes.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/devices.cpython-311.pyc b/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/devices.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0d6b30d96b7e8affa24af7fab8e105199a4e3ec2
Binary files /dev/null and b/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/devices.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/errors.cpython-311.pyc b/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/errors.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f151180a4432dca0060ef76c96a703fee16630ad
Binary files /dev/null and b/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/errors.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/prompt_parser.cpython-311.pyc b/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/prompt_parser.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0b3ea42cf1456d0097e09b7ff97820c445a35bfa
Binary files /dev/null and b/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/prompt_parser.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/rng_philox.cpython-311.pyc b/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/rng_philox.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b02b131d1bdc6eb77d66c24b9d0b7aa9f5d4c36b
Binary files /dev/null and b/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/rng_philox.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/script_callbacks.cpython-311.pyc b/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/script_callbacks.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..21a509c0a53d8b1c667871e67f9f7c13255f20fe
Binary files /dev/null and b/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/script_callbacks.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/sd_hijack.cpython-311.pyc b/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/sd_hijack.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e9bcd2fe387f6d37c587d506efec1fc9361ff1ba
Binary files /dev/null and b/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/sd_hijack.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/sd_hijack_clip.cpython-311.pyc b/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/sd_hijack_clip.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..741acc1140cf4ec9f85acfb72c9ca3610e1f7e2d
Binary files /dev/null and b/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/sd_hijack_clip.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/sd_hijack_open_clip.cpython-311.pyc b/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/sd_hijack_open_clip.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e384c3cefc6b96f67cc9c329174e1cfe17948f3f
Binary files /dev/null and b/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/sd_hijack_open_clip.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/sd_hijack_optimizations.cpython-311.pyc b/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/sd_hijack_optimizations.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f8951490eb6b7b41d300dc17ff21a6d6c9c09318
Binary files /dev/null and b/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/sd_hijack_optimizations.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/sd_hijack_unet.cpython-311.pyc b/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/sd_hijack_unet.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..9bc4ebd04234699ad0d4fa54028c1f53a7537472
Binary files /dev/null and b/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/sd_hijack_unet.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/sd_hijack_utils.cpython-311.pyc b/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/sd_hijack_utils.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e8844c44782f12e22bed77dff61f91e085320c23
Binary files /dev/null and b/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/sd_hijack_utils.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/sd_samplers_cfg_denoiser.cpython-311.pyc b/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/sd_samplers_cfg_denoiser.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..80acf9089072b2b102a43a5f8163d835b2941774
Binary files /dev/null and b/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/sd_samplers_cfg_denoiser.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/shared.cpython-311.pyc b/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/shared.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..60fe82c593e942ab05360121fc1fd450a62ab44f
Binary files /dev/null and b/custom_nodes/ComfyUI_smZNodes/modules/__pycache__/shared.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/devices.py b/custom_nodes/ComfyUI_smZNodes/modules/devices.py
new file mode 100644
index 0000000000000000000000000000000000000000..b93950d64849d5f4611e67c42b2fd21cd9ea0101
--- /dev/null
+++ b/custom_nodes/ComfyUI_smZNodes/modules/devices.py
@@ -0,0 +1,82 @@
+import sys
+import contextlib
+import torch
+from . import shared
+from comfy import model_management
+
+if sys.platform == "darwin":
+ from . import mac_specific
+
+
+def has_mps() -> bool:
+ if sys.platform != "darwin":
+ return False
+ else:
+ return mac_specific.has_mps
+
+cpu = torch.device("cpu")
+device = device_interrogate = device_gfpgan = device_esrgan = device_codeformer = None
+dtype = torch.float16
+dtype_vae = torch.float16
+dtype_unet = torch.float16
+unet_needs_upcast = False
+
+def cond_cast_unet(input):
+ return input.to(dtype_unet) if unet_needs_upcast else input
+
+
+def cond_cast_float(input):
+ return input.float() if unet_needs_upcast else input
+
+
+def randn(seed, shape):
+ from modules.shared import opts
+
+ torch.manual_seed(seed)
+ if opts.randn_source == "CPU" or device.type == 'mps':
+ return torch.randn(shape, device=cpu).to(device)
+ return torch.randn(shape, device=device)
+
+
+def randn_without_seed(shape):
+ from modules.shared import opts
+
+ if opts.randn_source == "CPU" or device.type == 'mps':
+ return torch.randn(shape, device=cpu).to(device)
+ return torch.randn(shape, device=device)
+
+def autocast(disable=False):
+ if disable:
+ return contextlib.nullcontext()
+
+ if dtype == torch.float32 or model_management.get_torch_device() == torch.device("mps"): # or shared.cmd_opts.precision == "full":
+ return contextlib.nullcontext()
+
+ # only cuda
+ autocast_device = model_management.get_autocast_device(model_management.get_torch_device())
+ # autocast_device = "cuda"
+ return torch.autocast(autocast_device)
+
+def without_autocast(disable=False):
+ return torch.autocast("cuda", enabled=False) if torch.is_autocast_enabled() and not disable else contextlib.nullcontext()
+
+class NansException(Exception):
+ pass
+
+def test_for_nans(x, where):
+ if shared.opts.disable_nan_check:
+ return
+ if not torch.all(torch.isnan(x)).item():
+ return
+ if where == "unet":
+ message = "A tensor with all NaNs was produced in Unet."
+ if not shared.opts.no_half:
+ message += " This could be either because there's not enough precision to represent the picture, or because your video card does not support half type. Try setting the \"Upcast cross attention layer to float32\" option in Settings > Stable Diffusion or using the --no-half commandline argument to fix this."
+ elif where == "vae":
+ message = "A tensor with all NaNs was produced in VAE."
+ if not shared.opts.no_half and not shared.opts.no_half_vae:
+ message += " This could be because there's not enough precision to represent the picture. Try adding --no-half-vae commandline argument to fix this."
+ else:
+ message = "A tensor with all NaNs was produced."
+ message += " Use --disable-nan-check commandline argument to disable this check."
+ raise NansException(message)
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/errors.py b/custom_nodes/ComfyUI_smZNodes/modules/errors.py
new file mode 100644
index 0000000000000000000000000000000000000000..23bc885d714817c17d3bd48b49ff64b830b29159
--- /dev/null
+++ b/custom_nodes/ComfyUI_smZNodes/modules/errors.py
@@ -0,0 +1,85 @@
+import sys
+import textwrap
+import traceback
+
+
+exception_records = []
+
+
+def record_exception():
+ _, e, tb = sys.exc_info()
+ if e is None:
+ return
+
+ if exception_records and exception_records[-1] == e:
+ return
+
+ exception_records.append((e, tb))
+
+ if len(exception_records) > 5:
+ exception_records.pop(0)
+
+
+def report(message: str, *, exc_info: bool = False) -> None:
+ """
+ Print an error message to stderr, with optional traceback.
+ """
+
+ record_exception()
+
+ for line in message.splitlines():
+ print("***", line, file=sys.stderr)
+ if exc_info:
+ print(textwrap.indent(traceback.format_exc(), " "), file=sys.stderr)
+ print("---", file=sys.stderr)
+
+
+def print_error_explanation(message):
+ record_exception()
+
+ lines = message.strip().split("\n")
+ max_len = max([len(x) for x in lines])
+
+ print('=' * max_len, file=sys.stderr)
+ for line in lines:
+ print(line, file=sys.stderr)
+ print('=' * max_len, file=sys.stderr)
+
+
+def display(e: Exception, task, *, full_traceback=False):
+ record_exception()
+
+ print(f"{task or 'error'}: {type(e).__name__}", file=sys.stderr)
+ te = traceback.TracebackException.from_exception(e)
+ if full_traceback:
+ # include frames leading up to the try-catch block
+ te.stack = traceback.StackSummary(traceback.extract_stack()[:-2] + te.stack)
+ print(*te.format(), sep="", file=sys.stderr)
+
+ message = str(e)
+ if "copying a param with shape torch.Size([640, 1024]) from checkpoint, the shape in current model is torch.Size([640, 768])" in message:
+ print_error_explanation("""
+The most likely cause of this is you are trying to load Stable Diffusion 2.0 model without specifying its config file.
+See https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#stable-diffusion-20 for how to solve this.
+ """)
+
+
+already_displayed = {}
+
+
+def display_once(e: Exception, task):
+ record_exception()
+
+ if task in already_displayed:
+ return
+
+ display(e, task)
+
+ already_displayed[task] = 1
+
+
+def run(code, task):
+ try:
+ code()
+ except Exception as e:
+ display(task, e)
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/hypernetworks/__pycache__/hypernetwork.cpython-311.pyc b/custom_nodes/ComfyUI_smZNodes/modules/hypernetworks/__pycache__/hypernetwork.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e84a90987a0f0daf404843734640d6b6adaa382e
Binary files /dev/null and b/custom_nodes/ComfyUI_smZNodes/modules/hypernetworks/__pycache__/hypernetwork.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/hypernetworks/hypernetwork.py b/custom_nodes/ComfyUI_smZNodes/modules/hypernetworks/hypernetwork.py
new file mode 100644
index 0000000000000000000000000000000000000000..aa15dbf91191517e81c18e718f82a0ef34cdcd5a
--- /dev/null
+++ b/custom_nodes/ComfyUI_smZNodes/modules/hypernetworks/hypernetwork.py
@@ -0,0 +1,23 @@
+from .. import devices
+def apply_single_hypernetwork(hypernetwork, context_k, context_v, layer=None):
+ hypernetwork_layers = (hypernetwork.layers if hypernetwork is not None else {}).get(context_k.shape[2], None)
+
+ if hypernetwork_layers is None:
+ return context_k, context_v
+
+ if layer is not None:
+ layer.hyper_k = hypernetwork_layers[0]
+ layer.hyper_v = hypernetwork_layers[1]
+
+ context_k = devices.cond_cast_unet(hypernetwork_layers[0](devices.cond_cast_float(context_k)))
+ context_v = devices.cond_cast_unet(hypernetwork_layers[1](devices.cond_cast_float(context_v)))
+ return context_k, context_v
+
+
+def apply_hypernetworks(hypernetworks, context, layer=None):
+ context_k = context
+ context_v = context
+ for hypernetwork in hypernetworks:
+ context_k, context_v = apply_single_hypernetwork(hypernetwork, context_k, context_v, layer)
+
+ return context_k, context_v
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/mac_specific.py b/custom_nodes/ComfyUI_smZNodes/modules/mac_specific.py
new file mode 100644
index 0000000000000000000000000000000000000000..c0a9e9b01ab0eab81d7783b32151c2258f8afb23
--- /dev/null
+++ b/custom_nodes/ComfyUI_smZNodes/modules/mac_specific.py
@@ -0,0 +1,85 @@
+import logging
+
+import torch
+import platform
+from .sd_hijack_utils import CondFunc
+from packaging import version
+
+log = logging.getLogger(__name__)
+
+
+# before torch version 1.13, has_mps is only available in nightly pytorch and macOS 12.3+,
+# use check `getattr` and try it for compatibility.
+# in torch version 1.13, backends.mps.is_available() and backends.mps.is_built() are introduced in to check mps availabilty,
+# since torch 2.0.1+ nightly build, getattr(torch, 'has_mps', False) was deprecated, see https://github.com/pytorch/pytorch/pull/103279
+def check_for_mps() -> bool:
+ if version.parse(torch.__version__) <= version.parse("2.0.1"):
+ if not getattr(torch, 'has_mps', False):
+ return False
+ try:
+ torch.zeros(1).to(torch.device("mps"))
+ return True
+ except Exception:
+ return False
+ else:
+ return torch.backends.mps.is_available() and torch.backends.mps.is_built()
+
+
+has_mps = check_for_mps()
+
+
+def torch_mps_gc() -> None:
+ try:
+ from .shared import state
+ if state.current_latent is not None:
+ log.debug("`current_latent` is set, skipping MPS garbage collection")
+ return
+ from torch.mps import empty_cache
+ empty_cache()
+ except Exception:
+ log.warning("MPS garbage collection failed", exc_info=True)
+
+
+# MPS workaround for https://github.com/pytorch/pytorch/issues/89784
+def cumsum_fix(input, cumsum_func, *args, **kwargs):
+ if input.device.type == 'mps':
+ output_dtype = kwargs.get('dtype', input.dtype)
+ if output_dtype == torch.int64:
+ return cumsum_func(input.cpu(), *args, **kwargs).to(input.device)
+ elif output_dtype == torch.bool or cumsum_needs_int_fix and (output_dtype == torch.int8 or output_dtype == torch.int16):
+ return cumsum_func(input.to(torch.int32), *args, **kwargs).to(torch.int64)
+ return cumsum_func(input, *args, **kwargs)
+
+if has_mps:
+ # MPS fix for randn in torchsde
+ CondFunc('torchsde._brownian.brownian_interval._randn', lambda _, size, dtype, device, seed: torch.randn(size, dtype=dtype, device=torch.device("cpu"), generator=torch.Generator(torch.device("cpu")).manual_seed(int(seed))).to(device), lambda _, size, dtype, device, seed: device.type == 'mps')
+
+ if platform.mac_ver()[0].startswith("13.2."):
+ # MPS workaround for https://github.com/pytorch/pytorch/issues/95188, thanks to danieldk (https://github.com/explosion/curated-transformers/pull/124)
+ CondFunc('torch.nn.functional.linear', lambda _, input, weight, bias: 1, lambda _, input, weight, bias: "1")
+
+ if version.parse(torch.__version__) < version.parse("1.13"):
+ # PyTorch 1.13 doesn't need these fixes but unfortunately is slower and has regressions that prevent training from working
+
+ # MPS workaround for https://github.com/pytorch/pytorch/issues/79383
+ CondFunc('torch.Tensor.to', lambda orig_func, self, *args, **kwargs: orig_func(self.contiguous(), *args, **kwargs),
+ lambda _, self, *args, **kwargs: self.device.type != 'mps' and (args and isinstance(args[0], torch.device) and args[0].type == 'mps' or isinstance(kwargs.get('device'), torch.device) and kwargs['device'].type == 'mps'))
+ # MPS workaround for https://github.com/pytorch/pytorch/issues/80800
+ CondFunc('torch.nn.functional.layer_norm', lambda orig_func, *args, **kwargs: orig_func(*([args[0].contiguous()] + list(args[1:])), **kwargs),
+ lambda _, *args, **kwargs: args and isinstance(args[0], torch.Tensor) and args[0].device.type == 'mps')
+ # MPS workaround for https://github.com/pytorch/pytorch/issues/90532
+ CondFunc('torch.Tensor.numpy', lambda orig_func, self, *args, **kwargs: orig_func(self.detach(), *args, **kwargs), lambda _, self, *args, **kwargs: self.requires_grad)
+ elif version.parse(torch.__version__) > version.parse("1.13.1"):
+ cumsum_needs_int_fix = not torch.Tensor([1,2]).to(torch.device("mps")).equal(torch.ShortTensor([1,1]).to(torch.device("mps")).cumsum(0))
+ cumsum_fix_func = lambda orig_func, input, *args, **kwargs: cumsum_fix(input, orig_func, *args, **kwargs)
+ CondFunc('torch.cumsum', cumsum_fix_func, None)
+ CondFunc('torch.Tensor.cumsum', cumsum_fix_func, None)
+ CondFunc('torch.narrow', lambda orig_func, *args, **kwargs: orig_func(*args, **kwargs).clone(), None)
+
+ # MPS workaround for https://github.com/pytorch/pytorch/issues/96113
+ CondFunc('torch.nn.functional.layer_norm', lambda orig_func, x, normalized_shape, weight, bias, eps, **kwargs: orig_func(x.float(), normalized_shape, weight.float() if weight is not None else None, bias.float() if bias is not None else bias, eps).to(x.dtype), lambda _, input, *args, **kwargs: len(args) == 4 and input.device.type == 'mps')
+
+ # MPS workaround for https://github.com/pytorch/pytorch/issues/92311
+ if platform.processor() == 'i386':
+ for funcName in ['torch.argmax', 'torch.Tensor.argmax']:
+ CondFunc(funcName, lambda _, input, *args, **kwargs: torch.max(input.float() if input.dtype == torch.int64 else input, *args, **kwargs)[1], lambda _, input, *args, **kwargs: input.device.type == 'mps')
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/prompt_parser.py b/custom_nodes/ComfyUI_smZNodes/modules/prompt_parser.py
new file mode 100644
index 0000000000000000000000000000000000000000..4e4c86c9bf18e71f6cb50a6a7705ca84c89f49e4
--- /dev/null
+++ b/custom_nodes/ComfyUI_smZNodes/modules/prompt_parser.py
@@ -0,0 +1,530 @@
+from __future__ import annotations
+
+import re
+from collections import namedtuple
+from typing import List
+import lark
+import torch
+from compel import Compel
+if __name__ == "__main__":
+ from shared import opts, log
+else:
+ from .shared import opts, log
+
+# a prompt like this: "fantasy landscape with a [mountain:lake:0.25] and [an oak:a christmas tree:0.75][ in foreground::0.6][ in background:0.25] [shoddy:masterful:0.5]"
+# will be represented with prompt_schedule like this (assuming steps=100):
+# [25, 'fantasy landscape with a mountain and an oak in foreground shoddy']
+# [50, 'fantasy landscape with a lake and an oak in foreground in background shoddy']
+# [60, 'fantasy landscape with a lake and an oak in foreground in background masterful']
+# [75, 'fantasy landscape with a lake and an oak in background masterful']
+# [100, 'fantasy landscape with a lake and a christmas tree in background masterful']
+
+round_bracket_multiplier = 1.1
+square_bracket_multiplier = 1.0 / 1.1
+ScheduledPromptConditioning = namedtuple("ScheduledPromptConditioning", ["end_at_step", "cond"])
+schedule_parser = lark.Lark(r"""
+!start: (prompt | /[][():]/+)*
+prompt: (emphasized | scheduled | alternate | plain | WHITESPACE)*
+!emphasized: "(" prompt ")"
+ | "(" prompt ":" prompt ")"
+ | "[" prompt "]"
+scheduled: "[" [prompt ":"] prompt ":" [WHITESPACE] NUMBER [WHITESPACE] "]"
+alternate: "[" prompt ("|" [prompt])+ "]"
+WHITESPACE: /\s+/
+plain: /([^\\\[\]():|]|\\.)+/
+%import common.SIGNED_NUMBER -> NUMBER
+""")
+re_clean = re.compile(r"^\W+", re.S)
+re_whitespace = re.compile(r"\s+", re.S)
+
+
+def get_learned_conditioning_prompt_schedules(prompts, base_steps, hires_steps=None, use_old_scheduling=False):
+ """
+ >>> g = lambda p: get_learned_conditioning_prompt_schedules([p], 10)[0]
+ >>> g("test")
+ [[10, 'test']]
+ >>> g("a [b:3]")
+ [[3, 'a '], [10, 'a b']]
+ >>> g("a [b: 3]")
+ [[3, 'a '], [10, 'a b']]
+ >>> g("a [[[b]]:2]")
+ [[2, 'a '], [10, 'a [[b]]']]
+ >>> g("[(a:2):3]")
+ [[3, ''], [10, '(a:2)']]
+ >>> g("a [b : c : 1] d")
+ [[1, 'a b d'], [10, 'a c d']]
+ >>> g("a[b:[c:d:2]:1]e")
+ [[1, 'abe'], [2, 'ace'], [10, 'ade']]
+ >>> g("a [unbalanced")
+ [[10, 'a [unbalanced']]
+ >>> g("a [b:.5] c")
+ [[5, 'a c'], [10, 'a b c']]
+ >>> g("a [{b|d{:.5] c") # not handling this right now
+ [[5, 'a c'], [10, 'a {b|d{ c']]
+ >>> g("((a][:b:c [d:3]")
+ [[3, '((a][:b:c '], [10, '((a][:b:c d']]
+ >>> g("[a|(b:1.1)]")
+ [[1, 'a'], [2, '(b:1.1)'], [3, 'a'], [4, '(b:1.1)'], [5, 'a'], [6, '(b:1.1)'], [7, 'a'], [8, '(b:1.1)'], [9, 'a'], [10, '(b:1.1)']]
+ >>> g("[fe|]male")
+ [[1, 'female'], [2, 'male'], [3, 'female'], [4, 'male'], [5, 'female'], [6, 'male'], [7, 'female'], [8, 'male'], [9, 'female'], [10, 'male']]
+ >>> g("[fe|||]male")
+ [[1, 'female'], [2, 'male'], [3, 'male'], [4, 'male'], [5, 'female'], [6, 'male'], [7, 'male'], [8, 'male'], [9, 'female'], [10, 'male']]
+ >>> g = lambda p: get_learned_conditioning_prompt_schedules([p], 10, 10)[0]
+ >>> g("a [b:.5] c")
+ [[10, 'a b c']]
+ >>> g("a [b:1.5] c")
+ [[5, 'a c'], [10, 'a b c']]
+ """
+
+ if hires_steps is None or use_old_scheduling:
+ int_offset = 0
+ flt_offset = 0
+ steps = base_steps
+ else:
+ int_offset = base_steps
+ flt_offset = 1.0
+ steps = hires_steps
+
+ def collect_steps(steps, tree):
+ res = [steps]
+
+ class CollectSteps(lark.Visitor):
+ def scheduled(self, tree):
+ s = tree.children[-2]
+ v = float(s)
+ if use_old_scheduling:
+ v = v*steps if v<1 else v
+ else:
+ if "." in s:
+ v = (v - flt_offset) * steps
+ else:
+ v = (v - int_offset)
+ tree.children[-2] = min(steps, int(v))
+ if tree.children[-2] >= 1:
+ res.append(tree.children[-2])
+
+ def alternate(self, tree):
+ res.extend(range(1, steps+1))
+
+ CollectSteps().visit(tree)
+ return sorted(set(res))
+
+ def at_step(step, tree):
+ class AtStep(lark.Transformer):
+ def scheduled(self, args):
+ before, after, _, when, _ = args
+ yield before or () if step <= when else after
+ def alternate(self, args):
+ args = ["" if not arg else arg for arg in args]
+ yield args[(step - 1) % len(args)]
+ def start(self, args):
+ def flatten(x):
+ if isinstance(x, str):
+ yield x
+ else:
+ for gen in x:
+ yield from flatten(gen)
+ return ''.join(flatten(args))
+ def plain(self, args):
+ yield args[0].value
+ def __default__(self, data, children, meta):
+ for child in children:
+ yield child
+ return AtStep().transform(tree)
+
+ def get_schedule(prompt):
+ try:
+ tree = schedule_parser.parse(prompt)
+ except lark.exceptions.LarkError:
+ if 0:
+ import traceback
+ traceback.print_exc()
+ return [[steps, prompt]]
+ return [[t, at_step(t, tree)] for t in collect_steps(steps, tree)]
+
+ promptdict = {prompt: get_schedule(prompt) for prompt in set(prompts)}
+ return [promptdict[prompt] for prompt in prompts]
+
+
+ScheduledPromptConditioning = namedtuple("ScheduledPromptConditioning", ["end_at_step", "cond"])
+
+
+class SdConditioning(list):
+ """
+ A list with prompts for stable diffusion's conditioner model.
+ Can also specify width and height of created image - SDXL needs it.
+ """
+ def __init__(self, prompts, is_negative_prompt=False, width=None, height=None, copy_from=None):
+ super().__init__()
+ self.extend(prompts)
+
+ if copy_from is None:
+ copy_from = prompts
+
+ self.is_negative_prompt = is_negative_prompt or getattr(copy_from, 'is_negative_prompt', False)
+ self.width = width or getattr(copy_from, 'width', None)
+ self.height = height or getattr(copy_from, 'height', None)
+
+
+
+def get_learned_conditioning(model, prompts: SdConditioning | list[str], steps, hires_steps=None, use_old_scheduling=False):
+ """converts a list of prompts into a list of prompt schedules - each schedule is a list of ScheduledPromptConditioning, specifying the comdition (cond),
+ and the sampling step at which this condition is to be replaced by the next one.
+
+ Input:
+ (model, ['a red crown', 'a [blue:green:5] jeweled crown'], 20)
+
+ Output:
+ [
+ [
+ ScheduledPromptConditioning(end_at_step=20, cond=tensor([[-0.3886, 0.0229, -0.0523, ..., -0.4901, -0.3066, 0.0674], ..., [ 0.3317, -0.5102, -0.4066, ..., 0.4119, -0.7647, -1.0160]], device='cuda:0'))
+ ],
+ [
+ ScheduledPromptConditioning(end_at_step=5, cond=tensor([[-0.3886, 0.0229, -0.0522, ..., -0.4901, -0.3067, 0.0673], ..., [-0.0192, 0.3867, -0.4644, ..., 0.1135, -0.3696, -0.4625]], device='cuda:0')),
+ ScheduledPromptConditioning(end_at_step=20, cond=tensor([[-0.3886, 0.0229, -0.0522, ..., -0.4901, -0.3067, 0.0673], ..., [-0.7352, -0.4356, -0.7888, ..., 0.6994, -0.4312, -1.2593]], device='cuda:0'))
+ ]
+ ]
+ """
+ res = []
+
+ prompt_schedules = get_learned_conditioning_prompt_schedules(prompts, steps, hires_steps, use_old_scheduling)
+ cache = {}
+ first_pooled = None
+ for prompt, prompt_schedule in zip(prompts, prompt_schedules):
+
+ cached = cache.get(prompt, None)
+ if cached is not None:
+ res.append(cached)
+ continue
+
+ texts = SdConditioning([x[1] for x in prompt_schedule], copy_from=prompts)
+ # conds = model.get_learned_conditioning(texts)
+ conds = model.forward(texts)
+ if first_pooled == None:
+ # first_pooled = conds.pooled
+ if conds.pooled.shape[0] > 1:
+ first_pooled = conds.pooled[1:2]
+ else:
+ first_pooled = conds.pooled[0:1]
+ cond_schedule = []
+ for i, (end_at_step, _) in enumerate(prompt_schedule):
+ if isinstance(conds, dict):
+ cond = {k: v[i] for k, v in conds.items()}
+ else:
+ cond = conds[i]
+ if i == 0:
+ cond.pooled = first_pooled
+ cond_schedule.append(ScheduledPromptConditioning(end_at_step, cond))
+
+ cache[prompt] = cond_schedule
+ res.append(cond_schedule)
+
+ return res
+
+
+re_AND = re.compile(r"\bAND\b")
+re_weight = re.compile(r"^((?:\s|.)*?)(?:\s*:\s*([-+]?(?:\d+\.?|\d*\.\d+)))?\s*$")
+
+
+def get_multicond_prompt_list(prompts: SdConditioning | list[str]):
+ res_indexes = []
+
+ prompt_indexes = {}
+ prompt_flat_list = SdConditioning(prompts)
+ prompt_flat_list.clear()
+
+ for prompt in prompts:
+ subprompts = re_AND.split(prompt)
+
+ indexes = []
+ for subprompt in subprompts:
+ match = re_weight.search(subprompt)
+
+ text, weight = match.groups() if match is not None else (subprompt, 1.0)
+
+ weight = float(weight) if weight is not None else 1.0
+
+ index = prompt_indexes.get(text, None)
+ if index is None:
+ index = len(prompt_flat_list)
+ prompt_flat_list.append(text)
+ prompt_indexes[text] = index
+
+ indexes.append((index, weight))
+
+ res_indexes.append(indexes)
+
+ return res_indexes, prompt_flat_list, prompt_indexes
+
+
+class ComposableScheduledPromptConditioning:
+ def __init__(self, schedules, weight=1.0):
+ self.schedules: List[ScheduledPromptConditioning] = schedules
+ self.weight: float = weight
+
+
+class MulticondLearnedConditioning:
+ def __init__(self, shape, batch):
+ self.shape: tuple = shape # the shape field is needed to send this object to DDIM/PLMS
+ self.batch: List[List[ComposableScheduledPromptConditioning]] = batch
+
+
+def get_multicond_learned_conditioning(model, prompts, steps, hires_steps=None, use_old_scheduling=False) -> MulticondLearnedConditioning:
+ """same as get_learned_conditioning, but returns a list of ScheduledPromptConditioning along with the weight objects for each prompt.
+ For each prompt, the list is obtained by splitting the prompt using the AND separator.
+
+ https://energy-based-model.github.io/Compositional-Visual-Generation-with-Composable-Diffusion-Models/
+ """
+
+ res_indexes, prompt_flat_list, prompt_indexes = get_multicond_prompt_list(prompts)
+
+ learned_conditioning = get_learned_conditioning(model, prompt_flat_list, steps, hires_steps, use_old_scheduling)
+
+ res = []
+ for indexes in res_indexes:
+ res.append([ComposableScheduledPromptConditioning(learned_conditioning[i], weight) for i, weight in indexes])
+
+ return MulticondLearnedConditioning(shape=(len(prompts),), batch=res)
+
+
+class DictWithShape(dict):
+ def __init__(self, x, shape):
+ super().__init__()
+ self.update(x)
+
+ @property
+ def shape(self):
+ return self["crossattn"].shape
+
+
+def reconstruct_cond_batch(c: List[List[ScheduledPromptConditioning]], current_step):
+ param = c[0][0].cond
+ is_dict = isinstance(param, dict)
+
+ if is_dict:
+ dict_cond = param
+ res = {k: torch.zeros((len(c),) + param.shape, device=param.device, dtype=param.dtype) for k, param in dict_cond.items()}
+ res = DictWithShape(res, (len(c),) + dict_cond['crossattn'].shape)
+ else:
+ res = torch.zeros((len(c),) + param.shape, device=param.device, dtype=param.dtype)
+
+ for i, cond_schedule in enumerate(c):
+ target_index = 0
+ for current, entry in enumerate(cond_schedule):
+ if current_step <= entry.end_at_step:
+ target_index = current
+ break
+
+ if is_dict:
+ for k, param in cond_schedule[target_index].cond.items():
+ res[k][i] = param
+ else:
+ res[i] = cond_schedule[target_index].cond
+
+ res.pooled = param.pooled
+ res.pooled.schedules = c
+ return res
+
+
+def stack_conds(tensors):
+ # if prompts have wildly different lengths above the limit we'll get tensors of different shapes
+ # and won't be able to torch.stack them. So this fixes that.
+ token_count = max([x.shape[0] for x in tensors])
+ for i in range(len(tensors)):
+ if tensors[i].shape[0] != token_count:
+ last_vector = tensors[i][-1:]
+ last_vector_repeated = last_vector.repeat([token_count - tensors[i].shape[0], 1])
+ tensors[i] = torch.vstack([tensors[i], last_vector_repeated])
+
+ return torch.stack(tensors)
+
+
+
+def reconstruct_multicond_batch(c: MulticondLearnedConditioning, current_step):
+ param = c.batch[0][0].schedules[0].cond
+
+ tensors = []
+ conds_list = []
+
+ for composable_prompts in c.batch:
+ conds_for_batch = []
+
+ for composable_prompt in composable_prompts:
+ target_index = 0
+ for current, entry in enumerate(composable_prompt.schedules):
+ if current_step <= entry.end_at_step:
+ target_index = current
+ break
+
+ conds_for_batch.append((len(tensors), composable_prompt.weight))
+ tensors.append(composable_prompt.schedules[target_index].cond)
+
+ conds_list.append(conds_for_batch)
+
+ if isinstance(tensors[0], dict):
+ keys = list(tensors[0].keys())
+ stacked = {k: stack_conds([x[k] for x in tensors]) for k in keys}
+ stacked = DictWithShape(stacked, stacked['crossattn'].shape)
+ else:
+ stacked = stack_conds(tensors).to(device=param.device, dtype=param.dtype)
+
+ stacked.pooled = param.pooled
+ stacked.pooled.schedules = c
+ return conds_list, stacked
+
+
+re_attention = re.compile(r"""
+\\\(|
+\\\)|
+\\\[|
+\\]|
+\\\\|
+\\|
+\(|
+\[|
+:\s*([+-]?[.\d]+)\s*\)|
+\)|
+]|
+[^\\()\[\]:]+|
+:
+""", re.X)
+
+re_break = re.compile(r"\s*\bBREAK\b\s*", re.S)
+re_attention_v1 = re_attention
+
+def parse_prompt_attention(text):
+ """
+ Parses a string with attention tokens and returns a list of pairs: text and its associated weight.
+ Accepted tokens are:
+ (abc) - increases attention to abc by a multiplier of 1.1
+ (abc:3.12) - increases attention to abc by a multiplier of 3.12
+ [abc] - decreases attention to abc by a multiplier of 1.1
+ \( - literal character '('
+ \[ - literal character '['
+ \) - literal character ')'
+ \] - literal character ']'
+ \\ - literal character '\'
+ anything else - just text
+
+ >>> parse_prompt_attention('normal text')
+ [['normal text', 1.0]]
+ >>> parse_prompt_attention('an (important) word')
+ [['an ', 1.0], ['important', 1.1], [' word', 1.0]]
+ >>> parse_prompt_attention('(unbalanced')
+ [['unbalanced', 1.1]]
+ >>> parse_prompt_attention('\(literal\]')
+ [['(literal]', 1.0]]
+ >>> parse_prompt_attention('(unnecessary)(parens)')
+ [['unnecessaryparens', 1.1]]
+ >>> parse_prompt_attention('a (((house:1.3)) [on] a (hill:0.5), sun, (((sky))).')
+ [['a ', 1.0],
+ ['house', 1.5730000000000004],
+ [' ', 1.1],
+ ['on', 1.0],
+ [' a ', 1.1],
+ ['hill', 0.55],
+ [', sun, ', 1.1],
+ ['sky', 1.4641000000000006],
+ ['.', 1.1]]
+ """
+
+ res = []
+ round_brackets = []
+ square_brackets = []
+
+ round_bracket_multiplier = 1.1
+ square_bracket_multiplier = 1 / 1.1
+ if opts.prompt_attention == 'Fixed attention':
+ res = [[text, 1.0]]
+ return res
+ elif opts.prompt_attention == 'Compel parser':
+ conjunction = Compel.parse_prompt_string(text)
+ if conjunction is None or conjunction.prompts is None or conjunction.prompts is None or len(conjunction.prompts[0].children) == 0:
+ return [["", 1.0]]
+ res = []
+ for frag in conjunction.prompts[0].children:
+ res.append([frag.text, frag.weight])
+ return res
+ elif opts.prompt_attention == 'A1111 parser':
+ re_attention = re_attention_v1
+ whitespace = ''
+ else:
+ re_attention = re_attention_v1
+ text = text.replace('\\n', ' ')
+ whitespace = ' '
+
+ def multiply_range(start_position, multiplier):
+ for p in range(start_position, len(res)):
+ res[p][1] *= multiplier
+
+ for m in re_attention.finditer(text):
+ text = m.group(0)
+ weight = m.group(1)
+
+ if text.startswith('\\'):
+ res.append([text[1:], 1.0])
+ elif text == '(':
+ round_brackets.append(len(res))
+ elif text == '[':
+ square_brackets.append(len(res))
+ elif weight is not None and round_brackets:
+ multiply_range(round_brackets.pop(), float(weight))
+ elif text == ')' and round_brackets:
+ multiply_range(round_brackets.pop(), round_bracket_multiplier)
+ elif text == ']' and square_brackets:
+ multiply_range(square_brackets.pop(), square_bracket_multiplier)
+ else:
+ parts = re.split(re_break, text)
+ for i, part in enumerate(parts):
+ if i > 0:
+ res.append(["BREAK", -1])
+ if opts.prompt_attention == 'Full parser':
+ part = re_clean.sub("", part)
+ part = re_whitespace.sub(" ", part).strip()
+ if len(part) == 0:
+ continue
+ res.append([part, 1.0])
+
+ for pos in round_brackets:
+ multiply_range(pos, round_bracket_multiplier)
+
+ for pos in square_brackets:
+ multiply_range(pos, square_bracket_multiplier)
+
+ if len(res) == 0:
+ res = [["", 1.0]]
+
+ # merge runs of identical weights
+ i = 0
+ while i + 1 < len(res):
+ if res[i][1] == res[i + 1][1]:
+ res[i][0] += whitespace + res[i + 1][0]
+ res.pop(i + 1)
+ else:
+ i += 1
+
+ return res
+
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE)
+ input_text = '[black] [[grey]] (white) ((gray)) ((orange:1.1) yellow) ((purple) and [dark] red:1.1) [mouse:0.2] [(cat:1.1):0.5]'
+ print(f'Prompt: {input_text}')
+ all_schedules = get_learned_conditioning_prompt_schedules([input_text], 100)[0]
+ print('Schedules', all_schedules)
+ for schedule in all_schedules:
+ print('Schedule', schedule[0])
+ opts.prompt_attention = 'Fixed attention'
+ output_list = parse_prompt_attention(schedule[1])
+ print(' Fixed:', output_list)
+ opts.prompt_attention = 'Compel parser'
+ output_list = parse_prompt_attention(schedule[1])
+ print(' Compel:', output_list)
+ opts.prompt_attention = 'A1111 parser'
+ output_list = parse_prompt_attention(schedule[1])
+ print(' A1111:', output_list)
+ opts.prompt_attention = 'Full parser'
+ output_list = parse_prompt_attention(schedule[1])
+ print(' Full :', output_list)
+else:
+ import torch # doctest faster
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/rng_philox.py b/custom_nodes/ComfyUI_smZNodes/modules/rng_philox.py
new file mode 100644
index 0000000000000000000000000000000000000000..8897dc3ac54beec0eb43e315e2201ccd6613576e
--- /dev/null
+++ b/custom_nodes/ComfyUI_smZNodes/modules/rng_philox.py
@@ -0,0 +1,102 @@
+"""RNG imitiating torch cuda randn on CPU. You are welcome.
+
+Usage:
+
+```
+g = Generator(seed=0)
+print(g.randn(shape=(3, 4)))
+```
+
+Expected output:
+```
+[[-0.92466259 -0.42534415 -2.6438457 0.14518388]
+ [-0.12086647 -0.57972564 -0.62285122 -0.32838709]
+ [-1.07454231 -0.36314407 -1.67105067 2.26550497]]
+```
+"""
+
+import numpy as np
+
+philox_m = [0xD2511F53, 0xCD9E8D57]
+philox_w = [0x9E3779B9, 0xBB67AE85]
+
+two_pow32_inv = np.array([2.3283064e-10], dtype=np.float32)
+two_pow32_inv_2pi = np.array([2.3283064e-10 * 6.2831855], dtype=np.float32)
+
+
+def uint32(x):
+ """Converts (N,) np.uint64 array into (2, N) np.unit32 array."""
+ return x.view(np.uint32).reshape(-1, 2).transpose(1, 0)
+
+
+def philox4_round(counter, key):
+ """A single round of the Philox 4x32 random number generator."""
+
+ v1 = uint32(counter[0].astype(np.uint64) * philox_m[0])
+ v2 = uint32(counter[2].astype(np.uint64) * philox_m[1])
+
+ counter[0] = v2[1] ^ counter[1] ^ key[0]
+ counter[1] = v2[0]
+ counter[2] = v1[1] ^ counter[3] ^ key[1]
+ counter[3] = v1[0]
+
+
+def philox4_32(counter, key, rounds=10):
+ """Generates 32-bit random numbers using the Philox 4x32 random number generator.
+
+ Parameters:
+ counter (numpy.ndarray): A 4xN array of 32-bit integers representing the counter values (offset into generation).
+ key (numpy.ndarray): A 2xN array of 32-bit integers representing the key values (seed).
+ rounds (int): The number of rounds to perform.
+
+ Returns:
+ numpy.ndarray: A 4xN array of 32-bit integers containing the generated random numbers.
+ """
+
+ for _ in range(rounds - 1):
+ philox4_round(counter, key)
+
+ key[0] = key[0] + philox_w[0]
+ key[1] = key[1] + philox_w[1]
+
+ philox4_round(counter, key)
+ return counter
+
+
+def box_muller(x, y):
+ """Returns just the first out of two numbers generated by Box–Muller transform algorithm."""
+ u = x * two_pow32_inv + two_pow32_inv / 2
+ v = y * two_pow32_inv_2pi + two_pow32_inv_2pi / 2
+
+ s = np.sqrt(-2.0 * np.log(u))
+
+ r1 = s * np.sin(v)
+ return r1.astype(np.float32)
+
+
+class Generator:
+ """RNG that produces same outputs as torch.randn(..., device='cuda') on CPU"""
+
+ def __init__(self, seed):
+ self.seed = seed
+ self.offset = 0
+
+ def randn(self, shape):
+ """Generate a sequence of n standard normal random variables using the Philox 4x32 random number generator and the Box-Muller transform."""
+
+ n = 1
+ for x in shape:
+ n *= x
+
+ counter = np.zeros((4, n), dtype=np.uint32)
+ counter[0] = self.offset
+ counter[2] = np.arange(n, dtype=np.uint32) # up to 2^32 numbers can be generated - if you want more you'd need to spill into counter[3]
+ self.offset += 1
+
+ key = np.empty(n, dtype=np.uint64)
+ key.fill(self.seed)
+ key = uint32(key)
+
+ g = philox4_32(counter, key)
+
+ return box_muller(g[0], g[1]).reshape(shape) # discard g[2] and g[3]
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/script_callbacks.py b/custom_nodes/ComfyUI_smZNodes/modules/script_callbacks.py
new file mode 100644
index 0000000000000000000000000000000000000000..fd9e3d635680645dfd6aaf50ce0804376b124656
--- /dev/null
+++ b/custom_nodes/ComfyUI_smZNodes/modules/script_callbacks.py
@@ -0,0 +1,55 @@
+
+import inspect
+from . import errors
+from collections import namedtuple
+
+def report_exception(c, job):
+ errors.report(f"Error executing callback {job} for {c.script}", exc_info=True)
+
+ScriptCallback = namedtuple("ScriptCallback", ["script", "callback"])
+callback_map = dict(
+ callbacks_app_started=[],
+ callbacks_model_loaded=[],
+ callbacks_ui_tabs=[],
+ callbacks_ui_train_tabs=[],
+ callbacks_ui_settings=[],
+ callbacks_before_image_saved=[],
+ callbacks_image_saved=[],
+ callbacks_cfg_denoiser=[],
+ callbacks_cfg_denoised=[],
+ callbacks_cfg_after_cfg=[],
+ callbacks_before_component=[],
+ callbacks_after_component=[],
+ callbacks_image_grid=[],
+ callbacks_infotext_pasted=[],
+ callbacks_script_unloaded=[],
+ callbacks_before_ui=[],
+ callbacks_on_reload=[],
+ callbacks_list_optimizers=[],
+ callbacks_list_unets=[],
+)
+
+def list_optimizers_callback():
+ res = []
+
+ for c in callback_map['callbacks_list_optimizers']:
+ try:
+ c.callback(res)
+ except Exception:
+ report_exception(c, 'list_optimizers')
+
+ return res
+
+
+def on_list_optimizers(callback):
+ """register a function to be called when UI is making a list of cross attention optimization options.
+ The function will be called with one argument, a list, and shall add objects of type modules.sd_hijack_optimizations.SdOptimization
+ to it."""
+
+ add_callback(callback_map['callbacks_list_optimizers'], callback)
+
+def add_callback(callbacks, fun):
+ stack = [x for x in inspect.stack() if x.filename != __file__]
+ filename = stack[0].filename if stack else 'unknown file'
+
+ callbacks.append(ScriptCallback(filename, fun))
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/sd_hijack.py b/custom_nodes/ComfyUI_smZNodes/modules/sd_hijack.py
new file mode 100644
index 0000000000000000000000000000000000000000..9ccc687f4f739ab05d2051b4e53686122b242ee8
--- /dev/null
+++ b/custom_nodes/ComfyUI_smZNodes/modules/sd_hijack.py
@@ -0,0 +1,251 @@
+import torch
+import comfy
+import comfy.sd1_clip
+from torch.nn.functional import silu
+from types import MethodType
+from comfy.sd import CLIP
+from comfy import ldm
+import ldm.modules.diffusionmodules
+import ldm.modules.diffusionmodules.model
+import ldm.modules.diffusionmodules.openaimodel
+import ldm.modules.attention
+from . import devices, shared, sd_hijack_unet, sd_hijack_optimizations, script_callbacks, errors
+from .textual_inversion import textual_inversion
+from ..smZNodes import FrozenCLIPEmbedderWithCustomWordsCustom, FrozenOpenCLIPEmbedder2WithCustomWordsCustom, get_learned_conditioning
+from functools import partial
+if not hasattr(ldm.modules.diffusionmodules.model, "nonlinearity_orig"):
+ ldm.modules.diffusionmodules.model.nonlinearity_orig = ldm.modules.diffusionmodules.model.nonlinearity
+if not hasattr(ldm.modules.diffusionmodules.openaimodel, "th_orig"):
+ ldm.modules.diffusionmodules.openaimodel.th_orig = ldm.modules.diffusionmodules.openaimodel.th
+
+ldm.modules.attention.CrossAttention.forward_orig = ldm.modules.attention.CrossAttention.forward
+ldm.modules.diffusionmodules.model.AttnBlock.forward_orig = ldm.modules.diffusionmodules.model.AttnBlock.forward
+
+optimizers = []
+current_optimizer: sd_hijack_optimizations.SdOptimization = None
+already_optimized = False # temp fix for displaying info since two cliptextencode's will run
+
+def list_optimizers():
+ script_callbacks.on_list_optimizers(sd_hijack_optimizations.list_optimizers)
+ new_optimizers = script_callbacks.list_optimizers_callback()
+
+ new_optimizers = [x for x in new_optimizers if x.is_available()]
+
+ new_optimizers = sorted(new_optimizers, key=lambda x: x.priority, reverse=True)
+
+ optimizers.clear()
+ optimizers.extend(new_optimizers)
+
+
+def apply_optimizations(option=None):
+ global already_optimized
+ if already_optimized:
+ display = False
+ list_optimizers()
+ global current_optimizer
+
+ undo_optimizations()
+
+ if len(optimizers) == 0:
+ # a script can access the model very early, and optimizations would not be filled by then
+ current_optimizer = None
+ return ''
+
+ ldm.modules.diffusionmodules.model.nonlinearity = silu
+ ldm.modules.diffusionmodules.openaimodel.th = sd_hijack_unet.th
+
+ # sgm.modules.diffusionmodules.model.nonlinearity = silu
+ # sgm.modules.diffusionmodules.openaimodel.th = sd_hijack_unet.th
+
+ if current_optimizer is not None:
+ current_optimizer.undo()
+ current_optimizer = None
+
+ selection = option or shared.opts.cross_attention_optimization
+ if selection == "Automatic" and len(optimizers) > 0:
+ matching_optimizer = next(iter([x for x in optimizers if x.cmd_opt and getattr(shared.cmd_opts, x.cmd_opt, False)]), optimizers[0])
+ else:
+ matching_optimizer = next(iter([x for x in optimizers if x.cmd_opt == selection]), None)
+ if selection == "None":
+ matching_optimizer = None
+ elif selection == "Automatic" and shared.cmd_opts.disable_opt_split_attention:
+ matching_optimizer = None
+ elif matching_optimizer is None:
+ matching_optimizer = optimizers[0]
+
+ if matching_optimizer is not None:
+ if shared.opts.debug:
+ print(f"Applying attention optimization: {matching_optimizer.name}... ", end='')
+ matching_optimizer.apply()
+ already_optimized = True
+ if shared.opts.debug:
+ print("done.")
+ current_optimizer = matching_optimizer
+ return current_optimizer
+ else:
+ # if shared.opts.debug:
+ # print("Disabling attention optimization")
+ return ''
+
+def undo_optimizations():
+ sd_hijack_optimizations.undo()
+ ldm.modules.diffusionmodules.model.nonlinearity = ldm.modules.diffusionmodules.model.nonlinearity_orig
+ ldm.modules.diffusionmodules.openaimodel.th = ldm.modules.diffusionmodules.openaimodel.th_orig
+
+class StableDiffusionModelHijack:
+ fixes = None
+ comments = []
+ layers = None
+ circular_enabled = False
+ clip = None
+ tokenizer = None
+ optimization_method = None
+ embedding_db = textual_inversion.EmbeddingDatabase()
+
+ def apply_optimizations(self, option=None):
+ try:
+ self.optimization_method = apply_optimizations(option)
+ except Exception as e:
+ errors.display(e, "applying optimizations")
+ undo_optimizations()
+
+ def hijack(self, m: comfy.sd1_clip.SD1ClipModel):
+ tokenizer_parent = m.tokenizer # SD1Tokenizer
+ # SDTokenizer
+ tokenizer_parent2 = getattr(tokenizer_parent, tokenizer_parent.clip) if hasattr(tokenizer_parent, 'clip') else tokenizer_parent
+ tokenizer = getattr(tokenizer_parent, tokenizer_parent.clip).tokenizer if hasattr(tokenizer_parent, 'clip') else tokenizer_parent.tokenizer
+ if hasattr(m, 'clip'):
+ m = getattr(m, m.clip)
+ model_embeddings = m.transformer.text_model.embeddings
+ model_embeddings.token_embedding = EmbeddingsWithFixes(model_embeddings.token_embedding, self)
+ model_embeddings.token_embedding.weight = model_embeddings.token_embedding.wrapped._parameters.get('weight').to(device=devices.device)
+ m.tokenizer_parent0 = tokenizer_parent
+ m.tokenizer_parent = tokenizer_parent2
+ m.tokenizer = tokenizer
+ m = FrozenOpenCLIPEmbedder2WithCustomWordsCustom(m, self) if "SDXLClipG" in type(m).__name__ else FrozenCLIPEmbedderWithCustomWordsCustom(m, self)
+ m.clip_layer = getattr(m.wrapped, "clip_layer", None)
+ m.reset_clip_layer = getattr(m.wrapped, "reset_clip_layer", None)
+ m.transformer = getattr(m.wrapped, "transformer", None)
+ self.cond_stage_model = m
+ self.clip = m
+
+ apply_weighted_forward(self.clip)
+ self.apply_optimizations()
+
+ def undo_hijack(self, m):
+ try:
+ m = m.wrapped
+ model_embeddings = m.transformer.text_model.embeddings
+ if type(model_embeddings.token_embedding) == EmbeddingsWithFixes:
+ model_embeddings.token_embedding = model_embeddings.token_embedding.wrapped
+ undo_optimizations()
+ undo_weighted_forward(m)
+ self.apply_circular(False)
+ # self.layers = None
+ self.clip = None
+ self.cond_stage_model = None
+ except Exception as err:
+ print(err)
+
+ def apply_circular(self, enable):
+ if self.circular_enabled == enable:
+ return
+
+ self.circular_enabled = enable
+
+ for layer in [layer for layer in self.layers if type(layer) == torch.nn.Conv2d]:
+ layer.padding_mode = 'circular' if enable else 'zeros'
+
+ def clear_comments(self):
+ self.comments = []
+
+ def get_prompt_lengths(self, text):
+ if self.clip is None:
+ return 0, 0
+ _, token_count = self.clip.process_texts([text])
+ return token_count, self.clip.get_target_prompt_token_count(token_count)
+
+model_hijack = StableDiffusionModelHijack()
+
+def weighted_loss(sd_model, pred, target, mean=True):
+ #Calculate the weight normally, but ignore the mean
+ loss = sd_model._old_get_loss(pred, target, mean=False) # pylint: disable=protected-access
+
+ #Check if we have weights available
+ weight = getattr(sd_model, '_custom_loss_weight', None)
+ if weight is not None:
+ loss *= weight
+
+ #Return the loss, as mean if specified
+ return loss.mean() if mean else loss
+
+def weighted_forward(sd_model, x, c, w, *args, **kwargs):
+ try:
+ #Temporarily append weights to a place accessible during loss calc
+ sd_model._custom_loss_weight = w # pylint: disable=protected-access
+
+ #Replace 'get_loss' with a weight-aware one. Otherwise we need to reimplement 'forward' completely
+ #Keep 'get_loss', but don't overwrite the previous old_get_loss if it's already set
+ if not hasattr(sd_model, '_old_get_loss'):
+ sd_model._old_get_loss = sd_model.get_loss # pylint: disable=protected-access
+ sd_model.get_loss = MethodType(weighted_loss, sd_model)
+
+ #Run the standard forward function, but with the patched 'get_loss'
+ return sd_model.forward(x, c, *args, **kwargs)
+ finally:
+ try:
+ #Delete temporary weights if appended
+ del sd_model._custom_loss_weight
+ except AttributeError:
+ pass
+
+ #If we have an old loss function, reset the loss function to the original one
+ if hasattr(sd_model, '_old_get_loss'):
+ sd_model.get_loss = sd_model._old_get_loss # pylint: disable=protected-access
+ del sd_model._old_get_loss
+
+def apply_weighted_forward(sd_model):
+ #Add new function 'weighted_forward' that can be called to calc weighted loss
+ sd_model.weighted_forward = MethodType(weighted_forward, sd_model)
+
+def undo_weighted_forward(sd_model):
+ try:
+ del sd_model.weighted_forward
+ except AttributeError:
+ pass
+
+
+class EmbeddingsWithFixes(torch.nn.Module):
+ def __init__(self, wrapped, embeddings, textual_inversion_key='clip_l'):
+ super().__init__()
+ self.wrapped = wrapped
+ self.embeddings = embeddings
+
+ def forward(self, input_ids):
+ batch_fixes = self.embeddings.fixes
+ self.embeddings.fixes = None
+
+ try:
+ inputs_embeds = self.wrapped(input_ids)
+ except:
+ inputs_embeds = self.wrapped(input_ids.cpu())
+
+ if batch_fixes is None or len(batch_fixes) == 0 or max([len(x) for x in batch_fixes]) == 0:
+ return inputs_embeds
+
+ vecs = []
+ for fixes, tensor in zip(batch_fixes, inputs_embeds):
+ for offset, embedding in fixes:
+ vec = embedding.vec[self.textual_inversion_key] if isinstance(embedding.vec, dict) else embedding.vec
+ emb = devices.cond_cast_unet(vec)
+ if emb.device != tensor.device:
+ emb = emb.to(device=tensor.device)
+ emb_len = min(tensor.shape[0] - offset - 1, emb.shape[0])
+ try:
+ tensor = torch.cat([tensor[0:offset + 1], emb[0:emb_len], tensor[offset + 1 + emb_len:]])
+ except Exception as err:
+ print("WARNING: shape mismatch when trying to apply embedding, embedding will be ignored", tensor.shape[0], emb.shape[1])
+ # raise err
+ vecs.append(tensor)
+
+ return torch.stack(vecs)
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/sd_hijack_clip.py b/custom_nodes/ComfyUI_smZNodes/modules/sd_hijack_clip.py
new file mode 100644
index 0000000000000000000000000000000000000000..f95ceab492662d07e7b5d690c9dfb86e346df907
--- /dev/null
+++ b/custom_nodes/ComfyUI_smZNodes/modules/sd_hijack_clip.py
@@ -0,0 +1,314 @@
+from __future__ import annotations
+import math
+from collections import namedtuple
+import torch
+from . import prompt_parser, devices, sd_hijack
+from .shared import opts
+from comfy.sd1_clip import SD1ClipModel
+from comfy.sdxl_clip import SDXLClipModel
+
+class PromptChunk:
+ """
+ This object contains token ids, weight (multipliers:1.4) and textual inversion embedding info for a chunk of prompt.
+ If a prompt is short, it is represented by one PromptChunk, otherwise, multiple are necessary.
+ Each PromptChunk contains an exact amount of tokens - 77, which includes one for start and end token,
+ so just 75 tokens from prompt.
+ """
+ def __init__(self):
+ self.tokens = []
+ self.multipliers = []
+ self.fixes = []
+
+
+PromptChunkFix = namedtuple('PromptChunkFix', ['offset', 'embedding'])
+"""An object of this type is a marker showing that textual inversion embedding's vectors have to placed at offset in the prompt
+chunk. Thos objects are found in PromptChunk.fixes and, are placed into FrozenCLIPEmbedderWithCustomWordsBase.hijack.fixes, and finally
+are applied by sd_hijack.EmbeddingsWithFixes's forward function."""
+
+
+class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module):
+ """A pytorch module that is a wrapper for FrozenCLIPEmbedder module. it enhances FrozenCLIPEmbedder, making it possible to
+ have unlimited prompt length and assign weights to tokens in prompt.
+ """
+ def __init__(self, wrapped: SD1ClipModel|SDXLClipModel, hijack):
+ super().__init__()
+ self.wrapped = wrapped
+ """Original FrozenCLIPEmbedder module; can also be FrozenOpenCLIPEmbedder or xlmr.BertSeriesModelWithTransformation,
+ depending on model."""
+ self.hijack: sd_hijack.StableDiffusionModelHijack = hijack
+ self.chunk_length = 75
+ self.is_trainable = getattr(wrapped, 'is_trainable', False)
+ self.input_key = getattr(wrapped, 'input_key', 'txt')
+ self.legacy_ucg_val = None
+
+ def empty_chunk(self):
+ """creates an empty PromptChunk and returns it"""
+ chunk = PromptChunk()
+ chunk.tokens = [self.id_start] + [self.id_end] * (self.chunk_length + 1)
+ chunk.multipliers = [1.0] * (self.chunk_length + 2)
+ return chunk
+
+ def get_target_prompt_token_count(self, token_count):
+ """returns the maximum number of tokens a prompt of a known length can have before it requires one more PromptChunk to be represented"""
+ return math.ceil(max(token_count, 1) / self.chunk_length) * self.chunk_length
+
+ def tokenize(self, texts):
+ """Converts a batch of texts into a batch of token ids"""
+ raise NotImplementedError
+
+ def encode_with_transformers(self, tokens):
+ """
+ converts a batch of token ids (in python lists) into a single tensor with numeric respresentation of those tokens;
+ All python lists with tokens are assumed to have same length, usually 77.
+ if input is a list with B elements and each element has T tokens, expected output shape is (B, T, C), where C depends on
+ model - can be 768 and 1024.
+ Among other things, this call will read self.hijack.fixes, apply it to its inputs, and clear it (setting it to None).
+ """
+ raise NotImplementedError
+
+ def encode_embedding_init_text(self, init_text, nvpt):
+ """Converts text into a tensor with this text's tokens' embeddings. Note that those are embeddings before they are passed through
+ transformers. nvpt is used as a maximum length in tokens. If text produces less teokens than nvpt, only this many is returned."""
+ raise NotImplementedError
+
+ def tokenize_line(self, line):
+ """
+ this transforms a single prompt into a list of PromptChunk objects - as many as needed to
+ represent the prompt.
+ Returns the list and the total number of tokens in the prompt.
+ """
+
+ if opts.enable_emphasis:
+ parsed = prompt_parser.parse_prompt_attention(line)
+ else:
+ parsed = [[line, 1.0]]
+
+ tokenized = self.tokenize([text for text, _ in parsed])
+ chunks = []
+ chunk = PromptChunk()
+ token_count = 0
+ last_comma = -1
+
+ def next_chunk(is_last=False):
+ """puts current chunk into the list of results and produces the next one - empty;
+ if is_last is true, tokens tokens at the end won't add to token_count"""
+ nonlocal token_count
+ nonlocal last_comma
+ nonlocal chunk
+ if is_last:
+ token_count += len(chunk.tokens)
+ else:
+ token_count += self.chunk_length
+ to_add = self.chunk_length - len(chunk.tokens)
+ if to_add > 0:
+ chunk.tokens += [self.id_end] * to_add
+ chunk.multipliers += [1.0] * to_add
+ chunk.tokens = [self.id_start] + chunk.tokens + [self.id_end]
+ chunk.multipliers = [1.0] + chunk.multipliers + [1.0]
+ last_comma = -1
+ chunks.append(chunk)
+ chunk = PromptChunk()
+
+ for tokens, (text, weight) in zip(tokenized, parsed):
+ if text == 'BREAK' and weight == -1:
+ next_chunk()
+ continue
+ position = 0
+ while position < len(tokens):
+ token = tokens[position]
+ if token == self.comma_token:
+ last_comma = len(chunk.tokens)
+ # this is when we are at the end of alloted 75 tokens for the current chunk, and the current token is not a comma. opts.comma_padding_backtrack
+ # is a setting that specifies that if there is a comma nearby, the text after the comma should be moved out of this chunk and into the next.
+ elif opts.comma_padding_backtrack != 0 and len(chunk.tokens) == self.chunk_length and last_comma != -1 and len(chunk.tokens) - last_comma <= opts.comma_padding_backtrack:
+ break_location = last_comma + 1
+ reloc_tokens = chunk.tokens[break_location:]
+ reloc_mults = chunk.multipliers[break_location:]
+ chunk.tokens = chunk.tokens[:break_location]
+ chunk.multipliers = chunk.multipliers[:break_location]
+ next_chunk()
+ chunk.tokens = reloc_tokens
+ chunk.multipliers = reloc_mults
+ if len(chunk.tokens) == self.chunk_length:
+ next_chunk()
+ embedding, embedding_length_in_tokens = self.hijack.embedding_db.find_embedding_at_position(tokens, position)
+ if embedding is None:
+ chunk.tokens.append(token)
+ chunk.multipliers.append(weight)
+ position += 1
+ continue
+ emb_len = int(embedding.vectors)
+ if len(chunk.tokens) + emb_len > self.chunk_length:
+ next_chunk()
+ chunk.fixes.append(PromptChunkFix(len(chunk.tokens), embedding))
+ chunk.tokens += [0] * emb_len
+ chunk.multipliers += [weight] * emb_len
+ position += embedding_length_in_tokens
+ if chunk.tokens or not chunks:
+ next_chunk(is_last=True)
+ return chunks, token_count
+
+ def process_texts(self, texts):
+ """
+ Accepts a list of texts and calls tokenize_line() on each, with cache. Returns the list of results and maximum
+ length, in tokens, of all texts.
+ """
+ token_count = 0
+ cache = {}
+ batch_chunks = []
+ for line in texts:
+ if line in cache:
+ chunks = cache[line]
+ else:
+ chunks, current_token_count = self.tokenize_line(line)
+ token_count = max(current_token_count, token_count)
+ cache[line] = chunks
+ batch_chunks.append(chunks)
+ return batch_chunks, token_count
+
+ def forward(self, texts):
+ """
+ Accepts an array of texts; Passes texts through transformers network to create a tensor with numerical representation of those texts.
+ Returns a tensor with shape of (B, T, C), where B is length of the array; T is length, in tokens, of texts (including padding) - T will
+ be a multiple of 77; and C is dimensionality of each token - for SD1 it's 768, for SD2 it's 1024, and for SDXL it's 1280.
+ An example shape returned by this function can be: (2, 77, 768).
+ For SDXL, instead of returning one tensor above, it returns a tuple with two: the other one with shape (B, 1280) with pooled values.
+ Webui usually sends just one text at a time through this function - the only time when texts is an array with more than one elemenet
+ is when you do prompt editing: "a picture of a [cat:dog:0.4] eating ice cream"
+ """
+ if opts.use_old_emphasis_implementation:
+ from . import sd_hijack_clip_old
+ ret = sd_hijack_clip_old.forward_old(self, texts)
+ return (ret, ret.pooled) if getattr(self.wrapped, 'return_pooled', False) else ret
+
+ batch_chunks, _token_count = self.process_texts(texts)
+ used_embeddings = {}
+ chunk_count = max([len(x) for x in batch_chunks])
+
+ if opts.return_batch_chunks:
+ return (batch_chunks, chunk_count)
+
+ to_pad_count = max(opts.max_chunk_count, chunk_count) - chunk_count
+ if to_pad_count > 0:
+ self.empty_batch_chunks, _ = self.process_texts([""])
+ batch_chunks=[z+x*to_pad_count for z,x in zip(batch_chunks, self.empty_batch_chunks)]
+ chunk_count = max([len(x) for x in batch_chunks])
+
+ zs = []
+ for i in range(chunk_count):
+ batch_chunk = [chunks[i] if i < len(chunks) else self.empty_chunk() for chunks in batch_chunks]
+ tokens = [x.tokens for x in batch_chunk]
+ multipliers = [x.multipliers for x in batch_chunk]
+ self.hijack.fixes = [x.fixes for x in batch_chunk]
+ for fixes in self.hijack.fixes:
+ for _position, embedding in fixes:
+ used_embeddings[embedding.name] = embedding
+ z = self.process_tokens(tokens, multipliers)
+ zs.append(z)
+ if len(used_embeddings) > 0:
+ embeddings_list = ", ".join([f'{name} [{embedding.checksum()}]' for name, embedding in used_embeddings.items()])
+ self.hijack.comments.append(f"Used embeddings: {embeddings_list}")
+ zst = torch.hstack(zs)
+ zst.pooled = zs[0].pooled
+ if getattr(self.wrapped, 'return_pooled', False):
+ return (zst, zst.pooled)
+ else:
+ return zst
+
+ def process_tokens(self, remade_batch_tokens, batch_multipliers):
+ """
+ sends one single prompt chunk to be encoded by transformers neural network.
+ remade_batch_tokens is a batch of tokens - a list, where every element is a list of tokens; usually
+ there are exactly 77 tokens in the list. batch_multipliers is the same but for multipliers instead of tokens.
+ Multipliers are used to give more or less weight to the outputs of transformers network. Each multiplier
+ corresponds to one token.
+ """
+ try:
+ tokens = torch.asarray(remade_batch_tokens).to(devices.device)
+
+ # this is for SD2: SD1 uses the same token for padding and end of text, while SD2 uses different ones.
+ if self.id_end != self.id_pad:
+ for batch_pos in range(len(remade_batch_tokens)):
+ index = remade_batch_tokens[batch_pos].index(self.id_end)
+ tokens[batch_pos, index+1:tokens.shape[1]] = self.id_pad
+
+ z = self.encode_with_transformers(tokens)
+ except ValueError:
+ # This is where Comfy tokens were fed in that has textual inversion embeddings in the list
+ # i.e tensors in the list along with tokens
+ z = self.encode_with_transformers(remade_batch_tokens)
+ pooled = getattr(z, 'pooled', None)
+
+ # restoring original mean is likely not correct, but it seems to work well to prevent artifacts that happen otherwise
+ batch_multipliers = torch.asarray(batch_multipliers).to(devices.device)
+ if opts.prompt_mean_norm:
+ original_mean = z.mean()
+ z = z * batch_multipliers.reshape(batch_multipliers.shape + (1,)).expand(z.shape)
+ new_mean = z.mean()
+ z = z * (original_mean / new_mean)
+ else:
+ z = z * batch_multipliers.reshape(batch_multipliers.shape + (1,)).expand(z.shape)
+ if pooled is not None:
+ z.pooled = pooled
+ return z
+
+
+class FrozenCLIPEmbedderWithCustomWords(FrozenCLIPEmbedderWithCustomWordsBase):
+ def __init__(self, wrapped, hijack):
+ super().__init__(wrapped, hijack)
+ self.tokenizer = wrapped.tokenizer
+ vocab = self.tokenizer.get_vocab()
+ self.comma_token = vocab.get(',', None)
+ self.token_mults = {}
+ tokens_with_parens = [(k, v) for k, v in vocab.items() if '(' in k or ')' in k or '[' in k or ']' in k]
+ for text, ident in tokens_with_parens:
+ mult = 1.0
+ for c in text:
+ if c == '[':
+ mult /= 1.1
+ if c == ']':
+ mult *= 1.1
+ if c == '(':
+ mult *= 1.1
+ if c == ')':
+ mult /= 1.1
+ if mult != 1.0:
+ self.token_mults[ident] = mult
+ self.id_start = self.wrapped.tokenizer.bos_token_id
+ self.id_end = self.wrapped.tokenizer.eos_token_id
+ self.id_pad = self.id_end
+
+ def tokenize(self, texts):
+ tokenized = self.wrapped.tokenizer(texts, truncation=False, add_special_tokens=False)["input_ids"]
+ return tokenized
+
+ def encode_with_transformers(self, tokens):
+ outputs = self.wrapped.transformer(input_ids=tokens, output_hidden_states=-opts.CLIP_stop_at_last_layers)
+
+ if opts.CLIP_stop_at_last_layers > 1:
+ z = outputs.hidden_states[-opts.CLIP_stop_at_last_layers]
+ z = self.wrapped.transformer.text_model.final_layer_norm(z)
+ else:
+ z = outputs.last_hidden_state
+
+ return z
+
+ def encode_embedding_init_text(self, init_text, nvpt):
+ embedding_layer = self.wrapped.transformer.text_model.embeddings
+ ids = self.wrapped.tokenizer(init_text, max_length=nvpt, return_tensors="pt", add_special_tokens=False)["input_ids"]
+ embedded = embedding_layer.token_embedding.wrapped(ids.to(embedding_layer.token_embedding.wrapped.weight.device)).squeeze(0)
+ return embedded
+
+class FrozenCLIPEmbedderForSDXLWithCustomWords(FrozenCLIPEmbedderWithCustomWords):
+ def __init__(self, wrapped, hijack):
+ super().__init__(wrapped, hijack)
+
+ def encode_with_transformers(self, tokens):
+ outputs = self.wrapped.transformer(input_ids=tokens, output_hidden_states=self.wrapped.layer == "hidden")
+
+ if self.wrapped.layer == "last":
+ z = outputs.last_hidden_state
+ else:
+ z = outputs.hidden_states[self.wrapped.layer_idx]
+
+ return z
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/sd_hijack_clip_old.py b/custom_nodes/ComfyUI_smZNodes/modules/sd_hijack_clip_old.py
new file mode 100644
index 0000000000000000000000000000000000000000..2f6520b99bb92e39c9010ee39449ce3308a7bf44
--- /dev/null
+++ b/custom_nodes/ComfyUI_smZNodes/modules/sd_hijack_clip_old.py
@@ -0,0 +1,87 @@
+from . import sd_hijack_clip
+from . import shared
+
+
+def process_text_old(self: sd_hijack_clip.FrozenCLIPEmbedderWithCustomWordsBase, texts):
+ id_start = self.id_start
+ id_end = self.id_end
+ maxlen = self.wrapped.max_length # you get to stay at 77
+ used_custom_terms = []
+ remade_batch_tokens = []
+ hijack_comments = []
+ hijack_fixes = []
+ token_count = 0
+
+ cache = {}
+ batch_tokens = self.tokenize(texts)
+ batch_multipliers = []
+ for tokens in batch_tokens:
+ tuple_tokens = tuple(tokens)
+
+ if tuple_tokens in cache:
+ remade_tokens, fixes, multipliers = cache[tuple_tokens]
+ else:
+ fixes = []
+ remade_tokens = []
+ multipliers = []
+ mult = 1.0
+
+ i = 0
+ while i < len(tokens):
+ token = tokens[i]
+
+ embedding, embedding_length_in_tokens = self.hijack.embedding_db.find_embedding_at_position(tokens, i)
+
+ mult_change = self.token_mults.get(token) if shared.opts.enable_emphasis else None
+ if mult_change is not None:
+ mult *= mult_change
+ i += 1
+ elif embedding is None:
+ remade_tokens.append(token)
+ multipliers.append(mult)
+ i += 1
+ else:
+ emb_len = int(embedding.vec.shape[0])
+ fixes.append((len(remade_tokens), embedding))
+ remade_tokens += [0] * emb_len
+ multipliers += [mult] * emb_len
+ used_custom_terms.append((embedding.name, embedding.checksum()))
+ i += embedding_length_in_tokens
+
+ if len(remade_tokens) > maxlen - 2:
+ vocab = {v: k for k, v in self.wrapped.tokenizer.get_vocab().items()}
+ ovf = remade_tokens[maxlen - 2:]
+ overflowing_words = [vocab.get(int(x), "") for x in ovf]
+ overflowing_text = self.wrapped.tokenizer.convert_tokens_to_string(''.join(overflowing_words))
+ hijack_comments.append(f"Warning: too many input tokens; some ({len(overflowing_words)}) have been truncated:\n{overflowing_text}\n")
+
+ token_count = len(remade_tokens)
+ remade_tokens = remade_tokens + [id_end] * (maxlen - 2 - len(remade_tokens))
+ remade_tokens = [id_start] + remade_tokens[0:maxlen - 2] + [id_end]
+ cache[tuple_tokens] = (remade_tokens, fixes, multipliers)
+
+ multipliers = multipliers + [1.0] * (maxlen - 2 - len(multipliers))
+ multipliers = [1.0] + multipliers[0:maxlen - 2] + [1.0]
+
+ remade_batch_tokens.append(remade_tokens)
+ hijack_fixes.append(fixes)
+ batch_multipliers.append(multipliers)
+ return batch_multipliers, remade_batch_tokens, used_custom_terms, hijack_comments, hijack_fixes, token_count
+
+
+def forward_old(self: sd_hijack_clip.FrozenCLIPEmbedderWithCustomWordsBase, texts):
+ batch_multipliers, remade_batch_tokens, used_custom_terms, hijack_comments, hijack_fixes, _token_count = process_text_old(self, texts)
+
+ chunk_count = max([len(x) for x in remade_batch_tokens])
+
+ if shared.opts.return_batch_chunks:
+ return (remade_batch_tokens, chunk_count)
+
+ self.hijack.comments += hijack_comments
+
+ if len(used_custom_terms) > 0:
+ embedding_names = ", ".join(f"{word} [{checksum}]" for word, checksum in used_custom_terms)
+ self.hijack.comments.append(f"Used embeddings: {embedding_names}")
+
+ self.hijack.fixes = hijack_fixes
+ return self.process_tokens(remade_batch_tokens, batch_multipliers)
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/sd_hijack_open_clip.py b/custom_nodes/ComfyUI_smZNodes/modules/sd_hijack_open_clip.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f47540bfbf1de2386f126141d4447405460c13d
--- /dev/null
+++ b/custom_nodes/ComfyUI_smZNodes/modules/sd_hijack_open_clip.py
@@ -0,0 +1,77 @@
+# import open_clip.tokenizer
+import torch
+
+from . import sd_hijack_clip, devices
+from .shared import opts
+
+# tokenizer = open_clip.tokenizer._tokenizer
+
+
+class FrozenOpenCLIPEmbedderWithCustomWords(sd_hijack_clip.FrozenCLIPEmbedderWithCustomWordsBase):
+ def __init__(self, wrapped, hijack):
+ super().__init__(wrapped, hijack)
+ self.tokenizer = tokenizer = self.wrapped.tokenizer
+
+ self.comma_token = [v for k, v in tokenizer.encoder.items() if k == ','][0]
+ # self.id_start = tokenizer.encoder[""]
+ # self.id_end = tokenizer.encoder[""]
+ self.id_start = tokenizer.bos_token_id
+ self.id_end = tokenizer.eos_token_id
+ self.id_pad = 0
+
+ def tokenize(self, texts):
+ assert not opts.use_old_emphasis_implementation, 'Old emphasis implementation not supported for Open Clip'
+
+ tokenized = [self.tokenizer.encode(text) for text in texts]
+
+ return tokenized
+
+ def encode_with_transformers(self, tokens):
+ # set self.wrapped.layer_idx here according to opts.CLIP_stop_at_last_layers
+ z = self.wrapped.encode_with_transformer(tokens)
+
+ return z
+
+ def encode_embedding_init_text(self, init_text, nvpt):
+ ids = self.tokenizer.encode(init_text)
+ ids = torch.asarray([ids], device=devices.device, dtype=torch.int)
+ embedded = self.wrapped.model.token_embedding.wrapped(ids).squeeze(0)
+
+ return embedded
+
+
+class FrozenOpenCLIPEmbedder2WithCustomWords(sd_hijack_clip.FrozenCLIPEmbedderWithCustomWordsBase):
+ def __init__(self, wrapped, hijack):
+ super().__init__(wrapped, hijack)
+ self.tokenizer = tokenizer = self.wrapped.tokenizer
+
+ self.comma_token = [v for k, v in tokenizer.encoder.items() if k == ','][0]
+ # self.id_start = tokenizer.encoder[""]
+ # self.id_end = tokenizer.encoder[""]
+ self.id_start = tokenizer.bos_token_id
+ self.id_end = tokenizer.eos_token_id
+ self.id_pad = 0
+
+ def tokenize(self, texts):
+ assert not opts.use_old_emphasis_implementation, 'Old emphasis implementation not supported for Open Clip'
+
+ tokenized = [self.tokenizer.encode(text) for text in texts]
+
+ return tokenized
+
+ def encode_with_transformers(self, tokens):
+ d = self.wrapped.encode_with_transformer(tokens)
+ z = d[self.wrapped.layer]
+
+ pooled = d.get("pooled")
+ if pooled is not None:
+ z.pooled = pooled
+
+ return z
+
+ def encode_embedding_init_text(self, init_text, nvpt):
+ ids = self.tokenizer.encode(init_text)
+ ids = torch.asarray([ids], device=devices.device, dtype=torch.int)
+ embedded = self.wrapped.model.token_embedding.wrapped(ids.to(self.wrapped.model.token_embedding.wrapped.weight.device)).squeeze(0)
+
+ return embedded
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/sd_hijack_optimizations.py b/custom_nodes/ComfyUI_smZNodes/modules/sd_hijack_optimizations.py
new file mode 100644
index 0000000000000000000000000000000000000000..6157c874bc942725c21bd11a98fe722d0da5bcc6
--- /dev/null
+++ b/custom_nodes/ComfyUI_smZNodes/modules/sd_hijack_optimizations.py
@@ -0,0 +1,671 @@
+from __future__ import annotations
+import math
+import psutil
+
+import torch
+from torch import einsum
+
+from comfy import ldm
+from ldm.util import default
+from einops import rearrange
+
+from . import shared, errors, devices
+from ldm.modules import sub_quadratic_attention
+from .hypernetworks import hypernetwork
+
+def apply_funcs(undo=False):
+ def _apply_funcs(class_name):
+ import ldm.modules.diffusionmodules.model
+ import ldm.modules.attention
+ module = ldm.modules.diffusionmodules.model if "Attn" in class_name else ldm.modules.attention
+ if not hasattr(module, class_name): return
+ m = getattr(module, class_name, object())
+ if not hasattr(m, "forward_orig") and hasattr(m, "forward"):
+ setattr(m, "forward_orig", m.forward)
+ if undo and hasattr(m, "forward_orig"):
+ setattr(m, "forward", m.forward_orig)
+ cross_attention = ["CrossAttention", "MemoryEfficientCrossAttention", "CrossAttentionPytorch", "CrossAttentionBirchSan"]
+ attn_block = ["AttnBlock", "MemoryEfficientAttnBlock", "MemoryEfficientAttnBlockPytorch"]
+ for class_name in cross_attention+attn_block:
+ _apply_funcs(class_name)
+apply_funcs()
+
+def apply_func(m, x, fn):
+ if hasattr(m, x):
+ setattr(getattr(m, x, object()), 'forward', fn)
+
+
+class SdOptimization:
+ name: str = None
+ label: str | None = None
+ cmd_opt: str | None = None
+ priority: int = 0
+
+ def title(self):
+ if self.label is None:
+ return self.name
+
+ return f"{self.name} - {self.label}"
+
+ def is_available(self):
+ return True
+
+ def apply(self):
+ pass
+
+ def undo(self):
+ return undo()
+
+
+def undo():
+ apply_funcs(undo=True)
+ # ldm.modules.attention.CrossAttention.forward = hypernetwork.attention_ldm.modules.attention.CrossAttention_forward
+ # sgm.modules.attention.ldm.modules.attention.CrossAttention.forward = hypernetwork.attention_ldm.modules.attention.CrossAttention_forward
+ # sgm.modules.diffusionmodules.model.ldm.modules.diffusionmodules.model.AttnBlock.forward = sgm_diffusionmodules_model_AttnBlock_forward
+
+
+class SdOptimizationXformers(SdOptimization):
+ name = "xformers"
+ cmd_opt = "xformers"
+ priority = 100
+
+ def is_available(self):
+ return shared.cmd_opts.force_enable_xformers or (shared.xformers_available and torch.cuda.is_available() and (6, 0) <= torch.cuda.get_device_capability(shared.device) <= (9, 0))
+
+ def apply(self):
+ apply_func(ldm.modules.attention, 'CrossAttention', xformers_attention_forward)
+ apply_func(ldm.modules.attention, 'MemoryEfficientCrossAttention', xformers_attention_forward)
+ apply_func(ldm.modules.diffusionmodules.model, 'MemoryEfficientAttnBlock', xformers_attnblock_forward)
+
+
+class SdOptimizationSdpNoMem(SdOptimization):
+ name = "sdp-no-mem"
+ label = "scaled dot product without memory efficient attention"
+ cmd_opt = "opt_sdp_no_mem_attention"
+ priority = 80
+
+ def is_available(self):
+ return hasattr(torch.nn.functional, "scaled_dot_product_attention") and callable(torch.nn.functional.scaled_dot_product_attention)
+
+ def apply(self):
+ apply_func(ldm.modules.attention, 'CrossAttention', scaled_dot_product_no_mem_attention_forward)
+ apply_func(ldm.modules.attention, 'CrossAttentionPytorch', scaled_dot_product_no_mem_attention_forward)
+ apply_func(ldm.modules.diffusionmodules.model, 'AttnBlock', sdp_no_mem_attnblock_forward)
+ apply_func(ldm.modules.diffusionmodules.model, 'MemoryEfficientAttnBlock', sdp_no_mem_attnblock_forward)
+
+
+class SdOptimizationSdp(SdOptimizationSdpNoMem):
+ name = "sdp"
+ label = "scaled dot product"
+ cmd_opt = "opt_sdp_attention"
+ priority = 70
+
+ def apply(self):
+ apply_func(ldm.modules.attention, 'CrossAttention', scaled_dot_product_attention_forward)
+ apply_func(ldm.modules.attention, 'CrossAttentionPytorch', scaled_dot_product_attention_forward)
+ apply_func(ldm.modules.diffusionmodules.model, 'AttnBlock', sdp_attnblock_forward)
+
+
+class SdOptimizationSubQuad(SdOptimization):
+ name = "sub-quadratic"
+ cmd_opt = "opt_sub_quad_attention"
+ priority = 10
+
+ def apply(self):
+ apply_func(ldm.modules.attention, 'CrossAttention', sub_quad_attention_forward)
+ apply_func(ldm.modules.attention, 'CrossAttentionBirchSan', sub_quad_attention_forward)
+ apply_func(ldm.modules.diffusionmodules.model, 'AttnBlock', sub_quad_attnblock_forward)
+
+class SdOptimizationV1(SdOptimization):
+ name = "V1"
+ label = "original v1"
+ cmd_opt = "opt_split_attention_v1"
+ priority = 10
+
+ def apply(self):
+ apply_func(ldm.modules.attention, 'CrossAttention', split_cross_attention_forward_v1)
+ apply_func(ldm.modules.attention, 'CrossAttentionPytorch', split_cross_attention_forward_v1)
+
+
+class SdOptimizationInvokeAI(SdOptimization):
+ name = "InvokeAI"
+ cmd_opt = "opt_split_attention_invokeai"
+
+ @property
+ def priority(self):
+ return 1000 if not torch.cuda.is_available() else 10
+
+ def apply(self):
+ apply_func(ldm.modules.attention, 'CrossAttention', split_cross_attention_forward_invokeAI)
+ apply_func(ldm.modules.attention, 'CrossAttentionPytorch', split_cross_attention_forward_invokeAI)
+
+
+class SdOptimizationDoggettx(SdOptimization):
+ name = "Doggettx"
+ cmd_opt = "opt_split_attention"
+ priority = 90
+
+ def apply(self):
+ apply_func(ldm.modules.attention, 'CrossAttention', split_cross_attention_forward)
+ apply_func(ldm.modules.attention, 'CrossAttentionPytorch', split_cross_attention_forward)
+ apply_func(ldm.modules.diffusionmodules.model, 'AttnBlock', cross_attention_attnblock_forward)
+
+
+def list_optimizers(res):
+ res.extend([
+ SdOptimizationXformers(),
+ SdOptimizationSdpNoMem(),
+ SdOptimizationSdp(),
+ SdOptimizationSubQuad(),
+ SdOptimizationV1(),
+ SdOptimizationInvokeAI(),
+ SdOptimizationDoggettx(),
+ ])
+
+
+def get_available_vram():
+ if shared.device.type == 'cuda':
+ stats = torch.cuda.memory_stats(shared.device)
+ mem_active = stats['active_bytes.all.current']
+ mem_reserved = stats['reserved_bytes.all.current']
+ mem_free_cuda, _ = torch.cuda.mem_get_info(torch.cuda.current_device())
+ mem_free_torch = mem_reserved - mem_active
+ mem_free_total = mem_free_cuda + mem_free_torch
+ return mem_free_total
+ else:
+ return psutil.virtual_memory().available
+
+
+# see https://github.com/basujindal/stable-diffusion/pull/117 for discussion
+def split_cross_attention_forward_v1(self, x, context=None, mask=None, **kwargs):
+ h = self.heads
+
+ q_in = self.to_q(x)
+ context = default(context, x)
+
+ context_k, context_v = hypernetwork.apply_hypernetworks(shared.loaded_hypernetworks, context)
+ k_in = self.to_k(context_k)
+ v_in = self.to_v(context_v)
+ del context, context_k, context_v, x
+
+ q, k, v = (rearrange(t, 'b n (h d) -> (b h) n d', h=h) for t in (q_in, k_in, v_in))
+ del q_in, k_in, v_in
+
+ dtype = q.dtype
+ if shared.opts.upcast_attn:
+ q, k, v = q.float(), k.float(), v.float()
+
+ with devices.without_autocast(disable=not shared.opts.upcast_attn):
+ r1 = torch.zeros(q.shape[0], q.shape[1], v.shape[2], device=q.device, dtype=q.dtype)
+ for i in range(0, q.shape[0], 2):
+ end = i + 2
+ s1 = einsum('b i d, b j d -> b i j', q[i:end], k[i:end])
+ s1 *= self.scale
+
+ s2 = s1.softmax(dim=-1)
+ del s1
+
+ r1[i:end] = einsum('b i j, b j d -> b i d', s2, v[i:end])
+ del s2
+ del q, k, v
+
+ r1 = r1.to(dtype)
+
+ r2 = rearrange(r1, '(b h) n d -> b n (h d)', h=h)
+ del r1
+
+ return self.to_out(r2)
+
+
+# taken from https://github.com/Doggettx/stable-diffusion and modified
+def split_cross_attention_forward(self, x, context=None, mask=None, **kwargs):
+ h = self.heads
+
+ q_in = self.to_q(x)
+ context = default(context, x)
+
+ context_k, context_v = hypernetwork.apply_hypernetworks(shared.loaded_hypernetworks, context)
+ k_in = self.to_k(context_k)
+ v_in = self.to_v(context_v)
+
+ dtype = q_in.dtype
+ if shared.opts.upcast_attn:
+ q_in, k_in, v_in = q_in.float(), k_in.float(), v_in if v_in.device.type == 'mps' else v_in.float()
+
+ with devices.without_autocast(disable=not shared.opts.upcast_attn):
+ k_in = k_in * self.scale
+
+ del context, x
+
+ q, k, v = (rearrange(t, 'b n (h d) -> (b h) n d', h=h) for t in (q_in, k_in, v_in))
+ del q_in, k_in, v_in
+
+ r1 = torch.zeros(q.shape[0], q.shape[1], v.shape[2], device=q.device, dtype=q.dtype)
+
+ mem_free_total = get_available_vram()
+
+ gb = 1024 ** 3
+ tensor_size = q.shape[0] * q.shape[1] * k.shape[1] * q.element_size()
+ modifier = 3 if q.element_size() == 2 else 2.5
+ mem_required = tensor_size * modifier
+ steps = 1
+
+ if mem_required > mem_free_total:
+ steps = 2 ** (math.ceil(math.log(mem_required / mem_free_total, 2)))
+ # print(f"Expected tensor size:{tensor_size/gb:0.1f}GB, cuda free:{mem_free_cuda/gb:0.1f}GB "
+ # f"torch free:{mem_free_torch/gb:0.1f} total:{mem_free_total/gb:0.1f} steps:{steps}")
+
+ if steps > 64:
+ max_res = math.floor(math.sqrt(math.sqrt(mem_free_total / 2.5)) / 8) * 64
+ raise RuntimeError(f'Not enough memory, use lower resolution (max approx. {max_res}x{max_res}). '
+ f'Need: {mem_required / 64 / gb:0.1f}GB free, Have:{mem_free_total / gb:0.1f}GB free')
+
+ slice_size = q.shape[1] // steps if (q.shape[1] % steps) == 0 else q.shape[1]
+ for i in range(0, q.shape[1], slice_size):
+ end = i + slice_size
+ s1 = einsum('b i d, b j d -> b i j', q[:, i:end], k)
+
+ s2 = s1.softmax(dim=-1, dtype=q.dtype)
+ del s1
+
+ r1[:, i:end] = einsum('b i j, b j d -> b i d', s2, v)
+ del s2
+
+ del q, k, v
+
+ r1 = r1.to(dtype)
+
+ r2 = rearrange(r1, '(b h) n d -> b n (h d)', h=h)
+ del r1
+
+ return self.to_out(r2)
+
+
+# -- Taken from https://github.com/invoke-ai/InvokeAI and modified --
+mem_total_gb = psutil.virtual_memory().total // (1 << 30)
+
+
+def einsum_op_compvis(q, k, v):
+ s = einsum('b i d, b j d -> b i j', q, k)
+ s = s.softmax(dim=-1, dtype=s.dtype)
+ return einsum('b i j, b j d -> b i d', s, v)
+
+
+def einsum_op_slice_0(q, k, v, slice_size):
+ r = torch.zeros(q.shape[0], q.shape[1], v.shape[2], device=q.device, dtype=q.dtype)
+ for i in range(0, q.shape[0], slice_size):
+ end = i + slice_size
+ r[i:end] = einsum_op_compvis(q[i:end], k[i:end], v[i:end])
+ return r
+
+
+def einsum_op_slice_1(q, k, v, slice_size):
+ r = torch.zeros(q.shape[0], q.shape[1], v.shape[2], device=q.device, dtype=q.dtype)
+ for i in range(0, q.shape[1], slice_size):
+ end = i + slice_size
+ r[:, i:end] = einsum_op_compvis(q[:, i:end], k, v)
+ return r
+
+
+def einsum_op_mps_v1(q, k, v):
+ if q.shape[0] * q.shape[1] <= 2**16: # (512x512) max q.shape[1]: 4096
+ return einsum_op_compvis(q, k, v)
+ else:
+ slice_size = math.floor(2**30 / (q.shape[0] * q.shape[1]))
+ if slice_size % 4096 == 0:
+ slice_size -= 1
+ return einsum_op_slice_1(q, k, v, slice_size)
+
+
+def einsum_op_mps_v2(q, k, v):
+ if mem_total_gb > 8 and q.shape[0] * q.shape[1] <= 2**16:
+ return einsum_op_compvis(q, k, v)
+ else:
+ return einsum_op_slice_0(q, k, v, 1)
+
+
+def einsum_op_tensor_mem(q, k, v, max_tensor_mb):
+ size_mb = q.shape[0] * q.shape[1] * k.shape[1] * q.element_size() // (1 << 20)
+ if size_mb <= max_tensor_mb:
+ return einsum_op_compvis(q, k, v)
+ div = 1 << int((size_mb - 1) / max_tensor_mb).bit_length()
+ if div <= q.shape[0]:
+ return einsum_op_slice_0(q, k, v, q.shape[0] // div)
+ return einsum_op_slice_1(q, k, v, max(q.shape[1] // div, 1))
+
+
+def einsum_op_cuda(q, k, v):
+ stats = torch.cuda.memory_stats(q.device)
+ mem_active = stats['active_bytes.all.current']
+ mem_reserved = stats['reserved_bytes.all.current']
+ mem_free_cuda, _ = torch.cuda.mem_get_info(q.device)
+ mem_free_torch = mem_reserved - mem_active
+ mem_free_total = mem_free_cuda + mem_free_torch
+ # Divide factor of safety as there's copying and fragmentation
+ return einsum_op_tensor_mem(q, k, v, mem_free_total / 3.3 / (1 << 20))
+
+
+def einsum_op(q, k, v):
+ if q.device.type == 'cuda':
+ return einsum_op_cuda(q, k, v)
+
+ if q.device.type == 'mps':
+ if mem_total_gb >= 32 and q.shape[0] % 32 != 0 and q.shape[0] * q.shape[1] < 2**18:
+ return einsum_op_mps_v1(q, k, v)
+ return einsum_op_mps_v2(q, k, v)
+
+ # Smaller slices are faster due to L2/L3/SLC caches.
+ # Tested on i7 with 8MB L3 cache.
+ return einsum_op_tensor_mem(q, k, v, 32)
+
+
+def split_cross_attention_forward_invokeAI(self, x, context=None, mask=None, **kwargs):
+ h = self.heads
+
+ q = self.to_q(x)
+ context = default(context, x)
+
+ context_k, context_v = hypernetwork.apply_hypernetworks(shared.loaded_hypernetworks, context)
+ k = self.to_k(context_k)
+ v = self.to_v(context_v)
+ del context, context_k, context_v, x
+
+ dtype = q.dtype
+ if shared.opts.upcast_attn:
+ q, k, v = q.float(), k.float(), v if v.device.type == 'mps' else v.float()
+
+ with devices.without_autocast(disable=not shared.opts.upcast_attn):
+ k = k * self.scale
+
+ q, k, v = (rearrange(t, 'b n (h d) -> (b h) n d', h=h) for t in (q, k, v))
+ r = einsum_op(q, k, v)
+ r = r.to(dtype)
+ return self.to_out(rearrange(r, '(b h) n d -> b n (h d)', h=h))
+
+# -- End of code from https://github.com/invoke-ai/InvokeAI --
+
+
+# Based on Birch-san's modified implementation of sub-quadratic attention from https://github.com/Birch-san/diffusers/pull/1
+# The sub_quad_attention_forward function is under the MIT License listed under Memory Efficient Attention in the Licenses section of the web UI interface
+def sub_quad_attention_forward(self, x, context=None, mask=None, **kwargs):
+ assert mask is None, "attention-mask not currently implemented for SubQuadraticCrossAttnProcessor."
+
+ h = self.heads
+
+ q = self.to_q(x)
+ context = default(context, x)
+
+ context_k, context_v = hypernetwork.apply_hypernetworks(shared.loaded_hypernetworks, context)
+ k = self.to_k(context_k)
+ v = self.to_v(context_v)
+ del context, context_k, context_v, x
+
+ q = q.unflatten(-1, (h, -1)).transpose(1,2).flatten(end_dim=1)
+ k = k.unflatten(-1, (h, -1)).transpose(1,2).flatten(end_dim=1)
+ v = v.unflatten(-1, (h, -1)).transpose(1,2).flatten(end_dim=1)
+
+ if q.device.type == 'mps':
+ q, k, v = q.contiguous(), k.contiguous(), v.contiguous()
+
+ dtype = q.dtype
+ if shared.opts.upcast_attn:
+ q, k = q.float(), k.float()
+
+ x = sub_quad_attention(q, k, v, q_chunk_size=shared.cmd_opts.sub_quad_q_chunk_size, kv_chunk_size=shared.cmd_opts.sub_quad_kv_chunk_size, chunk_threshold=shared.cmd_opts.sub_quad_chunk_threshold, use_checkpoint=self.training)
+
+ x = x.to(dtype)
+
+ x = x.unflatten(0, (-1, h)).transpose(1,2).flatten(start_dim=2)
+
+ out_proj, dropout = self.to_out
+ x = out_proj(x)
+ x = dropout(x)
+
+ return x
+
+
+def sub_quad_attention(q, k, v, q_chunk_size=1024, kv_chunk_size=None, kv_chunk_size_min=None, chunk_threshold=None, use_checkpoint=True):
+ bytes_per_token = torch.finfo(q.dtype).bits//8
+ batch_x_heads, q_tokens, _ = q.shape
+ _, k_tokens, _ = k.shape
+ qk_matmul_size_bytes = batch_x_heads * bytes_per_token * q_tokens * k_tokens
+
+ if chunk_threshold is None:
+ chunk_threshold_bytes = int(get_available_vram() * 0.9) if q.device.type == 'mps' else int(get_available_vram() * 0.7)
+ elif chunk_threshold == 0:
+ chunk_threshold_bytes = None
+ else:
+ chunk_threshold_bytes = int(0.01 * chunk_threshold * get_available_vram())
+
+ if kv_chunk_size_min is None and chunk_threshold_bytes is not None:
+ kv_chunk_size_min = chunk_threshold_bytes // (batch_x_heads * bytes_per_token * (k.shape[2] + v.shape[2]))
+ elif kv_chunk_size_min == 0:
+ kv_chunk_size_min = None
+
+ if chunk_threshold_bytes is not None and qk_matmul_size_bytes <= chunk_threshold_bytes:
+ # the big matmul fits into our memory limit; do everything in 1 chunk,
+ # i.e. send it down the unchunked fast-path
+ kv_chunk_size = k_tokens
+
+ with devices.without_autocast(disable=q.dtype == v.dtype):
+ return sub_quadratic_attention.efficient_dot_product_attention(
+ q,
+ k,
+ v,
+ query_chunk_size=q_chunk_size,
+ kv_chunk_size=kv_chunk_size,
+ kv_chunk_size_min = kv_chunk_size_min,
+ use_checkpoint=use_checkpoint,
+ )
+
+
+def get_xformers_flash_attention_op(q, k, v):
+ if not shared.cmd_opts.xformers_flash_attention:
+ return None
+
+ try:
+ flash_attention_op = xformers.ops.MemoryEfficientAttentionFlashAttentionOp
+ fw, bw = flash_attention_op
+ if fw.supports(xformers.ops.fmha.Inputs(query=q, key=k, value=v, attn_bias=None)):
+ return flash_attention_op
+ except Exception as e:
+ errors.display_once(e, "enabling flash attention")
+
+ return None
+
+
+def xformers_attention_forward(self, x, context=None, mask=None, **kwargs):
+ h = self.heads
+ q_in = self.to_q(x)
+ context = default(context, x)
+
+ context_k, context_v = hypernetwork.apply_hypernetworks(shared.loaded_hypernetworks, context)
+ k_in = self.to_k(context_k)
+ v_in = self.to_v(context_v)
+
+ q, k, v = (rearrange(t, 'b n (h d) -> b n h d', h=h) for t in (q_in, k_in, v_in))
+ del q_in, k_in, v_in
+
+ dtype = q.dtype
+ if shared.opts.upcast_attn:
+ q, k, v = q.float(), k.float(), v.float()
+
+ out = xformers.ops.memory_efficient_attention(q, k, v, attn_bias=None, op=get_xformers_flash_attention_op(q, k, v))
+
+ out = out.to(dtype)
+
+ out = rearrange(out, 'b n h d -> b n (h d)', h=h)
+ return self.to_out(out)
+
+
+# Based on Diffusers usage of scaled dot product attention from https://github.com/huggingface/diffusers/blob/c7da8fd23359a22d0df2741688b5b4f33c26df21/src/diffusers/models/cross_attention.py
+# The scaled_dot_product_attention_forward function contains parts of code under Apache-2.0 license listed under Scaled Dot Product Attention in the Licenses section of the web UI interface
+def scaled_dot_product_attention_forward(self, x, context=None, mask=None, **kwargs):
+ batch_size, sequence_length, inner_dim = x.shape
+ if mask is not None:
+ mask = self.prepare_attention_mask(mask, sequence_length, batch_size)
+ mask = mask.view(batch_size, self.heads, -1, mask.shape[-1])
+
+ h = self.heads
+ q_in = self.to_q(x)
+ context = default(context, x)
+
+ context_k, context_v = hypernetwork.apply_hypernetworks(shared.loaded_hypernetworks, context)
+ k_in = self.to_k(context_k)
+ v_in = self.to_v(context_v)
+
+ head_dim = inner_dim // h
+ q = q_in.view(batch_size, -1, h, head_dim).transpose(1, 2)
+ k = k_in.view(batch_size, -1, h, head_dim).transpose(1, 2)
+ v = v_in.view(batch_size, -1, h, head_dim).transpose(1, 2)
+
+ del q_in, k_in, v_in
+
+ dtype = q.dtype
+ if shared.opts.upcast_attn:
+ q, k, v = q.float(), k.float(), v.float()
+
+ # the output of sdp = (batch, num_heads, seq_len, head_dim)
+ hidden_states = torch.nn.functional.scaled_dot_product_attention(
+ q, k, v, attn_mask=mask, dropout_p=0.0, is_causal=False
+ )
+
+ hidden_states = hidden_states.transpose(1, 2).reshape(batch_size, -1, h * head_dim)
+ hidden_states = hidden_states.to(dtype)
+
+ # linear proj
+ hidden_states = self.to_out[0](hidden_states)
+ # dropout
+ hidden_states = self.to_out[1](hidden_states)
+ return hidden_states
+
+
+def scaled_dot_product_no_mem_attention_forward(self, x, context=None, mask=None, **kwargs):
+ with torch.backends.cuda.sdp_kernel(enable_flash=True, enable_math=True, enable_mem_efficient=False):
+ return scaled_dot_product_attention_forward(self, x, context, mask)
+
+
+def cross_attention_attnblock_forward(self, x):
+ h_ = x
+ h_ = self.norm(h_)
+ q1 = self.q(h_)
+ k1 = self.k(h_)
+ v = self.v(h_)
+
+ # compute attention
+ b, c, h, w = q1.shape
+
+ q2 = q1.reshape(b, c, h*w)
+ del q1
+
+ q = q2.permute(0, 2, 1) # b,hw,c
+ del q2
+
+ k = k1.reshape(b, c, h*w) # b,c,hw
+ del k1
+
+ h_ = torch.zeros_like(k, device=q.device)
+
+ mem_free_total = get_available_vram()
+
+ tensor_size = q.shape[0] * q.shape[1] * k.shape[2] * q.element_size()
+ mem_required = tensor_size * 2.5
+ steps = 1
+
+ if mem_required > mem_free_total:
+ steps = 2**(math.ceil(math.log(mem_required / mem_free_total, 2)))
+
+ slice_size = q.shape[1] // steps if (q.shape[1] % steps) == 0 else q.shape[1]
+ for i in range(0, q.shape[1], slice_size):
+ end = i + slice_size
+
+ w1 = torch.bmm(q[:, i:end], k) # b,hw,hw w[b,i,j]=sum_c q[b,i,c]k[b,c,j]
+ w2 = w1 * (int(c)**(-0.5))
+ del w1
+ w3 = torch.nn.functional.softmax(w2, dim=2, dtype=q.dtype)
+ del w2
+
+ # attend to values
+ v1 = v.reshape(b, c, h*w)
+ w4 = w3.permute(0, 2, 1) # b,hw,hw (first hw of k, second of q)
+ del w3
+
+ h_[:, :, i:end] = torch.bmm(v1, w4) # b, c,hw (hw of q) h_[b,c,j] = sum_i v[b,c,i] w_[b,i,j]
+ del v1, w4
+
+ h2 = h_.reshape(b, c, h, w)
+ del h_
+
+ h3 = self.proj_out(h2)
+ del h2
+
+ h3 += x
+
+ return h3
+
+
+def xformers_attnblock_forward(self, x):
+ try:
+ h_ = x
+ h_ = self.norm(h_)
+ q = self.q(h_)
+ k = self.k(h_)
+ v = self.v(h_)
+ b, c, h, w = q.shape
+ q, k, v = (rearrange(t, 'b c h w -> b (h w) c') for t in (q, k, v))
+ dtype = q.dtype
+ if shared.opts.upcast_attn:
+ q, k = q.float(), k.float()
+ q = q.contiguous()
+ k = k.contiguous()
+ v = v.contiguous()
+ out = xformers.ops.memory_efficient_attention(q, k, v, op=get_xformers_flash_attention_op(q, k, v))
+ out = out.to(dtype)
+ out = rearrange(out, 'b (h w) c -> b c h w', h=h)
+ out = self.proj_out(out)
+ return x + out
+ except NotImplementedError:
+ return cross_attention_attnblock_forward(self, x)
+
+
+def sdp_attnblock_forward(self, x):
+ h_ = x
+ h_ = self.norm(h_)
+ q = self.q(h_)
+ k = self.k(h_)
+ v = self.v(h_)
+ b, c, h, w = q.shape
+ q, k, v = (rearrange(t, 'b c h w -> b (h w) c') for t in (q, k, v))
+ dtype = q.dtype
+ if shared.opts.upcast_attn:
+ q, k, v = q.float(), k.float(), v.float()
+ q = q.contiguous()
+ k = k.contiguous()
+ v = v.contiguous()
+ out = torch.nn.functional.scaled_dot_product_attention(q, k, v, dropout_p=0.0, is_causal=False)
+ out = out.to(dtype)
+ out = rearrange(out, 'b (h w) c -> b c h w', h=h)
+ out = self.proj_out(out)
+ return x + out
+
+
+def sdp_no_mem_attnblock_forward(self, x):
+ with torch.backends.cuda.sdp_kernel(enable_flash=True, enable_math=True, enable_mem_efficient=False):
+ return sdp_attnblock_forward(self, x)
+
+
+def sub_quad_attnblock_forward(self, x):
+ h_ = x
+ h_ = self.norm(h_)
+ q = self.q(h_)
+ k = self.k(h_)
+ v = self.v(h_)
+ b, c, h, w = q.shape
+ q, k, v = (rearrange(t, 'b c h w -> b (h w) c') for t in (q, k, v))
+ q = q.contiguous()
+ k = k.contiguous()
+ v = v.contiguous()
+ out = sub_quad_attention(q, k, v, q_chunk_size=shared.cmd_opts.sub_quad_q_chunk_size, kv_chunk_size=shared.cmd_opts.sub_quad_kv_chunk_size, chunk_threshold=shared.cmd_opts.sub_quad_chunk_threshold, use_checkpoint=self.training)
+ out = rearrange(out, 'b (h w) c -> b c h w', h=h)
+ out = self.proj_out(out)
+ return x + out
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/sd_hijack_unet.py b/custom_nodes/ComfyUI_smZNodes/modules/sd_hijack_unet.py
new file mode 100644
index 0000000000000000000000000000000000000000..1028d7f7a1bcff1e58a8ee81d51ec16c2bbe1ae2
--- /dev/null
+++ b/custom_nodes/ComfyUI_smZNodes/modules/sd_hijack_unet.py
@@ -0,0 +1,134 @@
+import torch
+from packaging import version
+
+from . import devices
+from .sd_hijack_utils import CondFunc
+from torch.nn.functional import silu
+import comfy
+from comfy import ldm
+import contextlib
+
+class TorchHijackForUnet:
+ """
+ This is torch, but with cat that resizes tensors to appropriate dimensions if they do not match;
+ this makes it possible to create pictures with dimensions that are multiples of 8 rather than 64
+ """
+
+ def __getattr__(self, item):
+ if item == 'cat':
+ return self.cat
+
+ if hasattr(torch, item):
+ return getattr(torch, item)
+
+ raise AttributeError(f"'{type(self).__name__}' object has no attribute '{item}'")
+
+ def cat(self, tensors, *args, **kwargs):
+ if len(tensors) == 2:
+ a, b = tensors
+ if a.shape[-2:] != b.shape[-2:]:
+ a = torch.nn.functional.interpolate(a, b.shape[-2:], mode="nearest")
+
+ tensors = (a, b)
+
+ return torch.cat(tensors, *args, **kwargs)
+
+th = TorchHijackForUnet()
+
+from . import sd_hijack_optimizations
+from comfy.model_base import BaseModel
+from functools import wraps
+sdp_no_mem = sd_hijack_optimizations.SdOptimizationSdpNoMem()
+BaseModel.apply_model_orig = BaseModel.apply_model
+
+# @contextmanager
+class ApplyOptimizationsContext:
+ def __init__(self):
+ self.nonlinearity = ldm.modules.diffusionmodules.model.nonlinearity
+ self.th = ldm.modules.diffusionmodules.openaimodel.th
+ ldm.modules.diffusionmodules.model.nonlinearity = silu
+ ldm.modules.diffusionmodules.openaimodel.th = th
+ sdp_no_mem.apply()
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ ldm.modules.diffusionmodules.model.nonlinearity = self.nonlinearity
+ ldm.modules.diffusionmodules.openaimodel.th = self.th
+ sd_hijack_optimizations.undo()
+
+
+
+def ApplyOptimizationsContext3(func):
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ with ApplyOptimizationsContext():
+ return func(*args, **kwargs)
+ return wrapper
+
+precision_scope_null = lambda a, dtype=None: contextlib.nullcontext(a)
+
+# def apply_model(orig_func, self, x_noisy, t, c_concat=None, c_crossattn=None, c_adm=None, control=None, transformer_options={}, *args, **kwargs):
+def apply_model(orig_func, self, *args, **kwargs):
+ transformer_options = kwargs['transformer_options'] if 'transformer_options' in kwargs else {}
+ c_crossattn = kwargs['c_crossattn'] if 'c_crossattn' in kwargs else args[3]
+ x_noisy = kwargs['x_noisy'] if 'x_noisy' in kwargs else args[0]
+ if not transformer_options.get('from_smZ', False):
+ return self.apply_model_orig(*args, **kwargs)
+
+ cond=c_crossattn
+ if isinstance(cond, dict):
+ for y in cond.keys():
+ if isinstance(cond[y], list):
+ cond[y] = [x.to(devices.dtype_unet) if isinstance(x, torch.Tensor) else x for x in cond[y]]
+ else:
+ cond[y] = cond[y].to(devices.dtype_unet) if isinstance(cond[y], torch.Tensor) else cond[y]
+
+ if x_noisy.dtype != torch.float32:
+ precision_scope = torch.autocast
+ else:
+ precision_scope = precision_scope_null
+
+ with precision_scope(comfy.model_management.get_autocast_device(x_noisy.device), dtype=x_noisy.dtype): # , torch.float32):
+ # with devices.autocast():
+ out = orig_func(self, *args, **kwargs).float()
+ return out
+
+class GELUHijack(torch.nn.GELU, torch.nn.Module):
+ def __init__(self, *args, **kwargs):
+ torch.nn.GELU.__init__(self, *args, **kwargs)
+ def forward(self, x):
+ if devices.unet_needs_upcast:
+ return torch.nn.GELU.forward(self.float(), x.float()).to(devices.dtype_unet)
+ else:
+ return torch.nn.GELU.forward(self, x)
+
+ddpm_edit_hijack = None
+def hijack_ddpm_edit():
+ global ddpm_edit_hijack
+ if not ddpm_edit_hijack:
+ CondFunc('modules.models.diffusion.ddpm_edit.LatentDiffusion.decode_first_stage', first_stage_sub, first_stage_cond)
+ CondFunc('modules.models.diffusion.ddpm_edit.LatentDiffusion.encode_first_stage', first_stage_sub, first_stage_cond)
+ ddpm_edit_hijack = CondFunc('modules.models.diffusion.ddpm_edit.LatentDiffusion.apply_model', apply_model, unet_needs_upcast)
+
+
+unet_needs_upcast = lambda *args, **kwargs: devices.unet_needs_upcast
+# CondFunc('comfy.model_base.BaseModel.apply_model', apply_model, unet_needs_upcast)
+# CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.apply_model', apply_model, unet_needs_upcast)
+# CondFunc('ldm.modules.diffusionmodules.openaimodel.timestep_embedding', lambda orig_func, timesteps, *args, **kwargs: orig_func(timesteps, *args, **kwargs).to(torch.float32 if timesteps.dtype == torch.int64 else devices.dtype_unet), unet_needs_upcast)
+# if version.parse(torch.__version__) <= version.parse("1.13.2") or torch.cuda.is_available():
+# CondFunc('ldm.modules.diffusionmodules.util.GroupNorm32.forward', lambda orig_func, self, *args, **kwargs: orig_func(self.float(), *args, **kwargs), unet_needs_upcast)
+# CondFunc('ldm.modules.attention.GEGLU.forward', lambda orig_func, self, x: orig_func(self.float(), x.float()).to(devices.dtype_unet), unet_needs_upcast)
+# try:
+# CondFunc('open_clip.transformer.ResidualAttentionBlock.__init__', lambda orig_func, *args, **kwargs: kwargs.update({'act_layer': GELUHijack}) and False or orig_func(*args, **kwargs), lambda _, *args, **kwargs: kwargs.get('act_layer') is None or kwargs['act_layer'] == torch.nn.GELU)
+# except:
+# CondFunc('comfy.t2i_adapter.adapter.ResidualAttentionBlock.__init__', lambda orig_func, *args, **kwargs: kwargs.update({'act_layer': GELUHijack}) and False or orig_func(*args, **kwargs), lambda _, *args, **kwargs: kwargs.get('act_layer') is None or kwargs['act_layer'] == torch.nn.GELU)
+first_stage_cond = lambda _, self, *args, **kwargs: devices.unet_needs_upcast and self.model.diffusion_model.dtype == torch.float16
+first_stage_sub = lambda orig_func, self, x, **kwargs: orig_func(self, x.to(devices.dtype_vae), **kwargs)
+# CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.decode_first_stage', first_stage_sub, first_stage_cond)
+# CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.encode_first_stage', first_stage_sub, first_stage_cond)
+# CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.get_first_stage_encoding', lambda orig_func, *args, **kwargs: orig_func(*args, **kwargs).float(), first_stage_cond)
+
+# CondFunc('sgm.modules.diffusionmodules.wrappers.OpenAIWrapper.forward', apply_model, unet_needs_upcast)
+# CondFunc('sgm.modules.diffusionmodules.openaimodel.timestep_embedding', lambda orig_func, timesteps, *args, **kwargs: orig_func(timesteps, *args, **kwargs).to(torch.float32 if timesteps.dtype == torch.int64 else devices.dtype_unet), unet_needs_upcast)
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/sd_hijack_utils.py b/custom_nodes/ComfyUI_smZNodes/modules/sd_hijack_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..179ebc78e6a3d16e7a4318b8644fee690b447d12
--- /dev/null
+++ b/custom_nodes/ComfyUI_smZNodes/modules/sd_hijack_utils.py
@@ -0,0 +1,28 @@
+import importlib
+
+class CondFunc:
+ def __new__(cls, orig_func, sub_func, cond_func):
+ self = super(CondFunc, cls).__new__(cls)
+ if isinstance(orig_func, str):
+ func_path = orig_func.split('.')
+ for i in range(len(func_path)-1, -1, -1):
+ try:
+ resolved_obj = importlib.import_module('.'.join(func_path[:i]))
+ break
+ except ImportError:
+ pass
+ for attr_name in func_path[i:-1]:
+ resolved_obj = getattr(resolved_obj, attr_name)
+ orig_func = getattr(resolved_obj, func_path[-1])
+ setattr(resolved_obj, func_path[-1], lambda *args, **kwargs: self(*args, **kwargs))
+ self.__init__(orig_func, sub_func, cond_func)
+ return lambda *args, **kwargs: self(*args, **kwargs)
+ def __init__(self, orig_func, sub_func, cond_func):
+ self.__orig_func = orig_func
+ self.__sub_func = sub_func
+ self.__cond_func = cond_func
+ def __call__(self, *args, **kwargs):
+ if not self.__cond_func or self.__cond_func(self.__orig_func, *args, **kwargs):
+ return self.__sub_func(self.__orig_func, *args, **kwargs)
+ else:
+ return self.__orig_func(*args, **kwargs)
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/sd_samplers_cfg_denoiser.py b/custom_nodes/ComfyUI_smZNodes/modules/sd_samplers_cfg_denoiser.py
new file mode 100644
index 0000000000000000000000000000000000000000..76e592c5d56daa1d4731d9197d174bf0e48a22b0
--- /dev/null
+++ b/custom_nodes/ComfyUI_smZNodes/modules/sd_samplers_cfg_denoiser.py
@@ -0,0 +1,260 @@
+import torch
+from . import devices
+from . import prompt_parser
+from . import shared
+from comfy import model_management
+def catenate_conds(conds):
+ if not isinstance(conds[0], dict):
+ return torch.cat(conds)
+
+ return {key: torch.cat([x[key] for x in conds]) for key in conds[0].keys()}
+
+
+def subscript_cond(cond, a, b):
+ if not isinstance(cond, dict):
+ return cond[a:b]
+
+ return {key: vec[a:b] for key, vec in cond.items()}
+
+
+def pad_cond(tensor, repeats, empty):
+ if not isinstance(tensor, dict):
+ return torch.cat([tensor, empty.repeat((tensor.shape[0], repeats, 1)).to(device=tensor.device)], axis=1)
+
+ tensor['crossattn'] = pad_cond(tensor['crossattn'], repeats, empty)
+ return tensor
+
+
+class CFGDenoiser(torch.nn.Module):
+ """
+ Classifier free guidance denoiser. A wrapper for stable diffusion model (specifically for unet)
+ that can take a noisy picture and produce a noise-free picture using two guidances (prompts)
+ instead of one. Originally, the second prompt is just an empty string, but we use non-empty
+ negative prompt.
+ """
+
+ def __init__(self, model):
+ super().__init__()
+ self.inner_model = model
+ self.model_wrap = None
+ self.mask = None
+ self.nmask = None
+ self.init_latent = None
+ self.steps = None
+ """number of steps as specified by user in UI"""
+
+ self.total_steps = None
+ """expected number of calls to denoiser calculated from self.steps and specifics of the selected sampler"""
+
+ self.step = 0
+ self.image_cfg_scale = None
+ self.padded_cond_uncond = False
+ self.sampler = None
+ self.model_wrap = None
+ self.p = None
+ self.mask_before_denoising = False
+ import comfy
+ import inspect
+ apply_model_src = inspect.getsource(comfy.model_base.BaseModel.apply_model_orig)
+ self.c_crossattn_as_list = 'torch.cat(c_crossattn, 1)' in apply_model_src
+
+ # @property
+ # def inner_model(self):
+ # raise NotImplementedError()
+
+ def combine_denoised(self, x_out, conds_list, uncond, cond_scale):
+ denoised_uncond = x_out[-uncond.shape[0]:]
+ denoised = torch.clone(denoised_uncond)
+
+ for i, conds in enumerate(conds_list):
+ for cond_index, weight in conds:
+ denoised[i] += (x_out[cond_index] - denoised_uncond[i]) * (weight * cond_scale)
+
+ return denoised
+
+ def combine_denoised_for_edit_model(self, x_out, cond_scale):
+ out_cond, out_img_cond, out_uncond = x_out.chunk(3)
+ denoised = out_uncond + cond_scale * (out_cond - out_img_cond) + self.image_cfg_scale * (out_img_cond - out_uncond)
+
+ return denoised
+
+ def get_pred_x0(self, x_in, x_out, sigma):
+ return x_out
+
+ def update_inner_model(self):
+ self.model_wrap = None
+
+ c, uc = self.p.get_conds()
+ self.sampler.sampler_extra_args['cond'] = c
+ self.sampler.sampler_extra_args['uncond'] = uc
+
+ def make_condition_dict(self, x, d):
+ if x.c_adm is not None:
+ k = x.c_adm['key']
+ d[k] = x.c_adm[k]
+ d['c_crossattn'] = d['c_crossattn'].to(device=x.device)
+ return d
+
+ def forward(self, x, sigma, uncond, cond, cond_scale, s_min_uncond, image_cond):
+ model_management.throw_exception_if_processing_interrupted()
+ # if state.interrupted or state.skipped:
+ # raise sd_samplers_common.InterruptedException
+
+ # if sd_samplers_common.apply_refiner(self):
+ # cond = self.sampler.sampler_extra_args['cond']
+ # uncond = self.sampler.sampler_extra_args['uncond']
+
+ # at self.image_cfg_scale == 1.0 produced results for edit model are the same as with normal sampling,
+ # so is_edit_model is set to False to support AND composition.
+ # is_edit_model = shared.sd_model.cond_stage_key == "edit" and self.image_cfg_scale is not None and self.image_cfg_scale != 1.0
+ is_edit_model = False
+
+ conds_list, tensor = cond
+ assert not is_edit_model or all(len(conds) == 1 for conds in conds_list), "AND is not supported for InstructPix2Pix checkpoint (unless using Image CFG scale = 1.0)"
+
+ if self.mask_before_denoising and self.mask is not None:
+ x = self.init_latent * self.mask + self.nmask * x
+
+ batch_size = len(conds_list)
+ repeats = [len(conds_list[i]) for i in range(batch_size)]
+ if not hasattr(x, 'c_adm'):
+ x.c_adm = None
+
+ # if shared.sd_model.model.conditioning_key == "crossattn-adm":
+ # image_uncond = torch.zeros_like(image_cond)
+ # make_condition_dict = lambda c_crossattn: {"c_crossattn": c_crossattn} # pylint: disable=C3001
+ # else:
+ # image_uncond = image_cond
+ # if isinstance(uncond, dict):
+ # make_condition_dict = lambda c_crossattn, c_concat: {**c_crossattn, "c_concat": [c_concat]}
+ # else:
+ # make_condition_dict = lambda c_crossattn, c_concat: {"c_crossattn": [c_crossattn], "c_concat": [c_concat]}
+
+ # unclip
+ # if shared.sd_model.model.conditioning_key == "crossattn-adm":
+ if False:
+ image_uncond = torch.zeros_like(image_cond)
+ if self.c_crossattn_as_list:
+ make_condition_dict = lambda c_crossattn: {"c_crossattn": [ctn.to(device=self.device) for ctn in c_crossattn] if type(c_crossattn) is list else [c_crossattn.to(device=self.device)], 'transformer_options': {'from_smZ': True}} # pylint: disable=C3001
+ else:
+ make_condition_dict = lambda c_crossattn: {"c_crossattn": c_crossattn, 'transformer_options': {'from_smZ': True}} # pylint: disable=C3001
+ else:
+ image_uncond = image_cond
+ if isinstance(uncond, dict):
+ make_condition_dict = lambda c_crossattn, c_concat: {**c_crossattn, "c_concat": None, 'transformer_options': {'from_smZ': True}}
+ else:
+ if self.c_crossattn_as_list:
+ make_condition_dict = lambda c_crossattn, c_concat: {"c_crossattn": c_crossattn if type(c_crossattn) is list else [c_crossattn], "c_concat": None, 'transformer_options': {'from_smZ': True}}
+ else:
+ make_condition_dict = lambda c_crossattn, c_concat: {"c_crossattn": c_crossattn, "c_concat": None, 'transformer_options': {'from_smZ': True}}
+
+ _make_condition_dict = make_condition_dict
+ make_condition_dict = lambda *a, **kwa: self.make_condition_dict(x, _make_condition_dict(*a, **kwa))
+
+ if not is_edit_model:
+ x_in = torch.cat([torch.stack([x[i] for _ in range(n)]) for i, n in enumerate(repeats)] + [x])
+ sigma_in = torch.cat([torch.stack([sigma[i] for _ in range(n)]) for i, n in enumerate(repeats)] + [sigma])
+ image_cond_in = torch.cat([torch.stack([image_cond[i] for _ in range(n)]) for i, n in enumerate(repeats)] + [image_uncond])
+ else:
+ x_in = torch.cat([torch.stack([x[i] for _ in range(n)]) for i, n in enumerate(repeats)] + [x] + [x])
+ sigma_in = torch.cat([torch.stack([sigma[i] for _ in range(n)]) for i, n in enumerate(repeats)] + [sigma] + [sigma])
+ image_cond_in = torch.cat([torch.stack([image_cond[i] for _ in range(n)]) for i, n in enumerate(repeats)] + [image_uncond] + [torch.zeros_like(self.init_latent)])
+
+ # denoiser_params = CFGDenoiserParams(x_in, image_cond_in, sigma_in, state.sampling_step, state.sampling_steps, tensor, uncond)
+ # cfg_denoiser_callback(denoiser_params)
+ # x_in = denoiser_params.x
+ # image_cond_in = denoiser_params.image_cond
+ # sigma_in = denoiser_params.sigma
+ # tensor = denoiser_params.text_cond
+ # uncond = denoiser_params.text_uncond
+ skip_uncond = False
+
+ # alternating uncond allows for higher thresholds without the quality loss normally expected from raising it
+ if self.step % 2 and s_min_uncond > 0 and sigma[0] < s_min_uncond and not is_edit_model:
+ skip_uncond = True
+ x_in = x_in[:-batch_size]
+ sigma_in = sigma_in[:-batch_size]
+
+ self.padded_cond_uncond = False
+ if shared.opts.pad_cond_uncond and tensor.shape[1] != uncond.shape[1]:
+ empty = shared.sd_model.cond_stage_model_empty_prompt
+ num_repeats = (tensor.shape[1] - uncond.shape[1]) // empty.shape[1]
+
+ if num_repeats < 0:
+ tensor = pad_cond(tensor, -num_repeats, empty)
+ self.padded_cond_uncond = True
+ elif num_repeats > 0:
+ uncond = pad_cond(uncond, num_repeats, empty)
+ self.padded_cond_uncond = True
+
+ if tensor.shape[1] == uncond.shape[1] or skip_uncond:
+ if is_edit_model:
+ cond_in = catenate_conds([tensor, uncond, uncond])
+ elif skip_uncond:
+ cond_in = tensor
+ else:
+ cond_in = catenate_conds([tensor, uncond])
+
+ if shared.opts.batch_cond_uncond:
+ x_out = self.inner_model(x_in, sigma_in, **make_condition_dict(cond_in, image_cond_in))
+ else:
+ x_out = torch.zeros_like(x_in)
+ for batch_offset in range(0, x_out.shape[0], batch_size):
+ a = batch_offset
+ b = a + batch_size
+ x_out[a:b] = self.inner_model(x_in[a:b], sigma_in[a:b], **make_condition_dict(subscript_cond(cond_in, a, b), image_cond_in[a:b]))
+ else:
+ x_out = torch.zeros_like(x_in)
+ batch_size = batch_size*2 if shared.opts.batch_cond_uncond else batch_size
+ for batch_offset in range(0, tensor.shape[0], batch_size):
+ a = batch_offset
+ b = min(a + batch_size, tensor.shape[0])
+
+ if not is_edit_model:
+ c_crossattn = subscript_cond(tensor, a, b)
+ else:
+ c_crossattn = torch.cat([tensor[a:b]], uncond)
+
+ x_out[a:b] = self.inner_model(x_in[a:b], sigma_in[a:b], **make_condition_dict(c_crossattn, image_cond_in[a:b]))
+
+ if not skip_uncond:
+ x_out[-uncond.shape[0]:] = self.inner_model(x_in[-uncond.shape[0]:], sigma_in[-uncond.shape[0]:], **make_condition_dict(uncond, image_cond_in[-uncond.shape[0]:]))
+
+ denoised_image_indexes = [x[0][0] for x in conds_list]
+ if skip_uncond:
+ fake_uncond = torch.cat([x_out[i:i+1] for i in denoised_image_indexes])
+ x_out = torch.cat([x_out, fake_uncond]) # we skipped uncond denoising, so we put cond-denoised image to where the uncond-denoised image should be
+
+ # denoised_params = CFGDenoisedParams(x_out, state.sampling_step, state.sampling_steps, self.inner_model)
+ # cfg_denoised_callback(denoised_params)
+
+ devices.test_for_nans(x_out, "unet")
+
+ if is_edit_model:
+ denoised = self.combine_denoised_for_edit_model(x_out, cond_scale)
+ elif skip_uncond:
+ denoised = self.combine_denoised(x_out, conds_list, uncond, 1.0)
+ else:
+ denoised = self.combine_denoised(x_out, conds_list, uncond, cond_scale)
+
+ if not self.mask_before_denoising and self.mask is not None:
+ denoised = self.init_latent * self.mask + self.nmask * denoised
+
+ # self.sampler.last_latent = self.get_pred_x0(torch.cat([x_in[i:i + 1] for i in denoised_image_indexes]), torch.cat([x_out[i:i + 1] for i in denoised_image_indexes]), sigma)
+
+ # if opts.live_preview_content == "Prompt":
+ # preview = self.sampler.last_latent
+ # elif opts.live_preview_content == "Negative prompt":
+ # preview = self.get_pred_x0(x_in[-uncond.shape[0]:], x_out[-uncond.shape[0]:], sigma)
+ # else:
+ # preview = self.get_pred_x0(torch.cat([x_in[i:i+1] for i in denoised_image_indexes]), torch.cat([denoised[i:i+1] for i in denoised_image_indexes]), sigma)
+
+ # sd_samplers_common.store_latent(preview)
+
+ # after_cfg_callback_params = AfterCFGCallbackParams(denoised, state.sampling_step, state.sampling_steps)
+ # cfg_after_cfg_callback(after_cfg_callback_params)
+ # denoised = after_cfg_callback_params.x
+
+ self.step += 1
+ del x_out
+ return denoised
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/shared.py b/custom_nodes/ComfyUI_smZNodes/modules/shared.py
new file mode 100644
index 0000000000000000000000000000000000000000..dbc066af3551838dffef33949534cd25da8e81d5
--- /dev/null
+++ b/custom_nodes/ComfyUI_smZNodes/modules/shared.py
@@ -0,0 +1,101 @@
+from comfy.model_management import vram_state, VRAMState
+import logging
+import sys
+from comfy.cli_args import args
+from comfy import model_management
+from . import devices
+
+log = logging.getLogger("sd")
+options_templates = {}
+loaded_hypernetworks = []
+xformers_available = model_management.XFORMERS_IS_AVAILABLE
+device = devices.device
+
+class Options:
+ data = None
+ data_labels = options_templates
+ typemap = {int: float}
+
+ def __init__(self):
+ self.data = {k: v.default for k, v in self.data_labels.items()}
+
+ def __setattr__(self, key, value): # pylint: disable=inconsistent-return-statements
+ if self.data is not None:
+ if key in self.data or key in self.data_labels:
+ # if cmd_opts.freeze:
+ # log.warning(f'Settings are frozen: {key}')
+ # return
+ # if cmd_opts.hide_ui_dir_config and key in restricted_opts:
+ # log.warning(f'Settings key is restricted: {key}')
+ # return
+ self.data[key] = value
+ return
+ return super(Options, self).__setattr__(key, value) # pylint: disable=super-with-arguments
+
+ def __getattr__(self, item):
+ if self.data is not None:
+ if item in self.data:
+ return self.data[item]
+ if item in self.data_labels:
+ return self.data_labels[item].default
+ return super(Options, self).__getattribute__(item) # pylint: disable=super-with-arguments
+
+
+opts = Options()
+opts.prompt_attention = 'A1111 parser'
+opts.prompt_mean_norm = True
+opts.comma_padding_backtrack = 20
+opts.CLIP_stop_at_last_layers = 1
+opts.enable_emphasis = True
+opts.use_old_emphasis_implementation = False
+opts.disable_nan_check = True
+opts.pad_cond_uncond = False
+opts.s_min_uncond = 0.0
+opts.upcast_sampling = True
+opts.upcast_attn = not args.dont_upcast_attention
+opts.textual_inversion_add_hashes_to_infotext = False
+opts.encode_count = 0
+opts.max_chunk_count = 0
+opts.return_batch_chunks = False
+opts.noise = None
+opts.pad_with_repeats = True
+opts.randn_source = "cpu"
+opts.lora_functional = False
+opts.use_old_scheduling = False
+opts.eta_noise_seed_delta = 0
+
+opts.use_CFGDenoiser = False
+opts.sgm_noise_multiplier = True
+opts.debug= False
+
+
+opts.sdxl_crop_top = 0
+opts.sdxl_crop_left = 0
+opts.sdxl_refiner_low_aesthetic_score = 2.5
+opts.sdxl_refiner_high_aesthetic_score = 6.0
+
+sd_model = Options()
+sd_model.cond_stage_model = Options()
+
+cmd_opts = Options()
+
+opts.batch_cond_uncond = False
+cmd_opts.lowvram = vram_state == VRAMState.LOW_VRAM
+cmd_opts.medvram = vram_state == VRAMState.NORMAL_VRAM
+should_batch_cond_uncond = lambda: opts.batch_cond_uncond or not (cmd_opts.lowvram or cmd_opts.medvram)
+opts.batch_cond_uncond = should_batch_cond_uncond()
+
+cmd_opts.xformers = xformers_available
+cmd_opts.force_enable_xformers = xformers_available
+
+opts.cross_attention_optimization = "None"
+# opts.cross_attention_optimization = "opt_sdp_no_mem_attention"
+# opts.cross_attention_optimization = "opt_sub_quad_attention"
+cmd_opts.sub_quad_q_chunk_size = 512
+cmd_opts.sub_quad_kv_chunk_size = 512
+cmd_opts.sub_quad_chunk_threshold = 80
+cmd_opts.token_merging_ratio = 0.0
+cmd_opts.token_merging_ratio_img2img = 0.0
+cmd_opts.token_merging_ratio_hr = 0.0
+cmd_opts.sd_vae_sliced_encode = False
+cmd_opts.disable_opt_split_attention = False
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/textual_inversion/__pycache__/textual_inversion.cpython-311.pyc b/custom_nodes/ComfyUI_smZNodes/modules/textual_inversion/__pycache__/textual_inversion.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c45509b23d775f44d97c7861a8c9ee49f542e7f0
Binary files /dev/null and b/custom_nodes/ComfyUI_smZNodes/modules/textual_inversion/__pycache__/textual_inversion.cpython-311.pyc differ
diff --git a/custom_nodes/ComfyUI_smZNodes/modules/textual_inversion/textual_inversion.py b/custom_nodes/ComfyUI_smZNodes/modules/textual_inversion/textual_inversion.py
new file mode 100644
index 0000000000000000000000000000000000000000..0e24afefa3b47f685bc780497a5136a3ee16730d
--- /dev/null
+++ b/custom_nodes/ComfyUI_smZNodes/modules/textual_inversion/textual_inversion.py
@@ -0,0 +1,85 @@
+import torch
+from .. import shared
+
+class Embedding:
+ def __init__(self, vec, name, step=None):
+ self.vec = vec
+ self.name = name
+ self.step = step
+ self.shape = None
+ self.vectors = 0
+ self.cached_checksum = None
+ self.sd_checkpoint = None
+ self.sd_checkpoint_name = None
+ self.optimizer_state_dict = None
+ self.filename = None
+
+ self.shape = vec.shape[-1]
+ self.vectors = vec.shape[0]
+
+ def save(self, filename):
+ embedding_data = {
+ "string_to_token": {"*": 265},
+ "string_to_param": {"*": self.vec},
+ "name": self.name,
+ "step": self.step,
+ "sd_checkpoint": self.sd_checkpoint,
+ "sd_checkpoint_name": self.sd_checkpoint_name,
+ }
+
+ torch.save(embedding_data, filename)
+
+ if shared.opts.save_optimizer_state and self.optimizer_state_dict is not None:
+ optimizer_saved_dict = {
+ 'hash': self.checksum(),
+ 'optimizer_state_dict': self.optimizer_state_dict,
+ }
+ torch.save(optimizer_saved_dict, f"{filename}.optim")
+
+ def checksum(self):
+ if self.cached_checksum is not None:
+ return self.cached_checksum
+
+ def const_hash(a):
+ r = 0
+ for v in a:
+ r = (r * 281 ^ int(v) * 997) & 0xFFFFFFFF
+ return r
+
+ self.cached_checksum = f'{const_hash(self.vec.reshape(-1) * 100) & 0xffff:04x}'
+ return self.cached_checksum
+
+class EmbeddingDatabase:
+ def __init__(self):
+ self.ids_lookup = {}
+ self.word_embeddings = {}
+ self.skipped_embeddings = {}
+ self.expected_shape = -1
+ self.embedding_dirs = {}
+ self.previously_displayed_embeddings = ()
+
+ def register_embedding(self, embedding, model):
+ self.word_embeddings[embedding.name] = embedding
+
+ ids = model.tokenize([embedding.name])[0]
+
+ first_id = ids[0]
+ if first_id not in self.ids_lookup:
+ self.ids_lookup[first_id] = []
+
+ self.ids_lookup[first_id] = sorted(self.ids_lookup[first_id] + [(ids, embedding)], key=lambda x: len(x[0]), reverse=True)
+
+ return embedding
+
+ def find_embedding_at_position(self, tokens, offset):
+ token = tokens[offset]
+ possible_matches = self.ids_lookup.get(token, None)
+
+ if possible_matches is None:
+ return None, None
+
+ for ids, embedding in possible_matches:
+ if tokens[offset:offset + len(ids)] == ids:
+ return embedding, len(ids)
+
+ return None, None
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_smZNodes/nodes.py b/custom_nodes/ComfyUI_smZNodes/nodes.py
new file mode 100644
index 0000000000000000000000000000000000000000..9c488ccecd92f3b8d6d3943766867bde8b23c7c4
--- /dev/null
+++ b/custom_nodes/ComfyUI_smZNodes/nodes.py
@@ -0,0 +1,196 @@
+import torch
+import inspect
+from pathlib import Path
+from functools import partial
+import os
+from .modules import prompt_parser, devices, shared
+from .modules.sd_hijack import model_hijack
+from .smZNodes import run, prepare_noise
+from nodes import MAX_RESOLUTION
+import comfy.sd
+import comfy.model_management
+import comfy.samplers
+import comfy.sample
+
+BOOLEAN = [False, True]
+try:
+ cwd_path = Path(__file__).parent
+ comfy_path = cwd_path.parent.parent
+ widgets_path = os.path.join(comfy_path, "web", "scripts", "widgets.js")
+ with open(widgets_path, encoding='utf8') as f:
+ widgets_js = f.read()
+ if 'BOOLEAN(' in widgets_js:
+ BOOLEAN = "BOOLEAN"
+ del widgets_js
+except Exception as err:
+ print("[smZNodes]:", err)
+
+class smZ_CLIPTextEncode:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {
+ "text": ("STRING", {"multiline": True}),
+ "clip": ("CLIP", ),
+ "parser": (["comfy", "comfy++", "A1111", "full", "compel", "fixed attention"],{"default": "comfy"}),
+ # whether weights are normalized by taking the mean
+ "mean_normalization": (BOOLEAN, {"default": True}),
+ "multi_conditioning": (BOOLEAN, {"default": True}),
+ "use_old_emphasis_implementation": (BOOLEAN, {"default": False}),
+ "with_SDXL": (BOOLEAN, {"default": False}),
+ "ascore": ("FLOAT", {"default": 6.0, "min": 0.0, "max": 1000.0, "step": 0.01}),
+ "width": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}),
+ "height": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}),
+ "crop_w": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION}),
+ "crop_h": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION}),
+ "target_width": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}),
+ "target_height": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}),
+ "text_g": ("STRING", {"multiline": True, "placeholder": "CLIP_G"}),
+ "text_l": ("STRING", {"multiline": True, "placeholder": "CLIP_L"}),
+ },
+ "optional": {
+ "smZ_steps": ("INT", {"default": 1, "min": 1, "max": 0xffffffffffffffff}),
+ },
+ }
+ RETURN_TYPES = ("CONDITIONING",)
+ FUNCTION = "encode"
+ CATEGORY = "conditioning"
+
+ def encode(self, clip: comfy.sd.CLIP, text, parser, mean_normalization,
+ multi_conditioning, use_old_emphasis_implementation,
+ with_SDXL, ascore, width, height, crop_w,
+ crop_h, target_width, target_height, text_g, text_l, smZ_steps=1):
+ params = locals()
+ params['steps'] = params.pop('smZ_steps', smZ_steps)
+ from .modules.shared import opts
+ is_sdxl = "SDXL" in type(clip.cond_stage_model).__name__
+
+ should_use_fp16_signature = inspect.signature(comfy.model_management.should_use_fp16)
+ _p = should_use_fp16_signature.parameters
+ devices.device = shared.device = clip.patcher.load_device if hasattr(clip.patcher, 'load_device') else clip.device
+ if 'device' in _p and 'prioritize_performance' in _p:
+ should_use_fp16 = partial(comfy.model_management.should_use_fp16, device=devices.device, prioritize_performance=False)
+ elif 'device' in should_use_fp16_signature.parameters:
+ should_use_fp16 = partial(comfy.model_management.should_use_fp16, device=devices.device)
+ else:
+ should_use_fp16 = comfy.model_management.should_use_fp16
+ dtype = torch.float16 if should_use_fp16() else torch.float32
+ dtype_unet= dtype
+ devices.dtype = dtype
+ # devices.dtype_unet was hijacked so it will be the correct dtype by default
+ if devices.dtype_unet == torch.float16:
+ devices.dtype_unet = dtype_unet
+ devices.unet_needs_upcast = opts.upcast_sampling and devices.dtype == torch.float16 and devices.dtype_unet == torch.float16
+ devices.dtype_vae = comfy.model_management.vae_dtype() if hasattr(comfy.model_management, 'vae_dtype') else torch.float32
+
+ params.pop('self', None)
+ result = run(**params)
+ result[0][0][1]['params'] = {}
+ result[0][0][1]['params'].update(params)
+ if opts.pad_cond_uncond:
+ text=params['text']
+ with_SDXL=params['with_SDXL']
+ params['text'] = ''
+ params['with_SDXL'] = False
+ empty = run(**params)[0]
+ params['text'] = text
+ params['with_SDXL'] = with_SDXL
+ shared.sd_model.cond_stage_model_empty_prompt = empty[0][0]
+ return result
+
+# Hack: string type that is always equal in not equal comparisons
+class AnyType(str):
+ def __ne__(self, __value: object) -> bool:
+ return False
+
+# Our any instance wants to be a wildcard string
+anytype = AnyType("*")
+
+class smZ_Settings:
+ @classmethod
+ def INPUT_TYPES(s):
+ from .modules.shared import opts
+ return {"required": {
+ "clip": ("CLIP", ),
+ },
+ "optional": {
+ "extra": ("STRING", {"multiline": True, "default": '{"show":true}'}),
+
+ "ㅤ"*1: ( "STRING", {"multiline": False, "default": "Stable Diffusion"}),
+ "info_comma_padding_backtrack": ("STRING", {"multiline": True, "default": "Prompt word wrap length limit\nin tokens - for texts shorter than specified, if they don't fit into 75 token limit, move them to the next 75 token chunk"}),
+ "Prompt word wrap length limit": ("INT", {"default": opts.comma_padding_backtrack, "min": 0, "max": 74, "step": 1}),
+ # "enable_emphasis": (BOOLEAN, {"default": opts.enable_emphasis}),
+ "info_RNG": ("STRING", {"multiline": True, "default": "Random number generator source.\nchanges seeds drastically; use CPU to produce the same picture across different videocard vendors; use NV to produce same picture as on NVidia videocards"}),
+ "RNG": (["cpu", "gpu", "nv"],{"default": opts.randn_source}),
+
+ "ㅤ"*2: ("STRING", {"multiline": False, "default": "Compute Settings"}),
+ "info_disable_nan_check": ("STRING", {"multiline": True, "default": "Disable NaN check in produced images/latent spaces. Only for CFGDenoiser."}),
+ "disable_nan_check": (BOOLEAN, {"default": opts.disable_nan_check}),
+
+ "ㅤ"*3: ("STRING", {"multiline": False, "default": "Sampler parameters"}),
+ "info_eta_noise_seed_delta": ("STRING", {"multiline": True, "default": "Eta noise seed delta\ndoes not improve anything, just produces different results for ancestral samplers - only useful for reproducing images"}),
+ "ENSD": ("INT", {"default": opts.eta_noise_seed_delta, "min": 0, "max": 0xffffffffffffffff, "step": 1}),
+ "info_sgm_noise_multiplier": ("STRING", {"multiline": True, "default": "SGM noise multiplier\nmatch initial noise to official SDXL implementation - only useful for reproducing images\nsee https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/12818"}),
+ "sgm_noise_multiplier": (BOOLEAN, {"default": opts.sgm_noise_multiplier}),
+ "info_upcast_sampling": ("STRING", {"multiline": True, "default": "upcast sampling.\nNo effect with --force-fp32. Usually produces similar results to --force-fp32 with better performance while using less memory."}),
+ "upcast_sampling": (BOOLEAN, {"default": opts.upcast_sampling}),
+
+ "ㅤ"*4: ("STRING", {"multiline": False, "default": "Optimizations"}),
+ "info_NGMS": ("STRING", {"multiline": True, "default": "Negative Guidance minimum sigma\nskip negative prompt for some steps when the image is almost ready; 0=disable, higher=faster. Only for CFGDenoiser.\nsee https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9177"}),
+ "NGMS": ("FLOAT", {"default": opts.s_min_uncond, "min": 0.0, "max": 4.0, "step": 0.01}),
+ "info_pad_cond_uncond": ("STRING", {"multiline": True, "default": "Pad prompt/negative prompt to be same length\nimproves performance when prompt and negative prompt have different lengths; changes seeds. Only for CFGDenoiser."}),
+ "pad_cond_uncond": (BOOLEAN, {"default": opts.pad_cond_uncond}),
+ "info_batch_cond_uncond": ("STRING", {"multiline": True, "default": "Batch cond/uncond\ndo both conditional and unconditional denoising in one batch; uses a bit more VRAM during sampling, but improves speed – enabled on SDXL models. Only for CFGDenoiser."}),
+ "batch_cond_uncond": (BOOLEAN, {"default": opts.batch_cond_uncond}),
+
+ "ㅤ"*5: ("STRING", {"multiline": False, "default": "Compatibility"}),
+ "info_use_prev_scheduling": ("STRING", {"multiline": True, "default": "Previous prompt editing timelines\nFor [red:green:N]; previous: If N < 1, it's a fraction of steps (and hires fix uses range from 0 to 1), if N >= 1, it's an absolute number of steps; new: If N has a decimal point in it, it's a fraction of steps (and hires fix uses range from 1 to 2), othewrwise it's an absolute number of steps"}),
+ "Use previous prompt editing timelines": (BOOLEAN, {"default": opts.use_old_scheduling}),
+
+ "ㅤ"*6: ("STRING", {"multiline": False, "default": "Experimental"}),
+ "info_use_CFGDenoiser": ("STRING", {"multiline": True, "default": "CFGDenoiser\nAn experimental option to use stable-diffusion-webui's denoiser. It may not work as expected with inpainting/UnCLIP models or ComfyUI's Conditioning nodes, but it allows you to get identical images regardless of the prompt."}),
+ "Use CFGDenoiser": (BOOLEAN, {"default": opts.use_CFGDenoiser}),
+ "info_debug": ("STRING", {"multiline": True, "default": "Debugging messages in the console."}),
+ "Debug": (BOOLEAN, {"default": opts.debug, "label_on": "on", "label_off": "off"}),
+ }}
+ RETURN_TYPES = ("CLIP",)
+ OUTPUT_NODE = False
+ FUNCTION = "run"
+ CATEGORY = "advanced"
+
+ def run(self, *args, **kwargs):
+ from .modules.shared import opts
+ device = comfy.model_management.get_torch_device()
+
+ clip = kwargs.pop('clip', None) if 'clip' in kwargs else args[0]
+ kwargs['s_min_uncond'] = max(min(kwargs.pop('NGMS'), 4.0), 0)
+ kwargs['comma_padding_backtrack'] = kwargs.pop('Prompt word wrap length limit')
+ kwargs['comma_padding_backtrack'] = max(min(kwargs['comma_padding_backtrack'], 74), 0)
+ kwargs['use_old_scheduling']=kwargs.pop("Use previous prompt editing timelines")
+ kwargs['use_CFGDenoiser'] = kwargs.pop("Use CFGDenoiser")
+ kwargs['debug'] = kwargs.pop('Debug')
+ kwargs['randn_source'] = kwargs.pop('RNG')
+ kwargs['eta_noise_seed_delta'] = kwargs.pop('ENSD')
+
+ [kwargs.pop(k, None) for k in [k for k in kwargs.keys() if 'info' in k or 'heading' in k or 'ㅤ' in k]]
+ for k,v in kwargs.items():
+ setattr(opts, k, v)
+
+ if not hasattr(comfy.sample, 'prepare_noise_orig'):
+ comfy.sample.prepare_noise_orig = comfy.sample.prepare_noise
+ if opts.randn_source == 'cpu':
+ device = torch.device("cpu")
+ _prepare_noise = partial(prepare_noise, device=device.type)
+ comfy.sample.prepare_noise = _prepare_noise
+ return (clip,)
+
+# A dictionary that contains all nodes you want to export with their names
+# NOTE: names should be globally unique
+NODE_CLASS_MAPPINGS = {
+ "smZ CLIPTextEncode": smZ_CLIPTextEncode,
+ "smZ Settings": smZ_Settings,
+}
+# A dictionary that contains the friendly/humanly readable titles for the nodes
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "smZ CLIPTextEncode" : "CLIP Text Encode++",
+ "smZ Settings" : "Settings (smZ)",
+}
\ No newline at end of file
diff --git a/custom_nodes/ComfyUI_smZNodes/smZNodes.py b/custom_nodes/ComfyUI_smZNodes/smZNodes.py
new file mode 100644
index 0000000000000000000000000000000000000000..74263c1b15f382d9c1e6f36a76d01349c627792b
--- /dev/null
+++ b/custom_nodes/ComfyUI_smZNodes/smZNodes.py
@@ -0,0 +1,1058 @@
+from __future__ import annotations
+import comfy
+import torch
+from typing import List, Tuple
+from functools import partial
+from .modules import prompt_parser, shared, devices
+from .modules.shared import opts
+from .modules.sd_samplers_cfg_denoiser import CFGDenoiser
+from .modules.sd_hijack_clip import FrozenCLIPEmbedderForSDXLWithCustomWords
+from .modules.sd_hijack_open_clip import FrozenOpenCLIPEmbedder2WithCustomWords
+from .modules.textual_inversion.textual_inversion import Embedding
+import comfy.sdxl_clip
+import comfy.sd1_clip
+import comfy.sample
+from comfy.sd1_clip import SD1Tokenizer, unescape_important, escape_important, token_weights, expand_directory_list
+from nodes import CLIPTextEncode
+from comfy.ldm.modules.distributions.distributions import DiagonalGaussianDistribution
+from comfy import model_management
+import inspect
+from textwrap import dedent, indent
+import functools
+import tempfile
+import importlib
+import sys
+import os
+import re
+import contextlib
+import itertools
+import binascii
+
+try:
+ from comfy_extras.nodes_clip_sdxl import CLIPTextEncodeSDXL, CLIPTextEncodeSDXLRefiner
+except Exception as err:
+ print(f"[smZNodes]: Your ComfyUI version is outdated. Please update to the latest version. ({err})")
+ class CLIPTextEncodeSDXL(CLIPTextEncode): ...
+ class CLIPTextEncodeSDXLRefiner(CLIPTextEncode): ...
+
+def get_learned_conditioning(self, c):
+ if self.cond_stage_forward is None:
+ if hasattr(self.cond_stage_model, 'encode') and callable(self.cond_stage_model.encode):
+ c = self.cond_stage_model.encode(c)
+ if isinstance(c, DiagonalGaussianDistribution):
+ c = c.mode()
+ else:
+ c = self.cond_stage_model(c)
+ else:
+ assert hasattr(self.cond_stage_model, self.cond_stage_forward)
+ c = getattr(self.cond_stage_model, self.cond_stage_forward)(c)
+ return c
+
+class PopulateVars:
+ def populate_self_variables(self, from_):
+ super_attrs = vars(from_)
+ self_attrs = vars(self)
+ self_attrs.update(super_attrs)
+
+should_use_fp16_signature = inspect.signature(comfy.model_management.should_use_fp16)
+class ClipTextEncoderCustom:
+
+ def _forward(self: comfy.sd1_clip.SD1ClipModel, tokens):
+ def set_dtype_compat(dtype, newv = False):
+ dtype_num = lambda d : int(re.sub(r'.*?(\d+)', r'\1', repr(d)))
+ _p = should_use_fp16_signature.parameters
+ # newer versions of ComfyUI upcasts the transformer embeddings, which is technically correct
+ # when it's a newer version, we want to downcast it to torch.float16, so set newv=True
+ # newv = 'device' in _p and 'prioritize_performance' in _p # comment this to have default comfy behaviour
+ if dtype_num(dtype) >= 32:
+ newv = False
+ if not newv: return
+ dtype = devices.dtype if dtype != devices.dtype else dtype
+ # self.transformer.text_model.embeddings.position_embedding.to(dtype)
+ # self.transformer.text_model.embeddings.token_embedding.to(dtype)
+ inner_model = getattr(self.transformer, self.inner_name, None)
+ if inner_model is not None and hasattr(inner_model, "embeddings"):
+ inner_model.embeddings.to(dtype)
+ else:
+ self.transformer.set_input_embeddings(self.transformer.get_input_embeddings().to(dtype))
+ def reset_dtype_compat():
+ # token_embedding_dtype = position_embedding_dtype = torch.float32
+ # self.transformer.text_model.embeddings.token_embedding.to(token_embedding_dtype)
+ # self.transformer.text_model.embeddings.position_embedding.to(position_embedding_dtype)
+ inner_model = getattr(self.transformer, self.inner_name, None)
+ if inner_model is not None and hasattr(inner_model, "embeddings"):
+ inner_model.embeddings.to(torch.float32)
+ else:
+ self.transformer.set_input_embeddings(self.transformer.get_input_embeddings().to(torch.float32))
+ enable_compat = False
+ if enable_compat: set_dtype_compat(torch.float16, enable_compat)
+
+ backup_embeds = self.transformer.get_input_embeddings()
+ device = backup_embeds.weight.device
+ tokens = self.set_up_textual_embeddings(tokens, backup_embeds)
+ tokens = torch.LongTensor(tokens).to(device)
+
+ # dtype=backup_embeds.weight.dtype
+ if hasattr(self.transformer, 'dtype'):
+ dtype = self.transformer.dtype
+ else:
+ dtype = getattr(self.transformer, self.inner_name, self.transformer.text_model).final_layer_norm.weight.dtype
+
+ if dtype != torch.float32:
+ precision_scope = torch.autocast
+ else:
+ precision_scope = lambda a, dtype=None: contextlib.nullcontext(a)
+
+ with precision_scope(model_management.get_autocast_device(device), dtype=dtype if enable_compat else torch.float32):
+ attention_mask = None
+ if self.enable_attention_masks:
+ attention_mask = torch.zeros_like(tokens)
+ max_token = self.transformer.get_input_embeddings().weight.shape[0] - 1
+ for x in range(attention_mask.shape[0]):
+ for y in range(attention_mask.shape[1]):
+ attention_mask[x, y] = 1
+ if tokens[x, y] == max_token:
+ break
+
+ outputs = self.transformer(tokens, attention_mask, intermediate_output=self.layer_idx, final_layer_norm_intermediate=self.layer_norm_hidden_state)
+ self.transformer.set_input_embeddings(backup_embeds)
+
+ if self.layer == "last":
+ z = outputs[0]
+ else:
+ z = outputs[1]
+
+ if outputs[2] is not None:
+ pooled_output = outputs[2].float()
+ else:
+ pooled_output = None
+
+ if enable_compat: reset_dtype_compat()
+
+ if self.text_projection is not None and pooled_output is not None:
+ pooled_output = pooled_output.float().to(self.text_projection.device) @ self.text_projection.float()
+ return z.float(), pooled_output
+
+ def encode_with_transformers_comfy_(self, tokens: List[List[int]], return_pooled=False):
+ tokens_orig = tokens
+ try:
+ if isinstance(tokens, torch.Tensor):
+ tokens = tokens.tolist()
+ z, pooled = ClipTextEncoderCustom._forward(self.wrapped, tokens) # self.wrapped.encode(tokens)
+ except Exception as e:
+ z, pooled = ClipTextEncoderCustom._forward(self.wrapped, tokens_orig)
+
+ # z = self.encode_with_transformers__(tokens_bak)
+ if z.device != devices.device:
+ z = z.to(device=devices.device)
+ # if z.dtype != devices.dtype:
+ # z = z.to(dtype=devices.dtype)
+ # if pooled.dtype != devices.dtype:
+ # pooled = pooled.to(dtype=devices.dtype)
+ z.pooled = pooled
+ return (z, pooled) if return_pooled else z
+
+ def encode_with_transformers_comfy(self, tokens: List[List[int]], return_pooled=False) -> Tuple[torch.Tensor, torch.Tensor]:
+ '''
+ This function is different from `clip.cond_stage_model.encode_token_weights()`
+ in that the tokens are `List[List[int]]`, not including the weights.
+
+ Originally from `sd1_clip.py`: `encode()` -> `forward()`
+ '''
+ tokens_orig = tokens
+ try:
+ if isinstance(tokens, torch.Tensor):
+ tokens = tokens.tolist()
+ z, pooled = self.wrapped(tokens) # self.wrapped.encode(tokens)
+ except Exception as e:
+ z, pooled = self.wrapped(tokens_orig)
+
+ # z = self.encode_with_transformers__(tokens_bak)
+ if z.device != devices.device:
+ z = z.to(device=devices.device)
+ # if z.dtype != devices.dtype:
+ # z = z.to(dtype=devices.dtype)
+ # if pooled.dtype != devices.dtype:
+ # pooled = pooled.to(dtype=devices.dtype)
+ z.pooled = pooled
+ return (z, pooled) if return_pooled else z
+
+class FrozenOpenCLIPEmbedder2WithCustomWordsCustom(FrozenOpenCLIPEmbedder2WithCustomWords, ClipTextEncoderCustom, PopulateVars):
+ def __init__(self, wrapped: comfy.sdxl_clip.SDXLClipG, hijack):
+ self.populate_self_variables(wrapped.tokenizer_parent)
+ super().__init__(wrapped, hijack)
+ self.id_start = self.wrapped.tokenizer.bos_token_id
+ self.id_end = self.wrapped.tokenizer.eos_token_id
+ self.id_pad = 0
+ # Below is safe to do since ComfyUI uses the same CLIP model
+ # for Open Clip instead of an actual Open Clip model?
+ self.token_mults = {}
+ vocab = self.tokenizer.get_vocab()
+ self.comma_token = vocab.get(',', None)
+ tokens_with_parens = [(k, v) for k, v in vocab.items() if '(' in k or ')' in k or '[' in k or ']' in k]
+ for text, ident in tokens_with_parens:
+ mult = 1.0
+ for c in text:
+ if c == '[':
+ mult /= 1.1
+ if c == ']':
+ mult *= 1.1
+ if c == '(':
+ mult *= 1.1
+ if c == ')':
+ mult /= 1.1
+ if mult != 1.0:
+ self.token_mults[ident] = mult
+
+ def tokenize_line(self, line):
+ line = parse_and_register_embeddings(self, line)
+ return super().tokenize_line(line)
+
+ def encode(self, tokens):
+ return self.encode_with_transformers(tokens, True)
+
+ def encode_with_transformers(self, tokens, return_pooled=False):
+ return self.encode_with_transformers_comfy_(tokens, return_pooled)
+
+ def encode_token_weights(self, tokens):
+ pass
+
+ def tokenize(self, texts):
+ # assert not opts.use_old_emphasis_implementation, 'Old emphasis implementation not supported for Open Clip'
+ tokenized = [self.tokenizer(text)["input_ids"][1:-1] for text in texts]
+ return tokenized
+
+
+class FrozenCLIPEmbedderWithCustomWordsCustom(FrozenCLIPEmbedderForSDXLWithCustomWords, ClipTextEncoderCustom, PopulateVars):
+ '''
+ Custom class that also inherits a tokenizer to have the `_try_get_embedding()` method.
+ '''
+ def __init__(self, wrapped: comfy.sd1_clip.SD1ClipModel, hijack):
+ self.populate_self_variables(wrapped.tokenizer_parent) # SD1Tokenizer
+ # self.embedding_identifier_tokenized = wrapped.tokenizer([self.embedding_identifier])["input_ids"][0][1:-1]
+ super().__init__(wrapped, hijack)
+
+ def encode_token_weights(self, tokens):
+ pass
+
+ def encode(self, tokens):
+ return self.encode_with_transformers(tokens, True)
+
+ def encode_with_transformers(self, tokens, return_pooled=False):
+ return self.encode_with_transformers_comfy_(tokens, return_pooled)
+
+ def tokenize_line(self, line):
+ line = parse_and_register_embeddings(self, line)
+ return super().tokenize_line(line)
+
+ def tokenize(self, texts):
+ tokenized = [self.tokenizer(text)["input_ids"][1:-1] for text in texts]
+ return tokenized
+
+emb_re_ = r"(embedding:)?(?:({}[\w\.\-\!\$\/\\]+(\.safetensors|\.pt|\.bin)|(?(1)[\w\.\-\!\$\/\\]+|(?!)))(\.safetensors|\.pt|\.bin)?)(?::(\d+\.?\d*|\d*\.\d+))?"
+
+def tokenize_with_weights_custom(self, text:str, return_word_ids=False):
+ '''
+ Takes a prompt and converts it to a list of (token, weight, word id) elements.
+ Tokens can both be integer tokens and pre computed CLIP tensors.
+ Word id values are unique per word and embedding, where the id 0 is reserved for non word tokens.
+ Returned list has the dimensions NxM where M is the input size of CLIP
+ '''
+ if self.pad_with_end:
+ pad_token = self.end_token
+ else:
+ pad_token = 0
+
+ text = escape_important(text)
+ parsed_weights = token_weights(text, 1.0)
+
+ embs = get_valid_embeddings(self.embedding_directory) if self.embedding_directory is not None else []
+ embs_str = embs_str + '|' if (embs_str:='|'.join(embs)) else ''
+ emb_re = emb_re_.format(embs_str)
+ emb_re = re.compile(emb_re, flags=re.MULTILINE | re.UNICODE | re.IGNORECASE)
+
+ #tokenize words
+ tokens = []
+ for weighted_segment, weight in parsed_weights:
+ to_tokenize = unescape_important(weighted_segment).replace("\n", " ").split(' ')
+ to_tokenize = [x for x in to_tokenize if x != ""]
+ for word in to_tokenize:
+ matches = emb_re.finditer(word)
+ last_end = 0
+ leftovers=[]
+ for _, match in enumerate(matches, start=1):
+ start=match.start()
+ end=match.end()
+ if (fragment:=word[last_end:start]):
+ leftovers.append(fragment)
+ ext = ext if (ext:=match.group(4)) else ''
+ embedding_sname = embedding_sname if (embedding_sname:=match.group(2)) else ''
+ embedding_name = embedding_sname + ext
+ if embedding_name:
+ embed, leftover = self._try_get_embedding(embedding_name)
+ if embed is None:
+ print(f"warning, embedding:{embedding_name} does not exist, ignoring")
+ else:
+ if opts.debug:
+ print(f'[smZNodes] using embedding:{embedding_name}')
+ if len(embed.shape) == 1:
+ tokens.append([(embed, weight)])
+ else:
+ tokens.append([(embed[x], weight) for x in range(embed.shape[0])])
+ last_end = end
+ if (fragment:=word[last_end:]):
+ leftovers.append(fragment)
+ word_new = ''.join(leftovers)
+ tokens.append([(t, weight) for t in self.tokenizer(word)["input_ids"][self.tokens_start:-1]])
+
+ #reshape token array to CLIP input size
+ batched_tokens = []
+ batch = []
+ if self.start_token is not None:
+ batch.append((self.start_token, 1.0, 0))
+ batched_tokens.append(batch)
+ for i, t_group in enumerate(tokens):
+ #determine if we're going to try and keep the tokens in a single batch
+ is_large = len(t_group) >= self.max_word_length
+
+ while len(t_group) > 0:
+ if len(t_group) + len(batch) > self.max_length - 1:
+ remaining_length = self.max_length - len(batch) - 1
+ #break word in two and add end token
+ if is_large:
+ batch.extend([(t,w,i+1) for t,w in t_group[:remaining_length]])
+ batch.append((self.end_token, 1.0, 0))
+ t_group = t_group[remaining_length:]
+ #add end token and pad
+ else:
+ batch.append((self.end_token, 1.0, 0))
+ if self.pad_to_max_length:
+ batch.extend([(pad_token, 1.0, 0)] * (remaining_length))
+ #start new batch
+ batch = []
+ if self.start_token is not None:
+ batch.append((self.start_token, 1.0, 0))
+ batched_tokens.append(batch)
+ else:
+ batch.extend([(t,w,i+1) for t,w in t_group])
+ t_group = []
+
+ #fill last batch
+ batch.append((self.end_token, 1.0, 0))
+ if self.pad_to_max_length:
+ batch.extend([(pad_token, 1.0, 0)] * (self.max_length - len(batch)))
+
+ if not return_word_ids:
+ batched_tokens = [[(t, w) for t, w,_ in x] for x in batched_tokens]
+
+ return batched_tokens
+
+def get_valid_embeddings(embedding_directory):
+ from builtins import any as b_any
+ exts = ['.safetensors', '.pt', '.bin']
+ if isinstance(embedding_directory, str):
+ embedding_directory = [embedding_directory]
+ embedding_directory = expand_directory_list(embedding_directory)
+ embs = []
+ for embd in embedding_directory:
+ for root, dirs, files in os.walk(embd, topdown=False):
+ for name in files:
+ if not b_any(x in os.path.splitext(name)[1] for x in exts): continue
+ n = os.path.basename(name)
+ for ext in exts: n=n.removesuffix(ext)
+ embs.append(re.escape(n))
+ embs.sort(key=len, reverse=True)
+ return embs
+
+def parse_and_register_embeddings(self: FrozenCLIPEmbedderWithCustomWordsCustom|FrozenOpenCLIPEmbedder2WithCustomWordsCustom, text: str, return_word_ids=False):
+ from builtins import any as b_any
+ embedding_directory = self.wrapped.tokenizer_parent.embedding_directory
+ embs = get_valid_embeddings(embedding_directory)
+ embs_str = '|'.join(embs)
+ emb_re = emb_re_.format(embs_str + '|' if embs_str else '')
+ emb_re = re.compile(emb_re, flags=re.MULTILINE | re.UNICODE | re.IGNORECASE)
+ matches = emb_re.finditer(text)
+ for matchNum, match in enumerate(matches, start=1):
+ found=False
+ ext = ext if (ext:=match.group(4)) else ''
+ embedding_sname = embedding_sname if (embedding_sname:=match.group(2)) else ''
+ embedding_name = embedding_sname + ext
+ if embedding_name:
+ embed, _ = self.wrapped.tokenizer_parent._try_get_embedding(embedding_name)
+ if embed is not None:
+ found=True
+ if opts.debug:
+ print(f'[smZNodes] using embedding:{embedding_name}')
+ if embed.device != devices.device:
+ embed = embed.to(device=devices.device)
+ self.hijack.embedding_db.register_embedding(Embedding(embed, embedding_sname), self)
+ if not found:
+ print(f"warning, embedding:{embedding_name} does not exist, ignoring")
+ out = emb_re.sub(r"\2", text)
+ return out
+
+def expand(tensor1, tensor2):
+ def adjust_tensor_shape(tensor_small, tensor_big):
+ # Calculate replication factor
+ # -(-a // b) is ceiling of division without importing math.ceil
+ replication_factor = -(-tensor_big.size(1) // tensor_small.size(1))
+
+ # Use repeat to extend tensor_small
+ tensor_small_extended = tensor_small.repeat(1, replication_factor, 1)
+
+ # Take the rows of the extended tensor_small to match tensor_big
+ tensor_small_matched = tensor_small_extended[:, :tensor_big.size(1), :]
+
+ return tensor_small_matched
+
+ # Check if their second dimensions are different
+ if tensor1.size(1) != tensor2.size(1):
+ # Check which tensor has the smaller second dimension and adjust its shape
+ if tensor1.size(1) < tensor2.size(1):
+ tensor1 = adjust_tensor_shape(tensor1, tensor2)
+ else:
+ tensor2 = adjust_tensor_shape(tensor2, tensor1)
+ return (tensor1, tensor2)
+
+def reconstruct_schedules(schedules, step):
+ create_reconstruct_fn = lambda _cc: prompt_parser.reconstruct_multicond_batch if type(_cc).__name__ == "MulticondLearnedConditioning" else prompt_parser.reconstruct_cond_batch
+ reconstruct_fn = create_reconstruct_fn(schedules)
+ return reconstruct_fn(schedules, step)
+
+
+class ClipTokenWeightEncoder:
+ def encode_token_weights(self, token_weight_pairs, steps=0, current_step=0, multi=False):
+ schedules = token_weight_pairs
+ texts = token_weight_pairs
+ conds_list = [[(0, 1.0)]]
+ from .modules.sd_hijack import model_hijack
+ try:
+ model_hijack.hijack(self)
+ if isinstance(token_weight_pairs, list) and isinstance(token_weight_pairs[0], str):
+ if multi: schedules = prompt_parser.get_multicond_learned_conditioning(model_hijack.cond_stage_model, texts, steps, None, opts.use_old_scheduling)
+ else: schedules = prompt_parser.get_learned_conditioning(model_hijack.cond_stage_model, texts, steps, None, opts.use_old_scheduling)
+ cond = reconstruct_schedules(schedules, current_step)
+ if type(cond) is tuple:
+ conds_list, cond = cond
+ pooled = cond.pooled.cpu()
+ cond = cond.cpu()
+ cond.pooled = pooled
+ cond.pooled.conds_list = conds_list
+ cond.pooled.schedules = schedules
+ else:
+ # comfy++
+ def encode_toks(_token_weight_pairs):
+ zs = []
+ first_pooled = None
+ for batch_chunk in _token_weight_pairs:
+ tokens = [x[0] for x in batch_chunk]
+ multipliers = [x[1] for x in batch_chunk]
+ z = model_hijack.cond_stage_model.process_tokens([tokens], [multipliers])
+ if first_pooled == None:
+ first_pooled = z.pooled
+ zs.append(z)
+ zcond = torch.hstack(zs)
+ zcond.pooled = first_pooled
+ return zcond
+ # non-sdxl will be something like: {"l": [[]]}
+ if isinstance(token_weight_pairs, dict):
+ token_weight_pairs = next(iter(token_weight_pairs.values()))
+ cond = encode_toks(token_weight_pairs)
+ pooled = cond.pooled.cpu()
+ cond = cond.cpu()
+ cond.pooled = pooled
+ cond.pooled.conds_list = conds_list
+ finally:
+ model_hijack.undo_hijack(model_hijack.cond_stage_model)
+ return (cond, cond.pooled)
+
+class SD1ClipModel(ClipTokenWeightEncoder): ...
+
+class SDXLClipG(ClipTokenWeightEncoder): ...
+
+class SDXLClipModel(ClipTokenWeightEncoder):
+
+ def encode_token_weights(self: comfy.sdxl_clip.SDXLClipModel, token_weight_pairs, steps=0, current_step=0, multi=False):
+ token_weight_pairs_g = token_weight_pairs["g"]
+ token_weight_pairs_l = token_weight_pairs["l"]
+
+ self.clip_g.encode_token_weights_orig = self.clip_g.encode_token_weights
+ self.clip_l.encode_token_weights_orig = self.clip_l.encode_token_weights
+ self.clip_g.cond_stage_model = self.clip_g
+ self.clip_l.cond_stage_model = self.clip_l
+ self.clip_g.encode_token_weights = partial(SDXLClipG.encode_token_weights, self.clip_g)
+ self.clip_l.encode_token_weights = partial(SD1ClipModel.encode_token_weights, self.clip_l)
+ try:
+ g_out, g_pooled = self.clip_g.encode_token_weights(token_weight_pairs_g, steps, current_step, multi)
+ l_out, l_pooled = self.clip_l.encode_token_weights(token_weight_pairs_l, steps, current_step, multi)
+ # g_out, g_pooled = SDXLClipG.encode_token_weights(self.clip_g, token_weight_pairs_g, steps, current_step, multi)
+ # l_out, l_pooled = self.clip_l.encode_token_weights(token_weight_pairs_l, steps, current_step, multi)
+ finally:
+ self.clip_g.encode_token_weights = self.clip_g.encode_token_weights_orig
+ self.clip_l.encode_token_weights = self.clip_l.encode_token_weights_orig
+ self.clip_g.cond_stage_model = None
+ self.clip_l.cond_stage_model = None
+
+ if hasattr(g_pooled, 'schedules') and hasattr(l_pooled, 'schedules'):
+ g_pooled.schedules = {"g": g_pooled.schedules, "l": l_pooled.schedules}
+
+ g_out, l_out = expand(g_out, l_out)
+ l_out, g_out = expand(l_out, g_out)
+
+ return torch.cat([l_out, g_out], dim=-1), g_pooled
+
+class SDXLRefinerClipModel(ClipTokenWeightEncoder):
+
+ def encode_token_weights(self: comfy.sdxl_clip.SDXLClipModel, token_weight_pairs, steps=0, current_step=0, multi=False):
+ self.clip_g.encode_token_weights_orig = self.clip_g.encode_token_weights
+ self.clip_g.encode_token_weights = partial(SDXLClipG.encode_token_weights, self.clip_g)
+ token_weight_pairs_g = token_weight_pairs["g"]
+ try: g_out, g_pooled = self.clip_g.encode_token_weights(token_weight_pairs_g, steps, current_step, multi)
+ finally: self.clip_g.encode_token_weights = self.clip_g.encode_token_weights_orig
+ if hasattr(g_pooled, 'schedules'):
+ g_pooled.schedules = {"g": g_pooled.schedules}
+ return (g_out, g_pooled)
+
+def is_prompt_editing(schedules):
+ if schedules == None: return False
+ if not isinstance(schedules, dict):
+ schedules = {'g': schedules}
+ for k,v in schedules.items():
+ if type(v) == list:
+ if len(v[0]) != 1: return True
+ else:
+ if len(v.batch[0][0].schedules) != 1: return True
+ return False
+
+# ===================================================================
+# RNG
+from .modules import rng_philox
+def randn_without_seed(x, generator=None, randn_source="cpu"):
+ """Generate a tensor with random numbers from a normal distribution using the previously initialized genrator.
+
+ Use either randn() or manual_seed() to initialize the generator."""
+ if randn_source == "nv":
+ return torch.asarray(generator.randn(x.size()), device=x.device)
+ else:
+ if generator is not None and generator.device.type == "cpu":
+ return torch.randn(x.size(), dtype=x.dtype, layout=x.layout, device=devices.cpu, generator=generator).to(device=x.device)
+ else:
+ return torch.randn(x.size(), dtype=x.dtype, layout=x.layout, device=x.device, generator=generator)
+
+class TorchHijack:
+ """This is here to replace torch.randn_like of k-diffusion.
+
+ k-diffusion has random_sampler argument for most samplers, but not for all, so
+ this is needed to properly replace every use of torch.randn_like.
+
+ We need to replace to make images generated in batches to be same as images generated individually."""
+
+ def __init__(self, generator, randn_source):
+ # self.rng = p.rng
+ self.generator = generator
+ self.randn_source = randn_source
+
+ def __getattr__(self, item):
+ if item == 'randn_like':
+ return self.randn_like
+
+ if hasattr(torch, item):
+ return getattr(torch, item)
+
+ raise AttributeError(f"'{type(self).__name__}' object has no attribute '{item}'")
+
+ def randn_like(self, x):
+ return randn_without_seed(x, generator=self.generator, randn_source=self.randn_source)
+
+def prepare_noise(latent_image, seed, noise_inds=None, device='cpu'):
+ """
+ creates random noise given a latent image and a seed.
+ optional arg skip can be used to skip and discard x number of noise generations for a given seed
+ """
+ from .modules.shared import opts
+ from comfy.sample import np
+ def get_generator(seed):
+ nonlocal device
+ nonlocal opts
+ _generator = torch.Generator(device=device)
+ generator = _generator.manual_seed(seed)
+ if opts.randn_source == 'nv':
+ generator = rng_philox.Generator(seed)
+ return generator
+ generator = generator_eta = get_generator(seed)
+
+ if opts.eta_noise_seed_delta > 0:
+ seed = min(int(seed + opts.eta_noise_seed_delta), int(0xffffffffffffffff))
+ generator_eta = get_generator(seed)
+
+
+ # hijack randn_like
+ import comfy.k_diffusion.sampling
+ comfy.k_diffusion.sampling.torch = TorchHijack(generator_eta, opts.randn_source)
+
+ if noise_inds is None:
+ shape = latent_image.size()
+ if opts.randn_source == 'nv':
+ return torch.asarray(generator.randn(shape), device=devices.cpu)
+ else:
+ return torch.randn(shape, dtype=latent_image.dtype, layout=latent_image.layout, device=device, generator=generator)
+
+ unique_inds, inverse = np.unique(noise_inds, return_inverse=True)
+ noises = []
+ for i in range(unique_inds[-1]+1):
+ shape = [1] + list(latent_image.size())[1:]
+ if opts.randn_source == 'nv':
+ noise = torch.asarray(generator.randn(shape), device=devices.cpu)
+ else:
+ noise = torch.randn(shape, dtype=latent_image.dtype, layout=latent_image.layout, device=device, generator=generator)
+ if i in unique_inds:
+ noises.append(noise)
+ noises = [noises[i] for i in inverse]
+ noises = torch.cat(noises, axis=0)
+ return noises
+
+# ===========================================================
+
+def run(clip: comfy.sd.CLIP, text, parser, mean_normalization,
+ multi_conditioning, use_old_emphasis_implementation, with_SDXL,
+ ascore, width, height, crop_w, crop_h, target_width, target_height,
+ text_g, text_l, steps=1, step=0):
+ opts.prompt_mean_norm = mean_normalization
+ opts.use_old_emphasis_implementation = use_old_emphasis_implementation
+ opts.CLIP_stop_at_last_layers = abs(clip.layer_idx or 1)
+ is_sdxl = "SDXL" in type(clip.cond_stage_model).__name__
+ if is_sdxl:
+ # Prevents tensor shape mismatch
+ # This is what comfy does by default
+ opts.batch_cond_uncond = True
+
+ parser_d = {"full": "Full parser",
+ "compel": "Compel parser",
+ "A1111": "A1111 parser",
+ "fixed attention": "Fixed attention",
+ "comfy++": "Comfy++ parser",
+ }
+ opts.prompt_attention = parser_d.get(parser, "Comfy parser")
+
+ sdxl_params = {}
+ if with_SDXL and is_sdxl:
+ sdxl_params = {
+ "aesthetic_score": ascore, "width": width, "height": height,
+ "crop_w": crop_w, "crop_h": crop_h, "target_width": target_width,
+ "target_height": target_height, "text_g": text_g, "text_l": text_l
+ }
+ pooled={}
+ if hasattr(comfy.sd1_clip, 'SDTokenizer'):
+ SDTokenizer = comfy.sd1_clip.SDTokenizer
+ else:
+ SDTokenizer = comfy.sd1_clip.SD1Tokenizer
+ tokenize_with_weights_orig = SDTokenizer.tokenize_with_weights
+ if parser == "comfy":
+ SDTokenizer.tokenize_with_weights = tokenize_with_weights_custom
+ clip_model_type_name = type(clip.cond_stage_model).__name__
+ if with_SDXL and is_sdxl:
+ if clip_model_type_name== "SDXLClipModel":
+ out = CLIPTextEncodeSDXL().encode(clip, width, height, crop_w, crop_h, target_width, target_height, text_g, text_l)
+ out[0][0][1]['aesthetic_score'] = sdxl_params['aesthetic_score']
+ elif clip_model_type_name == "SDXLRefinerClipModel":
+ out = CLIPTextEncodeSDXLRefiner().encode(clip, ascore, width, height, text)
+ for item in ['aesthetic_score', 'width', 'height', 'text_g', 'text_l']:
+ sdxl_params.pop(item)
+ out[0][0][1].update(sdxl_params)
+ else:
+ raise NotImplementedError()
+ else:
+ out = CLIPTextEncode().encode(clip, text)
+ SDTokenizer.tokenize_with_weights = tokenize_with_weights_orig
+ return out
+ else:
+ texts = [text]
+ create_prompts = lambda txts: prompt_parser.SdConditioning(txts)
+ texts = create_prompts(texts)
+ if is_sdxl:
+ if with_SDXL:
+ texts = {"g": create_prompts([text_g]), "l": create_prompts([text_l])}
+ else:
+ texts = {"g": texts, "l": texts}
+
+ # clip_clone = clip.clone()
+ clip_clone = clip
+ clip_clone.cond_stage_model_orig = clip_clone.cond_stage_model
+ clip_clone.cond_stage_model.encode_token_weights_orig = clip_clone.cond_stage_model.encode_token_weights
+
+ def patch_cond_stage_model():
+ nonlocal clip_clone
+ from .smZNodes import SD1ClipModel, SDXLClipModel, SDXLRefinerClipModel
+ ctp = type(clip_clone.cond_stage_model)
+ clip_clone.cond_stage_model.tokenizer = clip_clone.tokenizer
+ if ctp is comfy.sdxl_clip.SDXLClipModel:
+ clip_clone.cond_stage_model.encode_token_weights = SDXLClipModel.encode_token_weights
+ clip_clone.cond_stage_model.clip_g.tokenizer = clip_clone.tokenizer.clip_g
+ clip_clone.cond_stage_model.clip_l.tokenizer = clip_clone.tokenizer.clip_l
+ elif ctp is comfy.sdxl_clip.SDXLRefinerClipModel:
+ clip_clone.cond_stage_model.encode_token_weights = SDXLRefinerClipModel.encode_token_weights
+ clip_clone.cond_stage_model.clip_g.tokenizer = clip_clone.tokenizer.clip_g
+ else:
+ clip_clone.cond_stage_model.encode_token_weights = SD1ClipModel.encode_token_weights
+
+ tokens = texts
+ if parser == "comfy++":
+ SDTokenizer.tokenize_with_weights = tokenize_with_weights_custom
+ tokens = clip_clone.tokenize(text)
+ SDTokenizer.tokenize_with_weights = tokenize_with_weights_orig
+ cond = pooled = None
+ patch_cond_stage_model()
+ try:
+ clip_clone.cond_stage_model.encode_token_weights = partial(clip_clone.cond_stage_model.encode_token_weights, clip_clone.cond_stage_model, steps=steps, current_step=step, multi=multi_conditioning)
+ cond, pooled = clip_clone.encode_from_tokens(tokens, True)
+ finally:
+ clip_clone.cond_stage_model = clip_clone.cond_stage_model_orig
+ clip_clone.cond_stage_model.encode_token_weights = clip_clone.cond_stage_model.encode_token_weights_orig
+
+ if opts.debug:
+ print('[smZNodes] using steps', steps)
+ gen_id = lambda : binascii.hexlify(os.urandom(1024))[64:72]
+ id=gen_id()
+ schedules = getattr(pooled, 'schedules', [[(0, 1.0)]])
+ pooled = {"pooled_output": pooled, "from_smZ": True, "smZid": id, "conds_list": pooled.conds_list, **sdxl_params}
+ out = [[cond, pooled]]
+ if is_prompt_editing(schedules):
+ for x in range(1,steps):
+ if type(schedules) is not dict:
+ cond=reconstruct_schedules(schedules, x)
+ if type(cond) is tuple:
+ conds_list, cond = cond
+ pooled['conds_list'] = conds_list
+ cond=cond.cpu()
+ elif type(schedules) is dict and len(schedules) == 1: # SDXLRefiner
+ cond = reconstruct_schedules(next(iter(schedules.values())), x)
+ if type(cond) is tuple:
+ conds_list, cond = cond
+ pooled['conds_list'] = conds_list
+ cond=cond.cpu()
+ elif type(schedules) is dict:
+ g_out = reconstruct_schedules(schedules['g'], x)
+ if type(g_out) is tuple: _, g_out = g_out
+ l_out = reconstruct_schedules(schedules['l'], x)
+ if type(l_out) is tuple: _, l_out = l_out
+ g_out, l_out = expand(g_out, l_out)
+ l_out, g_out = expand(l_out, g_out)
+ cond = torch.cat([l_out, g_out], dim=-1).cpu()
+ else:
+ raise NotImplementedError
+ out = out + [[cond, pooled]]
+ out[0][1]['orig_len'] = len(out)
+ return (out,)
+
+# ========================================================================
+
+from server import PromptServer
+def prompt_handler(json_data):
+ data=json_data['prompt']
+ def tmp():
+ nonlocal data
+ current_clip_id = None
+ def find_nearest_ksampler(clip_id):
+ """Find the nearest KSampler node that references the given CLIPTextEncode id."""
+ for ksampler_id, node in data.items():
+ if "Sampler" in node["class_type"] or "sampler" in node["class_type"]:
+ # Check if this KSampler node directly or indirectly references the given CLIPTextEncode node
+ if check_link_to_clip(ksampler_id, clip_id):
+ return get_steps(data, ksampler_id)
+ return None
+
+ def get_steps(graph, node_id):
+ node = graph.get(str(node_id), {})
+ steps_input_value = node.get("inputs", {}).get("steps", None)
+ if steps_input_value is None:
+ steps_input_value = node.get("inputs", {}).get("sigmas", None)
+
+ while(True):
+ # Base case: it's a direct value
+ if isinstance(steps_input_value, (int, float, str)):
+ return min(max(1, int(steps_input_value)), 10000)
+
+ # Loop case: it's a reference to another node
+ elif isinstance(steps_input_value, list):
+ ref_node_id, ref_input_index = steps_input_value
+ ref_node = graph.get(str(ref_node_id), {})
+ steps_input_value = ref_node.get("inputs", {}).get("steps", None)
+ if steps_input_value is None:
+ keys = list(ref_node.get("inputs", {}).keys())
+ ref_input_key = keys[ref_input_index % len(keys)]
+ steps_input_value = ref_node.get("inputs", {}).get(ref_input_key)
+ else:
+ return None
+
+ def check_link_to_clip(node_id, clip_id, visited=None):
+ """Check if a given node links directly or indirectly to a CLIPTextEncode node."""
+ if visited is None:
+ visited = set()
+
+ node = data[node_id]
+
+ if node_id in visited:
+ return False
+ visited.add(node_id)
+
+ for input_value in node["inputs"].values():
+ if isinstance(input_value, list) and input_value[0] == clip_id:
+ return True
+ if isinstance(input_value, list) and check_link_to_clip(input_value[0], clip_id, visited):
+ return True
+
+ return False
+
+
+ # Update each CLIPTextEncode node's steps with the steps from its nearest referencing KSampler node
+ for clip_id, node in data.items():
+ if node["class_type"] == "smZ CLIPTextEncode":
+ current_clip_id = clip_id
+ steps = find_nearest_ksampler(clip_id)
+ if steps is not None:
+ node["inputs"]["smZ_steps"] = steps
+ if opts.debug:
+ print(f'[smZNodes] id: {current_clip_id} | steps: {steps}')
+ tmp()
+ return json_data
+
+if hasattr(PromptServer.instance, 'add_on_prompt_handler'):
+ PromptServer.instance.add_on_prompt_handler(prompt_handler)
+
+# ========================================================================
+def bounded_modulo(number, modulo_value):
+ return number if number < modulo_value else modulo_value
+
+def get_adm(c):
+ for y in ["adm_encoded", "c_adm", "y"]:
+ if y in c:
+ c_c_adm = c[y]
+ if y == "adm_encoded": y="c_adm"
+ if type(c_c_adm) is not torch.Tensor: c_c_adm = c_c_adm.cond
+ return {y: c_c_adm, 'key': y}
+ return None
+
+getp=lambda x: x[1] if type(x) is list else x
+def calc_cond(c, current_step):
+ """Group by smZ conds that may do prompt-editing / regular conds / comfy conds."""
+ _cond = []
+ # Group by conds from smZ
+ fn=lambda x : getp(x).get("from_smZ", None) is not None
+ an_iterator = itertools.groupby(c, fn )
+ for key, group in an_iterator:
+ ls=list(group)
+ # Group by prompt-editing conds
+ fn2=lambda x : getp(x).get("smZid", None)
+ an_iterator2 = itertools.groupby(ls, fn2)
+ for key2, group2 in an_iterator2:
+ ls2=list(group2)
+ if key2 is not None:
+ orig_len = getp(ls2[0]).get('orig_len', 1)
+ i = bounded_modulo(current_step, orig_len - 1)
+ _cond = _cond + [ls2[i]]
+ else:
+ _cond = _cond + ls2
+ return _cond
+
+CFGNoisePredictorOrig = comfy.samplers.CFGNoisePredictor
+class CFGNoisePredictor(CFGNoisePredictorOrig):
+ def __init__(self, model):
+ super().__init__(model)
+ self.step = 0
+ self.inner_model2 = CFGDenoiser(model.apply_model)
+ self.s_min_uncond = opts.s_min_uncond
+ self.c_adm = None
+ self.init_cond = None
+ self.init_uncond = None
+ self.is_prompt_editing_u = False
+ self.is_prompt_editing_c = False
+
+ def apply_model(self, *args, **kwargs):
+ x=kwargs['x'] if 'x' in kwargs else args[0]
+ timestep=kwargs['timestep'] if 'timestep' in kwargs else args[1]
+ cond=kwargs['cond'] if 'cond' in kwargs else args[2]
+ uncond=kwargs['uncond'] if 'uncond' in kwargs else args[3]
+ cond_scale=kwargs['cond_scale'] if 'cond_scale' in kwargs else args[4]
+ model_options=kwargs['model_options'] if 'model_options' in kwargs else {}
+
+ cc=calc_cond(cond, self.step)
+ uu=calc_cond(uncond, self.step)
+ self.step += 1
+
+ if (any([getp(p).get('from_smZ', False) for p in cc]) or
+ any([getp(p).get('from_smZ', False) for p in uu])):
+ if model_options.get('transformer_options',None) is None:
+ model_options['transformer_options'] = {}
+ model_options['transformer_options']['from_smZ'] = True
+
+ if not opts.use_CFGDenoiser or not model_options['transformer_options'].get('from_smZ', False):
+ if 'cond' in kwargs: kwargs['cond'] = cc
+ else: args[2]=cc
+ if 'uncond' in kwargs: kwargs['uncond'] = uu
+ else: args[3]=uu
+ out = super().apply_model(*args, **kwargs)
+ else:
+ # Only supports one cond
+ for ix in range(len(cc)):
+ if getp(cc[ix]).get('from_smZ', False):
+ cc = [cc[ix]]
+ break
+ for ix in range(len(uu)):
+ if getp(uu[ix]).get('from_smZ', False):
+ uu = [uu[ix]]
+ break
+ c=getp(cc[0])
+ u=getp(uu[0])
+ _cc = cc[0][0] if type(cc[0]) is list else cc[0]['model_conds']['c_crossattn'].cond
+ _uu = uu[0][0] if type(uu[0]) is list else uu[0]['model_conds']['c_crossattn'].cond
+ conds_list = c.get('conds_list', [[(0, 1.0)]])
+ if 'model_conds' in c: c = c['model_conds']
+ if 'model_conds' in u: u = u['model_conds']
+ c_c_adm = get_adm(c)
+ if c_c_adm is not None:
+ u_c_adm = get_adm(u)
+ k = c_c_adm['key']
+ self.c_adm = {k: torch.cat([c_c_adm[k], u_c_adm[u_c_adm['key']]]).to(device=x.device), 'key': k}
+ # SDXL. Need to pad with repeats
+ _cc, _uu = expand(_cc, _uu)
+ _uu, _cc = expand(_uu, _cc)
+ x.c_adm = self.c_adm
+ image_cond = txt2img_image_conditioning(None, x)
+ out = self.inner_model2(x, timestep, cond=(conds_list, _cc), uncond=_uu, cond_scale=cond_scale, s_min_uncond=self.s_min_uncond, image_cond=image_cond)
+ return out
+
+def txt2img_image_conditioning(sd_model, x, width=None, height=None):
+ return x.new_zeros(x.shape[0], 5, 1, 1, dtype=x.dtype, device=x.device)
+ # if sd_model.model.conditioning_key in {'hybrid', 'concat'}: # Inpainting models
+ # # The "masked-image" in this case will just be all zeros since the entire image is masked.
+ # image_conditioning = torch.zeros(x.shape[0], 3, height, width, device=x.device)
+ # image_conditioning = sd_model.get_first_stage_encoding(sd_model.encode_first_stage(image_conditioning))
+ # # Add the fake full 1s mask to the first dimension.
+ # image_conditioning = torch.nn.functional.pad(image_conditioning, (0, 0, 0, 0, 1, 0), value=1.0)
+ # image_conditioning = image_conditioning.to(x.dtype)
+ # return image_conditioning
+ # elif sd_model.model.conditioning_key == "crossattn-adm": # UnCLIP models
+ # return x.new_zeros(x.shape[0], 2*sd_model.noise_augmentor.time_embed.dim, dtype=x.dtype, device=x.device)
+ # else:
+ # # Dummy zero conditioning if we're not using inpainting or unclip models.
+ # # Still takes up a bit of memory, but no encoder call.
+ # # Pretty sure we can just make this a 1x1 image since its not going to be used besides its batch size.
+ # return x.new_zeros(x.shape[0], 5, 1, 1, dtype=x.dtype, device=x.device)
+
+# =======================================================================================
+
+def inject_code(original_func, data):
+ # Get the source code of the original function
+ original_source = inspect.getsource(original_func)
+
+ # Split the source code into lines
+ lines = original_source.split("\n")
+
+ for item in data:
+ # Find the line number of the target line
+ target_line_number = None
+ for i, line in enumerate(lines):
+ if item['target_line'] in line:
+ target_line_number = i + 1
+
+ # Find the indentation of the line where the new code will be inserted
+ indentation = ''
+ for char in line:
+ if char == ' ':
+ indentation += char
+ else:
+ break
+
+ # Indent the new code to match the original
+ code_to_insert = dedent(item['code_to_insert'])
+ code_to_insert = indent(code_to_insert, indentation)
+ break
+
+ if target_line_number is None:
+ raise FileNotFoundError
+ # Target line not found, return the original function
+ # return original_func
+
+ # Insert the code to be injected after the target line
+ lines.insert(target_line_number, code_to_insert)
+
+ # Recreate the modified source code
+ modified_source = "\n".join(lines)
+ modified_source = dedent(modified_source.strip("\n"))
+
+ # Create a temporary file to write the modified source code so I can still view the
+ # source code when debugging.
+ with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.py') as temp_file:
+ temp_file.write(modified_source)
+ temp_file.flush()
+
+ MODULE_PATH = temp_file.name
+ MODULE_NAME = __name__.split('.')[0] + "_patch_modules"
+ spec = importlib.util.spec_from_file_location(MODULE_NAME, MODULE_PATH)
+ module = importlib.util.module_from_spec(spec)
+ sys.modules[spec.name] = module
+ spec.loader.exec_module(module)
+
+ # Pass global variables to the modified module
+ globals_dict = original_func.__globals__
+ for key, value in globals_dict.items():
+ setattr(module, key, value)
+ modified_module = module
+
+ # Retrieve the modified function from the module
+ modified_function = getattr(modified_module, original_func.__name__)
+
+ # If the original function was a method, bind it to the first argument (self)
+ if inspect.ismethod(original_func):
+ modified_function = modified_function.__get__(original_func.__self__, original_func.__class__)
+
+ # Update the metadata of the modified function to associate it with the original function
+ functools.update_wrapper(modified_function, original_func)
+
+ # Return the modified function
+ return modified_function
+
+# ========================================================================
+
+# DPM++ 2M alt
+
+from tqdm.auto import trange
+@torch.no_grad()
+def sample_dpmpp_2m_alt(model, x, sigmas, extra_args=None, callback=None, disable=None):
+ """DPM-Solver++(2M)."""
+ extra_args = {} if extra_args is None else extra_args
+ s_in = x.new_ones([x.shape[0]])
+ sigma_fn = lambda t: t.neg().exp()
+ t_fn = lambda sigma: sigma.log().neg()
+ old_denoised = None
+
+ for i in trange(len(sigmas) - 1, disable=disable):
+ denoised = model(x, sigmas[i] * s_in, **extra_args)
+ if callback is not None:
+ callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigmas[i], 'denoised': denoised})
+ t, t_next = t_fn(sigmas[i]), t_fn(sigmas[i + 1])
+ h = t_next - t
+ if old_denoised is None or sigmas[i + 1] == 0:
+ x = (sigma_fn(t_next) / sigma_fn(t)) * x - (-h).expm1() * denoised
+ else:
+ h_last = t - t_fn(sigmas[i - 1])
+ r = h_last / h
+ denoised_d = (1 + 1 / (2 * r)) * denoised - (1 / (2 * r)) * old_denoised
+ x = (sigma_fn(t_next) / sigma_fn(t)) * x - (-h).expm1() * denoised_d
+ sigma_progress = i / len(sigmas)
+ adjustment_factor = 1 + (0.15 * (sigma_progress * sigma_progress))
+ old_denoised = denoised * adjustment_factor
+ return x
+
+
+def add_sample_dpmpp_2m_alt():
+ from comfy.samplers import KSampler, k_diffusion_sampling
+ if "dpmpp_2m_alt" not in KSampler.SAMPLERS:
+ try:
+ idx = KSampler.SAMPLERS.index("dpmpp_2m")
+ KSampler.SAMPLERS.insert(idx+1, "dpmpp_2m_alt")
+ setattr(k_diffusion_sampling, 'sample_dpmpp_2m_alt', sample_dpmpp_2m_alt)
+ import importlib
+ importlib.reload(k_diffusion_sampling)
+ except ValueError as err:
+ pass
diff --git a/custom_nodes/ComfyUI_smZNodes/web/js/smZdynamicWidgets.js b/custom_nodes/ComfyUI_smZNodes/web/js/smZdynamicWidgets.js
new file mode 100644
index 0000000000000000000000000000000000000000..2c01bb1d540e0eec8f55044d3f13fe30a598e11f
--- /dev/null
+++ b/custom_nodes/ComfyUI_smZNodes/web/js/smZdynamicWidgets.js
@@ -0,0 +1,335 @@
+import { app } from "/scripts/app.js";
+// import { app } from "../../../scripts/app.js";
+// import { ComfyWidgets } from "../../../scripts/widgets.js";
+
+const ids1 = new Set(["smZ CLIPTextEncode"])
+const ids2 = new Set(["smZ Settings"])
+const widgets = ['mean_normalization', 'multi_conditioning', 'use_old_emphasis_implementation', 'with_SDXL']
+const widgets_sdxl = ['ascore', 'width', 'height', 'crop_w', 'crop_h', 'target_width', 'target_height', 'text_g', 'text_l']
+const getSetWidgets = new Set(['parser', 'with_SDXL'])
+
+let origProps = {};
+const HIDDEN_TAG = "smZhidden"
+
+const findWidgetByName = (node, name) => node.widgets.find((w) => w.name === name);
+const findWidgetsByName = (node, name) => node.widgets.filter((w) => w.name.endsWith(name));
+
+const doesInputWithNameExist = (node, name) => node.inputs ? node.inputs.some((input) => input.name === name) : false;
+
+// round in increments of x, with an offset
+function round(number, increment = 10, offset = 0) {
+ return Math.ceil((number - offset) / increment ) * increment + offset;
+}
+
+function toggleWidget(node, widget, show = false, suffix = "") {
+ if (!widget || doesInputWithNameExist(node, widget.name)) return;
+ if (!origProps[widget.name]) {
+ origProps[widget.name] = { origType: widget.type, origComputeSize: widget.computeSize};
+ }
+ const origSize = node.size;
+
+ widget.type = show ? origProps[widget.name].origType : HIDDEN_TAG + suffix;
+ widget.computeSize = show ? origProps[widget.name].origComputeSize : () => [0, -3.3];
+
+ widget.linkedWidgets?.forEach(w => toggleWidget(node, w, ":" + widget.name, show));
+
+ const height = show ? Math.max(node.computeSize()[1], origSize[1]) : node.size[1];
+ node.setSize([node.size[0], height]);
+ if (show)
+ delete widget.computedHeight;
+ else
+ widget.computedHeight = 0;
+}
+
+function widgetLogic(node, widget) {
+ const wname = widget.name
+ if (wname.endsWith("parser")) {
+ const in_comfy = widget.value.includes("comfy")
+ toggleMenuOption(node, ['multi_conditioning', wname], !in_comfy)
+ toggleMenuOption(node, ['mean_normalization', wname], widget.value !== "comfy")
+ if (in_comfy) {
+ toggleMenuOption(node, ['use_old_emphasis_implementation', wname], false)
+ }
+ } else if (wname.endsWith("with_SDXL")) {
+ toggleMenuOption(node, ['text', wname], !widget.value)
+
+ // Resize node when widget is set to false
+ if (!widget.value) {
+ // Prevents resizing on init/webpage reload
+ if(widget.init === false) {
+ // Resize when set to false
+ node.setSize([node.size[0], Math.max(100, round(node.size[1]/1.5))])
+ }
+ } else {
+ // When enabled, set init to false
+ widget.init = false
+ }
+
+ // Toggle sdxl widgets if sdxl widget value is true/false
+ for (const w of widgets_sdxl) {
+ toggleMenuOption(node, [w, wname], widget.value)
+ }
+
+ // Keep showing the widget if it's enabled
+ if (widget.value && widget.type === HIDDEN_TAG) {
+ toggleMenuOption(node, [widget.name, wname], true)
+ }
+ }
+}
+
+function getGroupNodeConfig(node) {
+ let ls = []
+ let nodeData = node.constructor?.nodeData
+ if (nodeData) {
+ for(let sym of Object.getOwnPropertySymbols(nodeData) ) {
+ const o = nodeData[sym];
+ if (!o) continue;
+ ls.push(o)
+ }
+ }
+ return ls
+}
+
+function getSetters(node) {
+ if (node.widgets) {
+ let gncl = getGroupNodeConfig(node)
+ for (const w of node.widgets) {
+ for (const gsw of [...getSetWidgets]) {
+ if (!w.name.endsWith(gsw)) continue;
+ let shouldBreak = false
+ for (const gnc of gncl) {
+ const nwmap = gnc.newToOldWidgetMap[w.name]
+ if (nwmap && !(nwmap.node.type === [...ids1][0] && nwmap.inputName === gsw))
+ shouldBreak = true
+ }
+ if (shouldBreak) break;
+ widgetLogic(node, w);
+ w._value = w.value
+
+ Object.defineProperty(w, 'value', {
+ get() {
+ return w._value;
+ },
+ set(newVal) {
+ w._value = newVal
+ widgetLogic(node, w);
+ }
+ });
+
+ // Hide SDXL widget on init
+ // Doing it in nodeCreated fixes its toggling for some reason
+ if (w.name.endsWith('with_SDXL')) {
+ toggleMenuOption(node, ['with_SDXL', w.name])
+ w.init = true
+
+ // Hide steps
+ toggleMenuOption(node, ['smZ_steps', w.name] , false)
+ }
+ }
+ }
+ }
+}
+
+function toggleMenuOption(node, widget_arr, _show = null, perform_action = true) {
+ const gncl = getGroupNodeConfig(node)
+ const [widget_name, companion_widget_name] = Array.isArray(widget_arr) ? widget_arr : [widget_arr]
+ let nwname = widget_name
+ // Use companion_widget_name to get the correct widget with the new name
+ if (companion_widget_name) {
+ for (const gnc of gncl) {
+ const omap = Object.values(gnc.oldToNewWidgetMap).find(x => Object.values(x).find(z => z === companion_widget_name))
+ const tmp2 = omap[widget_name]
+ if (tmp2) nwname = tmp2;
+ }
+ }
+ const widgets = companion_widget_name ? [findWidgetByName(node, nwname)] : findWidgetsByName(node, nwname)
+ for (const widget of widgets)
+ toggleMenuOption0(node, widget, _show, perform_action)
+}
+
+function toggleMenuOption0(node, widget, _show = null, perform_action = true) {
+ if (!widget || doesInputWithNameExist(node, widget.name)) return;
+ if (!origProps[widget.name]) {
+ origProps[widget.name] = { origType: widget.type, origComputeSize: widget.computeSize};
+ }
+ const show = (widget.type === origProps[widget.name].origType)
+ if (perform_action) {
+ toggleWidget(node, widget, _show !== null ? _show : !show)
+ node.setDirtyCanvas(true);
+ }
+}
+
+function toggle_all_settings_desc_widgets(node, _show = null) {
+ let found_widgets = node.widgets.filter((w) => w.name.includes('info'));
+ let is_showing = _show !== null ? _show : null
+ found_widgets.forEach(w => {
+ toggleMenuOption(node, [w.name, w.name], _show)
+ is_showing = _show !== null ? _show : w.type === origProps[w.name].origType
+ });
+
+ let w = node.widgets.find((w) => w.name === 'extra');
+ if (w) {
+ let value = null;
+ try {
+ value =JSON.parse(w.value);
+ } catch (error) {
+ // when node definitions change due to an update or some other error
+ value = {"show":true}
+ }
+ value.show = is_showing;
+ w.value = JSON.stringify(value);
+ }
+
+ // Collapse the node if the widgets aren't showing
+ if (!is_showing) {
+ node.setSize([node.size[0], node.computeSize()[1]])
+ }
+}
+
+function create_custom_option(content, _callback) {
+ return {
+ content: content,
+ callback: () => _callback(),
+ }
+};
+
+app.registerExtension({
+ name: "comfy.smZ.dynamicWidgets",
+
+ /**
+ * Called when a node is created. Used to add menu options to nodes.
+ * @param node The node that was created.
+ * @param app The app.
+ */
+ nodeCreated(node) {
+ const nodeType = node.type || node.constructor?.type
+ let inGroupNode = false
+ let inGroupNode2 = false
+ let innerNodes = node.getInnerNodes?.()
+ if (innerNodes) {
+ for (const inode of innerNodes) {
+ const _nodeType = inode.type || inode.constructor?.type
+ if (ids1.has(_nodeType))
+ inGroupNode = ids1.has(_nodeType)
+ if (inGroupNode)
+ ids1.add(nodeType) // GroupNode's type
+ if (ids2.has(_nodeType))
+ inGroupNode2 = ids2.has(_nodeType)
+ if (inGroupNode2)
+ ids2.add(nodeType) // GroupNode's type
+ }
+ }
+ // let nodeData = node.constructor?.nodeData
+ // if (nodeData) {
+ // for(let sym of Object.getOwnPropertySymbols(nodeData) ) {
+ // const nds = nodeData[sym];
+ // if (nds) {
+ // inGroupNode=true
+ // inGroupNode2=true
+ // break
+ // }
+ // }
+ // }
+ // ClipTextEncode++ node
+ if (ids1.has(nodeType) || inGroupNode) {
+ node.widgets.forEach(w => w._name = w.name)
+
+ getSetters(node)
+
+ // Reduce initial node size cause of SDXL widgets
+ // node.setSize([node.size[0], Math.max(node.size[1]/1.5, 220)])
+ node.setSize([node.size[0], 220])
+ }
+ // Settings node
+ if (ids2.has(nodeType) || inGroupNode2) {
+ node.serialize_widgets = true
+
+ const onConfigure = node.onConfigure;
+ node.onConfigure = function(o) {
+ const r = onConfigure ? onConfigure.apply(this, arguments) : undefined;
+ const w = this.widgets.find(w => w.name === 'extra')
+ let value = null
+ try {
+ value = JSON.parse(w.value);
+ } catch (error) {
+ // when node definitions change due to an update or some other error
+ value = {"show":true}
+ }
+ toggle_all_settings_desc_widgets(this, value.show)
+ return r;
+ }
+
+ // Styling.
+ node.widgets.forEach(function(w) {
+ w._name = w.name
+ if (w.name.includes('ㅤ')) {
+ w.heading = true
+ } else if (w.name.includes('info')) {
+ w.info = true
+ w.inputEl.disabled = true;
+ w.inputEl.readOnly = true;
+ w.inputEl.style.opacity = 0.75;
+ w.inputEl.style.alignContent = 'center';
+ w.inputEl.style.textAlign = 'center';
+ }
+ })
+ // Hide `extra` widget
+ toggleMenuOption(node, 'extra', false)
+ }
+ // Add extra MenuOptions for
+ // ClipTextEncode++ and Settings node
+ if (ids1.has(nodeType) || inGroupNode || ids2.has(nodeType) || inGroupNode2) {
+ // Save the original options
+ const getExtraMenuOptions = node.getExtraMenuOptions;
+
+ node.getExtraMenuOptions = function (_, options) {
+ // Call the original function for the default menu options
+ const r = getExtraMenuOptions ? getExtraMenuOptions.apply(this, arguments) : undefined;
+ let customOptions = []
+ node.setDirtyCanvas(true, true);
+ // if (!r) return r;
+
+ if (ids2.has(nodeType) || inGroupNode2) {
+ const content_hide_show = "Hide/show all descriptions";
+ customOptions.push(null) // seperator
+ customOptions.push(create_custom_option(content_hide_show, toggle_all_settings_desc_widgets.bind(this, node)))
+
+ // Alternate way to cleanup MenuOption
+ const toHideWidgets = node.widgets.filter(w => w.name.includes('ㅤ') || w.name.includes('info') || w.name.includes('extra'))
+ const wo = options.filter(o => o === null || (o && !toHideWidgets.some(w => o.content.includes(`Convert ${w.name} to input`))))
+ options.splice(0, options.length, ...wo);
+ }
+
+ if (ids1.has(nodeType) || inGroupNode) {
+ // Dynamic MenuOption depending on the widgets
+ const content_hide_show = "Hide/show ";
+ // const whWidgets = node.widgets.filter(w => w.name === 'width' || w.name === 'height')
+ const hiddenWidgets = node.widgets.filter(w => w.type === HIDDEN_TAG)
+ // doesn't take GroupNode into account
+ const with_SDXL = node.widgets.find(w => w.name === 'with_SDXL')
+ const parser = node.widgets.find(w => w.name === 'parser')
+ const in_comfy = parser.value.includes("comfy")
+ let ws = widgets.map(widget_name => create_custom_option(content_hide_show + widget_name, toggleMenuOption.bind(this, node, widget_name)))
+ ws = ws.filter((w) => (in_comfy && parser.value !== 'comfy' && w.content.includes('mean_normalization')) || (in_comfy && w.content.includes('with_SDXL')) || !in_comfy )
+ // customOptions.push(null) // seperator
+ customOptions.push(...ws)
+
+ let wo = options.filter(o => o === null || (o && !hiddenWidgets.some(w => o.content.includes(`Convert ${w.name} to input`))))
+ const width = node.widgets.find(w => w.name === 'width')
+ const height = node.widgets.find(w => w.name === 'height')
+ if (width && height) {
+ const width_type = width.type.toLowerCase()
+ const height_type = height.type.toLowerCase()
+ if (!(width_type.includes('number') || width_type.includes('int') || width_type.includes('float') ||
+ height_type.includes('number') || height_type.includes('int') || height_type.includes('float')))
+ wo = wo.filter(o => o === null || (o && !o.content.includes('Swap width/height')))
+ }
+ options.splice(0, options.length, ...wo);
+ }
+ // options.unshift(...customOptions); // top
+ options.splice(options.length - 1, 0, ...customOptions)
+ // return r;
+ }
+ }
+ }
+});
diff --git a/custom_nodes/ControlNet-LLLite-ComfyUI/LICENSE b/custom_nodes/ControlNet-LLLite-ComfyUI/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..f288702d2fa16d3cdf0035b15a9fcbc552cd88e7
--- /dev/null
+++ b/custom_nodes/ControlNet-LLLite-ComfyUI/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/custom_nodes/ControlNet-LLLite-ComfyUI/README.md b/custom_nodes/ControlNet-LLLite-ComfyUI/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..40b53e670ca79a1d368d36471e7d201902362d22
--- /dev/null
+++ b/custom_nodes/ControlNet-LLLite-ComfyUI/README.md
@@ -0,0 +1,67 @@
+# ControlNet-LLLite-ComfyUI
+
+日本語版ドキュメントは後半にあります。
+
+This is a UI for inference of [ControlNet-LLLite](https://github.com/kohya-ss/sd-scripts/blob/sdxl/docs/train_lllite_README.md).
+
+ControlNet-LLLite is an experimental implementation, so there may be some problems.
+
+
+
+## Installation
+
+1. Clone this repository to `custom_nodes`.
+2. Put ControlNet-LLLite models to `ControlNet-LLLite-ComfyUI/models`. You can download sample models from [here](https://huggingface.co/kohya-ss/controlnet-lllite/tree/main).
+
+## Usage
+
+Load [sample workflow](lllite_workflow.json).
+
+You can specify the strength of the effect with `strength`. 1.0 is default, 0.0 is no effect.
+
+You can apply only to some diffusion steps with `steps`, `start_percent`, and `end_percent`. Specify the number of steps specified in the sampler in `steps`, and specify the start and end steps from 0 to 100 in `start_percent` and `end_percent`, respectively.
+
+(Because we cannot check the total number of steps in the node, this specification has been made. Please check the console output for the specific application range.)
+
+## Tips
+
++ If the generated image size is different from the control image size, resize it with `image/upscaling/UpscaleImage` as shown in the workflow.
+
++ You can create a Canny image from a normal image with the `image/preprocessors/Canny` node.
+
+## Acknowledgements
+
+This repository is based on [IPAdapter-ComfyUI](https://github.com/laksjdjf/IPAdapter-ComfyUI) by laksjdjf. Thanks to laksjdjf.
+
+
+# ControlNet-LLLite-ComfyUI:日本語版ドキュメント
+
+[ControlNet-LLLite](https://github.com/kohya-ss/sd-scripts/blob/sdxl/docs/train_lllite_README.md) の推論用のUIです。
+
+ControlNet-LLLiteがそもそもきわめて実験的な実装のため、問題がいろいろあるかもしれません。
+
+
+
+## インストール方法
+
+1. `custom_nodes`にcloneします。
+2. `ControlNet-LLLite-ComfyUI/models` にモデルを入れます。サンプルは[こちら](https://huggingface.co/kohya-ss/controlnet-lllite/tree/main)からダウンロードできます。
+
+## 使い方
+
+[サンプルのワークフロー](lllite_workflow.json)を読み込んでください。
+
+`strength`に効果の強さを指定できます。1.0でデフォルト、0.0で効果なしです。
+
+`steps`と`start_percent`、`end_percent`で拡散ステップの一部にだけ効果を適用できます。`steps`にsamplerに指定したステップ数を指定し、`start_percent`と`end_percent`にそれぞれ開始と終了のステップを0から100で指定します。
+
+(ノード内で全体のステップ数を確認できないためこのような仕様になっています。具体的な適用範囲はコンソール出力を確認してください。)
+
+## ヒント
+
++ 生成画像サイズと制御用画像サイズが異なる場合はワークフローにあるように `image/upscaling/UpscaleImage` でリサイズしてください。
++ 通常の画像からCanny画像を作るには `image/preprocessors/Canny` のノードが使えます。
+
+## 謝辞
+
+laksjdjf 氏の [IPAdapter-ComfyUI](https://github.com/laksjdjf/IPAdapter-ComfyUI) を参考にしています。laksjdjf 氏に感謝します。
diff --git a/custom_nodes/ControlNet-LLLite-ComfyUI/__init__.py b/custom_nodes/ControlNet-LLLite-ComfyUI/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..ea5b1ccd97a2b4cb186b6af0a3c9a357f732dd07
--- /dev/null
+++ b/custom_nodes/ControlNet-LLLite-ComfyUI/__init__.py
@@ -0,0 +1,2 @@
+from .node_control_net_lllite import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
+__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
\ No newline at end of file
diff --git a/custom_nodes/ControlNet-LLLite-ComfyUI/__pycache__/__init__.cpython-311.pyc b/custom_nodes/ControlNet-LLLite-ComfyUI/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ce72c6a642066e274d342dee87c9be1bf31fe83c
Binary files /dev/null and b/custom_nodes/ControlNet-LLLite-ComfyUI/__pycache__/__init__.cpython-311.pyc differ
diff --git a/custom_nodes/ControlNet-LLLite-ComfyUI/__pycache__/node_control_net_lllite.cpython-311.pyc b/custom_nodes/ControlNet-LLLite-ComfyUI/__pycache__/node_control_net_lllite.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..9742a297c8e415cc346f064fac7685d1362c9760
Binary files /dev/null and b/custom_nodes/ControlNet-LLLite-ComfyUI/__pycache__/node_control_net_lllite.cpython-311.pyc differ
diff --git a/custom_nodes/ControlNet-LLLite-ComfyUI/lllite_workflow.json b/custom_nodes/ControlNet-LLLite-ComfyUI/lllite_workflow.json
new file mode 100644
index 0000000000000000000000000000000000000000..eaf11acb3b958f5a9daf18ed1458a1760d3c5601
--- /dev/null
+++ b/custom_nodes/ControlNet-LLLite-ComfyUI/lllite_workflow.json
@@ -0,0 +1,717 @@
+{
+ "last_node_id": 62,
+ "last_link_id": 108,
+ "nodes": [
+ {
+ "id": 16,
+ "type": "PrimitiveNode",
+ "pos": [
+ -90,
+ -40
+ ],
+ "size": {
+ "0": 398,
+ "1": 140
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 55,
+ 57
+ ],
+ "slot_index": 0,
+ "widget": {
+ "name": "text_g",
+ "config": [
+ "STRING",
+ {
+ "multiline": true,
+ "default": "CLIP_G"
+ }
+ ]
+ }
+ }
+ ],
+ "title": "Negative Prompt",
+ "properties": {},
+ "widgets_values": [
+ "nsfw, bad face, lowres, low quality, worst quality, low effort, watermark, signature, ugly, poorly drawn "
+ ]
+ },
+ {
+ "id": 32,
+ "type": "PrimitiveNode",
+ "pos": [
+ -90,
+ -220
+ ],
+ "size": {
+ "0": 398,
+ "1": 140
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 51,
+ 52
+ ],
+ "widget": {
+ "name": "text_g",
+ "config": [
+ "STRING",
+ {
+ "multiline": true,
+ "default": "CLIP_G"
+ }
+ ]
+ },
+ "slot_index": 0
+ }
+ ],
+ "title": "Positive Prompt",
+ "properties": {},
+ "widgets_values": [
+ "anime screen cap 1girl standing at classroom, looking at viewer, in school uniform, (solo), teen age, smile, long hair, upper body, trending on pixiv, 8k wallpaper, beautiful face"
+ ]
+ },
+ {
+ "id": 26,
+ "type": "VAEDecode",
+ "pos": [
+ 1203,
+ -216
+ ],
+ "size": {
+ "0": 140,
+ "1": 46
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 45,
+ "slot_index": 0
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 46,
+ "slot_index": 1
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 48
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 30,
+ "type": "CLIPTextEncodeSDXL",
+ "pos": [
+ 407,
+ -219
+ ],
+ "size": {
+ "0": 399.84454345703125,
+ "1": 262.6287841796875
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 49,
+ "slot_index": 0
+ },
+ {
+ "name": "text_g",
+ "type": "STRING",
+ "link": 51,
+ "widget": {
+ "name": "text_g",
+ "config": [
+ "STRING",
+ {
+ "multiline": true,
+ "default": "CLIP_G"
+ }
+ ]
+ }
+ },
+ {
+ "name": "text_l",
+ "type": "STRING",
+ "link": 52,
+ "widget": {
+ "name": "text_l",
+ "config": [
+ "STRING",
+ {
+ "multiline": true,
+ "default": "CLIP_L"
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 50
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncodeSDXL"
+ },
+ "widgets_values": [
+ 1024,
+ 1024,
+ 0,
+ 0,
+ 1024,
+ 1024,
+ "anime screen cap 1girl standing at classroom, looking at viewer, in school uniform, (solo), teen age, smile, long hair, upper body, trending on pixiv, 8k wallpaper, beautiful face",
+ "anime screen cap 1girl standing at classroom, looking at viewer, in school uniform, (solo), teen age, smile, long hair, upper body, trending on pixiv, 8k wallpaper, beautiful face"
+ ]
+ },
+ {
+ "id": 33,
+ "type": "CLIPTextEncodeSDXL",
+ "pos": [
+ 405,
+ 92
+ ],
+ "size": {
+ "0": 400,
+ "1": 270.0000305175781
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 54,
+ "slot_index": 0
+ },
+ {
+ "name": "text_g",
+ "type": "STRING",
+ "link": 55,
+ "widget": {
+ "name": "text_g",
+ "config": [
+ "STRING",
+ {
+ "multiline": true,
+ "default": "CLIP_G"
+ }
+ ]
+ }
+ },
+ {
+ "name": "text_l",
+ "type": "STRING",
+ "link": 57,
+ "widget": {
+ "name": "text_l",
+ "config": [
+ "STRING",
+ {
+ "multiline": true,
+ "default": "CLIP_L"
+ }
+ ]
+ },
+ "slot_index": 2
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 56
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncodeSDXL"
+ },
+ "widgets_values": [
+ 1024,
+ 1024,
+ 0,
+ 0,
+ 1024,
+ 1024,
+ "nsfw, bad face, lowres, low quality, worst quality, low effort, watermark, signature, ugly, poorly drawn ",
+ "nsfw, bad face, lowres, low quality, worst quality, low effort, watermark, signature, ugly, poorly drawn "
+ ]
+ },
+ {
+ "id": 19,
+ "type": "SaveImage",
+ "pos": [
+ 863,
+ 321
+ ],
+ "size": {
+ "0": 486.4142761230469,
+ "1": 454.6273498535156
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 48
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "lllite_output"
+ ]
+ },
+ {
+ "id": 43,
+ "type": "EmptyLatentImage",
+ "pos": [
+ -18,
+ 165
+ ],
+ "size": {
+ "0": 315,
+ "1": 106
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 68
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 1024,
+ 1024,
+ 1
+ ]
+ },
+ {
+ "id": 17,
+ "type": "KSampler",
+ "pos": [
+ 868,
+ -220
+ ],
+ "size": {
+ "0": 315,
+ "1": 474
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 108
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 50
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 56
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 68
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 45
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 1,
+ "fixed",
+ 36,
+ 7.5,
+ "ddim",
+ "normal",
+ 1
+ ]
+ },
+ {
+ "id": 59,
+ "type": "LoadImage",
+ "pos": [
+ -395,
+ 380
+ ],
+ "size": {
+ "0": 428.49603271484375,
+ "1": 442.3898620605469
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 104
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "canny1.png",
+ "image"
+ ]
+ },
+ {
+ "id": 61,
+ "type": "ImageScale",
+ "pos": [
+ 52,
+ 510
+ ],
+ "size": {
+ "0": 315,
+ "1": 130
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 104
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 106
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ImageScale"
+ },
+ "widgets_values": [
+ "bilinear",
+ 1024,
+ 1024,
+ "disabled"
+ ]
+ },
+ {
+ "id": 4,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ -541,
+ 35
+ ],
+ "size": {
+ "0": 397,
+ "1": 98
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 107
+ ],
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 49,
+ 54
+ ],
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 46
+ ],
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "sd_xl_base_1.0_0.9vae.safetensors"
+ ]
+ },
+ {
+ "id": 62,
+ "type": "LLLiteLoader",
+ "pos": [
+ 405,
+ 433
+ ],
+ "size": {
+ "0": 396.923095703125,
+ "1": 174
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 107
+ },
+ {
+ "name": "cond_image",
+ "type": "IMAGE",
+ "link": 106
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 108
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LLLiteLoader"
+ },
+ "widgets_values": [
+ "controllllite_v01032064e_sdxl_canny_anime.safetensors",
+ 1,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ],
+ "links": [
+ [
+ 45,
+ 17,
+ 0,
+ 26,
+ 0,
+ "LATENT"
+ ],
+ [
+ 46,
+ 4,
+ 2,
+ 26,
+ 1,
+ "VAE"
+ ],
+ [
+ 48,
+ 26,
+ 0,
+ 19,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 49,
+ 4,
+ 1,
+ 30,
+ 0,
+ "CLIP"
+ ],
+ [
+ 50,
+ 30,
+ 0,
+ 17,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 51,
+ 32,
+ 0,
+ 30,
+ 1,
+ "STRING"
+ ],
+ [
+ 52,
+ 32,
+ 0,
+ 30,
+ 2,
+ "STRING"
+ ],
+ [
+ 54,
+ 4,
+ 1,
+ 33,
+ 0,
+ "CLIP"
+ ],
+ [
+ 55,
+ 16,
+ 0,
+ 33,
+ 1,
+ "STRING"
+ ],
+ [
+ 56,
+ 33,
+ 0,
+ 17,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 57,
+ 16,
+ 0,
+ 33,
+ 2,
+ "STRING"
+ ],
+ [
+ 68,
+ 43,
+ 0,
+ 17,
+ 3,
+ "LATENT"
+ ],
+ [
+ 104,
+ 59,
+ 0,
+ 61,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 106,
+ 61,
+ 0,
+ 62,
+ 1,
+ "IMAGE"
+ ],
+ [
+ 107,
+ 4,
+ 0,
+ 62,
+ 0,
+ "MODEL"
+ ],
+ [
+ 108,
+ 62,
+ 0,
+ 17,
+ 0,
+ "MODEL"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/ControlNet-LLLite-ComfyUI/models/put_models_here.txt b/custom_nodes/ControlNet-LLLite-ComfyUI/models/put_models_here.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a43ed46cd15151db4b75473b8bb7d1350fad85c4
--- /dev/null
+++ b/custom_nodes/ControlNet-LLLite-ComfyUI/models/put_models_here.txt
@@ -0,0 +1 @@
+( •̀ ω •́ )✧
diff --git a/custom_nodes/ControlNet-LLLite-ComfyUI/node_control_net_lllite.py b/custom_nodes/ControlNet-LLLite-ComfyUI/node_control_net_lllite.py
new file mode 100644
index 0000000000000000000000000000000000000000..cb67f7df54cfa569c477e17f7b65f21b0f638923
--- /dev/null
+++ b/custom_nodes/ControlNet-LLLite-ComfyUI/node_control_net_lllite.py
@@ -0,0 +1,289 @@
+import math
+import torch
+import os
+
+import comfy
+
+CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
+
+
+def get_file_list(path):
+ return [file for file in os.listdir(path) if file != "put_models_here.txt"]
+
+
+def extra_options_to_module_prefix(extra_options):
+ # extra_options = {'transformer_index': 2, 'block_index': 8, 'original_shape': [2, 4, 128, 128], 'block': ('input', 7), 'n_heads': 20, 'dim_head': 64}
+
+ # block is: [('input', 4), ('input', 5), ('input', 7), ('input', 8), ('middle', 0),
+ # ('output', 0), ('output', 1), ('output', 2), ('output', 3), ('output', 4), ('output', 5)]
+ # transformer_index is: [0, 1, 2, 3, 4, 5, 6, 7, 8], for each block
+ # block_index is: 0-1 or 0-9, depends on the block
+ # input 7 and 8, middle has 10 blocks
+
+ # make module name from extra_options
+ block = extra_options["block"]
+ block_index = extra_options["block_index"]
+ if block[0] == "input":
+ module_pfx = f"lllite_unet_input_blocks_{block[1]}_1_transformer_blocks_{block_index}"
+ elif block[0] == "middle":
+ module_pfx = f"lllite_unet_middle_block_1_transformer_blocks_{block_index}"
+ elif block[0] == "output":
+ module_pfx = f"lllite_unet_output_blocks_{block[1]}_1_transformer_blocks_{block_index}"
+ else:
+ raise Exception("invalid block name")
+ return module_pfx
+
+
+def load_control_net_lllite_patch(path, cond_image, multiplier, num_steps, start_percent, end_percent):
+ # calculate start and end step
+ start_step = math.floor(num_steps * start_percent * 0.01) if start_percent > 0 else 0
+ end_step = math.floor(num_steps * end_percent * 0.01) if end_percent > 0 else num_steps
+
+ # load weights
+ ctrl_sd = comfy.utils.load_torch_file(path, safe_load=True)
+
+ # split each weights for each module
+ module_weights = {}
+ for key, value in ctrl_sd.items():
+ fragments = key.split(".")
+ module_name = fragments[0]
+ weight_name = ".".join(fragments[1:])
+
+ if module_name not in module_weights:
+ module_weights[module_name] = {}
+ module_weights[module_name][weight_name] = value
+
+ # load each module
+ modules = {}
+ for module_name, weights in module_weights.items():
+ # ここの自動判定を何とかしたい
+ if "conditioning1.4.weight" in weights:
+ depth = 3
+ elif weights["conditioning1.2.weight"].shape[-1] == 4:
+ depth = 2
+ else:
+ depth = 1
+
+ module = LLLiteModule(
+ name=module_name,
+ is_conv2d=weights["down.0.weight"].ndim == 4,
+ in_dim=weights["down.0.weight"].shape[1],
+ depth=depth,
+ cond_emb_dim=weights["conditioning1.0.weight"].shape[0] * 2,
+ mlp_dim=weights["down.0.weight"].shape[0],
+ multiplier=multiplier,
+ num_steps=num_steps,
+ start_step=start_step,
+ end_step=end_step,
+ )
+ info = module.load_state_dict(weights)
+ modules[module_name] = module
+ if len(modules) == 1:
+ module.is_first = True
+
+ print(f"loaded {path} successfully, {len(modules)} modules")
+
+ # cond imageをセットする
+ cond_image = cond_image.permute(0, 3, 1, 2) # b,h,w,3 -> b,3,h,w
+ cond_image = cond_image * 2.0 - 1.0 # 0-1 -> -1-+1
+
+ for module in modules.values():
+ module.set_cond_image(cond_image)
+
+ class control_net_lllite_patch:
+ def __init__(self, modules):
+ self.modules = modules
+
+ def __call__(self, q, k, v, extra_options):
+ module_pfx = extra_options_to_module_prefix(extra_options)
+
+ is_attn1 = q.shape[-1] == k.shape[-1] # self attention
+ if is_attn1:
+ module_pfx = module_pfx + "_attn1"
+ else:
+ module_pfx = module_pfx + "_attn2"
+
+ module_pfx_to_q = module_pfx + "_to_q"
+ module_pfx_to_k = module_pfx + "_to_k"
+ module_pfx_to_v = module_pfx + "_to_v"
+
+ if module_pfx_to_q in self.modules:
+ q = q + self.modules[module_pfx_to_q](q)
+ if module_pfx_to_k in self.modules:
+ k = k + self.modules[module_pfx_to_k](k)
+ if module_pfx_to_v in self.modules:
+ v = v + self.modules[module_pfx_to_v](v)
+
+ return q, k, v
+
+ def to(self, device):
+ for d in self.modules.keys():
+ self.modules[d] = self.modules[d].to(device)
+ return self
+
+ return control_net_lllite_patch(modules)
+
+
+class LLLiteModule(torch.nn.Module):
+ def __init__(
+ self,
+ name: str,
+ is_conv2d: bool,
+ in_dim: int,
+ depth: int,
+ cond_emb_dim: int,
+ mlp_dim: int,
+ multiplier: int,
+ num_steps: int,
+ start_step: int,
+ end_step: int,
+ ):
+ super().__init__()
+ self.name = name
+ self.is_conv2d = is_conv2d
+ self.multiplier = multiplier
+ self.num_steps = num_steps
+ self.start_step = start_step
+ self.end_step = end_step
+ self.is_first = False
+
+ modules = []
+ modules.append(torch.nn.Conv2d(3, cond_emb_dim // 2, kernel_size=4, stride=4, padding=0)) # to latent (from VAE) size*2
+ if depth == 1:
+ modules.append(torch.nn.ReLU(inplace=True))
+ modules.append(torch.nn.Conv2d(cond_emb_dim // 2, cond_emb_dim, kernel_size=2, stride=2, padding=0))
+ elif depth == 2:
+ modules.append(torch.nn.ReLU(inplace=True))
+ modules.append(torch.nn.Conv2d(cond_emb_dim // 2, cond_emb_dim, kernel_size=4, stride=4, padding=0))
+ elif depth == 3:
+ # kernel size 8は大きすぎるので、4にする / kernel size 8 is too large, so set it to 4
+ modules.append(torch.nn.ReLU(inplace=True))
+ modules.append(torch.nn.Conv2d(cond_emb_dim // 2, cond_emb_dim // 2, kernel_size=4, stride=4, padding=0))
+ modules.append(torch.nn.ReLU(inplace=True))
+ modules.append(torch.nn.Conv2d(cond_emb_dim // 2, cond_emb_dim, kernel_size=2, stride=2, padding=0))
+
+ self.conditioning1 = torch.nn.Sequential(*modules)
+
+ if self.is_conv2d:
+ self.down = torch.nn.Sequential(
+ torch.nn.Conv2d(in_dim, mlp_dim, kernel_size=1, stride=1, padding=0),
+ torch.nn.ReLU(inplace=True),
+ )
+ self.mid = torch.nn.Sequential(
+ torch.nn.Conv2d(mlp_dim + cond_emb_dim, mlp_dim, kernel_size=1, stride=1, padding=0),
+ torch.nn.ReLU(inplace=True),
+ )
+ self.up = torch.nn.Sequential(
+ torch.nn.Conv2d(mlp_dim, in_dim, kernel_size=1, stride=1, padding=0),
+ )
+ else:
+ self.down = torch.nn.Sequential(
+ torch.nn.Linear(in_dim, mlp_dim),
+ torch.nn.ReLU(inplace=True),
+ )
+ self.mid = torch.nn.Sequential(
+ torch.nn.Linear(mlp_dim + cond_emb_dim, mlp_dim),
+ torch.nn.ReLU(inplace=True),
+ )
+ self.up = torch.nn.Sequential(
+ torch.nn.Linear(mlp_dim, in_dim),
+ )
+
+ self.depth = depth
+ self.cond_image = None
+ self.cond_emb = None
+ self.current_step = 0
+
+ # @torch.inference_mode()
+ def set_cond_image(self, cond_image):
+ # print("set_cond_image", self.name)
+ self.cond_image = cond_image
+ self.cond_emb = None
+ self.current_step = 0
+
+ def forward(self, x):
+ if self.num_steps > 0:
+ if self.current_step < self.start_step:
+ self.current_step += 1
+ return torch.zeros_like(x)
+ elif self.current_step >= self.end_step:
+ if self.is_first and self.current_step == self.end_step:
+ print(f"end LLLite: step {self.current_step}")
+ self.current_step += 1
+ if self.current_step >= self.num_steps:
+ self.current_step = 0 # reset
+ return torch.zeros_like(x)
+ else:
+ if self.is_first and self.current_step == self.start_step:
+ print(f"start LLLite: step {self.current_step}")
+ self.current_step += 1
+ if self.current_step >= self.num_steps:
+ self.current_step = 0 # reset
+
+ if self.cond_emb is None:
+ # print(f"cond_emb is None, {self.name}")
+ cx = self.conditioning1(self.cond_image.to(x.device, dtype=x.dtype))
+ if not self.is_conv2d:
+ # reshape / b,c,h,w -> b,h*w,c
+ n, c, h, w = cx.shape
+ cx = cx.view(n, c, h * w).permute(0, 2, 1)
+ self.cond_emb = cx
+
+ cx = self.cond_emb
+ # print(f"forward {self.name}, {cx.shape}, {x.shape}")
+
+ # uncond/condでxはバッチサイズが2倍
+ if x.shape[0] != cx.shape[0]:
+ if self.is_conv2d:
+ cx = cx.repeat(x.shape[0] // cx.shape[0], 1, 1, 1)
+ else:
+ # print("x.shape[0] != cx.shape[0]", x.shape[0], cx.shape[0])
+ cx = cx.repeat(x.shape[0] // cx.shape[0], 1, 1)
+
+ cx = torch.cat([cx, self.down(x)], dim=1 if self.is_conv2d else 2)
+ cx = self.mid(cx)
+ cx = self.up(cx)
+ return cx * self.multiplier
+
+
+class LLLiteLoader:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "model": ("MODEL",),
+ "model_name": (get_file_list(os.path.join(CURRENT_DIR, "models")),),
+ "cond_image": ("IMAGE",),
+ "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
+ "steps": ("INT", {"default": 0, "min": 0, "max": 200, "step": 1}),
+ "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step": 0.1}),
+ "end_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step": 0.1}),
+ }
+ }
+
+ RETURN_TYPES = ("MODEL",)
+ FUNCTION = "load_lllite"
+ CATEGORY = "loaders"
+
+ def load_lllite(self, model, model_name, cond_image, strength, steps, start_percent, end_percent):
+ # cond_image is b,h,w,3, 0-1
+
+ model_path = os.path.join(CURRENT_DIR, os.path.join(CURRENT_DIR, "models", model_name))
+
+ model_lllite = model.clone()
+ patch = load_control_net_lllite_patch(model_path, cond_image, strength, steps, start_percent, end_percent)
+ if patch is not None:
+ model_lllite.set_model_attn1_patch(patch)
+ model_lllite.set_model_attn2_patch(patch)
+
+ return (model_lllite,)
+
+
+NODE_CLASS_MAPPINGS = {"LLLiteLoader": LLLiteLoader}
+
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "LLLiteLoader": "Load LLLite",
+}
diff --git a/custom_nodes/FreeU_Advanced/.gitattributes b/custom_nodes/FreeU_Advanced/.gitattributes
new file mode 100644
index 0000000000000000000000000000000000000000..dfe0770424b2a19faf507a501ebfc23be8f54e7b
--- /dev/null
+++ b/custom_nodes/FreeU_Advanced/.gitattributes
@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text=auto
diff --git a/custom_nodes/FreeU_Advanced/.gitignore b/custom_nodes/FreeU_Advanced/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..d9005f2cc7fc4e65f14ed5518276007c08cf2fd0
--- /dev/null
+++ b/custom_nodes/FreeU_Advanced/.gitignore
@@ -0,0 +1,152 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
diff --git a/custom_nodes/FreeU_Advanced/LICENSE b/custom_nodes/FreeU_Advanced/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..302dbfa90d5585377679e8fe49dee870dc05e822
--- /dev/null
+++ b/custom_nodes/FreeU_Advanced/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Jordan Thompson
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/custom_nodes/FreeU_Advanced/README.md b/custom_nodes/FreeU_Advanced/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..06caa2b7b5054f7ce169d3016a707ae9019ffd0a
--- /dev/null
+++ b/custom_nodes/FreeU_Advanced/README.md
@@ -0,0 +1,54 @@
+# FreeU Advanced Plus
+Let's say you and I grab dinner, and movie after lunch? 🌃📺😏
+
+
+
+### Exmaple of default node settings applied across blocks.
+
+
+
+
+
+
+
+
+## Input Parameters
+
+- `model` (`MODEL`): Model to patch
+- `target_block` (`COMBO`): Which block to target; `input_block`, `middle_block`, and `output_block`
+- `multiscale_mode` (`COMBO`): A list of available multiscale modes:
+ - `["Default", "Bandpass", "Low-Pass", "High-Pass", "Pass-Through", "Gaussian-Blur", "Edge-Enhancement", "Sharpen", "Multi-Bandpass", "Multi-Low-Pass", "Multi-High-Pass", "Multi-Pass-Through", "Multi-Gaussian-Blur", "Multi-Edge-Enhancement", "Multi-Sharpen"]`
+- `multiscale_strength` (`FLOAT`, Default: 1.0, Range: [0.0, 1.0], Step: 0.001): Strength of scaling
+- `b1_slice` (`INT`, Default: 640, Range: [64, 1280], Step: 1): The size of the array slice for b1 operation
+- `b2_slice` (`INT`, Default: 640, Range: [64, 640], Step: 1): The size of the array slice for b2 operation
+- `b1` (`FLOAT`, Default: 1.1, Range: [0.0, 10.0], Step: 0.001): `b1` output multiplier
+- `b2` (`FLOAT`, Default: 1.2, Range: [0.0, 10.0], Step: 0.001): `b2` output multiplier
+- `s1` (`FLOAT`, Default: 0.9, Range: [0.0, 10.0], Step: 0.001): `s1` Fourier transform scale strength
+- `s2` (`FLOAT`, Default: 0.2, Range: [0.0, 10.0], Step: 0.001): `s2` Fourier transform scale strength
+
+### Optional Parameters
+
+- `b1_mode` (`COMBO`): Blending modes for `b1` multiplied result.
+ - `['bislerp', 'colorize', 'cosine interp', 'cuberp', 'hslerp', 'inject', 'lerp', 'linear dodge', 'slerp']`
+- `b1_blend` (`FLOAT`, Default: 1.0, Range: [0.0, 100], Step: 0.001): Blending strength for `b1`.
+- `b2_mode` (`COMBO`): Blending modes for `b2` multiplied result.
+ - `['bislerp', 'colorize', 'cosine interp', 'cuberp', 'hslerp', 'inject', 'lerp', 'linear dodge', 'slerp']`
+- `b2_blend` (`FLOAT`, Default: 1.0, Range: [0.0, 100], Step: 0.001): Blending strength for `b2`.
+- `threshold` (`INT`, Default: 1.0, Range: [1, 10], Step: 1): The exposed threshold value of the Fourier transform function.
+- `use_override_scales` (`COMBO`): "true", or "false" on whether to use `override_scales`
+- `override_scales` (`STRING`, Default: [Multiline String]): Override scales. Create custom scales and experiment with results.
+ - Example `10, 1.5` would create the `multiscale_mode` effect `Sharpen`
+ - You can use `#`, `//` and `!` to comment out lines.
+
+### FreeU BibTex
+ ```
+@article{Si2023FreeU,
+ author = {Chenyang Si, Ziqi Huang, Yuming Jiang, Ziwei Liu},
+ title = {FreeU: Free Lunch in Diffusion U-Net},
+ journal = {arXiv},
+ year = {2023},
+}
+```
+## :newspaper_roll: License
+
+Distributed under the MIT License. See `LICENSE` for more information.
diff --git a/custom_nodes/FreeU_Advanced/__init__.py b/custom_nodes/FreeU_Advanced/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d721463be66961a2f388b3a756760d167ea5d510
--- /dev/null
+++ b/custom_nodes/FreeU_Advanced/__init__.py
@@ -0,0 +1,3 @@
+from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
+
+__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
\ No newline at end of file
diff --git a/custom_nodes/FreeU_Advanced/__pycache__/__init__.cpython-311.pyc b/custom_nodes/FreeU_Advanced/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..65e795f44a44b99960a89b54b1a4661ec01ede8c
Binary files /dev/null and b/custom_nodes/FreeU_Advanced/__pycache__/__init__.cpython-311.pyc differ
diff --git a/custom_nodes/FreeU_Advanced/__pycache__/nodes.cpython-311.pyc b/custom_nodes/FreeU_Advanced/__pycache__/nodes.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..336536a6a1e0cb1b859353a881042fc143e3c0c3
Binary files /dev/null and b/custom_nodes/FreeU_Advanced/__pycache__/nodes.cpython-311.pyc differ
diff --git a/custom_nodes/FreeU_Advanced/nodes.py b/custom_nodes/FreeU_Advanced/nodes.py
new file mode 100644
index 0000000000000000000000000000000000000000..11bcc9f9bb6a5bba60fc4685ddcc2c7cb1c594ca
--- /dev/null
+++ b/custom_nodes/FreeU_Advanced/nodes.py
@@ -0,0 +1,375 @@
+#code originally taken from: https://github.com/ChenyangSi/FreeU (under MIT License)
+
+import torch
+import torch as th
+import torch.fft as fft
+import torch.nn.functional as F
+import math
+
+def normalize(latent, target_min=None, target_max=None):
+ """
+ Normalize a tensor `latent` between `target_min` and `target_max`.
+
+ Args:
+ latent (torch.Tensor): The input tensor to be normalized.
+ target_min (float, optional): The minimum value after normalization.
+ - When `None` min will be tensor min range value.
+ target_max (float, optional): The maximum value after normalization.
+ - When `None` max will be tensor max range value.
+
+ Returns:
+ torch.Tensor: The normalized tensor
+ """
+ min_val = latent.min()
+ max_val = latent.max()
+
+ if target_min is None:
+ target_min = min_val
+ if target_max is None:
+ target_max = max_val
+
+ normalized = (latent - min_val) / (max_val - min_val)
+ scaled = normalized * (target_max - target_min) + target_min
+ return scaled
+
+def hslerp(a, b, t):
+ """
+ Perform Hybrid Spherical Linear Interpolation (HSLERP) between two tensors.
+
+ This function combines two input tensors `a` and `b` using HSLERP, which is a specialized
+ interpolation method for smooth transitions between orientations or colors.
+
+ Args:
+ a (tensor): The first input tensor.
+ b (tensor): The second input tensor.
+ t (float): The blending factor, a value between 0 and 1 that controls the interpolation.
+
+ Returns:
+ tensor: The result of HSLERP interpolation between `a` and `b`.
+
+ Note:
+ HSLERP provides smooth transitions between orientations or colors, particularly useful
+ in applications like image processing and 3D graphics.
+ """
+ if a.shape != b.shape:
+ raise ValueError("Input tensors a and b must have the same shape.")
+
+ num_channels = a.size(1)
+
+ interpolation_tensor = torch.zeros(1, num_channels, 1, 1, device=a.device, dtype=a.dtype)
+ interpolation_tensor[0, 0, 0, 0] = 1.0
+
+ result = (1 - t) * a + t * b
+
+ if t < 0.5:
+ result += (torch.norm(b - a, dim=1, keepdim=True) / 6) * interpolation_tensor
+ else:
+ result -= (torch.norm(b - a, dim=1, keepdim=True) / 6) * interpolation_tensor
+
+ return result
+
+blending_modes = {
+ # Args:
+ # - a (tensor): Latent input 1
+ # - b (tensor): Latent input 2
+ # - t (float): Blending factor
+
+ # Interpolates between tensors a and b using normalized linear interpolation.
+ 'bislerp': lambda a, b, t: normalize((1 - t) * a + t * b),
+ # Transfer the color from `b` to `a` by t` factor
+ 'colorize': lambda a, b, t: a + (b - a) * t,
+ # Interpolates between tensors a and b using cosine interpolation.
+ 'cosine interp': lambda a, b, t: (a + b - (a - b) * torch.cos(t * torch.tensor(math.pi))) / 2,
+ # Interpolates between tensors a and b using cubic interpolation.
+ 'cuberp': lambda a, b, t: a + (b - a) * (3 * t ** 2 - 2 * t ** 3),
+ # Interpolates between tensors a and b using normalized linear interpolation,
+ # with a twist when t is greater than or equal to 0.5.
+ 'hslerp': hslerp,
+ # Adds tensor b to tensor a, scaled by t.
+ 'inject': lambda a, b, t: a + b * t,
+ # Interpolates between tensors a and b using linear interpolation.
+ 'lerp': lambda a, b, t: (1 - t) * a + t * b,
+ # Simulates a brightening effect by adding tensor b to tensor a, scaled by t.
+ 'linear dodge': lambda a, b, t: normalize(a + b * t),
+}
+
+mscales = {
+ "Default": None,
+ "Bandpass": [
+ (5, 0.0), # Low-pass filter
+ (15, 1.0), # Pass-through filter (allows mid-range frequencies)
+ (25, 0.0), # High-pass filter
+ ],
+ "Low-Pass": [
+ (10, 1.0), # Allows low-frequency components, suppresses high-frequency components
+ ],
+ "High-Pass": [
+ (10, 0.0), # Suppresses low-frequency components, allows high-frequency components
+ ],
+ "Pass-Through": [
+ (10, 1.0), # Passes all frequencies unchanged, no filtering
+ ],
+ "Gaussian-Blur": [
+ (10, 0.5), # Blurs the image by allowing a range of frequencies with a Gaussian shape
+ ],
+ "Edge-Enhancement": [
+ (10, 2.0), # Enhances edges and high-frequency features while suppressing low-frequency details
+ ],
+ "Sharpen": [
+ (10, 1.5), # Increases the sharpness of the image by emphasizing high-frequency components
+ ],
+ "Multi-Bandpass": [
+ [(5, 0.0), (15, 1.0), (25, 0.0)], # Multi-scale bandpass filter
+ ],
+ "Multi-Low-Pass": [
+ [(5, 1.0), (10, 0.5), (15, 0.2)], # Multi-scale low-pass filter
+ ],
+ "Multi-High-Pass": [
+ [(5, 0.0), (10, 0.5), (15, 0.8)], # Multi-scale high-pass filter
+ ],
+ "Multi-Pass-Through": [
+ [(5, 1.0), (10, 1.0), (15, 1.0)], # Pass-through at different scales
+ ],
+ "Multi-Gaussian-Blur": [
+ [(5, 0.5), (10, 0.8), (15, 0.2)], # Multi-scale Gaussian blur
+ ],
+ "Multi-Edge-Enhancement": [
+ [(5, 1.2), (10, 1.5), (15, 2.0)], # Multi-scale edge enhancement
+ ],
+ "Multi-Sharpen": [
+ [(5, 1.5), (10, 2.0), (15, 2.5)], # Multi-scale sharpening
+ ],
+}
+
+# forward function from comfy.ldm.modules.diuffusionmodules.openaimodel
+# Hopefully temporary replacement
+def __temp__forward(self, x, timesteps=None, context=None, y=None, control=None, transformer_options={}, **kwargs):
+ """
+ Apply the model to an input batch.
+ :param x: an [N x C x ...] Tensor of inputs.
+ :param timesteps: a 1-D batch of timesteps.
+ :param context: conditioning plugged in via crossattn
+ :param y: an [N] Tensor of labels, if class-conditional.
+ :return: an [N x C x ...] Tensor of outputs.
+ """
+ transformer_options["original_shape"] = list(x.shape)
+ transformer_options["current_index"] = 0
+ transformer_patches = transformer_options.get("patches", {})
+
+ assert (y is not None) == (
+ self.num_classes is not None
+ ), "must specify y if and only if the model is class-conditional"
+ hs = []
+ t_emb = timestep_embedding(timesteps, self.model_channels, repeat_only=False).to(self.dtype)
+ emb = self.time_embed(t_emb)
+
+ if self.num_classes is not None:
+ assert y.shape[0] == x.shape[0]
+ emb = emb + self.label_emb(y)
+
+ h = x.type(self.dtype)
+ for id, module in enumerate(self.input_blocks):
+ transformer_options["block"] = ("input", id)
+ h = forward_timestep_embed(module, h, emb, context, transformer_options)
+ if control is not None and 'input' in control and len(control['input']) > 0:
+ ctrl = control['input'].pop()
+ if ctrl is not None:
+ h += ctrl
+ hs.append(h)
+
+ hsp = hs
+ if "input_block_patch" in transformer_patches:
+ patch = transformer_patches["input_block_patch"]
+ for p in patch:
+ h, hsp = p(h, hsp, transformer_options)
+ del hsp
+
+ transformer_options["block"] = ("middle", 0)
+ h = forward_timestep_embed(self.middle_block, h, emb, context, transformer_options)
+ if control is not None and 'middle' in control and len(control['middle']) > 0:
+ ctrl = control['middle'].pop()
+ if ctrl is not None:
+ h += ctrl
+
+ hsp = [h]
+ if "middle_block_patch" in transformer_patches:
+ patch = transformer_patches["middle_block_patch"]
+ for p in patch:
+ h, hsp = p(h, hsp, transformer_options)
+ del hsp
+
+ for id, module in enumerate(self.output_blocks):
+ transformer_options["block"] = ("output", id)
+ hsp = hs.pop()
+ if control is not None and 'output' in control and len(control['output']) > 0:
+ ctrl = control['output'].pop()
+ if ctrl is not None:
+ hsp += ctrl
+
+ if "output_block_patch" in transformer_patches:
+ patch = transformer_patches["output_block_patch"]
+ for p in patch:
+ h, hsp = p(h, hsp, transformer_options)
+
+ h = th.cat([h, hsp], dim=1)
+ del hsp
+ if len(hs) > 0:
+ output_shape = hs[-1].shape
+ else:
+ output_shape = None
+ h = forward_timestep_embed(module, h, emb, context, transformer_options, output_shape)
+ h = h.type(x.dtype)
+ if self.predict_codebook_ids:
+ return self.id_predictor(h)
+ else:
+ return self.out(h)
+
+print("Patching UNetModel.forward")
+import comfy.ldm.modules.diffusionmodules.openaimodel
+from comfy.ldm.modules.diffusionmodules.openaimodel import forward_timestep_embed
+from comfy.ldm.modules.diffusionmodules.util import timestep_embedding
+comfy.ldm.modules.diffusionmodules.openaimodel.UNetModel.forward = __temp__forward
+if comfy.ldm.modules.diffusionmodules.openaimodel.UNetModel.forward is __temp__forward:
+ print("UNetModel.forward has been successfully patched.")
+else:
+ print("UNetModel.forward patching failed.")
+
+def Fourier_filter(x, threshold, scale, scales=None, strength=1.0):
+ # FFT
+ if isinstance(x, list):
+ x = x[0]
+ if isinstance(x, torch.Tensor):
+ x_freq = fft.fftn(x.float(), dim=(-2, -1))
+ x_freq = fft.fftshift(x_freq, dim=(-2, -1))
+
+ B, C, H, W = x_freq.shape
+ mask = torch.ones((B, C, H, W), device=x.device)
+
+ crow, ccol = H // 2, W // 2
+ mask[..., crow - threshold:crow + threshold, ccol - threshold:ccol + threshold] = scale
+
+ if scales is not None:
+ if isinstance(scales[0], tuple):
+ # Single-scale mode
+ for scale_params in scales:
+ if len(scale_params) == 2:
+ scale_threshold, scale_value = scale_params
+ scaled_scale_value = scale_value * strength
+ scale_mask = torch.ones((B, C, H, W), device=x.device)
+ scale_mask[..., crow - scale_threshold:crow + scale_threshold, ccol - scale_threshold:ccol + scale_threshold] = scaled_scale_value
+ mask = mask + (scale_mask - mask) * strength
+ else:
+ # Multi-scale mode
+ for scale_params in scales:
+ if isinstance(scale_params, list):
+ for scale_tuple in scale_params:
+ if len(scale_tuple) == 2:
+ scale_threshold, scale_value = scale_tuple
+ scaled_scale_value = scale_value * strength
+ scale_mask = torch.ones((B, C, H, W), device=x.device)
+ scale_mask[..., crow - scale_threshold:crow + scale_threshold, ccol - scale_threshold:ccol + scale_threshold] = scaled_scale_value
+ mask = mask + (scale_mask - mask) * strength
+
+ x_freq = x_freq * mask
+
+ # IFFT
+ x_freq = fft.ifftshift(x_freq, dim=(-2, -1))
+ x_filtered = fft.ifftn(x_freq, dim=(-2, -1)).real
+
+ return x_filtered.to(x.dtype)
+
+ return x
+
+class WAS_FreeU:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {
+ "model": ("MODEL",),
+ "target_block": (["output_block", "middle_block", "input_block", "all"],),
+ "multiscale_mode": (list(mscales.keys()),),
+ "multiscale_strength": ("FLOAT", {"default": 1.0, "max": 1.0, "min": 0, "step": 0.001}),
+ "slice_b1": ("INT", {"default": 640, "min": 64, "max": 1280, "step": 1}),
+ "slice_b2": ("INT", {"default": 320, "min": 64, "max": 640, "step": 1}),
+ "b1": ("FLOAT", {"default": 1.1, "min": 0.0, "max": 10.0, "step": 0.001}),
+ "b2": ("FLOAT", {"default": 1.2, "min": 0.0, "max": 10.0, "step": 0.001}),
+ "s1": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 10.0, "step": 0.001}),
+ "s2": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 10.0, "step": 0.001}),
+ },
+ "optional": {
+ "b1_mode": (list(blending_modes.keys()),),
+ "b1_blend": ("FLOAT", {"default": 1.0, "max": 100, "min": 0, "step": 0.001}),
+ "b2_mode": (list(blending_modes.keys()),),
+ "b2_blend": ("FLOAT", {"default": 1.0, "max": 100, "min": 0, "step": 0.001}),
+ "threshold": ("INT", {"default": 1.0, "max": 10, "min": 1, "step": 1}),
+ "use_override_scales": (["false", "true"],),
+ "override_scales": ("STRING", {"default": '''# OVERRIDE SCALES
+
+# Sharpen
+# 10, 1.5''', "multiline": True}),
+ }
+ }
+
+ RETURN_TYPES = ("MODEL",)
+ FUNCTION = "patch"
+
+ CATEGORY = "_for_testing"
+
+ def patch(self, model, target_block, multiscale_mode, multiscale_strength, slice_b1, slice_b2, b1, b2, s1, s2, b1_mode="add", b1_blend=1.0, b2_mode="add", b2_blend=1.0, threshold=1.0, use_override_scales="false", override_scales=""):
+
+ min_slice = 64
+ max_slice_b1 = 1280
+ max_slice_b2 = 640
+ slice_b1 = max(min(max_slice_b1, slice_b1), min_slice)
+ slice_b2 = max(min(min(slice_b1, max_slice_b2), slice_b2), min_slice)
+
+ scales_list = []
+ if use_override_scales == "true":
+ if override_scales.strip() != "":
+ scales_str = override_scales.strip().splitlines()
+ for line in scales_str:
+ if not line.strip().startswith('#') and not line.strip().startswith('!') and not line.strip().startswith('//'):
+ scale_values = line.split(',')
+ if len(scale_values) == 2:
+ scales_list.append((int(scale_values[0]), float(scale_values[1])))
+
+ if use_override_scales == "true" and not scales_list:
+ print("No valid override scales found. Using default scale.")
+ scales_list = None
+
+ scales = mscales[multiscale_mode] if use_override_scales == "false" else scales_list
+
+ print(f"FreeU Plate Portions: {slice_b1} over {slice_b2}")
+ print(f"FreeU Multi-Scales: {scales}")
+
+ def block_patch(h, hsp, transformer_options):
+ if h.shape[1] == 1280:
+ h_t = h[:,:slice_b1]
+ h_r = h_t * b1
+ h[:,:slice_b1] = blending_modes[b1_mode](h_t, h_r, b1_blend)
+ hsp = Fourier_filter(hsp, threshold=threshold, scale=s1, scales=scales, strength=multiscale_strength)
+ if h.shape[1] == 640:
+ h_t = h[:,:slice_b2]
+ h_r = h_t * b2
+ h[:,:slice_b2] = blending_modes[b2_mode](h_t, h_r, b2_blend)
+ hsp = Fourier_filter(hsp, threshold=threshold, scale=s2, scales=scales, strength=multiscale_strength)
+ return h, hsp
+
+ print(f"Patching {target_block}")
+
+ m = model.clone()
+ if target_block == "all" or target_block == "output_block":
+ m.set_model_output_block_patch(block_patch)
+ if target_block == "all" or target_block == "input_block":
+ m.set_model_patch(block_patch, "input_block_patch")
+ if target_block == "all" or target_block == "middle_block":
+ m.set_model_patch(block_patch, "middle_block_patch")
+ return (m, )
+
+
+NODE_CLASS_MAPPINGS = {
+ "FreeU (Advanced)": WAS_FreeU,
+}
+
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "FreeU (Advanced)": "FreeU (Advanced Plus)",
+}
diff --git a/custom_nodes/IPAdapter-ComfyUI/LICENSE b/custom_nodes/IPAdapter-ComfyUI/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..f288702d2fa16d3cdf0035b15a9fcbc552cd88e7
--- /dev/null
+++ b/custom_nodes/IPAdapter-ComfyUI/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/custom_nodes/IPAdapter-ComfyUI/README.md b/custom_nodes/IPAdapter-ComfyUI/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..de1354e942fa93a662923a4fa0d18cdb109ce093
--- /dev/null
+++ b/custom_nodes/IPAdapter-ComfyUI/README.md
@@ -0,0 +1,49 @@
+> [!IMPORTANT]
+> **I decided to move my development to the better [cubiq's repository](https://github.com/cubiq/ComfyUI_IPAdapter_plus).**
+>
+> **This repository may not be available anymore due to future updates of ComfyUI.**
+
+
+
+# IPAdapter-ComfyUI
+[IP-Adapter](https://github.com/tencent-ailab/IP-Adapter)の[ComfyUI](https://github.com/comfyanonymous/ComfyUI)カスタムノードです。
+
+# Install
+
+1. custom_nodesにclone
+2. `IPAdapter-ComfyUI/models`にip-adapterのモデル(例:[SDv1.5用モデル](https://huggingface.co/h94/IP-Adapter/blob/main/models/ip-adapter_sd15.bin))を入れる。
+3. `ComfyUI/models/clip_vision`にCLIP_visionモデル(例:[SDv1.5用モデル](https://huggingface.co/h94/IP-Adapter/blob/main/models/image_encoder/pytorch_model.bin))を入れる。
+
+# Usage
+`ip-adapter.json`を参照してください。
+
+## Input
++ **model**:modelをつなげてください。LoRALoaderなどとつなげる順番の違いについては影響ありません。
++ **image**:画像をつなげてください。
++ **clip_vision**:`Load CLIP Vision`の出力とつなげてください。
++ **mask**:任意です。マスクをつなげると適用領域を制限できます。必ず生成画像と同じ解像度にしてください。
++ **weight**:適用強度です。
++ **model_name**:使うモデルのファイル名を指定してください。
++ **dtype**:黒い画像が生成される場合、`fp32`を選択してください。ほとんど生成時間が変わらないのでずっと`fp32`のままでもよいかもしれません。
+
+## Output
++ **MODEL**:KSampler等につなげてください。
++ **CLIP_VISION_OUTPUT**:ふつうは気にしなくていいです。Revision等を使うときに無駄な計算を省くことができます。
+
+## Multiple condition.
+ノードを自然につなげることで、複数画像を入力することができます。Maskと組み合わせることで、左右で条件付けを分けるみたいなこともできます。
+
+背景も分割されてしまうことが問題ですね^^;
+
+# Hint
++ 入力画像は自動で中央切り抜きによって正方形にされるので、避けたい場合は予め切り取り処理をするか、`preprocess/furusu Image crop`を使うとよいかもしれません。`preprocess/furusu Image crop`にはパディングをする`padding`とキャラの顔位置を基準に切り取りをする`face_crop`があります。`face_crop`に必要な[lbpcascade_animeface.xml](https://github.com/nagadomi/lbpcascade_animefacehttps://github.com/nagadomi/lbpcascade_animeface)は自動ダウンロードできない場合があるので、その場合は手動でリポジトリ直下に入れてください。
+
+# Bug
++ ~~Apply ControlNetはなぜかバグるので、代わりにApply ControlNet(Advanced)を使ってください。~~ 多分治った。
+
+# Models
++ official models:https://huggingface.co/h94/IP-Adapter
++ my models:https:https://huggingface.co/furusu/IP-Adapter
+
+# CITIATION
+IP-Adapter:https://github.com/tencent-ailab/IP-Adapter
diff --git a/custom_nodes/IPAdapter-ComfyUI/__init__.py b/custom_nodes/IPAdapter-ComfyUI/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..c6926c0fb40c0c8a537d32a43876bda2bef79167
--- /dev/null
+++ b/custom_nodes/IPAdapter-ComfyUI/__init__.py
@@ -0,0 +1,14 @@
+from .image_preprocessor import ImageCrop
+from .ip_adapter import IPAdapter
+
+NODE_CLASS_MAPPINGS = {
+ "IPAdapter": IPAdapter,
+ "ImageCrop": ImageCrop,
+}
+
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "IPAdapter": "Load IPAdapter",
+ "ImageCrop": "furusu Image Crop",
+}
+
+__all__ = [NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS]
\ No newline at end of file
diff --git a/custom_nodes/IPAdapter-ComfyUI/__pycache__/__init__.cpython-311.pyc b/custom_nodes/IPAdapter-ComfyUI/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..3fcc054baacdc178db1d36a1a92925308c6cb75c
Binary files /dev/null and b/custom_nodes/IPAdapter-ComfyUI/__pycache__/__init__.cpython-311.pyc differ
diff --git a/custom_nodes/IPAdapter-ComfyUI/__pycache__/image_preprocessor.cpython-311.pyc b/custom_nodes/IPAdapter-ComfyUI/__pycache__/image_preprocessor.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c3049c8b68749733ca3854476f6bd2eae29d9513
Binary files /dev/null and b/custom_nodes/IPAdapter-ComfyUI/__pycache__/image_preprocessor.cpython-311.pyc differ
diff --git a/custom_nodes/IPAdapter-ComfyUI/__pycache__/ip_adapter.cpython-311.pyc b/custom_nodes/IPAdapter-ComfyUI/__pycache__/ip_adapter.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..19bec6dd2bdcfcf04fc0f847005569f04f7f0b9c
Binary files /dev/null and b/custom_nodes/IPAdapter-ComfyUI/__pycache__/ip_adapter.cpython-311.pyc differ
diff --git a/custom_nodes/IPAdapter-ComfyUI/__pycache__/resampler.cpython-311.pyc b/custom_nodes/IPAdapter-ComfyUI/__pycache__/resampler.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..84011c77c60bef9415f97b1f62a622842fd4a03f
Binary files /dev/null and b/custom_nodes/IPAdapter-ComfyUI/__pycache__/resampler.cpython-311.pyc differ
diff --git a/custom_nodes/IPAdapter-ComfyUI/image_preprocessor.py b/custom_nodes/IPAdapter-ComfyUI/image_preprocessor.py
new file mode 100644
index 0000000000000000000000000000000000000000..31c509cf48e144119cb713d82f40767696c341a2
--- /dev/null
+++ b/custom_nodes/IPAdapter-ComfyUI/image_preprocessor.py
@@ -0,0 +1,92 @@
+import torch
+import torch.nn.functional as F
+import os
+import subprocess
+
+CV2_AVAILABLE = True
+try:
+ import cv2
+except:
+ print("OpenCV is not installed so face cropping is not available.")
+ CV2_AVAILABLE = False
+
+CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
+DETECTOR_FILE = "lbpcascade_animeface.xml"
+
+if not os.path.exists(os.path.join(CURRENT_DIR, DETECTOR_FILE)):
+ print("Downloading anime face detector...")
+ try:
+ subprocess.run(["wget", "https://raw.githubusercontent.com/nagadomi/lbpcascade_animeface/master/lbpcascade_animeface.xml", "-P", CURRENT_DIR])
+ except:
+ print(f"Failed to download lbpcascade_animeface.xml so please download it in {CURRENT_DIR}.")
+ CV2_AVAILABLE = False
+
+CROP_MODES = ["padding", "face_crop", "none"] if CV2_AVAILABLE else ["padding", "none"]
+
+def image_to_numpy(image):
+ image = image.squeeze(0) * 255
+ return image.numpy().astype("uint8")
+
+def numpy_to_image(image):
+ image = torch.tensor(image).float() / 255
+ return image.unsqueeze(0)
+
+def pad_to_square(tensor):
+ tensor = tensor.squeeze(0).permute(2, 0, 1)
+ _, h, w = tensor.shape
+
+ target_length = max(h, w)
+
+ pad_l = (target_length - w) // 2
+ pad_r = (target_length - w) - pad_l
+
+ pad_t = (target_length - h) // 2
+ pad_b = (target_length - h) - pad_t
+
+ padded_tensor = F.pad(tensor, (pad_l, pad_r, pad_t, pad_b), mode="constant", value=0)
+
+ return padded_tensor.permute(1, 2, 0).unsqueeze(0)
+
+def face_crop(image):
+ image = image_to_numpy(image)
+ face_cascade = cv2.CascadeClassifier(os.path.join(CURRENT_DIR, DETECTOR_FILE))
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
+ faces = face_cascade.detectMultiScale(gray, 1.3, 5)
+
+ w, h = image.shape[1], image.shape[0]
+
+ target_length = min(w, h)
+ fx, fy, fw, fh = (0, 0, w, h) if len(faces) == 0 else faces[0]
+
+ dx = target_length - fw // 2
+ dy = target_length - fh // 2
+
+ target_x = 0 if w < h else max(0, fx - dx)
+ target_y = 0 if w > h else max(0, fy - dy)
+
+ image = image[target_y:target_y+target_length, target_x:target_x+target_length]
+ image = numpy_to_image(image)
+
+ return image
+
+class ImageCrop:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "image": ("IMAGE", ),
+ "mode": (CROP_MODES, ),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "preprocess"
+ CATEGORY = "image/preprocessors"
+
+ def preprocess(self, image, mode):
+ if mode == "padding":
+ image = pad_to_square(image)
+ elif mode == "face_crop":
+ image = face_crop(image)
+
+ return (image,)
\ No newline at end of file
diff --git a/custom_nodes/IPAdapter-ComfyUI/ip-adapter.json b/custom_nodes/IPAdapter-ComfyUI/ip-adapter.json
new file mode 100644
index 0000000000000000000000000000000000000000..1bd0a7595b80582ff661224cab1fc59ac724f3f6
--- /dev/null
+++ b/custom_nodes/IPAdapter-ComfyUI/ip-adapter.json
@@ -0,0 +1,523 @@
+{
+ "last_node_id": 33,
+ "last_link_id": 78,
+ "nodes": [
+ {
+ "id": 8,
+ "type": "VAEDecode",
+ "pos": [
+ 1260,
+ 190
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 7
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 8
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 9
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 6,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 434,
+ 190
+ ],
+ "size": {
+ "0": 407.8851318359375,
+ "1": 84.13611602783203
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 3
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 4
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "masterpiece best quality, 1girl"
+ ]
+ },
+ {
+ "id": 7,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 440,
+ 320
+ ],
+ "size": {
+ "0": 421.13385009765625,
+ "1": 108.7844009399414
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 5
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 6
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "low quality,jpeg artifacts,signature,watermark,username,blurry,missing fingers,missing arms,Humpbacked,shadow\n"
+ ]
+ },
+ {
+ "id": 4,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ 32,
+ 188
+ ],
+ "size": {
+ "0": 315,
+ "1": 98
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 75
+ ],
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 3,
+ 5
+ ],
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 8
+ ],
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "Counterfeit-V3.0_fp16.safetensors"
+ ]
+ },
+ {
+ "id": 11,
+ "type": "LoadImage",
+ "pos": [
+ 30,
+ 491
+ ],
+ "size": {
+ "0": 366.6781005859375,
+ "1": 583.0964965820312
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 76
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "segsegs (1).png",
+ "image"
+ ]
+ },
+ {
+ "id": 13,
+ "type": "CLIPVisionLoader",
+ "pos": [
+ 31,
+ 358
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "CLIP_VISION",
+ "type": "CLIP_VISION",
+ "links": [
+ 74
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPVisionLoader"
+ },
+ "widgets_values": [
+ "pytorch_model.fp16.bin"
+ ]
+ },
+ {
+ "id": 33,
+ "type": "IPAdapter",
+ "pos": [
+ 446,
+ 483
+ ],
+ "size": [
+ 415.49357104492185,
+ 170.70820529174807
+ ],
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 75
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 76
+ },
+ {
+ "name": "clip_vision",
+ "type": "CLIP_VISION",
+ "link": 74
+ },
+ {
+ "name": "mask",
+ "type": "MASK",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 78
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP_VISION_OUTPUT",
+ "type": "CLIP_VISION_OUTPUT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "IPAdapter"
+ },
+ "widgets_values": [
+ 1,
+ "ip-adapter-plus_sd15.bin",
+ "fp16"
+ ]
+ },
+ {
+ "id": 5,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 900,
+ 193
+ ],
+ "size": {
+ "0": 315,
+ "1": 106
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 2
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 640,
+ 896,
+ 1
+ ]
+ },
+ {
+ "id": 9,
+ "type": "SaveImage",
+ "pos": [
+ 1263,
+ 295
+ ],
+ "size": [
+ 686.0428685974125,
+ 987.6802014801028
+ ],
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 9
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ "ComfyUI"
+ ]
+ },
+ {
+ "id": 3,
+ "type": "KSampler",
+ "pos": [
+ 897,
+ 353
+ ],
+ "size": {
+ "0": 310.4039306640625,
+ "1": 474
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 78
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 4
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 6
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 2
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 7
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 4624,
+ "increment",
+ 30,
+ 7,
+ "euler",
+ "normal",
+ 1
+ ]
+ }
+ ],
+ "links": [
+ [
+ 2,
+ 5,
+ 0,
+ 3,
+ 3,
+ "LATENT"
+ ],
+ [
+ 3,
+ 4,
+ 1,
+ 6,
+ 0,
+ "CLIP"
+ ],
+ [
+ 4,
+ 6,
+ 0,
+ 3,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 5,
+ 4,
+ 1,
+ 7,
+ 0,
+ "CLIP"
+ ],
+ [
+ 6,
+ 7,
+ 0,
+ 3,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 7,
+ 3,
+ 0,
+ 8,
+ 0,
+ "LATENT"
+ ],
+ [
+ 8,
+ 4,
+ 2,
+ 8,
+ 1,
+ "VAE"
+ ],
+ [
+ 9,
+ 8,
+ 0,
+ 9,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 74,
+ 13,
+ 0,
+ 33,
+ 2,
+ "CLIP_VISION"
+ ],
+ [
+ 75,
+ 4,
+ 0,
+ 33,
+ 0,
+ "MODEL"
+ ],
+ [
+ 76,
+ 11,
+ 0,
+ 33,
+ 1,
+ "IMAGE"
+ ],
+ [
+ 78,
+ 33,
+ 0,
+ 3,
+ 0,
+ "MODEL"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/IPAdapter-ComfyUI/ip_adapter.py b/custom_nodes/IPAdapter-ComfyUI/ip_adapter.py
new file mode 100644
index 0000000000000000000000000000000000000000..24602c602d3858793f0b208a4c06e6614b0c4767
--- /dev/null
+++ b/custom_nodes/IPAdapter-ComfyUI/ip_adapter.py
@@ -0,0 +1,319 @@
+import torch
+import os
+from .resampler import Resampler
+
+import contextlib
+import comfy.model_management
+from comfy.ldm.modules.attention import optimized_attention
+from comfy.clip_vision import clip_preprocess
+
+CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
+
+# attention_channels of input, output, middle
+SD_V12_CHANNELS = [320] * 4 + [640] * 4 + [1280] * 4 + [1280] * 6 + [640] * 6 + [320] * 6 + [1280] * 2
+SD_XL_CHANNELS = [640] * 8 + [1280] * 40 + [1280] * 60 + [640] * 12 + [1280] * 20
+
+def get_file_list(path):
+ return [f for f in os.listdir(path) if f.endswith('.bin') or f.endswith('.safetensors')]
+
+def set_model_patch_replace(model, patch_kwargs, key):
+ to = model.model_options["transformer_options"]
+ if "patches_replace" not in to:
+ to["patches_replace"] = {}
+ if "attn2" not in to["patches_replace"]:
+ to["patches_replace"]["attn2"] = {}
+ if key not in to["patches_replace"]["attn2"]:
+ patch = CrossAttentionPatch(**patch_kwargs)
+ to["patches_replace"]["attn2"][key] = patch
+ else:
+ to["patches_replace"]["attn2"][key].set_new_condition(**patch_kwargs)
+
+def load_ipadapter(ckpt_path):
+ model = comfy.utils.load_torch_file(ckpt_path, safe_load=True)
+
+ if ckpt_path.lower().endswith(".safetensors"):
+ st_model = {"image_proj": {}, "ip_adapter": {}}
+ for key in model.keys():
+ if key.startswith("image_proj."):
+ st_model["image_proj"][key.replace("image_proj.", "")] = model[key]
+ elif key.startswith("ip_adapter."):
+ st_model["ip_adapter"][key.replace("ip_adapter.", "")] = model[key]
+ # sort keys
+ model = {"image_proj": st_model["image_proj"], "ip_adapter": {}}
+ sorted_keys = sorted(st_model["ip_adapter"].keys(), key=lambda x: int(x.split(".")[0]))
+ for key in sorted_keys:
+ model["ip_adapter"][key] = st_model["ip_adapter"][key]
+ st_model = None
+
+ if not "ip_adapter" in model.keys() or not model["ip_adapter"]:
+ raise Exception("invalid IPAdapter model {}".format(ckpt_path))
+
+ return model
+
+
+class ImageProjModel(torch.nn.Module):
+ """Projection Model"""
+ def __init__(self, cross_attention_dim=1024, clip_embeddings_dim=1024, clip_extra_context_tokens=4):
+ super().__init__()
+
+ self.cross_attention_dim = cross_attention_dim
+ self.clip_extra_context_tokens = clip_extra_context_tokens
+ self.proj = torch.nn.Linear(clip_embeddings_dim, self.clip_extra_context_tokens * cross_attention_dim)
+ self.norm = torch.nn.LayerNorm(cross_attention_dim)
+
+ def forward(self, image_embeds):
+ embeds = image_embeds
+ clip_extra_context_tokens = self.proj(embeds).reshape(-1, self.clip_extra_context_tokens, self.cross_attention_dim)
+ clip_extra_context_tokens = self.norm(clip_extra_context_tokens)
+ return clip_extra_context_tokens
+
+# Cross Attention to_k, to_v for IPAdapter
+class To_KV(torch.nn.Module):
+ def __init__(self, cross_attention_dim):
+ super().__init__()
+
+ channels = SD_XL_CHANNELS if cross_attention_dim == 2048 else SD_V12_CHANNELS
+ self.to_kvs = torch.nn.ModuleList([torch.nn.Linear(cross_attention_dim, channel, bias=False) for channel in channels])
+
+ def load_state_dict(self, state_dict):
+ # input -> output -> middle
+ for i, key in enumerate(state_dict.keys()):
+ self.to_kvs[i].weight.data = state_dict[key]
+
+class IPAdapterModel(torch.nn.Module):
+ def __init__(self, state_dict, plus, cross_attention_dim=768, clip_embeddings_dim=1024, clip_extra_context_tokens=4, sdxl_plus=False):
+ super().__init__()
+ self.plus = plus
+ if self.plus:
+ self.image_proj_model = Resampler(
+ dim=1280 if sdxl_plus else cross_attention_dim,
+ depth=4,
+ dim_head=64,
+ heads=20 if sdxl_plus else 12,
+ num_queries=clip_extra_context_tokens,
+ embedding_dim=clip_embeddings_dim,
+ output_dim=cross_attention_dim,
+ ff_mult=4
+ )
+ else:
+ self.image_proj_model = ImageProjModel(
+ cross_attention_dim=cross_attention_dim,
+ clip_embeddings_dim=clip_embeddings_dim,
+ clip_extra_context_tokens=clip_extra_context_tokens
+ )
+
+ self.image_proj_model.load_state_dict(state_dict["image_proj"])
+ self.ip_layers = To_KV(cross_attention_dim)
+ self.ip_layers.load_state_dict(state_dict["ip_adapter"])
+
+ @torch.inference_mode()
+ def get_image_embeds(self, cond, uncond):
+ image_prompt_embeds = self.image_proj_model(cond)
+ uncond_image_prompt_embeds = self.image_proj_model(uncond)
+ return image_prompt_embeds, uncond_image_prompt_embeds
+
+
+class IPAdapter:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "model": ("MODEL", ),
+ "image": ("IMAGE", ),
+ "clip_vision": ("CLIP_VISION", ),
+ "weight": ("FLOAT", {
+ "default": 1,
+ "min": -1, #Minimum value
+ "max": 3, #Maximum value
+ "step": 0.05 #Slider's step
+ }),
+ "model_name": (get_file_list(os.path.join(CURRENT_DIR,"models")), ),
+ "dtype": (["fp16", "fp32"], ),
+ },
+ "optional": {
+ "mask": ("MASK",),
+ }
+ }
+
+ RETURN_TYPES = ("MODEL", "CLIP_VISION_OUTPUT")
+ FUNCTION = "adapter"
+ CATEGORY = "loaders"
+
+ def adapter(self, model, image, clip_vision, weight, model_name, dtype, mask=None):
+ device = comfy.model_management.get_torch_device()
+ self.dtype = torch.float32 if dtype == "fp32" or device.type == "mps" else torch.float16
+ self.weight = weight # ip_adapter scale
+
+ ip_state_dict = load_ipadapter(os.path.join(CURRENT_DIR, os.path.join(CURRENT_DIR, "models", model_name)))
+ self.plus = "latents" in ip_state_dict["image_proj"]
+
+ # cross_attention_dim is equal to text_encoder output
+ self.cross_attention_dim = ip_state_dict["ip_adapter"]["1.to_k_ip.weight"].shape[1]
+
+ self.sdxl = self.cross_attention_dim == 2048
+ self.sdxl_plus = self.sdxl and self.plus
+
+ # number of tokens of ip_adapter embedding
+ if self.plus:
+ self.clip_extra_context_tokens = ip_state_dict["image_proj"]["latents"].shape[1]
+ else:
+ self.clip_extra_context_tokens = ip_state_dict["image_proj"]["proj.weight"].shape[0] // self.cross_attention_dim
+
+ cond, uncond, outputs = self.clip_vision_encode(clip_vision, image, self.plus)
+ self.clip_embeddings_dim = cond.shape[-1]
+
+ self.ipadapter = IPAdapterModel(
+ ip_state_dict,
+ plus = self.plus,
+ cross_attention_dim = self.cross_attention_dim,
+ clip_embeddings_dim = self.clip_embeddings_dim,
+ clip_extra_context_tokens = self.clip_extra_context_tokens,
+ sdxl_plus = self.sdxl_plus
+ )
+
+ self.ipadapter.to(device, dtype=self.dtype)
+
+ self.image_emb, self.uncond_image_emb = self.ipadapter.get_image_embeds(cond.to(device, dtype=self.dtype), uncond.to(device, dtype=self.dtype))
+ self.image_emb = self.image_emb.to(device, dtype=self.dtype)
+ self.uncond_image_emb = self.uncond_image_emb.to(device, dtype=self.dtype)
+ # Not sure of batch size at this point.
+ self.cond_uncond_image_emb = None
+
+ new_model = model.clone()
+
+ if mask is not None:
+ mask = mask.squeeze().to(device)
+
+ '''
+ patch_name of sdv1-2: ("input" or "output" or "middle", block_id)
+ patch_name of sdxl: ("input" or "output" or "middle", block_id, transformer_index)
+ '''
+ patch_kwargs = {
+ "number": 0,
+ "weight": self.weight,
+ "ipadapter": self.ipadapter,
+ "dtype": self.dtype,
+ "cond": self.image_emb,
+ "uncond": self.uncond_image_emb,
+ "mask": mask
+ }
+
+ if not self.sdxl:
+ for id in [1,2,4,5,7,8]: # id of input_blocks that have cross attention
+ set_model_patch_replace(new_model, patch_kwargs, ("input", id))
+ patch_kwargs["number"] += 1
+ for id in [3,4,5,6,7,8,9,10,11]: # id of output_blocks that have cross attention
+ set_model_patch_replace(new_model, patch_kwargs, ("output", id))
+ patch_kwargs["number"] += 1
+ set_model_patch_replace(new_model, patch_kwargs, ("middle", 0))
+ else:
+ for id in [4,5,7,8]: # id of input_blocks that have cross attention
+ block_indices = range(2) if id in [4, 5] else range(10) # transformer_depth
+ for index in block_indices:
+ set_model_patch_replace(new_model, patch_kwargs, ("input", id, index))
+ patch_kwargs["number"] += 1
+ for id in range(6): # id of output_blocks that have cross attention
+ block_indices = range(2) if id in [3, 4, 5] else range(10) # transformer_depth
+ for index in block_indices:
+ set_model_patch_replace(new_model, patch_kwargs, ("output", id, index))
+ patch_kwargs["number"] += 1
+ for index in range(10):
+ set_model_patch_replace(new_model, patch_kwargs, ("middle", 0, index))
+ patch_kwargs["number"] += 1
+
+ return (new_model, outputs)
+
+ def clip_vision_encode(self, clip_vision, image, plus=False):
+
+ inputs = clip_preprocess(image)
+ comfy.model_management.load_model_gpu(clip_vision.patcher)
+ pixel_values = inputs.to(clip_vision.load_device)
+
+ if clip_vision.dtype != torch.float32:
+ precision_scope = torch.autocast
+ else:
+ precision_scope = lambda a, b: contextlib.nullcontext(a)
+
+ with precision_scope(comfy.model_management.get_autocast_device(clip_vision.load_device), torch.float32):
+ outputs = clip_vision.model(pixel_values=pixel_values, output_hidden_states=True)
+
+ if plus:
+ cond = outputs.hidden_states[-2]
+ with precision_scope(comfy.model_management.get_autocast_device(clip_vision.load_device), torch.float32):
+ uncond = clip_vision.model(torch.zeros_like(pixel_values), output_hidden_states=True).hidden_states[-2]
+ else:
+ cond = outputs.image_embeds
+ uncond = torch.zeros_like(cond)
+ for k in outputs:
+ t = outputs[k]
+ if k == "hidden_states":
+ outputs[k] = None
+ elif t is not None:
+ outputs[k] = t.cpu()
+ return cond, uncond, outputs
+
+
+class CrossAttentionPatch:
+ # forward for patching
+ def __init__(self, weight, ipadapter, dtype, number, cond, uncond, mask=None):
+ self.weights = [weight]
+ self.ipadapters = [ipadapter]
+ self.conds = [cond]
+ self.unconds = [uncond]
+ self.dtype = dtype
+ self.number = number
+ self.masks = [mask]
+
+ def set_new_condition(self, weight, ipadapter, cond, uncond, dtype, number, mask=None):
+ self.weights.append(weight)
+ self.ipadapters.append(ipadapter)
+ self.conds.append(cond)
+ self.unconds.append(uncond)
+ self.masks.append(mask)
+ self.dtype = dtype
+
+ def __call__(self, n, context_attn2, value_attn2, extra_options):
+ org_dtype = n.dtype
+ cond_or_uncond = extra_options["cond_or_uncond"]
+ original_shape = (extra_options["original_shape"][2], extra_options["original_shape"][3])
+ with torch.autocast("cuda", dtype=self.dtype):
+ q = n
+ k = context_attn2
+ v = value_attn2
+ b, _, _ = q.shape
+ batch_prompt = b // len(cond_or_uncond)
+ out = optimized_attention(q, k, v, extra_options["n_heads"])
+
+ for weight, cond, uncond, ipadapter, mask in zip(self.weights, self.conds, self.unconds, self.ipadapters, self.masks):
+ k_cond = ipadapter.ip_layers.to_kvs[self.number*2](cond).repeat(batch_prompt, 1, 1)
+ k_uncond = ipadapter.ip_layers.to_kvs[self.number*2](uncond).repeat(batch_prompt, 1, 1)
+ v_cond = ipadapter.ip_layers.to_kvs[self.number*2+1](cond).repeat(batch_prompt, 1, 1)
+ v_uncond = ipadapter.ip_layers.to_kvs[self.number*2+1](uncond).repeat(batch_prompt, 1, 1)
+
+ ip_k = torch.cat([(k_cond, k_uncond)[i] for i in cond_or_uncond], dim=0)
+ ip_v = torch.cat([(v_cond, v_uncond)[i] for i in cond_or_uncond], dim=0)
+
+ # Convert ip_k and ip_v to the same dtype as q
+ ip_k = ip_k.to(dtype=q.dtype)
+ ip_v = ip_v.to(dtype=q.dtype)
+
+ ip_out = optimized_attention(q, ip_k, ip_v, extra_options["n_heads"])
+
+ if mask is not None:
+ # 良い方法募集
+ if original_shape[0] * original_shape[1] == q.shape[1]:
+ down_sample_rate = 1
+ elif (original_shape[0] // 2) * (original_shape[1] // 2) == q.shape[1]:
+ down_sample_rate = 2
+ elif (original_shape[0] // 4) * (original_shape[1] // 4) == q.shape[1]:
+ down_sample_rate = 4
+ else:
+ down_sample_rate = 8
+ mask_downsample = torch.nn.functional.interpolate(mask.unsqueeze(0).unsqueeze(0), size=(original_shape[0] // down_sample_rate, original_shape[1] // down_sample_rate), mode="nearest").squeeze(0)
+ mask_downsample = mask_downsample.view(1, -1, 1).repeat(out.shape[0], 1, out.shape[2])
+ ip_out = ip_out * mask_downsample
+
+ out = out + ip_out * weight
+
+ return out.to(dtype=org_dtype)
+
diff --git a/custom_nodes/IPAdapter-ComfyUI/memo.md b/custom_nodes/IPAdapter-ComfyUI/memo.md
new file mode 100644
index 0000000000000000000000000000000000000000..ae20b553890d0e8673caa5975c18353dd918d0a0
--- /dev/null
+++ b/custom_nodes/IPAdapter-ComfyUI/memo.md
@@ -0,0 +1,34 @@
+## plusかどうか
+`state_dict["image_proj"]["lantents"]`の存在で判断
+
+## テキストエンコーダの隠れ状態次元数:
+keyの入力次元で判断
+
+`cross_attention_dim = state_dict["ip_adapter"]["1.to_k_ip.weight"].shape[1]`
+
+## SDXLかどうか
+`sdxl = self.cross_attention_dim == 2048`
+
+## IP-Adapterのトークン数
+plusでない場合image_projの出力次元からcross_attention_dimを割る
+
+`clip_extra_context_tokens = state_dict["image_proj"]["proj.weight"].shape[0] // cross_attention_dim`
+
+plusの場合latentsのトークン数で判断
+
+`self.clip_extra_context_tokens = ip_state_dict["image_proj"]["latents"].shape[1]`
+
+## CLIP特徴量の次元数
+実際の出力で判断
+
+`clip_embeddings_dim = cond.shape[-1]`
+
+## 残り
+plusの場合のresamplerの設定は保留・・・
+
+```
+depth=4
+dim_head=64
+heads=12
+ff_mult=4
+```
diff --git a/custom_nodes/IPAdapter-ComfyUI/models/ip-adapter_sd15.bin b/custom_nodes/IPAdapter-ComfyUI/models/ip-adapter_sd15.bin
new file mode 100644
index 0000000000000000000000000000000000000000..0fb1fd7d32243e0395bcfb03cd6cc2f223857bfb
--- /dev/null
+++ b/custom_nodes/IPAdapter-ComfyUI/models/ip-adapter_sd15.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:68e1df30d760f280e578c302f1e73b37ea08654eff16a31153588047affe0058
+size 44642825
diff --git a/custom_nodes/IPAdapter-ComfyUI/models/put_models_here.txt b/custom_nodes/IPAdapter-ComfyUI/models/put_models_here.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c5aa96ecfe47827191bbf9507e8b68526241f80f
--- /dev/null
+++ b/custom_nodes/IPAdapter-ComfyUI/models/put_models_here.txt
@@ -0,0 +1 @@
+^q^q^
diff --git a/custom_nodes/IPAdapter-ComfyUI/resampler.py b/custom_nodes/IPAdapter-ComfyUI/resampler.py
new file mode 100644
index 0000000000000000000000000000000000000000..4521c8c3e6f17caf4547c3dd84118da760e5179f
--- /dev/null
+++ b/custom_nodes/IPAdapter-ComfyUI/resampler.py
@@ -0,0 +1,121 @@
+# modified from https://github.com/mlfoundations/open_flamingo/blob/main/open_flamingo/src/helpers.py
+import math
+
+import torch
+import torch.nn as nn
+
+
+# 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):
+ """
+ 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)
+
+ 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,
+ ):
+ 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)
\ No newline at end of file
diff --git a/custom_nodes/SD-Latent-Upscaler/.gitignore b/custom_nodes/SD-Latent-Upscaler/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..83bae750d0d71f352cadd85fefb08e70e806af46
--- /dev/null
+++ b/custom_nodes/SD-Latent-Upscaler/.gitignore
@@ -0,0 +1,175 @@
+raw/
+images/
+latent_*/
+vae/
+models/
+other/
+test.py
+*.png
+*.zip
+*.npy
+*.ckpt
+*.safetensors
+
+# default github .gitignore follows
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/#use-with-ide
+.pdm.toml
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
diff --git a/custom_nodes/SD-Latent-Upscaler/LICENSE b/custom_nodes/SD-Latent-Upscaler/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64
--- /dev/null
+++ b/custom_nodes/SD-Latent-Upscaler/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/custom_nodes/SD-Latent-Upscaler/README.md b/custom_nodes/SD-Latent-Upscaler/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..3da81996a7575a4054d3288af65da1da73f6c421
--- /dev/null
+++ b/custom_nodes/SD-Latent-Upscaler/README.md
@@ -0,0 +1,63 @@
+# SD-Latent-Upscaler
+Upscaling stable diffusion latents using a small neural network.
+
+Very similar to my [latent interposer](https://github.com/city96/SD-Latent-Interposer/tree/main), this small model can be used to upscale latents in a way that doesn't ruin the image. I mostly explain some of the issues with upscaling latents in [this issue](https://github.com/city96/SD-Advanced-Noise/issues/1#issuecomment-1678193121). Think of this as an ESRGAN for latents, except severely undertrained.
+
+**Currently, SDXL has some minimal hue shift issues.** Because of course it does.
+
+## Installation
+
+### ComfyUI
+
+To install it, simply clone this repo to your custom_nodes folder using the following command: `git clone https://github.com/city96/SD-Latent-Upscaler custom_nodes/SD-Latent-Upscaler`.
+
+Alternatively, you can download the [comfy_latent_upscaler.py](https://github.com/city96/SD-Latent-Upscaler/blob/main/comfy_latent_upscaler.py) file to your ComfyUI/custom_nodes folder as well. You may need to install hfhub using the command pip install huggingface-hub inside your venv.
+
+If you need the model weights for something else, they are [hosted on HF](https://huggingface.co/city96/SD-Latent-Upscaler/tree/main) under the same Apache2 license as the rest of the repo.
+
+### Auto1111
+
+Currently not supported but it should be possible to use it at the hires-fix part.
+
+### Local models
+
+The node pulls the required files from huggingface hub by default. You can create a `models` folder and place the modules there if you have a flaky connection or prefer to use it completely offline, it will load them locally instead. The path should be: `ComfyUI/custom_nodes/SD-Latent-Upscaler/models`
+
+Alternatively, just clone the entire HF repo to it: `git clone https://huggingface.co/city96/SD-Latent-Upscaler custom_nodes/SD-Latent-Upscaler/models`
+
+### Usage/principle
+
+Usage is fairly simple. You use it anywhere where you would upscale a latent. If you need a higher scale factor (e.g. x4), simply chain two of the upscalers.
+
+
+
+
+
+As part of a workflow - notice how the second stage works despite the low denoise of 0.2. The image remains relatively unchanged.
+
+
+
+## Training
+
+### Upscaler v2.0
+
+I decided to do some more research and change the network architecture alltogether. This one is just a bunch of `Conv2d` layers with an `Upsample` at the beginning, similar to before except I reduced the kernel size/padding and instead added more layers.
+
+Trained for 1M iterations on DIV2K + Flickr2K. I changed to AdamW + L1 loss (from SGD and MSE loss) and added a `OneCycleLR` scheduler.
+
+
+
+### Upscaler v1.0
+
+This version was still relatively undertrained. Mostly a proof-of-concept.
+
+Trained for 1M iterations on DIV2K + Flickr2K.
+
+
+ Loss graphs for v1.0 models
+
+ (Left is training loss, right is validation loss.)
+
+ 
+
+
diff --git a/custom_nodes/SD-Latent-Upscaler/__init__.py b/custom_nodes/SD-Latent-Upscaler/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..9229cd166e866150d6b12a1f350f2861575508d8
--- /dev/null
+++ b/custom_nodes/SD-Latent-Upscaler/__init__.py
@@ -0,0 +1,8 @@
+# only import if running as a custom node
+try:
+ import comfy.utils
+except ImportError:
+ pass
+else:
+ from .comfy_latent_upscaler import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
+ __all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
diff --git a/custom_nodes/SD-Latent-Upscaler/__pycache__/__init__.cpython-311.pyc b/custom_nodes/SD-Latent-Upscaler/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5352a322ea5667dac0ef281f21c4f6e7252ec87c
Binary files /dev/null and b/custom_nodes/SD-Latent-Upscaler/__pycache__/__init__.cpython-311.pyc differ
diff --git a/custom_nodes/SD-Latent-Upscaler/__pycache__/comfy_latent_upscaler.cpython-311.pyc b/custom_nodes/SD-Latent-Upscaler/__pycache__/comfy_latent_upscaler.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..cba7da5022876a47541d6069d514c04ee3e85d6a
Binary files /dev/null and b/custom_nodes/SD-Latent-Upscaler/__pycache__/comfy_latent_upscaler.cpython-311.pyc differ
diff --git a/custom_nodes/SD-Latent-Upscaler/comfy_latent_upscaler.py b/custom_nodes/SD-Latent-Upscaler/comfy_latent_upscaler.py
new file mode 100644
index 0000000000000000000000000000000000000000..e3a4fb40154c41c2b805f045e7613dfd8e417cfd
--- /dev/null
+++ b/custom_nodes/SD-Latent-Upscaler/comfy_latent_upscaler.py
@@ -0,0 +1,105 @@
+import os
+import torch
+import torch.nn as nn
+from safetensors.torch import load_file
+from huggingface_hub import hf_hub_download
+
+
+class Upscaler(nn.Module):
+ """
+ Basic NN layout, ported from:
+ https://github.com/city96/SD-Latent-Upscaler/blob/main/upscaler.py
+ """
+ version = 2.1 # network revision
+ def head(self):
+ return [
+ nn.Conv2d(self.chan, self.size, kernel_size=self.krn, padding=self.pad),
+ nn.ReLU(),
+ nn.Upsample(scale_factor=self.fac, mode="nearest"),
+ nn.ReLU(),
+ ]
+ def core(self):
+ layers = []
+ for _ in range(self.depth):
+ layers += [
+ nn.Conv2d(self.size, self.size, kernel_size=self.krn, padding=self.pad),
+ nn.ReLU(),
+ ]
+ return layers
+ def tail(self):
+ return [
+ nn.Conv2d(self.size, self.chan, kernel_size=self.krn, padding=self.pad),
+ ]
+
+ def __init__(self, fac, depth=16):
+ super().__init__()
+ self.size = 64 # Conv2d size
+ self.chan = 4 # in/out channels
+ self.depth = depth # no. of layers
+ self.fac = fac # scale factor
+ self.krn = 3 # kernel size
+ self.pad = 1 # padding
+
+ self.sequential = nn.Sequential(
+ *self.head(),
+ *self.core(),
+ *self.tail(),
+ )
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ return self.sequential(x)
+
+
+class LatentUpscaler:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "samples": ("LATENT", ),
+ "latent_ver": (["v1", "xl"],),
+ "scale_factor": (["1.25", "1.5", "2.0"],),
+ }
+ }
+
+ RETURN_TYPES = ("LATENT",)
+ FUNCTION = "upscale"
+ CATEGORY = "latent"
+
+ def upscale(self, samples, latent_ver, scale_factor):
+ model = Upscaler(scale_factor)
+ filename = f"latent-upscaler-v{model.version}_SD{latent_ver}-x{scale_factor}.safetensors"
+ local = os.path.join(
+ os.path.join(os.path.dirname(os.path.realpath(__file__)),"models"),
+ filename
+ )
+
+ if os.path.isfile(local):
+ print("LatentUpscaler: Using local model")
+ weights = local
+ else:
+ print("LatentUpscaler: Using HF Hub model")
+ weights = str(hf_hub_download(
+ repo_id="city96/SD-Latent-Upscaler",
+ filename=filename)
+ )
+
+ model.load_state_dict(load_file(weights))
+ lt = samples["samples"]
+ lt = model(lt)
+ del model
+ if "noise_mask" in samples.keys():
+ # expand the noise mask to the same shape as the latent
+ mask = torch.nn.functional.interpolate(samples['noise_mask'], scale_factor=float(scale_factor), mode='bicubic')
+ return ({"samples": lt, "noise_mask": mask},)
+ return ({"samples": lt},)
+
+NODE_CLASS_MAPPINGS = {
+ "LatentUpscaler": LatentUpscaler,
+}
+
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "LatentUpscaler": "Latent Upscaler"
+}
diff --git a/custom_nodes/SD-Latent-Upscaler/log_loss.py b/custom_nodes/SD-Latent-Upscaler/log_loss.py
new file mode 100644
index 0000000000000000000000000000000000000000..a7d12eb87e13fcd8b6bc44cb38c2b923c3452745
--- /dev/null
+++ b/custom_nodes/SD-Latent-Upscaler/log_loss.py
@@ -0,0 +1,48 @@
+import os
+import math
+import matplotlib.pyplot as plt
+
+files = [f"models/{x}" for x in os.listdir("models") if x.endswith(".csv")]
+train_loss = {}
+eval_loss = {}
+
+def process_lines(lines):
+ global train_loss
+ global eval_loss
+ name = fp.split("/")[1]
+ vals = [x.split(",") for x in lines]
+ train_loss[name] = (
+ [int(x[0]) for x in vals],
+ [math.log(float(x[1])) for x in vals],
+ )
+ if len(vals[0]) >= 3:
+ eval_loss[name] = (
+ [int(x[0]) for x in vals],
+ [math.log(float(x[2])) for x in vals],
+ )
+
+# https://stackoverflow.com/a/49357445
+def smooth(scalars, weight):
+ last = scalars[0]
+ smoothed = list()
+ for point in scalars:
+ smoothed_val = last * weight + (1 - weight) * point
+ smoothed.append(smoothed_val)
+ last = smoothed_val
+ return smoothed
+
+def plot(data, fname):
+ fig, ax = plt.subplots()
+ ax.grid()
+ for name, val in data.items():
+ ax.plot(val[0], smooth(val[1], 0.9), label=name)
+ plt.legend(loc="upper right")
+ plt.savefig(fname, dpi=300, bbox_inches='tight')
+
+for fp in files:
+ with open(fp) as f:
+ lines = f.readlines()
+ process_lines(lines)
+
+plot(train_loss, "loss.png")
+plot(eval_loss, "loss-eval.png")
diff --git a/custom_nodes/SD-Latent-Upscaler/preprocess_latents.py b/custom_nodes/SD-Latent-Upscaler/preprocess_latents.py
new file mode 100644
index 0000000000000000000000000000000000000000..37d2bd55a9b7c81753a914d6681fccb7a2ffc52e
--- /dev/null
+++ b/custom_nodes/SD-Latent-Upscaler/preprocess_latents.py
@@ -0,0 +1,80 @@
+import os
+import torch
+import hashlib
+import argparse
+import numpy as np
+from torchvision import transforms
+from diffusers import AutoencoderKL
+from tqdm import tqdm
+from PIL import Image
+
+from vae import get_vae
+
+def parse_args():
+ parser = argparse.ArgumentParser(description="Preprocess images into latents")
+ parser.add_argument("-r", "--res", type=int, default=512, help="Source resolution")
+ parser.add_argument("-f", "--fac", type=float, default=1.5, help="Upscale factor")
+ parser.add_argument("-v", "--ver", choices=["v1","xl"], default="v1", help="SD version")
+ parser.add_argument('--vae', help="Path to VAE (Optional)")
+ parser.add_argument('--src', default="raw", help="Source folder with images")
+ return parser.parse_args()
+
+def encode(vae, img):
+ """image [PIL Image] -> latent [np array]"""
+ inp = transforms.ToTensor()(img).unsqueeze(0)
+ inp = inp.to("cuda") # move to GPU
+ latent = vae.encode(inp*2.0-1.0)
+ latent = latent.latent_dist.sample()
+ return latent.cpu().detach()
+
+def scale(path, res):
+ """Crop image to the top-left corner"""
+ img = Image.open(path)
+ img = img.convert('RGB')
+ target = (res, res)
+ if min(img.height, img.width) < 256:
+ return
+ if img.width > img.height:
+ target = (int(img.width/img.height*res), res)
+ elif img.height > img.width:
+ target = (res, int(img.height/img.width*res))
+ img = img.resize(target, Image.LANCZOS)
+ img = img.crop([0,0,res,res])
+ return img
+
+def process_folder(vae, src_dir, ver, res):
+ dst_dir = f"latents/{ver}_{res}px"
+ if not os.path.isdir(dst_dir):
+ os.mkdir(dst_dir)
+
+ for file in tqdm(os.listdir(src_dir)):
+ src = os.path.join(src_dir, file)
+ md5 = hashlib.md5(open(src,'rb').read()).hexdigest()
+ dst = os.path.join(dst_dir, f"{md5}.npy")
+ if os.path.isfile(dst):
+ continue
+ img = scale(src, res)
+ latent = encode(vae, img)
+ np.save(dst, latent)
+
+def process_res(vae, src_dir, ver, res):
+ process_folder(vae, src_dir, ver, res)
+ # test image, optional
+ if os.path.isfile("test.png"):
+ if os.path.isfile(f"test_{ver}_{res}px.npy"):
+ return
+ img = scale("test.png", res)
+ latent = encode(vae, img)
+ np.save(f"test_{ver}_{res}px.npy", latent)
+ torch.cuda.empty_cache()
+
+if __name__ == "__main__":
+ if not os.path.isdir("latents"):
+ os.mkdir("latents")
+ args = parse_args()
+ vae = get_vae(args.ver, args.vae)
+ vae.to("cuda")
+ ## args
+ dst_res = int(args.res*args.fac)
+ process_res(vae, args.src, args.ver, args.res)
+ process_res(vae, args.src, args.ver, dst_res)
diff --git a/custom_nodes/SD-Latent-Upscaler/train.py b/custom_nodes/SD-Latent-Upscaler/train.py
new file mode 100644
index 0000000000000000000000000000000000000000..5b3f331a6d480ba0e77f323f14887f2268f9108c
--- /dev/null
+++ b/custom_nodes/SD-Latent-Upscaler/train.py
@@ -0,0 +1,164 @@
+import os
+import torch
+import torch.nn as nn
+import numpy as np
+import argparse
+import random
+from PIL import Image
+from tqdm import tqdm
+from safetensors.torch import save_file, load_file
+from torch.utils.data import DataLoader, Dataset
+
+from upscaler import LatentUpscaler as Upscaler
+from vae import get_vae
+
+torch.backends.cudnn.benchmark = True
+
+def parse_args():
+ parser = argparse.ArgumentParser(description="Train latent interposer model")
+ parser.add_argument("--steps", type=int, default=500000, help="No. of training steps")
+ parser.add_argument('--bs', type=int, default=4, help="Batch size")
+ parser.add_argument('--lr', default="5e-4", help="Learning rate")
+ parser.add_argument("-n", "--save_every_n", type=int, dest="save", default=50000, help="Save model/sample periodically")
+ parser.add_argument("-r", "--res", type=int, default=512, help="Source resolution")
+ parser.add_argument("-f", "--fac", type=float, default=1.5, help="Upscale factor")
+ parser.add_argument("-v", "--ver", choices=["v1","xl"], default="v1", help="SD version")
+ parser.add_argument('--vae', help="Path to VAE (Optional)")
+ parser.add_argument('--resume', help="Checkpoint to resume from")
+ args = parser.parse_args()
+ try:
+ float(args.lr)
+ except:
+ parser.error("--lr must be a valid float eg. 0.001 or 1e-3")
+ return args
+
+vae = None
+def sample_decode(latent, filename, version):
+ global vae
+ if not vae:
+ vae = get_vae(version, fp16=True)
+ vae.to("cuda")
+
+ latent = latent.half().to("cuda")
+ out = vae.decode(latent).sample
+ out = out.cpu().detach().numpy()
+ out = np.squeeze(out, 0)
+ out = out.transpose((1, 2, 0))
+ out = np.clip(out, -1.0, 1.0)
+ out = (out+1)/2 * 255
+ out = out.astype(np.uint8)
+ out = Image.fromarray(out)
+ out.save(filename)
+
+def eval_model(step, model, criterion, scheduler, src, dst):
+ with torch.no_grad():
+ t_pred = model(src)
+ t_loss = criterion(t_pred, dst)
+ tqdm.write(f"{str(step):<10} {loss.data.item():.4e}|{t_loss.data.item():.4e} @ {float(scheduler.get_last_lr()[0]):.4e}")
+ log.write(f"{step},{loss.data.item()},{t_loss.data.item()},{float(scheduler.get_last_lr()[0])}\n")
+ log.flush()
+
+def save_model(step, model, ver, fac, src):
+ out = model(src)
+ output_name = f"./models/latent-upscaler_SD{ver}-x{fac}_e{round(step/1000)}k"
+ sample_decode(out, f"{output_name}.png", ver)
+ save_file(model.state_dict(), f"{output_name}.safetensors")
+
+class Latent:
+ def __init__(self, md5, ver, src_res, dst_res):
+ src = os.path.join(f"latents/{ver}_{src_res}px", f"{md5}.npy")
+ dst = os.path.join(f"latents/{ver}_{dst_res}px", f"{md5}.npy")
+ self.src = torch.from_numpy(np.load(src)).to("cuda")
+ self.dst = torch.from_numpy(np.load(dst)).to("cuda")
+ self.src = torch.squeeze(self.src, 0)
+ self.dst = torch.squeeze(self.dst, 0)
+
+class LatentDataset(Dataset):
+ def __init__(self, ver, src_res, dst_res):
+ print("Loading latents from disk")
+ self.latents = []
+ for i in tqdm(os.listdir(f"latents/{ver}_{src_res}px")):
+ md5 = os.path.splitext(i)[0]
+ self.latents.append(
+ Latent(md5, ver, src_res, dst_res)
+ )
+
+ def __len__(self):
+ return len(self.latents)
+
+ def __getitem__(self, index):
+ return (
+ self.latents[index].src,
+ self.latents[index].dst,
+ )
+
+if __name__ == "__main__":
+ args = parse_args()
+ target_dev = "cuda"
+ dst_res = int(args.res*args.fac)
+
+ dataset = LatentDataset(args.ver, args.res, dst_res)
+ loader = DataLoader(
+ dataset,
+ batch_size=args.bs,
+ shuffle=True,
+ num_workers=0,
+ )
+
+ if not os.path.isdir("models"): os.mkdir("models")
+ log = open(f"models/latent-upscaler_SD{args.ver}-x{args.fac}.csv", "w")
+
+ if os.path.isfile(f"test_{args.ver}_{args.res}px.npy") and os.path.isfile(f"test_{args.ver}_{dst_res}px.npy"):
+ eval_src = torch.from_numpy(np.load(f"test_{args.ver}_{args.res}px.npy")).to(target_dev)
+ eval_dst = torch.from_numpy(np.load(f"test_{args.ver}_{dst_res}px.npy")).to(target_dev)
+ else:
+ eval_src = torch.unsqueeze(dataset[0][0],0)
+ eval_dst = torch.unsqueeze(dataset[0][1],0)
+
+ model = Upscaler(args.fac)
+ if args.resume:
+ model.load_state_dict(load_file(args.resume))
+ model.to(target_dev)
+
+ # criterion = torch.nn.MSELoss()
+ criterion = torch.nn.L1Loss()
+
+ # optimizer = torch.optim.SGD(model.parameters(), lr=float(args.lr)/args.bs)
+ optimizer = torch.optim.AdamW(model.parameters(), lr=float(args.lr)/args.bs)
+
+ scheduler = torch.optim.lr_scheduler.OneCycleLR(
+ optimizer,
+ total_steps=int(args.steps/args.bs),
+ max_lr=float(args.lr)/args.bs,
+ pct_start=0.015,
+ final_div_factor=2500,
+ )
+ # scaler = torch.cuda.amp.GradScaler()
+ progress = tqdm(total=args.steps)
+
+ while progress.n < args.steps:
+ for src, dst in loader:
+ with torch.cuda.amp.autocast():
+ y_pred = model(src) # forward
+ loss = criterion(y_pred, dst) # loss
+
+ # backward
+ optimizer.zero_grad()
+ loss.backward()
+ optimizer.step()
+ scheduler.step()
+
+ # eval/save
+ progress.update(args.bs)
+ if progress.n % (1000 + 1000%args.bs) == 0:
+ eval_model(progress.n, model, criterion, scheduler, eval_src, eval_dst)
+ if progress.n % (args.save + args.save%args.bs) == 0:
+ save_model(progress.n, model, args.ver, args.fac, eval_src)
+ if progress.n >= args.steps:
+ break
+ progress.close()
+
+ # save final output
+ eval_model(args.steps, model, criterion, scheduler, eval_src, eval_dst)
+ save_model(args.steps, model, args.ver, args.fac, eval_src)
+ log.close()
diff --git a/custom_nodes/SD-Latent-Upscaler/upscaler.py b/custom_nodes/SD-Latent-Upscaler/upscaler.py
new file mode 100644
index 0000000000000000000000000000000000000000..490e5af56d2b43727698ea11afe1a51d5774e3c6
--- /dev/null
+++ b/custom_nodes/SD-Latent-Upscaler/upscaler.py
@@ -0,0 +1,42 @@
+import torch
+import torch.nn as nn
+import numpy as np
+
+class LatentUpscaler(nn.Module):
+ def head(self):
+ return [
+ nn.Conv2d(self.chan, self.size, kernel_size=self.krn, padding=self.pad),
+ nn.ReLU(),
+ nn.Upsample(scale_factor=self.fac, mode="nearest"),
+ nn.ReLU(),
+ ]
+ def core(self):
+ layers = []
+ for _ in range(self.depth):
+ layers += [
+ nn.Conv2d(self.size, self.size, kernel_size=self.krn, padding=self.pad),
+ nn.ReLU(),
+ ]
+ return layers
+ def tail(self):
+ return [
+ nn.Conv2d(self.size, self.chan, kernel_size=self.krn, padding=self.pad),
+ ]
+
+ def __init__(self, fac, depth=16):
+ super().__init__()
+ self.size = 64 # Conv2d size
+ self.chan = 4 # in/out channels
+ self.depth = depth # no. of layers
+ self.fac = fac # scale factor
+ self.krn = 3 # kernel size
+ self.pad = 1 # padding
+
+ self.sequential = nn.Sequential(
+ *self.head(),
+ *self.core(),
+ *self.tail(),
+ )
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ return self.sequential(x)
diff --git a/custom_nodes/SD-Latent-Upscaler/vae.py b/custom_nodes/SD-Latent-Upscaler/vae.py
new file mode 100644
index 0000000000000000000000000000000000000000..1b15a1398920dffd1cae1584ec3e674c33bef54c
--- /dev/null
+++ b/custom_nodes/SD-Latent-Upscaler/vae.py
@@ -0,0 +1,48 @@
+import torch
+from diffusers import AutoencoderKL
+
+def get_vae(version, file_path=None, fp16=False):
+ """Load VAE from file or default hf repo. fp16 only works from hf"""
+ vae = None
+ dtype = torch.float16 if fp16 else torch.float32
+ if version == "v1" and file_path:
+ vae = AutoencoderKL.from_single_file(
+ file_path,
+ image_size=512,
+ )
+ elif version == "v1":
+ vae = AutoencoderKL.from_pretrained(
+ "runwayml/stable-diffusion-v1-5",
+ subfolder="vae",
+ torch_dtype=dtype,
+ )
+ elif version == "v2" and file_path:
+ vae = AutoencoderKL.from_single_file(
+ file_path,
+ image_size=768,
+ )
+ elif version == "v2":
+ vae = AutoencoderKL.from_pretrained(
+ "stabilityai/stable-diffusion-2-1",
+ subfolder="vae",
+ torch_dtype=dtype,
+ )
+ elif version == "xl" and file_path:
+ vae = AutoencoderKL.from_single_file(
+ file_path,
+ image_size=1024
+ )
+ elif version == "xl" and fp16:
+ vae = AutoencoderKL.from_pretrained(
+ "madebyollin/sdxl-vae-fp16-fix",
+ torch_dtype=torch.float16,
+ )
+ elif version == "xl":
+ vae = AutoencoderKL.from_pretrained(
+ "stabilityai/stable-diffusion-xl-base-1.0",
+ subfolder="vae"
+ )
+ else:
+ input("Invalid VAE version. Press any key to exit")
+ exit(1)
+ return vae
diff --git a/custom_nodes/cd-tuner_negpip-ComfyUI/README.md b/custom_nodes/cd-tuner_negpip-ComfyUI/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..e828fef4b68f62fcac1cfa56ff043e46c3a72275
--- /dev/null
+++ b/custom_nodes/cd-tuner_negpip-ComfyUI/README.md
@@ -0,0 +1,14 @@
+# cd-tuner_negpip-ComfyUI
+このカスタムノードは、[hako-mikan](https://github.com/hako-mikan)氏による以下の二つのwebUI拡張をComfyUI実装するものです。
+
++ https://github.com/hako-mikan/sd-webui-cd-tuner
+:色調や書き込み量を調節する機能、とりあえず一部の機能のみ実装・・・
++ https://github.com/hako-mikan/sd-webui-negpip
+:プロンプトにマイナスの重みを実装する機能
+
+# 説明
+loaderに二つのノードが追加されます。使い方の説明が必要なほど難しくないです。
+cd-tunerのstart、endは0,1000の範囲で指定してください(step単位じゃないのは実装の都合です)。
+
+# 謝辞
+二つの実装の考案者である[hako-mikan](https://github.com/hako-mikan)氏に感謝いたします。
diff --git a/custom_nodes/cd-tuner_negpip-ComfyUI/__init__.py b/custom_nodes/cd-tuner_negpip-ComfyUI/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..212f26f45719bff5d3bfd5e92ddee41753716830
--- /dev/null
+++ b/custom_nodes/cd-tuner_negpip-ComfyUI/__init__.py
@@ -0,0 +1,14 @@
+from .cd_tuner import CDTuner
+from .negpip import Negpip
+
+NODE_CLASS_MAPPINGS = {
+ "CDTuner": CDTuner,
+ "Negapip": Negpip,
+}
+
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "CDTuner": "Apply CDTuner",
+ "Negapip": "Apply Negapip",
+}
+
+__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
\ No newline at end of file
diff --git a/custom_nodes/cd-tuner_negpip-ComfyUI/__pycache__/__init__.cpython-311.pyc b/custom_nodes/cd-tuner_negpip-ComfyUI/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..01de3c6829a2d762492311a67676fb287b795fe6
Binary files /dev/null and b/custom_nodes/cd-tuner_negpip-ComfyUI/__pycache__/__init__.cpython-311.pyc differ
diff --git a/custom_nodes/cd-tuner_negpip-ComfyUI/__pycache__/cd_tuner.cpython-311.pyc b/custom_nodes/cd-tuner_negpip-ComfyUI/__pycache__/cd_tuner.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..06f4b86de074975ecc69dde266d7d4c474c64c25
Binary files /dev/null and b/custom_nodes/cd-tuner_negpip-ComfyUI/__pycache__/cd_tuner.cpython-311.pyc differ
diff --git a/custom_nodes/cd-tuner_negpip-ComfyUI/__pycache__/negpip.cpython-311.pyc b/custom_nodes/cd-tuner_negpip-ComfyUI/__pycache__/negpip.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0fcfe92fa9472187a77266dfc28729230cde5943
Binary files /dev/null and b/custom_nodes/cd-tuner_negpip-ComfyUI/__pycache__/negpip.cpython-311.pyc differ
diff --git a/custom_nodes/cd-tuner_negpip-ComfyUI/cd_tuner.py b/custom_nodes/cd-tuner_negpip-ComfyUI/cd_tuner.py
new file mode 100644
index 0000000000000000000000000000000000000000..8b1805bca843c2cf916148b92402e41f730141ff
--- /dev/null
+++ b/custom_nodes/cd-tuner_negpip-ComfyUI/cd_tuner.py
@@ -0,0 +1,134 @@
+import torch
+
+
+class CDTuner:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "model": ("MODEL", ),
+ "detail_1": ("FLOAT", {
+ "default": 0,
+ "min": -10,
+ "max": 10,
+ "step": 0.1
+ }),
+ "detail_2": ("FLOAT", {
+ "default": 0,
+ "min": -10,
+ "max": 10,
+ "step": 0.1
+ }),
+ "contrast_1": ("FLOAT", {
+ "default": 0,
+ "min": -20,
+ "max": 20,
+ "step": 0.1
+ }),
+ "start": ("INT", {
+ "default": 0,
+ "min": 0,
+ "max": 1000,
+ "step": 1,
+ "display": "number"
+ }),
+ "end": ("INT", {
+ "default": 1000,
+ "min": 0,
+ "max": 1000,
+ "step": 1,
+ "display": "number"
+ }),
+ },
+ }
+
+ RETURN_TYPES = ("MODEL", )
+ FUNCTION = "apply"
+ CATEGORY = "loaders"
+
+ def apply(self, model, detail_1, detail_2, contrast_1, start, end):
+ '''
+ detail_1: 最初のConv層のweightを減らしbiasを増やすことで、detailを増やす・・?
+ detail_2: 最後のConv層前のGroupNormの以下略
+ contrast_1: 最後のConv層のbiasの0チャンネル目を増やすことでコントラストを増やす・・・?
+ '''
+ new_model = model.clone()
+ ratios = fineman([detail_1, detail_2, contrast_1])
+ self.storedweights = {}
+ self.start = start
+ self.end = end
+
+ # unet計算前後のパッチ
+ def apply_cdtuner(model_function, kwargs):
+ if kwargs["timestep"][0] < (1000 - self.end) or kwargs["timestep"][0] > (1000 - self.start):
+ return model_function(kwargs["input"], kwargs["timestep"], **kwargs["c"])
+ for i, name in enumerate(ADJUSTS):
+ # 元の重みをロード
+ self.storedweights[name] = getset_nested_module_tensor(True, new_model, name).clone()
+ if 4 > i:
+ new_weight = self.storedweights[name] * ratios[i]
+ else:
+ device = self.storedweights[name].device
+ dtype = self.storedweights[name].dtype
+ new_weight = self.storedweights[name] + torch.tensor(ratios[i], device=device, dtype=dtype)
+ # 重みを書き換え
+ getset_nested_module_tensor(False, new_model, name, new_tensor=new_weight)
+ retval = model_function(kwargs["input"], kwargs["timestep"], **kwargs["c"])
+
+ # 重みを元に戻す
+ for name in ADJUSTS:
+ getset_nested_module_tensor(False, new_model, name, new_tensor=self.storedweights[name])
+
+ return retval
+
+ new_model.set_model_unet_function_wrapper(apply_cdtuner)
+
+ return (new_model, )
+
+
+def getset_nested_module_tensor(clone, model, tensor_path, new_tensor=None):
+ sdmodules = tensor_path.split('.')
+ target_module = model
+ last_attr = None
+
+ for module_name in sdmodules if clone else sdmodules[:-1]:
+ if module_name.isdigit():
+ target_module = target_module[int(module_name)]
+ else:
+ target_module = getattr(target_module, module_name)
+
+ if clone:
+ return target_module
+
+ last_attr = sdmodules[-1]
+ setattr(target_module, last_attr, torch.nn.Parameter(new_tensor))
+
+# なんでfineman?
+def fineman(fine):
+ fine = [
+ 1 - fine[0] * 0.01,
+ 1 + fine[0] * 0.02,
+ 1 - fine[1] * 0.01,
+ 1 + fine[1] * 0.02,
+ [fine[2] * 0.02, 0, 0, 0]
+ ]
+ return fine
+
+
+ADJUSTS = [
+ "model.diffusion_model.input_blocks.0.0.weight",
+ "model.diffusion_model.input_blocks.0.0.bias",
+ "model.diffusion_model.out.0.weight",
+ "model.diffusion_model.out.0.bias",
+ "model.diffusion_model.out.2.bias",
+]
+
+NODE_CLASS_MAPPINGS = {
+ "CDTuner": CDTuner,
+}
+
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "CDTuner": "Apply CDTuner",
+}
+
+__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
diff --git a/custom_nodes/cd-tuner_negpip-ComfyUI/negpip.py b/custom_nodes/cd-tuner_negpip-ComfyUI/negpip.py
new file mode 100644
index 0000000000000000000000000000000000000000..e9a440cf3f6824b3faf98feb6ec4e1abf102234c
--- /dev/null
+++ b/custom_nodes/cd-tuner_negpip-ComfyUI/negpip.py
@@ -0,0 +1,101 @@
+import torch
+import copy
+from comfy.sd1_clip import gen_empty_tokens
+
+class Negpip:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "model": ("MODEL", ),
+ "clip": ("CLIP", ),
+ },
+ }
+
+ RETURN_TYPES = ("MODEL", "CLIP")
+ FUNCTION = "apply"
+ CATEGORY = "loaders"
+
+ def apply(self, model, clip):
+ new_clip = copy.copy(clip)
+ if hasattr(new_clip.cond_stage_model, "clip_g"):
+ new_clip.cond_stage_model.clip_g.encode_token_weights = hook_clip_encode_token_weights(new_clip.cond_stage_model.clip_g)
+ if hasattr(new_clip.cond_stage_model, "clip_h"):
+ new_clip.cond_stage_model.clip_h.encode_token_weights = hook_clip_encode_token_weights(new_clip.cond_stage_model.clip_h)
+ if hasattr(new_clip.cond_stage_model, "clip_l"):
+ new_clip.cond_stage_model.clip_l.encode_token_weights = hook_clip_encode_token_weights(new_clip.cond_stage_model.clip_l)
+ new_model = model.clone()
+
+ def negpip_apply(q, k, v, extra_options):
+ new_k = k[:, 0::2]
+ new_v = v[:, 1::2]
+ return q, new_k, new_v
+
+ new_model.set_model_attn2_patch(negpip_apply)
+
+ return new_model, new_clip
+
+# prompt weightingの計算で無理やりk,vを計算
+# k,vはattn2_patchで分離する
+# weightがマイナスのときはvだけマイナスにする
+def hook_clip_encode_token_weights(self):
+
+ def encode_token_weights(token_weight_pairs):
+ to_encode = list()
+ max_token_len = 0
+ has_weights = False
+ for x in token_weight_pairs:
+ tokens = list(map(lambda a: a[0], x))
+ max_token_len = max(len(tokens), max_token_len)
+ has_weights = has_weights or not all(map(lambda a: a[1] == 1.0, x))
+ to_encode.append(tokens)
+
+ sections = len(to_encode)
+ if has_weights or sections == 0:
+ to_encode.append(gen_empty_tokens(self.special_tokens, max_token_len))
+
+ out, pooled = self.encode(to_encode)
+ if pooled is not None:
+ first_pooled = pooled[0:1].cpu()
+ else:
+ first_pooled = pooled
+
+ output = []
+ for k in range(0, sections):
+ zk = out[k:k+1].clone()
+ zv = out[k:k+1].clone()
+ if has_weights:
+ z_empty = out[-1]
+ for i in range(len(zk)):
+ for j in range(len(zk[i])):
+ weight = token_weight_pairs[k][j][1]
+ if weight < 0:
+ weight = -weight
+ sign = -1
+ else:
+ sign = 1
+ zk[i][j] = (zk[i][j] - z_empty[0][j]) * weight + z_empty[0][j]
+ zv[i][j] = sign * ((zv[i][j] - z_empty[0][j]) * weight + z_empty[0][j])
+
+ z = torch.zeros_like(zk).repeat(1, 2, 1)
+ for i in range(zk.shape[1]): # 頭悪いのでfor文
+ z[:, 2*i, :] += zk[:, i, :]
+ z[:, 2*i+1, :] += zv[:, i, :]
+ output.append(z)
+
+ if (len(output) == 0):
+ return out[-1:].cpu(), first_pooled
+ return torch.cat(output, dim=-2).cpu(), first_pooled
+
+ return encode_token_weights
+
+
+NODE_CLASS_MAPPINGS = {
+ "Negpip": Negpip,
+}
+
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "Negpip": "Apply Negpip",
+}
+
+__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
diff --git a/custom_nodes/comfyui-dream-project/.gitignore b/custom_nodes/comfyui-dream-project/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..bda22c3d18b6b8287aa4a7f57d43d7cb38151242
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/.gitignore
@@ -0,0 +1,3 @@
+.idea
+__pycache__
+config.json
\ No newline at end of file
diff --git a/custom_nodes/comfyui-dream-project/__init__.py b/custom_nodes/comfyui-dream-project/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..6a07a8e73018c2cb86576bdccafae0ece9f04970
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/__init__.py
@@ -0,0 +1,107 @@
+# -*- coding: utf-8 -*-
+from typing import Type
+
+from .base import *
+from .colors import *
+from .curves import *
+from .image_processing import *
+from .inputfields import *
+from .loaders import *
+from .noise import *
+from .output import *
+from .prompting import *
+from .seq_processing import *
+from .switches import *
+from .utility import *
+from .calculate import *
+from .laboratory import *
+
+_NODE_CLASSES: List[Type] = [DreamSineWave, DreamLinear, DreamCSVCurve, DreamBeatCurve, DreamFrameDimensions,
+ DreamImageMotion, DreamNoiseFromPalette, DreamAnalyzePalette, DreamColorShift,
+ DreamDirectoryFileCount, DreamFrameCounterOffset, DreamDirectoryBackedFrameCounter,
+ DreamSimpleFrameCounter, DreamImageSequenceInputWithDefaultFallback,
+ DreamImageSequenceOutput, DreamCSVGenerator, DreamImageAreaSampler,
+ DreamVideoEncoder, DreamSequenceTweening, DreamSequenceBlend, DreamColorAlign,
+ DreamImageSampler, DreamNoiseFromAreaPalettes,
+ DreamInputString, DreamInputFloat, DreamInputInt, DreamInputText, DreamBigLatentSwitch,
+ DreamFrameCountCalculator, DreamBigImageSwitch, DreamBigTextSwitch, DreamBigFloatSwitch,
+ DreamBigIntSwitch, DreamBigPaletteSwitch, DreamWeightedPromptBuilder, DreamPromptFinalizer,
+ DreamFrameCounterInfo, DreamBoolToFloat, DreamBoolToInt, DreamSawWave, DreamTriangleWave,
+ DreamTriangleEvent, DreamSmoothEvent, DreamCalculation, DreamImageColorShift,
+ DreamComparePalette, DreamImageContrast, DreamImageBrightness, DreamLogFile,
+ DreamLaboratory, DreamStringToLog, DreamIntToLog, DreamFloatToLog, DreamJoinLog,
+ DreamStringTokenizer, DreamWavCurve, DreamFrameCounterTimeOffset]
+_SIGNATURE_SUFFIX = " [Dream]"
+
+MANIFEST = {
+ "name": "Dream Project Animation",
+ "version": (5, 0, 0),
+ "author": "Dream Project",
+ "project": "https://github.com/alt-key-project/comfyui-dream-project",
+ "description": "Various utility nodes for creating animations with ComfyUI",
+}
+
+NODE_CLASS_MAPPINGS = {}
+
+NODE_DISPLAY_NAME_MAPPINGS = {}
+
+config = DreamConfig()
+
+
+def update_category(cls):
+ top = config.get("ui.top_category", "").strip().strip("/")
+ leaf_icon = ""
+ if top and "CATEGORY" in cls.__dict__:
+ cls.CATEGORY = top + "/" + cls.CATEGORY.lstrip("/")
+ if "CATEGORY" in cls.__dict__:
+ joined = []
+ for partial in cls.CATEGORY.split("/"):
+ icon = config.get("ui.category_icons." + partial, "")
+ if icon:
+ leaf_icon = icon
+ if config.get("ui.prepend_icon_to_category", False):
+ partial = icon.lstrip() + " " + partial
+ if config.get("ui.append_icon_to_category", False):
+ partial = partial + " " + icon.rstrip()
+ joined.append(partial)
+ cls.CATEGORY = "/".join(joined)
+ return leaf_icon
+
+
+def update_display_name(cls, category_icon, display_name):
+ icon = cls.__dict__.get("ICON", category_icon)
+ if config.get("ui.prepend_icon_to_node", False):
+ display_name = icon.lstrip() + " " + display_name
+ if config.get("ui.append_icon_to_node", False):
+ display_name = display_name + " " + icon.rstrip()
+ return display_name
+
+
+for cls in _NODE_CLASSES:
+ category_icon = update_category(cls)
+ clsname = cls.__name__
+ if "NODE_NAME" in cls.__dict__:
+ node_name = cls.__dict__["NODE_NAME"] + _SIGNATURE_SUFFIX
+ NODE_CLASS_MAPPINGS[node_name] = cls
+ NODE_DISPLAY_NAME_MAPPINGS[node_name] = update_display_name(cls, category_icon,
+ cls.__dict__.get("DISPLAY_NAME",
+ cls.__dict__["NODE_NAME"]))
+ else:
+ raise Exception("Class {} is missing NODE_NAME!".format(str(cls)))
+
+
+def update_node_index():
+ node_list_path = os.path.join(os.path.dirname(__file__), "node_list.json")
+ with open(node_list_path) as f:
+ node_list = json.loads(f.read())
+ updated = False
+ for nodename in NODE_CLASS_MAPPINGS.keys():
+ if nodename not in node_list:
+ node_list[nodename] = ""
+ updated = True
+ if updated or True:
+ with open(node_list_path, "w") as f:
+ f.write(json.dumps(node_list, indent=2, sort_keys=True))
+
+
+update_node_index()
diff --git a/custom_nodes/comfyui-dream-project/__pycache__/__init__.cpython-311.pyc b/custom_nodes/comfyui-dream-project/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..24fa5a4fb413b624e4e97208c7e00a479243b1d8
Binary files /dev/null and b/custom_nodes/comfyui-dream-project/__pycache__/__init__.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui-dream-project/__pycache__/base.cpython-311.pyc b/custom_nodes/comfyui-dream-project/__pycache__/base.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..192791a8a3fab7239870e107b3dfbdf0908c16e1
Binary files /dev/null and b/custom_nodes/comfyui-dream-project/__pycache__/base.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui-dream-project/__pycache__/calculate.cpython-311.pyc b/custom_nodes/comfyui-dream-project/__pycache__/calculate.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..720eccd094370eb03c53a28c949b0390607fb19f
Binary files /dev/null and b/custom_nodes/comfyui-dream-project/__pycache__/calculate.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui-dream-project/__pycache__/categories.cpython-311.pyc b/custom_nodes/comfyui-dream-project/__pycache__/categories.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..74ab64721a69f6ae1a2c8b5fb9bbb32b6fcfa75c
Binary files /dev/null and b/custom_nodes/comfyui-dream-project/__pycache__/categories.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui-dream-project/__pycache__/colors.cpython-311.pyc b/custom_nodes/comfyui-dream-project/__pycache__/colors.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8f2256e3b97edeb4f3aeb6c1343e1466c6cb3872
Binary files /dev/null and b/custom_nodes/comfyui-dream-project/__pycache__/colors.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui-dream-project/__pycache__/curves.cpython-311.pyc b/custom_nodes/comfyui-dream-project/__pycache__/curves.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..38bd2ecf6535e4582280352263525ffd527d73b5
Binary files /dev/null and b/custom_nodes/comfyui-dream-project/__pycache__/curves.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui-dream-project/__pycache__/dreamlogger.cpython-311.pyc b/custom_nodes/comfyui-dream-project/__pycache__/dreamlogger.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8485278eeeb7dbc60b9e4ba5b7f9a7861346e121
Binary files /dev/null and b/custom_nodes/comfyui-dream-project/__pycache__/dreamlogger.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui-dream-project/__pycache__/dreamtypes.cpython-311.pyc b/custom_nodes/comfyui-dream-project/__pycache__/dreamtypes.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..839a843fd1bcf9ca00de08502366ac681fa4ed61
Binary files /dev/null and b/custom_nodes/comfyui-dream-project/__pycache__/dreamtypes.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui-dream-project/__pycache__/embedded_config.cpython-311.pyc b/custom_nodes/comfyui-dream-project/__pycache__/embedded_config.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d32b5b795f895c7ca2eb4cb90c9f6b5c5940c3a8
Binary files /dev/null and b/custom_nodes/comfyui-dream-project/__pycache__/embedded_config.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui-dream-project/__pycache__/err.cpython-311.pyc b/custom_nodes/comfyui-dream-project/__pycache__/err.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..df22add20eb8add683f13d4c6166b42b76c87cc6
Binary files /dev/null and b/custom_nodes/comfyui-dream-project/__pycache__/err.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui-dream-project/__pycache__/image_processing.cpython-311.pyc b/custom_nodes/comfyui-dream-project/__pycache__/image_processing.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2f9e41b969231a5ecdefb4d4d33af0d1bd2e82ea
Binary files /dev/null and b/custom_nodes/comfyui-dream-project/__pycache__/image_processing.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui-dream-project/__pycache__/inputfields.cpython-311.pyc b/custom_nodes/comfyui-dream-project/__pycache__/inputfields.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0e82072fba2f0cfe2a67c5991b12cee2c850f648
Binary files /dev/null and b/custom_nodes/comfyui-dream-project/__pycache__/inputfields.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui-dream-project/__pycache__/laboratory.cpython-311.pyc b/custom_nodes/comfyui-dream-project/__pycache__/laboratory.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a0ceae26f94759c6ee5840fa811c4a042443713d
Binary files /dev/null and b/custom_nodes/comfyui-dream-project/__pycache__/laboratory.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui-dream-project/__pycache__/loaders.cpython-311.pyc b/custom_nodes/comfyui-dream-project/__pycache__/loaders.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..9de6f9bbf9c69828e819e197a6b486661591ead5
Binary files /dev/null and b/custom_nodes/comfyui-dream-project/__pycache__/loaders.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui-dream-project/__pycache__/noise.cpython-311.pyc b/custom_nodes/comfyui-dream-project/__pycache__/noise.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8bfe57688f63019446a9c3bffe95d7bb076f6e05
Binary files /dev/null and b/custom_nodes/comfyui-dream-project/__pycache__/noise.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui-dream-project/__pycache__/output.cpython-311.pyc b/custom_nodes/comfyui-dream-project/__pycache__/output.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..dbe36bbcccf9c501b42d68145c4c339cc11dccb4
Binary files /dev/null and b/custom_nodes/comfyui-dream-project/__pycache__/output.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui-dream-project/__pycache__/prompting.cpython-311.pyc b/custom_nodes/comfyui-dream-project/__pycache__/prompting.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a7ebb29cf709c87868cab5d4d7ceef2b46cd2415
Binary files /dev/null and b/custom_nodes/comfyui-dream-project/__pycache__/prompting.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui-dream-project/__pycache__/seq_processing.cpython-311.pyc b/custom_nodes/comfyui-dream-project/__pycache__/seq_processing.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..982f96aa3733c4881c76a6eb8cceafddcdf19470
Binary files /dev/null and b/custom_nodes/comfyui-dream-project/__pycache__/seq_processing.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui-dream-project/__pycache__/shared.cpython-311.pyc b/custom_nodes/comfyui-dream-project/__pycache__/shared.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..21394f2fd9eab26dbeb4d5dd500ff8e171455335
Binary files /dev/null and b/custom_nodes/comfyui-dream-project/__pycache__/shared.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui-dream-project/__pycache__/switches.cpython-311.pyc b/custom_nodes/comfyui-dream-project/__pycache__/switches.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0997108333c5de3765fa26fb6692b87d84bd00a5
Binary files /dev/null and b/custom_nodes/comfyui-dream-project/__pycache__/switches.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui-dream-project/__pycache__/utility.cpython-311.pyc b/custom_nodes/comfyui-dream-project/__pycache__/utility.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ebe2e23a3fd6e21eb6271dca1ce88be10032f5ce
Binary files /dev/null and b/custom_nodes/comfyui-dream-project/__pycache__/utility.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui-dream-project/base.py b/custom_nodes/comfyui-dream-project/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..fa024498ecf61c31bb39403625b2b7fd447bfcf2
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/base.py
@@ -0,0 +1,213 @@
+# -*- coding: utf-8 -*-
+import glob
+
+from .categories import NodeCategories
+from .shared import *
+from .dreamtypes import *
+
+
+class DreamFrameCounterInfo:
+ NODE_NAME = "Frame Counter Info"
+ ICON = "⚋"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": SharedTypes.frame_counter
+ }
+
+ CATEGORY = NodeCategories.ANIMATION
+ RETURN_TYPES = ("INT", "INT", "BOOLEAN", "BOOLEAN", "FLOAT", "FLOAT", "FLOAT", "FLOAT")
+ RETURN_NAMES = ("frames_completed", "total_frames", "first_frame", "last_frame",
+ "elapsed_seconds", "remaining_seconds", "total_seconds", "completion")
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *v):
+ return ALWAYS_CHANGED_FLAG
+
+ def result(self, frame_counter: FrameCounter):
+ return (frame_counter.current_frame,
+ frame_counter.total_frames,
+ frame_counter.is_first_frame,
+ frame_counter.is_final_frame,
+ frame_counter.current_time_in_seconds,
+ frame_counter.remaining_time_in_seconds,
+ frame_counter.total_time_in_seconds,
+ frame_counter.current_time_in_seconds / max(0.01, frame_counter.total_time_in_seconds))
+
+
+class DreamDirectoryFileCount:
+ NODE_NAME = "File Count"
+ ICON = "📂"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "directory_path": ("STRING", {"default": '', "multiline": False}),
+ "patterns": ("STRING", {"default": '*.jpg|*.png|*.jpeg', "multiline": False}),
+ },
+ }
+
+ CATEGORY = NodeCategories.ANIMATION
+ RETURN_TYPES = ("INT",)
+ RETURN_NAMES = ("TOTAL",)
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *v):
+ return ALWAYS_CHANGED_FLAG
+
+ def result(self, directory_path, patterns):
+ if not os.path.isdir(directory_path):
+ return (0,)
+ total = 0
+ for pattern in patterns.split("|"):
+ files = list(glob.glob(pattern, root_dir=directory_path))
+ total += len(files)
+ print("total " + str(total))
+ return (total,)
+
+
+class DreamFrameCounterOffset:
+ NODE_NAME = "Frame Counter Offset"
+
+ ICON = "±"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": SharedTypes.frame_counter | {
+ "offset": ("INT", {"default": -1}),
+ },
+ }
+
+ CATEGORY = NodeCategories.ANIMATION
+ RETURN_TYPES = (FrameCounter.ID,)
+ RETURN_NAMES = ("frame_counter",)
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, frame_counter, offset):
+ return hashed_as_strings(frame_counter, offset)
+
+ def result(self, frame_counter: FrameCounter, offset):
+ return (frame_counter.incremented(offset),)
+
+class DreamFrameCounterTimeOffset:
+ NODE_NAME = "Frame Counter Time Offset"
+
+ ICON = "±"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": SharedTypes.frame_counter | {
+ "offset_seconds": ("FLOAT", {"default": 0.0}),
+ },
+ }
+
+ CATEGORY = NodeCategories.ANIMATION
+ RETURN_TYPES = (FrameCounter.ID,)
+ RETURN_NAMES = ("frame_counter",)
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, frame_counter, offset):
+ return hashed_as_strings(frame_counter, offset)
+
+ def result(self, frame_counter: FrameCounter, offset_seconds):
+ offset = offset_seconds * frame_counter.frames_per_second
+ return (frame_counter.incremented(offset),)
+
+
+class DreamSimpleFrameCounter:
+ NODE_NAME = "Frame Counter (Simple)"
+ ICON = "⚋"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "frame_index": ("INT", {"min": 0, "default": 0}),
+ "total_frames": ("INT", {"default": 100, "min": 1, "max": 24 * 3600 * 60}),
+ "frames_per_second": ("INT", {"min": 1, "default": 25}),
+ },
+ }
+
+ CATEGORY = NodeCategories.ANIMATION
+ RETURN_TYPES = (FrameCounter.ID,)
+ RETURN_NAMES = ("frame_counter",)
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return ALWAYS_CHANGED_FLAG
+
+ def result(self, frame_index, total_frames, frames_per_second):
+ n = frame_index
+ return (FrameCounter(n, total_frames, frames_per_second),)
+
+
+class DreamDirectoryBackedFrameCounter:
+ NODE_NAME = "Frame Counter (Directory)"
+ ICON = "⚋"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "directory_path": ("STRING", {"default": '', "multiline": False}),
+ "pattern": ("STRING", {"default": '*', "multiline": False}),
+ "indexing": (["numeric", "alphabetic order"],),
+ "total_frames": ("INT", {"default": 100, "min": 2, "max": 24 * 3600 * 60}),
+ "frames_per_second": ("INT", {"min": 1, "default": 30}),
+ },
+ }
+
+ CATEGORY = NodeCategories.ANIMATION
+ RETURN_TYPES = (FrameCounter.ID,)
+ RETURN_NAMES = ("frame_counter",)
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return ALWAYS_CHANGED_FLAG
+
+ def result(self, directory_path, pattern, indexing, total_frames, frames_per_second):
+ results = list_images_in_directory(directory_path, pattern, indexing == "alphabetic order")
+ if not results:
+ return (FrameCounter(0, total_frames, frames_per_second),)
+ n = max(results.keys()) + 1
+ return (FrameCounter(n, total_frames, frames_per_second),)
+
+
+class DreamFrameCountCalculator:
+ NODE_NAME = "Frame Count Calculator"
+ ICON = "⌛"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "hours": ("INT", {"min": 0, "default": 0, "max": 23}),
+ "minutes": ("INT", {"min": 0, "default": 0, "max": 59}),
+ "seconds": ("INT", {"min": 0, "default": 10, "max": 59}),
+ "milliseconds": ("INT", {"min": 0, "default": 0, "max": 59}),
+ "frames_per_second": ("INT", {"min": 1, "default": 30})
+ },
+ }
+
+ CATEGORY = NodeCategories.ANIMATION
+ RETURN_TYPES = ("INT",)
+ RETURN_NAMES = ("TOTAL",)
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *v):
+ return ALWAYS_CHANGED_FLAG
+
+ def result(self, hours, minutes, seconds, milliseconds, frames_per_second):
+ total_s = seconds + 0.001 * milliseconds + minutes * 60 + hours * 3600
+ return (round(total_s * frames_per_second),)
diff --git a/custom_nodes/comfyui-dream-project/calculate.py b/custom_nodes/comfyui-dream-project/calculate.py
new file mode 100644
index 0000000000000000000000000000000000000000..47cd72525f9690a759352d5b00b3b1c474a80ab8
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/calculate.py
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+import math
+
+from evalidate import Expr, EvalException, base_eval_model
+
+from .categories import *
+from .err import on_error
+from .shared import hashed_as_strings
+
+
+class DreamCalculation:
+ NODE_NAME = "Calculation"
+ ICON = "🖩"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "expression": ("STRING", {"default": "a + b + c - (r * s * t)", "multiline": True})
+ },
+ "optional": {
+ "a_int": ("INT", {"default": 0, "multiline": False}),
+ "b_int": ("INT", {"default": 0, "multiline": False}),
+ "c_int": ("INT", {"default": 0, "multiline": False}),
+ "r_float": ("FLOAT", {"default": 0.0, "multiline": False}),
+ "s_float": ("FLOAT", {"default": 0.0, "multiline": False}),
+ "t_float": ("FLOAT", {"default": 0.0, "multiline": False})
+ }
+ }
+
+ CATEGORY = NodeCategories.UTILS
+ RETURN_TYPES = ("FLOAT", "INT")
+ RETURN_NAMES = ("FLOAT", "INT")
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return hashed_as_strings(*values)
+
+ def _make_model(self):
+ funcs = self._make_functions()
+ m = base_eval_model.clone()
+ m.nodes.append('Mult')
+ m.nodes.append('Call')
+ for funname in funcs.keys():
+ m.allowed_functions.append(funname)
+ return (m, funcs)
+
+ def _make_functions(self):
+ return {
+ "round": round,
+ "float": float,
+ "int": int,
+ "abs": abs,
+ "min": min,
+ "max": max,
+ "tan": math.tan,
+ "tanh": math.tanh,
+ "sin": math.sin,
+ "sinh": math.sinh,
+ "cos": math.cos,
+ "cosh": math.cosh,
+ "pow": math.pow,
+ "sqrt": math.sqrt,
+ "ceil": math.ceil,
+ "floor": math.floor,
+ "pi": math.pi,
+ "log": math.log,
+ "log2": math.log2,
+ "acos": math.acos,
+ "asin": math.asin,
+ "acosh": math.acosh,
+ "asinh": math.asinh,
+ "atan": math.atan,
+ "atanh": math.atanh,
+ "exp": math.exp,
+ "fmod": math.fmod,
+ "factorial": math.factorial,
+ "dist": math.dist,
+ "atan2": math.atan2,
+ "log10": math.log10
+ }
+
+ def result(self, expression, **values):
+ model, funcs = self._make_model()
+ vars = funcs
+ for key in ("a_int", "b_int", "c_int", "r_float", "s_float", "t_float"):
+ nm = key.split("_")[0]
+ v = values.get(key, None)
+ if v is not None:
+ vars[nm] = v
+ try:
+ data = Expr(expression, model=model).eval(vars)
+ if isinstance(data, (int, float)):
+ return float(data), int(round(data))
+ else:
+ return 0.0, 0
+ except EvalException as e:
+ on_error(DreamCalculation, str(e))
diff --git a/custom_nodes/comfyui-dream-project/categories.py b/custom_nodes/comfyui-dream-project/categories.py
new file mode 100644
index 0000000000000000000000000000000000000000..4299647857f987adbbcbe0ea6480231f2b8c4794
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/categories.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+class NodeCategories:
+ ANIMATION = "animation"
+ ANIMATION_POSTPROCESSING = ANIMATION + "/postprocessing"
+ ANIMATION_TRANSFORMS = ANIMATION + "/transforms"
+ ANIMATION_CURVES = "animation/curves"
+ CONDITIONING = "conditioning"
+ IMAGE_POSTPROCESSING = "image/postprocessing"
+ IMAGE_ANIMATION = "image/animation"
+ IMAGE_COLORS = "image/color"
+ IMAGE_GENERATE = "image/generate"
+ IMAGE = "image"
+ UTILS = "utils"
+ UTILS_SWITCHES = "utils/switches"
\ No newline at end of file
diff --git a/custom_nodes/comfyui-dream-project/colors.py b/custom_nodes/comfyui-dream-project/colors.py
new file mode 100644
index 0000000000000000000000000000000000000000..9628682e17cf558e8bc46764731abfc5dc3051a7
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/colors.py
@@ -0,0 +1,390 @@
+# -*- coding: utf-8 -*-
+
+from .categories import NodeCategories
+from .shared import *
+from .dreamtypes import *
+
+
+class DreamImageAreaSampler:
+ NODE_NAME = "Sample Image Area as Palette"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "image": ("IMAGE",),
+ "samples": ("INT", {"default": 256, "min": 1, "max": 1024 * 4}),
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
+ "area": (["top-left", "top-center", "top-right",
+ "center-left", "center", "center-right",
+ "bottom-left", "bottom-center", "bottom-right"],)
+ },
+ }
+
+ CATEGORY = NodeCategories.IMAGE_COLORS
+ RETURN_TYPES = (RGBPalette.ID,)
+ RETURN_NAMES = ("palette",)
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return ALWAYS_CHANGED_FLAG
+
+ def _get_pixel_area(self, img: DreamImage, area):
+ w = img.width
+ h = img.height
+ wpart = round(w / 3)
+ hpart = round(h / 3)
+ x0 = 0
+ x1 = wpart - 1
+ x2 = wpart
+ x3 = wpart + wpart - 1
+ x4 = wpart + wpart
+ x5 = w - 1
+ y0 = 0
+ y1 = hpart - 1
+ y2 = hpart
+ y3 = hpart + hpart - 1
+ y4 = hpart + hpart
+ y5 = h - 1
+ if area == "center":
+ return (x2, y2, x3, y3)
+ elif area == "top-center":
+ return (x2, y0, x3, y1)
+ elif area == "bottom-center":
+ return (x2, y4, x3, y5)
+ elif area == "center-left":
+ return (x0, y2, x1, y3)
+ elif area == "top-left":
+ return (x0, y0, x1, y1)
+ elif area == "bottom-left":
+ return (x0, y4, x1, y5)
+ elif area == "center-right":
+ return (x4, y2, x5, y3)
+ elif area == "top-right":
+ return (x4, y0, x5, y1)
+ elif area == "bottom-right":
+ return (x4, y4, x5, y5)
+
+ def result(self, image, samples, seed, area):
+ result = list()
+ r = random.Random()
+ r.seed(seed)
+ for data in image:
+ di = DreamImage(tensor_image=data)
+ area = self._get_pixel_area(di, area)
+
+ pixels = list()
+ for i in range(samples):
+ x = r.randint(area[0], area[2])
+ y = r.randint(area[1], area[3])
+ pixels.append(di.get_pixel(x, y))
+ result.append(RGBPalette(colors=pixels))
+
+ return (tuple(result),)
+
+
+class DreamImageSampler:
+ NODE_NAME = "Sample Image as Palette"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "image": ("IMAGE",),
+ "samples": ("INT", {"default": 1024, "min": 1, "max": 1024 * 4}),
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff})
+ },
+ }
+
+ CATEGORY = NodeCategories.IMAGE_COLORS
+ RETURN_TYPES = (RGBPalette.ID,)
+ RETURN_NAMES = ("palette",)
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return ALWAYS_CHANGED_FLAG
+
+ def result(self, image, samples, seed):
+ result = list()
+ r = random.Random()
+ r.seed(seed)
+ for data in image:
+ di = DreamImage(tensor_image=data)
+ pixels = list()
+ for i in range(samples):
+ x = r.randint(0, di.width - 1)
+ y = r.randint(0, di.height - 1)
+ pixels.append(di.get_pixel(x, y))
+ result.append(RGBPalette(colors=pixels))
+
+ return (tuple(result),)
+
+
+class DreamColorAlign:
+ NODE_NAME = "Palette Color Align"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": SharedTypes.palette | {
+ "target_align": (RGBPalette.ID,),
+ "alignment_factor": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 10.0, "step": 0.1}),
+ }
+ }
+
+ CATEGORY = NodeCategories.IMAGE_COLORS
+ RETURN_TYPES = (RGBPalette.ID,)
+ RETURN_NAMES = ("palette",)
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return ALWAYS_CHANGED_FLAG
+
+ def result(self, palette: Tuple[RGBPalette], target_align: Tuple[RGBPalette], alignment_factor: float):
+ results = list()
+
+ def _limit(c):
+ return max(min(c, 255), 0)
+
+ for i in range(len(palette)):
+ p = palette[i]
+ t = target_align[i]
+ (_, _, r1, g1, b1) = p.analyze()
+ (_, _, r2, g2, b2) = t.analyze()
+
+ dr = (r2 - r1) * alignment_factor
+ dg = (g2 - g1) * alignment_factor
+ db = (b2 - b1) * alignment_factor
+ new_pixels = list()
+ for pixel in p:
+ r = _limit(round(pixel[0] + (255 * dr)))
+ g = _limit(round(pixel[1] + (255 * dg)))
+ b = _limit(round(pixel[1] + (255 * db)))
+ new_pixels.append((r, g, b))
+ results.append(RGBPalette(colors=new_pixels))
+ return (tuple(results),)
+
+
+class DreamColorShift:
+ NODE_NAME = "Palette Color Shift"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": SharedTypes.palette | {
+ "red_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.1}),
+ "green_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.1}),
+ "blue_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.1}),
+ "fixed_brightness": (["yes", "no"],),
+ }
+ }
+
+ CATEGORY = NodeCategories.IMAGE_COLORS
+ RETURN_TYPES = (RGBPalette.ID,)
+ RETURN_NAMES = ("palette",)
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return ALWAYS_CHANGED_FLAG
+
+ def result(self, palette, red_multiplier, green_multiplier, blue_multiplier, fixed_brightness):
+ results = list()
+
+ def _limit(c):
+ return max(min(c, 255), 0)
+
+ for p in palette:
+ new_pixels = list()
+ for pixel in p:
+ s = pixel[0] + pixel[1] + pixel[2]
+ r = _limit(round(pixel[0] * red_multiplier))
+ g = _limit(round(pixel[1] * green_multiplier))
+ b = _limit(round(pixel[2] * blue_multiplier))
+ if fixed_brightness == "yes":
+ brightness_factor = max(s, 1) / float(max(r + g + b, 1))
+ r = _limit(round(r * brightness_factor))
+ g = _limit(round(g * brightness_factor))
+ b = _limit(round(b * brightness_factor))
+
+ new_pixels.append((r, g, b))
+ results.append(RGBPalette(colors=new_pixels))
+ return (tuple(results),)
+
+
+class DreamImageColorShift:
+ NODE_NAME = "Image Color Shift"
+ ICON = "🖼"
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {"image": ("IMAGE",),
+ "red_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0}),
+ "green_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0}),
+ "blue_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0}),
+ },
+
+ }
+
+ CATEGORY = NodeCategories.IMAGE_COLORS
+ RETURN_TYPES = ("IMAGE",)
+ RETURN_NAMES = ("image",)
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return ALWAYS_CHANGED_FLAG
+
+ def result(self, image, red_multiplier, green_multiplier, blue_multiplier):
+ proc = DreamImageProcessor(inputs=image)
+
+ def recolor(im: DreamImage, *a, **args):
+ return (im.adjust_colors(red_multiplier, green_multiplier, blue_multiplier),)
+
+ return proc.process(recolor)
+
+
+class DreamImageBrightness:
+ NODE_NAME = "Image Brightness Adjustment"
+ ICON = "☼"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {"image": ("IMAGE",),
+ "factor": ("FLOAT", {"default": 1.0, "min": 0.0}),
+ },
+
+ }
+
+ CATEGORY = NodeCategories.IMAGE_COLORS
+ RETURN_TYPES = ("IMAGE",)
+ RETURN_NAMES = ("image",)
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return ALWAYS_CHANGED_FLAG
+
+ def result(self, image, factor):
+ proc = DreamImageProcessor(inputs=image)
+
+ def change(im: DreamImage, *a, **args):
+ return (im.change_brightness(factor),)
+
+ return proc.process(change)
+
+
+class DreamImageContrast:
+ NODE_NAME = "Image Contrast Adjustment"
+ ICON = "◐"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {"image": ("IMAGE",),
+ "factor": ("FLOAT", {"default": 1.0, "min": 0.0}),
+ },
+
+ }
+
+ CATEGORY = NodeCategories.IMAGE_COLORS
+ RETURN_TYPES = ("IMAGE",)
+ RETURN_NAMES = ("image",)
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return ALWAYS_CHANGED_FLAG
+
+ def result(self, image, factor):
+ proc = DreamImageProcessor(inputs=image)
+
+ def change(im: DreamImage, *a, **args):
+ return (im.change_contrast(factor),)
+
+ return proc.process(change)
+
+
+class DreamComparePalette:
+ NODE_NAME = "Compare Palettes"
+ ICON = "📊"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "a": (RGBPalette.ID,),
+ "b": (RGBPalette.ID,),
+ },
+ }
+
+ CATEGORY = NodeCategories.IMAGE_COLORS
+ RETURN_TYPES = ("FLOAT", "FLOAT", "FLOAT", "FLOAT")
+ RETURN_NAMES = (
+ "brightness_multiplier", "contrast_multiplier", "red_multiplier", "green_multiplier", "blue_multiplier")
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return ALWAYS_CHANGED_FLAG
+
+ def result(self, a, b):
+ MIN_VALUE = 1 / 255.0
+
+ brightness = list()
+ contrasts = list()
+ reds = list()
+ greens = list()
+ blues = list()
+
+ for i in range(min(len(a), len(b))):
+ (bright, ctr, red, green, blue) = a[i].analyze()
+ (bright2, ctr2, red2, green2, blue2) = b[i].analyze()
+ brightness.append(bright2 / max(MIN_VALUE, bright))
+ contrasts.append(ctr2 / max(MIN_VALUE, ctr))
+ reds.append(red2 / max(MIN_VALUE, red))
+ greens.append(green2 / max(MIN_VALUE, green))
+ blues.append(blue2 / max(MIN_VALUE, blue))
+
+ n = len(brightness)
+
+ return (sum(brightness) / n, sum(contrasts) / n, sum(reds) / n,
+ sum(greens) / n, sum(blues) / n)
+
+
+class DreamAnalyzePalette:
+ NODE_NAME = "Analyze Palette"
+ ICON = "📊"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": SharedTypes.palette
+ ,
+ }
+
+ CATEGORY = NodeCategories.IMAGE_COLORS
+ RETURN_TYPES = ("FLOAT", "FLOAT", "FLOAT", "FLOAT", "FLOAT")
+ RETURN_NAMES = ("brightness", "contrast", "redness", "greenness", "blueness")
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return ALWAYS_CHANGED_FLAG
+
+ def result(self, palette):
+ f = 1.0 / len(palette)
+ (w, c, r, g, b) = (0, 0, 0, 0, 0)
+ for p in palette:
+ (brightness, contrast, red, green, blue) = p.analyze()
+ w += brightness
+ c += contrast
+ r += red
+ g += green
+ b += blue
+
+ return w * f, c * f, r * f, g * f, b * f
diff --git a/custom_nodes/comfyui-dream-project/config.json b/custom_nodes/comfyui-dream-project/config.json
new file mode 100644
index 0000000000000000000000000000000000000000..c1829880dd7d86ac6ac6de9da4619b14fc7cf80f
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/config.json
@@ -0,0 +1,54 @@
+{
+ "ffmpeg": {
+ "file_extension": "mp4",
+ "path": "ffmpeg",
+ "arguments": [
+ "-r",
+ "%FPS%",
+ "-f",
+ "concat",
+ "-safe",
+ "0",
+ "-vsync",
+ "cfr",
+ "-i",
+ "%FRAMES%",
+ "-c:v",
+ "libx264",
+ "-pix_fmt",
+ "yuv420p",
+ "%OUTPUT%"
+ ]
+ },
+ "mpeg_coder": {
+ "encoding_threads": 4,
+ "bitrate_factor": 1.0,
+ "max_b_frame": 2,
+ "file_extension": "mp4",
+ "codec_name": "libx264"
+ },
+ "encoding": {
+ "jpeg_quality": 95
+ },
+ "debug": false,
+ "ui": {
+ "top_category": "Dream",
+ "prepend_icon_to_category": true,
+ "append_icon_to_category": false,
+ "prepend_icon_to_node": true,
+ "append_icon_to_node": false,
+ "category_icons": {
+ "animation": "\ud83c\udfa5",
+ "postprocessing": "\u2699",
+ "transforms": "\ud83d\udd00",
+ "curves": "\ud83d\udcc8",
+ "color": "\ud83c\udfa8",
+ "generate": "\u26a1",
+ "utils": "\ud83d\udee0",
+ "image": "\ud83c\udf04",
+ "switches": "\u2b46",
+ "conditioning": "\u262f",
+ "Dream": "\u2728"
+ }
+ }
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui-dream-project/curves.py b/custom_nodes/comfyui-dream-project/curves.py
new file mode 100644
index 0000000000000000000000000000000000000000..acedce17af25b34df034c1419853219a9a443eaa
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/curves.py
@@ -0,0 +1,459 @@
+# -*- coding: utf-8 -*-
+import csv
+import functools
+import math
+import os
+
+from scipy.io.wavfile import read as wav_read
+
+from .categories import NodeCategories
+from .shared import hashed_as_strings
+from .dreamtypes import SharedTypes, FrameCounter
+
+
+def _linear_value_calc(x, x_start, x_end, y_start, y_end):
+ if x <= x_start:
+ return y_start
+ if x >= x_end:
+ return y_end
+ dx = max(x_end - x_start, 0.0001)
+ n = (x - x_start) / dx
+ return (y_end - y_start) * n + y_start
+
+
+def _curve_result(f: float):
+ return (f, int(round(f)))
+
+
+class DreamSineWave:
+ NODE_NAME = "Sine Curve"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": SharedTypes.frame_counter | {
+ "max_value": ("FLOAT", {"default": 1.0, "multiline": False}),
+ "min_value": ("FLOAT", {"default": 0.0, "multiline": False}),
+ "periodicity_seconds": ("FLOAT", {"default": 10.0, "multiline": False, "min": 0.01}),
+ "phase": ("FLOAT", {"default": 0.0, "multiline": False, "min": -1, "max": 1}),
+ },
+ }
+
+ CATEGORY = NodeCategories.ANIMATION_CURVES
+ RETURN_TYPES = ("FLOAT", "INT")
+ RETURN_NAMES = ("FLOAT", "INT")
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return hashed_as_strings(*values)
+
+ def result(self, frame_counter: FrameCounter, max_value, min_value, periodicity_seconds, phase):
+ x = frame_counter.current_time_in_seconds
+ a = (max_value - min_value) * 0.5
+ c = phase
+ b = 2 * math.pi / periodicity_seconds
+ d = (max_value + min_value) / 2
+ y = a * math.sin(b * (x + c)) + d
+ return _curve_result(y)
+
+
+class DreamSawWave:
+ NODE_NAME = "Saw Curve"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": SharedTypes.frame_counter | {
+ "max_value": ("FLOAT", {"default": 1.0, "multiline": False}),
+ "min_value": ("FLOAT", {"default": 0.0, "multiline": False}),
+ "periodicity_seconds": ("FLOAT", {"default": 10.0, "multiline": False, "min": 0.01}),
+ "phase": ("FLOAT", {"default": 0.0, "multiline": False, "min": -1, "max": 1}),
+ },
+ }
+
+ CATEGORY = NodeCategories.ANIMATION_CURVES
+ RETURN_TYPES = ("FLOAT", "INT")
+ RETURN_NAMES = ("FLOAT", "INT")
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return hashed_as_strings(*values)
+
+ def result(self, frame_counter: FrameCounter, max_value, min_value, periodicity_seconds, phase):
+ x = frame_counter.current_time_in_seconds
+ x = ((x + periodicity_seconds * phase) % periodicity_seconds) / periodicity_seconds
+ y = x * (max_value - min_value) + min_value
+ return _curve_result(y)
+
+
+class DreamTriangleWave:
+ NODE_NAME = "Triangle Curve"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": SharedTypes.frame_counter | {
+ "max_value": ("FLOAT", {"default": 1.0, "multiline": False}),
+ "min_value": ("FLOAT", {"default": 0.0, "multiline": False}),
+ "periodicity_seconds": ("FLOAT", {"default": 10.0, "multiline": False, "min": 0.01}),
+ "phase": ("FLOAT", {"default": 0.0, "multiline": False, "min": -1, "max": 1}),
+ },
+ }
+
+ CATEGORY = NodeCategories.ANIMATION_CURVES
+ RETURN_TYPES = ("FLOAT", "INT")
+ RETURN_NAMES = ("FLOAT", "INT")
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return hashed_as_strings(*values)
+
+ def result(self, frame_counter: FrameCounter, max_value, min_value, periodicity_seconds, phase):
+ x = frame_counter.current_time_in_seconds
+ x = ((x + periodicity_seconds * phase) % periodicity_seconds) / periodicity_seconds
+ if x <= 0.5:
+ x *= 2
+ y = x * (max_value - min_value) + min_value
+ else:
+ x = (x - 0.5) * 2
+ y = max_value - x * (max_value - min_value)
+ return _curve_result(y)
+
+
+class WavData:
+ def __init__(self, sampling_rate: float, single_channel_samples, fps: float):
+ self._length_in_seconds = len(single_channel_samples) / sampling_rate
+ self._num_buckets = round(self._length_in_seconds * fps * 3)
+ self._bucket_size = len(single_channel_samples) / float(self._num_buckets)
+ self._buckets = list()
+ self._rate = sampling_rate
+ self._max_bucket_value = 0
+ for i in range(self._num_buckets):
+ start_index = round(i * self._bucket_size)
+ end_index = round((i + 1) * self._bucket_size) - 1
+ samples = list(map(lambda n: abs(n), single_channel_samples[start_index:end_index]))
+ bucket_total = sum(samples)
+ self._buckets.append(bucket_total)
+ self._max_bucket_value=max(bucket_total, self._max_bucket_value)
+
+ for i in range(self._num_buckets):
+ self._buckets[i] = float(self._buckets[i]) / self._max_bucket_value
+
+ def value_at_time(self, second: float) -> float:
+ if second < 0.0 or second > self._length_in_seconds:
+ return 0.0
+ nsample = second * self._rate
+ nbucket = min(max(0, round(nsample / self._bucket_size)), self._num_buckets - 1)
+ return self._buckets[nbucket]
+
+
+@functools.lru_cache(4)
+def _wav_loader(filepath, fps):
+ sampling_rate, samples = wav_read(filepath)
+ single_channel = samples[:, 0]
+ return WavData(sampling_rate, single_channel, fps)
+
+
+class DreamWavCurve:
+ NODE_NAME = "WAV Curve"
+ CATEGORY = NodeCategories.ANIMATION_CURVES
+ RETURN_TYPES = ("FLOAT", "INT")
+ RETURN_NAMES = ("FLOAT", "INT")
+ FUNCTION = "result"
+ ICON = "∿"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": SharedTypes.frame_counter | {
+ "wav_path": ("STRING", {"default": "audio.wav"}),
+ "scale": ("FLOAT", {"default": 1.0, "multiline": False})
+ },
+ }
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return hashed_as_strings(*values)
+
+ def result(self, frame_counter: FrameCounter, wav_path, scale):
+ if not os.path.isfile(wav_path):
+ return (0.0, 0)
+ data = _wav_loader(wav_path, frame_counter.frames_per_second)
+ frame_counter.current_time_in_seconds
+ v = data.value_at_time(frame_counter.current_time_in_seconds)
+ return (v * scale, round(v * scale))
+
+
+class DreamTriangleEvent:
+ NODE_NAME = "Triangle Event Curve"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": SharedTypes.frame_counter | {
+ "max_value": ("FLOAT", {"default": 1.0, "multiline": False}),
+ "min_value": ("FLOAT", {"default": 0.0, "multiline": False}),
+ "width_seconds": ("FLOAT", {"default": 1.0, "multiline": False, "min": 0.1}),
+ "center_seconds": ("FLOAT", {"default": 10.0, "multiline": False, "min": 0.0}),
+ },
+ }
+
+ CATEGORY = NodeCategories.ANIMATION_CURVES
+ RETURN_TYPES = ("FLOAT", "INT")
+ RETURN_NAMES = ("FLOAT", "INT")
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return hashed_as_strings(*values)
+
+ def result(self, frame_counter: FrameCounter, max_value, min_value, width_seconds, center_seconds):
+ x = frame_counter.current_time_in_seconds
+ start = center_seconds - width_seconds * 0.5
+ end = center_seconds + width_seconds * 0.5
+ if start <= x <= center_seconds:
+ y = _linear_value_calc(x, start, center_seconds, min_value, max_value)
+ elif center_seconds < x <= end:
+ y = _linear_value_calc(x, center_seconds, end, max_value, min_value)
+ else:
+ y = min_value
+ return _curve_result(y)
+
+
+class DreamSmoothEvent:
+ NODE_NAME = "Smooth Event Curve"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": SharedTypes.frame_counter | {
+ "max_value": ("FLOAT", {"default": 1.0, "multiline": False}),
+ "min_value": ("FLOAT", {"default": 0.0, "multiline": False}),
+ "width_seconds": ("FLOAT", {"default": 1.0, "multiline": False, "min": 0.1}),
+ "center_seconds": ("FLOAT", {"default": 10.0, "multiline": False, "min": 0.0}),
+ },
+ }
+
+ CATEGORY = NodeCategories.ANIMATION_CURVES
+ RETURN_TYPES = ("FLOAT", "INT")
+ RETURN_NAMES = ("FLOAT", "INT")
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return hashed_as_strings(*values)
+
+ def result(self, frame_counter: FrameCounter, max_value, min_value, width_seconds, center_seconds):
+ x = frame_counter.current_time_in_seconds
+ start = center_seconds - width_seconds * 0.5
+ end = center_seconds + width_seconds * 0.5
+ if start <= x <= center_seconds:
+ y = _linear_value_calc(x, start, center_seconds, 0.0, 1.0)
+ elif center_seconds < x <= end:
+ y = _linear_value_calc(x, center_seconds, end, 1.0, 0.0)
+ else:
+ y = 0.0
+ if y < 0.5:
+ y = ((y + y) * (y + y)) * 0.5
+ else:
+ a = (y - 0.5) * 2
+ y = math.pow(a, 0.25) * 0.5 + 0.5
+ return _curve_result(y * (max_value - min_value) + min_value)
+
+
+class DreamBeatCurve:
+ NODE_NAME = "Beat Curve"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": SharedTypes.frame_counter | {
+ "bpm": ("FLOAT", {"default": 100.0, "multiline": False}),
+ "time_offset": ("FLOAT", {"default": 0.0, "multiline": False}),
+ "measure_length": ("INT", {"default": 4, "min": 1}),
+ "low_value": ("FLOAT", {"default": 0.0}),
+ "high_value": ("FLOAT", {"default": 1.0}),
+ "invert": (["no", "yes"],),
+ "power": ("FLOAT", {"default": 2.0, "min": 0.25, "max": 4}),
+ "accent_1": ("INT", {"default": 1, "min": 1, "max": 24}),
+ },
+ "optional": {
+ "accent_2": ("INT", {"default": 3, "min": 1, "max": 24}),
+ "accent_3": ("INT", {"default": 0}),
+ "accent_4": ("INT", {"default": 0}),
+ }
+ }
+
+ CATEGORY = NodeCategories.ANIMATION_CURVES
+ RETURN_TYPES = ("FLOAT", "INT")
+ RETURN_NAMES = ("FLOAT", "INT")
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return hashed_as_strings(*values)
+
+ def _get_value_for_accent(self, accent, measure_length, bpm, frame_counter: FrameCounter, frame_offset):
+ current_frame = frame_counter.current_frame + frame_offset
+ frames_per_minute = frame_counter.frames_per_second * 60.0
+ frames_per_beat = frames_per_minute / bpm
+ frames_per_measure = frames_per_beat * measure_length
+ frame = (current_frame % frames_per_measure)
+ accent_start = (accent - 1) * frames_per_beat
+ accent_end = accent * frames_per_beat
+ if frame >= accent_start and frame < accent_end:
+ return 1.0 - ((frame - accent_start) / frames_per_beat)
+ return 0
+
+ def result(self, bpm, frame_counter: FrameCounter, measure_length, low_value, high_value, power, invert,
+ time_offset, **accents):
+ frame_offset = int(round(time_offset * frame_counter.frames_per_second))
+ accents_set = set(filter(lambda v: v >= 1 and v <= measure_length,
+ map(lambda i: accents.get("accent_" + str(i), -1), range(30))))
+ v = 0.0
+ for a in accents_set:
+ v += math.pow(self._get_value_for_accent(a, measure_length, bpm, frame_counter, frame_offset), power)
+ if invert == "yes":
+ v = 1.0 - v
+
+ r = low_value + v * (high_value - low_value)
+ return _curve_result(r)
+
+
+class DreamLinear:
+ NODE_NAME = "Linear Curve"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": SharedTypes.frame_counter | {
+ "initial_value": ("FLOAT", {"default": 0.0, "multiline": False}),
+ "final_value": ("FLOAT", {"default": 100.0, "multiline": False}),
+ },
+ }
+
+ CATEGORY = NodeCategories.ANIMATION_CURVES
+ RETURN_TYPES = ("FLOAT", "INT")
+ RETURN_NAMES = ("FLOAT", "INT")
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return hashed_as_strings(*values)
+
+ def result(self, initial_value, final_value, frame_counter: FrameCounter):
+ d = final_value - initial_value
+ v = initial_value + frame_counter.progress * d
+ return (v, int(round(v)))
+
+
+def _is_as_float(s: str):
+ try:
+ float(s)
+ return True
+ except ValueError:
+ return False
+
+
+class DreamCSVGenerator:
+ NODE_NAME = "CSV Generator"
+ ICON = "⌗"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": SharedTypes.frame_counter | {
+ "value": ("FLOAT", {"forceInput": True, "default": 0.0}),
+ "csvfile": ("STRING", {"default": "", "multiline": False}),
+ "csv_dialect": (csv.list_dialects(),)
+ },
+ }
+
+ CATEGORY = NodeCategories.ANIMATION_CURVES
+ RETURN_TYPES = ()
+ RETURN_NAMES = ()
+ FUNCTION = "write"
+ OUTPUT_NODE = True
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return hashed_as_strings(*values)
+
+ def write(self, csvfile, frame_counter: FrameCounter, value, csv_dialect):
+ if frame_counter.is_first_frame and csvfile:
+ with open(csvfile, 'w', newline='') as csvfile:
+ csvwriter = csv.writer(csvfile, dialect=csv_dialect)
+ csvwriter.writerow(['Frame', 'Value'])
+ csvwriter.writerow([frame_counter.current_frame, str(value)])
+ else:
+ with open(csvfile, 'a', newline='') as csvfile:
+ csvwriter = csv.writer(csvfile, dialect=csv_dialect)
+ csvwriter.writerow([frame_counter.current_frame, str(value)])
+ return ()
+
+
+class DreamCSVCurve:
+ NODE_NAME = "CSV Curve"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": SharedTypes.frame_counter | {
+ "csvfile": ("STRING", {"default": "", "multiline": False}),
+ "first_column_type": (["seconds", "frames"],),
+ "interpolate": (["true", "false"],),
+ "csv_dialect": (csv.list_dialects(),)
+ },
+ }
+
+ CATEGORY = NodeCategories.ANIMATION_CURVES
+ RETURN_TYPES = ("FLOAT", "INT")
+ RETURN_NAMES = ("FLOAT", "INT")
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return hashed_as_strings(*values)
+
+ def _row_yield(self, file, csv_dialect):
+ prev_row = None
+ for row in csv.reader(file, dialect=csv_dialect):
+ if len(row) == 2 and _is_as_float(row[0]) and _is_as_float(row[1]):
+ row = list(map(float, row))
+ yield (prev_row, row)
+ prev_row = row
+ if prev_row is not None:
+ yield (prev_row, None)
+
+ def result(self, csvfile, frame_counter: FrameCounter, first_column_type, interpolate, csv_dialect):
+ interpolate = interpolate == "true"
+
+ def _first_col_to_frame(v: float):
+ if first_column_type == "frames":
+ return round(v)
+ else:
+ return round(v * frame_counter.frames_per_second)
+
+ with open(csvfile) as f:
+ for (prev, current) in self._row_yield(f, csv_dialect):
+ if prev is None and frame_counter.current_frame < _first_col_to_frame(current[0]):
+ # before first row
+ return (current[1], int(round(current[1])))
+ if current is None:
+ # after last row
+ return (prev[1], int(round(prev[1])))
+ if prev is not None and current is not None:
+ frame1 = _first_col_to_frame(prev[0])
+ value1 = prev[1]
+ frame2 = _first_col_to_frame(current[0])
+ value2 = current[1]
+ if frame1 <= frame_counter.current_frame and interpolate and frame2 > frame_counter.current_frame:
+ offset = (frame_counter.current_frame - frame1) / float(frame2 - frame1)
+ v = value1 * (1.0 - offset) + value2 * offset
+ return (v, int(round(v)))
+ elif frame1 <= frame_counter.current_frame and frame2 > frame_counter.current_frame:
+ return (value1, int(round(value1)))
+ return (0.0, 0)
diff --git a/custom_nodes/comfyui-dream-project/disable.py b/custom_nodes/comfyui-dream-project/disable.py
new file mode 100644
index 0000000000000000000000000000000000000000..d8e651c52d074b9fc9c55f191b439e26f0b6673e
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/disable.py
@@ -0,0 +1,7 @@
+# -*- coding: utf-8 -*-
+def run_disable():
+ pass
+
+
+if __name__ == "__main__":
+ run_disable()
diff --git a/custom_nodes/comfyui-dream-project/dreamlogger.py b/custom_nodes/comfyui-dream-project/dreamlogger.py
new file mode 100644
index 0000000000000000000000000000000000000000..511cf461d0e692bd6034ed038ef357a5a9c0b35f
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/dreamlogger.py
@@ -0,0 +1,18 @@
+class DreamLog:
+ def __init__(self, debug_active=False):
+ self._debug = debug_active
+
+ def _print(self, text: str, *args, **kwargs):
+ if args or kwargs:
+ text = text.format(*args, **kwargs)
+ print("[DREAM] " + text)
+
+ def error(self, text: str, *args, **kwargs):
+ self._print(text, *args, **kwargs)
+
+ def info(self, text: str, *args, **kwargs):
+ self._print(text, *args, **kwargs)
+
+ def debug(self, text: str, *args, **kwargs):
+ if self._debug:
+ self._print(text, *args, **kwargs)
diff --git a/custom_nodes/comfyui-dream-project/dreamtypes.py b/custom_nodes/comfyui-dream-project/dreamtypes.py
new file mode 100644
index 0000000000000000000000000000000000000000..746275932b6aa024743cc0940b508b1a92c6de3b
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/dreamtypes.py
@@ -0,0 +1,238 @@
+# -*- coding: utf-8 -*-
+import random
+import time
+
+from typing import List, Dict, Tuple
+
+from .shared import DreamImage
+
+
+class RGBPalette:
+ ID = "RGB_PALETTE"
+
+ def __init__(self, colors: List[tuple[int, int, int]] = None, image: DreamImage = None):
+ self._colors = []
+
+ def _fix_tuple(t):
+ if len(t) < 3:
+ return (t[0], t[0], t[0])
+ else:
+ return t
+
+ if image:
+ for p, _, _ in image:
+ self._colors.append(_fix_tuple(p))
+ if colors:
+ for c in colors:
+ self._colors.append(_fix_tuple(c))
+
+ def _calculate_channel_contrast(self, c):
+ hist = list(map(lambda _: 0, range(16)))
+ for pixel in self._colors:
+ hist[pixel[c] // 16] += 1
+ s = 0
+ max_possible = (15 - 0) * (len(self) // 2) * (len(self) // 2)
+ for i in range(16):
+ for j in range(i):
+ if i != j:
+ s += abs(i - j) * hist[i] * hist[j]
+ return s / max_possible
+
+ def _calculate_combined_contrast(self):
+ s = 0
+ for c in range(3):
+ s += self._calculate_channel_contrast(c)
+ return s / 3
+
+ def analyze(self):
+ total_red = 0
+ total_blue = 0
+ total_green = 0
+ for pixel in self:
+ total_red += pixel[0]
+ total_green += pixel[1]
+ total_blue += pixel[2]
+ n = len(self._colors)
+ r = float(total_red) / (255 * n)
+ g = float(total_green) / (255 * n)
+ b = float(total_blue) / (255 * n)
+ return ((r + g + b) / 3.0, self._calculate_combined_contrast(), r, g, b)
+
+ def __len__(self):
+ return len(self._colors)
+
+ def __iter__(self):
+ return iter(self._colors)
+
+ def random_iteration(self, seed=None):
+ s = seed if seed is not None else int(time.time() * 1000)
+ n = len(self._colors) - 1
+ c = self._colors
+
+ class _ColorIterator:
+ def __init__(self):
+ self._r = random.Random()
+ self._r.seed(s)
+ self._n = n
+ self._c = c
+
+ def __next__(self):
+ return self._c[self._r.randint(0, self._n)]
+
+ return _ColorIterator()
+
+
+class PartialPrompt:
+ ID = "PARTIAL_PROMPT"
+
+ def __init__(self):
+ self._data = {}
+
+ def add(self, text: str, weight: float):
+ output = PartialPrompt()
+ output._data = dict(self._data)
+ for parts in text.split(","):
+ parts = parts.strip()
+ if " " in parts:
+ output._data["(" + parts + ")"] = weight
+ else:
+ output._data[parts] = weight
+ return output
+
+ def is_empty(self):
+ return not self._data
+
+ def abs_sum(self):
+ if not self._data:
+ return 0.0
+ return sum(map(abs, self._data.values()))
+
+ def abs_max(self):
+ if not self._data:
+ return 0.0
+ return max(map(abs, self._data.values()))
+
+ def scaled_by(self, f: float):
+ new_data = PartialPrompt()
+ new_data._data = dict(self._data)
+ for text, weight in new_data._data.items():
+ new_data._data[text] = weight * f
+ return new_data
+
+ def finalize(self, clamp: float):
+ items = self._data.items()
+ items = sorted(items, key=lambda pair: (pair[1], pair[0]))
+ pos = list()
+ neg = list()
+ for text, w in sorted(items, key=lambda pair: (-pair[1], pair[0])):
+ if w >= 0.0001:
+ pos.append("({}:{:.3f})".format(text, min(clamp, w)))
+ for text, w in sorted(items, key=lambda pair: (pair[1], pair[0])):
+ if w <= -0.0001:
+ neg.append("({}:{:.3f})".format(text, min(clamp, -w)))
+ return ", ".join(pos), ", ".join(neg)
+
+
+class LogEntry:
+ ID = "LOG_ENTRY"
+
+ @classmethod
+ def new(cls, text):
+ return LogEntry([(time.time(), text)])
+
+ def __init__(self, data: List[Tuple[float, str]] = None):
+ if data is None:
+ self._data = list()
+ else:
+ self._data = list(data)
+
+ def add(self, text: str):
+ new_data = list(self._data)
+ new_data.append((time.time(), text))
+ return LogEntry(new_data)
+
+ def merge(self, log_entry):
+ new_data = list(self._data)
+ new_data.extend(log_entry._data)
+ return LogEntry(new_data)
+
+ def get_filtered_entries(self, t: float):
+ for d in sorted(self._data):
+ if d[0] > t:
+ yield d
+
+
+class FrameCounter:
+ ID = "FRAME_COUNTER"
+
+ def __init__(self, current_frame=0, total_frames=1, frames_per_second=25.0):
+ self.current_frame = max(0, current_frame)
+ self.total_frames = max(total_frames, 1)
+ self.frames_per_second = float(max(1.0, frames_per_second))
+
+ def incremented(self, amount: int):
+ return FrameCounter(self.current_frame + amount, self.total_frames, self.frames_per_second)
+
+ @property
+ def is_first_frame(self):
+ return self.current_frame == 0
+
+ @property
+ def is_final_frame(self):
+ return (self.current_frame + 1) == self.total_frames
+
+ @property
+ def is_after_last_frame(self):
+ return self.current_frame >= self.total_frames
+
+ @property
+ def current_time_in_seconds(self):
+ return float(self.current_frame) / self.frames_per_second
+
+ @property
+ def total_time_in_seconds(self):
+ return float(self.total_frames) / self.frames_per_second
+
+ @property
+ def remaining_time_in_seconds(self):
+ return self.total_time_in_seconds - self.current_time_in_seconds
+
+ @property
+ def progress(self):
+ return float(self.current_frame) / (max(2, self.total_frames) - 1)
+
+
+class AnimationSequence:
+ ID = "ANIMATION_SEQUENCE"
+
+ def __init__(self, frame_counter: FrameCounter, frames: Dict[int, List[str]] = None):
+ self.frames = frames
+ self.fps = frame_counter.frames_per_second
+ self.frame_counter = frame_counter
+ if self.is_defined:
+ self.keys_in_order = sorted(frames.keys())
+ self.num_batches = min(map(len, self.frames.values()))
+ else:
+ self.keys_in_order = []
+ self.num_batches = 0
+
+ @property
+ def batches(self):
+ return range(self.num_batches)
+
+ def get_image_files_of_batch(self, batch_num):
+ for key in self.keys_in_order:
+ yield self.frames[key][batch_num]
+
+ @property
+ def is_defined(self):
+ if self.frames:
+ return True
+ else:
+ return False
+
+
+class SharedTypes:
+ frame_counter = {"frame_counter": (FrameCounter.ID,)}
+ sequence = {"sequence": (AnimationSequence.ID,)}
+ palette = {"palette": (RGBPalette.ID,)}
diff --git a/custom_nodes/comfyui-dream-project/embedded_config.py b/custom_nodes/comfyui-dream-project/embedded_config.py
new file mode 100644
index 0000000000000000000000000000000000000000..b795cdcc437ea376f7a14fe949fb94125d8851c7
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/embedded_config.py
@@ -0,0 +1,41 @@
+EMBEDDED_CONFIGURATION = {
+ "ffmpeg": {
+ "file_extension": "mp4",
+ "path": "ffmpeg",
+ "arguments": ["-r", "%FPS%", "-f", "concat", "-safe", "0", "-vsync",
+ "cfr", "-i", "%FRAMES%", "-c:v", "libx264", "-pix_fmt",
+ "yuv420p", "%OUTPUT%"]
+ },
+ "mpeg_coder": {
+ "encoding_threads": 4,
+ "bitrate_factor": 1.0,
+ "max_b_frame": 2,
+ "file_extension": "mp4",
+ "codec_name": "libx264"
+ },
+ "encoding": {
+ "jpeg_quality": 95
+ },
+ "debug": False,
+ "ui": {
+ "top_category": "Dream",
+ "prepend_icon_to_category": True,
+ "append_icon_to_category": False,
+ "prepend_icon_to_node": True,
+ "append_icon_to_node": False,
+ "category_icons": {
+ "animation": "🎥",
+ "postprocessing": "⚙",
+ "transforms": "🔀",
+ "curves": "📈",
+ "color": "🎨",
+ "generate": "⚡",
+ "utils": "🛠",
+ "image": "🌄",
+ "switches": "⭆",
+ "conditioning": "☯",
+ "Dream": "✨"
+ }
+ },
+
+}
diff --git a/custom_nodes/comfyui-dream-project/enable.py b/custom_nodes/comfyui-dream-project/enable.py
new file mode 100644
index 0000000000000000000000000000000000000000..32c0cabcbb2cac55bc0650c74041a3606054792d
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/enable.py
@@ -0,0 +1,7 @@
+# -*- coding: utf-8 -*-
+def run_enable():
+ pass
+
+
+if __name__ == "__main__":
+ run_enable()
diff --git a/custom_nodes/comfyui-dream-project/err.py b/custom_nodes/comfyui-dream-project/err.py
new file mode 100644
index 0000000000000000000000000000000000000000..8d0e16fd1da58843d6540f9e7784b975af1408db
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/err.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+def _get_node_name(cls):
+ return cls.__dict__.get("NODE_NAME", str(cls))
+
+
+def on_error(node_cls: type, message: str):
+ msg = "Failure in [" + _get_node_name(node_cls) + "]:" + message
+ print(msg)
+ raise Exception(msg)
diff --git a/custom_nodes/comfyui-dream-project/examples/area-sampled-noise.json b/custom_nodes/comfyui-dream-project/examples/area-sampled-noise.json
new file mode 100644
index 0000000000000000000000000000000000000000..c75e02b646948e6147e778538d9a15149d53b3f9
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/examples/area-sampled-noise.json
@@ -0,0 +1,1475 @@
+{
+ "last_node_id": 32,
+ "last_link_id": 37,
+ "nodes": [
+ {
+ "id": 1,
+ "type": "LoadImage",
+ "pos": [
+ -290,
+ 250
+ ],
+ "size": {
+ "0": 430,
+ "1": 530
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "forest (2).jpg",
+ "image"
+ ]
+ },
+ {
+ "id": 4,
+ "type": "Sample Image Area as Palette [Dream]",
+ "pos": [
+ 530,
+ 440
+ ],
+ "size": {
+ "0": 315,
+ "1": 130
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 1
+ }
+ ],
+ "outputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "links": [
+ 18,
+ 32
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Sample Image Area as Palette [Dream]"
+ },
+ "widgets_values": [
+ 256,
+ 6263246444646,
+ "randomize",
+ "center"
+ ]
+ },
+ {
+ "id": 12,
+ "type": "Sample Image Area as Palette [Dream]",
+ "pos": [
+ 530,
+ 70
+ ],
+ "size": {
+ "0": 315,
+ "1": 130
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 2
+ }
+ ],
+ "outputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "links": [
+ 15,
+ 30
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Sample Image Area as Palette [Dream]"
+ },
+ "widgets_values": [
+ 256,
+ 602439956783214,
+ "randomize",
+ "top-right"
+ ]
+ },
+ {
+ "id": 5,
+ "type": "Sample Image Area as Palette [Dream]",
+ "pos": [
+ 530,
+ 250
+ ],
+ "size": {
+ "0": 315,
+ "1": 130
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 3
+ }
+ ],
+ "outputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "links": [
+ 17,
+ 31
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Sample Image Area as Palette [Dream]"
+ },
+ "widgets_values": [
+ 256,
+ 347895810515905,
+ "randomize",
+ "center-left"
+ ]
+ },
+ {
+ "id": 6,
+ "type": "Sample Image Area as Palette [Dream]",
+ "pos": [
+ 530,
+ 640
+ ],
+ "size": {
+ "0": 315,
+ "1": 130
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 4
+ }
+ ],
+ "outputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "links": [
+ 19,
+ 33
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Sample Image Area as Palette [Dream]"
+ },
+ "widgets_values": [
+ 256,
+ 442018658189454,
+ "randomize",
+ "center-right"
+ ]
+ },
+ {
+ "id": 7,
+ "type": "Sample Image Area as Palette [Dream]",
+ "pos": [
+ 530,
+ 810
+ ],
+ "size": {
+ "0": 315,
+ "1": 130
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 5
+ }
+ ],
+ "outputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "links": [
+ 20,
+ 34
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Sample Image Area as Palette [Dream]"
+ },
+ "widgets_values": [
+ 256,
+ 369707362068911,
+ "randomize",
+ "bottom-left"
+ ]
+ },
+ {
+ "id": 10,
+ "type": "Sample Image Area as Palette [Dream]",
+ "pos": [
+ 530,
+ 1010
+ ],
+ "size": {
+ "0": 315,
+ "1": 130
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 6
+ }
+ ],
+ "outputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "links": [
+ 21,
+ 35
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Sample Image Area as Palette [Dream]"
+ },
+ "widgets_values": [
+ 256,
+ 495981514872635,
+ "randomize",
+ "bottom-center"
+ ]
+ },
+ {
+ "id": 8,
+ "type": "Sample Image Area as Palette [Dream]",
+ "pos": [
+ 530,
+ 1190
+ ],
+ "size": {
+ "0": 315,
+ "1": 130
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 7
+ }
+ ],
+ "outputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "links": [
+ 22,
+ 36
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Sample Image Area as Palette [Dream]"
+ },
+ "widgets_values": [
+ 256,
+ 531491245299573,
+ "randomize",
+ "bottom-right"
+ ]
+ },
+ {
+ "id": 9,
+ "type": "Sample Image Area as Palette [Dream]",
+ "pos": [
+ 530,
+ -110
+ ],
+ "size": {
+ "0": 315,
+ "1": 130
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 8
+ }
+ ],
+ "outputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "links": [
+ 14,
+ 29
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Sample Image Area as Palette [Dream]"
+ },
+ "widgets_values": [
+ 256,
+ 275748471798978,
+ "randomize",
+ "top-center"
+ ]
+ },
+ {
+ "id": 11,
+ "type": "Sample Image Area as Palette [Dream]",
+ "pos": [
+ 530,
+ -300
+ ],
+ "size": {
+ "0": 315,
+ "1": 130
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 9,
+ "slot_index": 0
+ }
+ ],
+ "outputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "links": [
+ 10,
+ 28
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Sample Image Area as Palette [Dream]"
+ },
+ "widgets_values": [
+ 256,
+ 1078875074929860,
+ "randomize",
+ "top-left"
+ ]
+ },
+ {
+ "id": 21,
+ "type": "Noise from Palette [Dream]",
+ "pos": [
+ 1050,
+ 620
+ ],
+ "size": {
+ "0": 315,
+ "1": 178
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "link": 18
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 23
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Noise from Palette [Dream]"
+ },
+ "widgets_values": [
+ 256,
+ 256,
+ 0.3,
+ 0.5,
+ 524929391212381,
+ "randomize"
+ ]
+ },
+ {
+ "id": 17,
+ "type": "Noise from Palette [Dream]",
+ "pos": [
+ 1050,
+ 80
+ ],
+ "size": {
+ "0": 315,
+ "1": 178
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "link": 15
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 13
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Noise from Palette [Dream]"
+ },
+ "widgets_values": [
+ 256,
+ 256,
+ 0.3,
+ 0.5,
+ 78435709751137,
+ "randomize"
+ ]
+ },
+ {
+ "id": 19,
+ "type": "Noise from Palette [Dream]",
+ "pos": [
+ 1050,
+ 330
+ ],
+ "size": {
+ "0": 315,
+ "1": 178
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "link": 17
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 16
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Noise from Palette [Dream]"
+ },
+ "widgets_values": [
+ 256,
+ 256,
+ 0.3,
+ 0.5,
+ 525418659561865,
+ "randomize"
+ ]
+ },
+ {
+ "id": 23,
+ "type": "Noise from Palette [Dream]",
+ "pos": [
+ 1050,
+ 870
+ ],
+ "size": {
+ "0": 315,
+ "1": 178
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "link": 19
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 24
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Noise from Palette [Dream]"
+ },
+ "widgets_values": [
+ 256,
+ 256,
+ 0.3,
+ 0.5,
+ 635328469053725,
+ "randomize"
+ ]
+ },
+ {
+ "id": 25,
+ "type": "Noise from Palette [Dream]",
+ "pos": [
+ 1050,
+ 1150
+ ],
+ "size": {
+ "0": 315,
+ "1": 178
+ },
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "link": 20
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 25
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Noise from Palette [Dream]"
+ },
+ "widgets_values": [
+ 256,
+ 256,
+ 0.3,
+ 0.5,
+ 383192376875704,
+ "randomize"
+ ]
+ },
+ {
+ "id": 27,
+ "type": "Noise from Palette [Dream]",
+ "pos": [
+ 1050,
+ 1440
+ ],
+ "size": {
+ "0": 315,
+ "1": 178
+ },
+ "flags": {},
+ "order": 15,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "link": 21
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 26
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Noise from Palette [Dream]"
+ },
+ "widgets_values": [
+ 256,
+ 256,
+ 0.3,
+ 0.5,
+ 1096999909838714,
+ "randomize"
+ ]
+ },
+ {
+ "id": 29,
+ "type": "Noise from Palette [Dream]",
+ "pos": [
+ 1050,
+ 1690
+ ],
+ "size": {
+ "0": 315,
+ "1": 178
+ },
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "link": 22
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 27
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Noise from Palette [Dream]"
+ },
+ "widgets_values": [
+ 256,
+ 256,
+ 0.3,
+ 0.5,
+ 945264383034375,
+ "randomize"
+ ]
+ },
+ {
+ "id": 15,
+ "type": "Noise from Palette [Dream]",
+ "pos": [
+ 1050,
+ -210
+ ],
+ "size": {
+ "0": 315,
+ "1": 178
+ },
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "link": 14
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 12
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Noise from Palette [Dream]"
+ },
+ "widgets_values": [
+ 256,
+ 256,
+ 0.3,
+ 0.5,
+ 385670812107820,
+ "randomize"
+ ]
+ },
+ {
+ "id": 13,
+ "type": "Noise from Palette [Dream]",
+ "pos": [
+ 1050,
+ -490
+ ],
+ "size": {
+ "0": 315,
+ "1": 178
+ },
+ "flags": {},
+ "order": 18,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "link": 10
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 11
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Noise from Palette [Dream]"
+ },
+ "widgets_values": [
+ 256,
+ 256,
+ 0.3,
+ 0.5,
+ 869209746177441,
+ "randomize"
+ ]
+ },
+ {
+ "id": 31,
+ "type": "Noise from Area Palettes [Dream]",
+ "pos": [
+ 1810,
+ 140
+ ],
+ "size": {
+ "0": 342.5999755859375,
+ "1": 362
+ },
+ "flags": {},
+ "order": 19,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "top_left_palette",
+ "type": "RGB_PALETTE",
+ "link": 28
+ },
+ {
+ "name": "top_center_palette",
+ "type": "RGB_PALETTE",
+ "link": 29
+ },
+ {
+ "name": "top_right_palette",
+ "type": "RGB_PALETTE",
+ "link": 30
+ },
+ {
+ "name": "center_left_palette",
+ "type": "RGB_PALETTE",
+ "link": 31
+ },
+ {
+ "name": "center_palette",
+ "type": "RGB_PALETTE",
+ "link": 32
+ },
+ {
+ "name": "center_right_palette",
+ "type": "RGB_PALETTE",
+ "link": 33
+ },
+ {
+ "name": "bottom_left_palette",
+ "type": "RGB_PALETTE",
+ "link": 34
+ },
+ {
+ "name": "bottom_center_palette",
+ "type": "RGB_PALETTE",
+ "link": 35
+ },
+ {
+ "name": "bottom_right_palette",
+ "type": "RGB_PALETTE",
+ "link": 36
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 37
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Noise from Area Palettes [Dream]"
+ },
+ "widgets_values": [
+ 0.5,
+ 512,
+ 512,
+ 0.22727050781249997,
+ 0.5,
+ 336106403318857,
+ "randomize"
+ ]
+ },
+ {
+ "id": 22,
+ "type": "PreviewImage",
+ "pos": [
+ 1580,
+ 540
+ ],
+ "size": [
+ 140,
+ 250
+ ],
+ "flags": {},
+ "order": 20,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 23
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 18,
+ "type": "PreviewImage",
+ "pos": [
+ 1580,
+ 60
+ ],
+ "size": [
+ 140,
+ 190
+ ],
+ "flags": {},
+ "order": 21,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 13
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 20,
+ "type": "PreviewImage",
+ "pos": [
+ 1580,
+ 310
+ ],
+ "size": [
+ 140,
+ 180
+ ],
+ "flags": {},
+ "order": 22,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 16
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 24,
+ "type": "PreviewImage",
+ "pos": [
+ 1580,
+ 840
+ ],
+ "size": [
+ 140,
+ 180
+ ],
+ "flags": {},
+ "order": 23,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 24
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 26,
+ "type": "PreviewImage",
+ "pos": [
+ 1580,
+ 1070
+ ],
+ "size": [
+ 140,
+ 250
+ ],
+ "flags": {},
+ "order": 24,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 25
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 28,
+ "type": "PreviewImage",
+ "pos": [
+ 1580,
+ 1390
+ ],
+ "size": [
+ 140,
+ 250
+ ],
+ "flags": {},
+ "order": 25,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 26
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 30,
+ "type": "PreviewImage",
+ "pos": [
+ 1580,
+ 1690
+ ],
+ "size": [
+ 140,
+ 250
+ ],
+ "flags": {},
+ "order": 26,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 27
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 16,
+ "type": "PreviewImage",
+ "pos": [
+ 1580,
+ -240
+ ],
+ "size": [
+ 140,
+ 250
+ ],
+ "flags": {},
+ "order": 27,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 12
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 14,
+ "type": "PreviewImage",
+ "pos": [
+ 1580,
+ -540
+ ],
+ "size": [
+ 140,
+ 250
+ ],
+ "flags": {},
+ "order": 28,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 11
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 32,
+ "type": "PreviewImage",
+ "pos": [
+ 1790,
+ 600
+ ],
+ "size": [
+ 460,
+ 510
+ ],
+ "flags": {},
+ "order": 29,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 37
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ }
+ ],
+ "links": [
+ [
+ 1,
+ 1,
+ 0,
+ 4,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 2,
+ 1,
+ 0,
+ 12,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 3,
+ 1,
+ 0,
+ 5,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 4,
+ 1,
+ 0,
+ 6,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5,
+ 1,
+ 0,
+ 7,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 6,
+ 1,
+ 0,
+ 10,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 7,
+ 1,
+ 0,
+ 8,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 8,
+ 1,
+ 0,
+ 9,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 9,
+ 1,
+ 0,
+ 11,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 10,
+ 11,
+ 0,
+ 13,
+ 0,
+ "RGB_PALETTE"
+ ],
+ [
+ 11,
+ 13,
+ 0,
+ 14,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 12,
+ 15,
+ 0,
+ 16,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 13,
+ 17,
+ 0,
+ 18,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 14,
+ 9,
+ 0,
+ 15,
+ 0,
+ "RGB_PALETTE"
+ ],
+ [
+ 15,
+ 12,
+ 0,
+ 17,
+ 0,
+ "RGB_PALETTE"
+ ],
+ [
+ 16,
+ 19,
+ 0,
+ 20,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 17,
+ 5,
+ 0,
+ 19,
+ 0,
+ "RGB_PALETTE"
+ ],
+ [
+ 18,
+ 4,
+ 0,
+ 21,
+ 0,
+ "RGB_PALETTE"
+ ],
+ [
+ 19,
+ 6,
+ 0,
+ 23,
+ 0,
+ "RGB_PALETTE"
+ ],
+ [
+ 20,
+ 7,
+ 0,
+ 25,
+ 0,
+ "RGB_PALETTE"
+ ],
+ [
+ 21,
+ 10,
+ 0,
+ 27,
+ 0,
+ "RGB_PALETTE"
+ ],
+ [
+ 22,
+ 8,
+ 0,
+ 29,
+ 0,
+ "RGB_PALETTE"
+ ],
+ [
+ 23,
+ 21,
+ 0,
+ 22,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 24,
+ 23,
+ 0,
+ 24,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 25,
+ 25,
+ 0,
+ 26,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 26,
+ 27,
+ 0,
+ 28,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 27,
+ 29,
+ 0,
+ 30,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 28,
+ 11,
+ 0,
+ 31,
+ 0,
+ "RGB_PALETTE"
+ ],
+ [
+ 29,
+ 9,
+ 0,
+ 31,
+ 1,
+ "RGB_PALETTE"
+ ],
+ [
+ 30,
+ 12,
+ 0,
+ 31,
+ 2,
+ "RGB_PALETTE"
+ ],
+ [
+ 31,
+ 5,
+ 0,
+ 31,
+ 3,
+ "RGB_PALETTE"
+ ],
+ [
+ 32,
+ 4,
+ 0,
+ 31,
+ 4,
+ "RGB_PALETTE"
+ ],
+ [
+ 33,
+ 6,
+ 0,
+ 31,
+ 5,
+ "RGB_PALETTE"
+ ],
+ [
+ 34,
+ 7,
+ 0,
+ 31,
+ 6,
+ "RGB_PALETTE"
+ ],
+ [
+ 35,
+ 10,
+ 0,
+ 31,
+ 7,
+ "RGB_PALETTE"
+ ],
+ [
+ 36,
+ 8,
+ 0,
+ 31,
+ 8,
+ "RGB_PALETTE"
+ ],
+ [
+ 37,
+ 31,
+ 0,
+ 32,
+ 0,
+ "IMAGE"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui-dream-project/examples/laboratory.json b/custom_nodes/comfyui-dream-project/examples/laboratory.json
new file mode 100644
index 0000000000000000000000000000000000000000..19d2337c87d480a5693e9f79a679af95fb9a422d
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/examples/laboratory.json
@@ -0,0 +1,3626 @@
+{
+ "last_node_id": 80,
+ "last_link_id": 116,
+ "nodes": [
+ {
+ "id": 14,
+ "type": "Build Prompt [Dream]",
+ "pos": [
+ 1010,
+ -280
+ ],
+ "size": [
+ 245.1999969482422,
+ 108.295654296875
+ ],
+ "flags": {},
+ "order": 39,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "partial_prompt",
+ "type": "PARTIAL_PROMPT",
+ "link": 13
+ },
+ {
+ "name": "weight",
+ "type": "FLOAT",
+ "link": 23,
+ "widget": {
+ "name": "weight",
+ "config": [
+ "FLOAT",
+ {
+ "default": 1
+ }
+ ]
+ }
+ },
+ {
+ "name": "added_prompt",
+ "type": "STRING",
+ "link": 84,
+ "widget": {
+ "name": "added_prompt",
+ "config": [
+ "STRING",
+ {
+ "default": "",
+ "multiline": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "partial_prompt",
+ "type": "PARTIAL_PROMPT",
+ "links": [
+ 75
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Build Prompt [Dream]"
+ },
+ "widgets_values": [
+ "rainbow",
+ 1
+ ]
+ },
+ {
+ "id": 13,
+ "type": "Build Prompt [Dream]",
+ "pos": [
+ 730,
+ -280
+ ],
+ "size": [
+ 245.1999969482422,
+ 108.295654296875
+ ],
+ "flags": {},
+ "order": 36,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "partial_prompt",
+ "type": "PARTIAL_PROMPT",
+ "link": 12
+ },
+ {
+ "name": "weight",
+ "type": "FLOAT",
+ "link": 22,
+ "widget": {
+ "name": "weight",
+ "config": [
+ "FLOAT",
+ {
+ "default": 1
+ }
+ ]
+ }
+ },
+ {
+ "name": "added_prompt",
+ "type": "STRING",
+ "link": 83,
+ "widget": {
+ "name": "added_prompt",
+ "config": [
+ "STRING",
+ {
+ "default": "",
+ "multiline": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "partial_prompt",
+ "type": "PARTIAL_PROMPT",
+ "links": [
+ 13
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Build Prompt [Dream]"
+ },
+ "widgets_values": [
+ "christmas",
+ 1
+ ]
+ },
+ {
+ "id": 51,
+ "type": "Reroute",
+ "pos": [
+ 1469.1866925231607,
+ 562.588884695758
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 20,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 64,
+ "slot_index": 0
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 63
+ ]
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 52,
+ "type": "Reroute",
+ "pos": [
+ -428.86312922343996,
+ 564.5376609822581
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 65
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 64
+ ]
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 10,
+ "type": "Build Prompt [Dream]",
+ "pos": [
+ -173,
+ -284
+ ],
+ "size": [
+ 245.1999969482422,
+ 108.295654296875
+ ],
+ "flags": {},
+ "order": 26,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "partial_prompt",
+ "type": "PARTIAL_PROMPT",
+ "link": null
+ },
+ {
+ "name": "weight",
+ "type": "FLOAT",
+ "link": 19,
+ "widget": {
+ "name": "weight",
+ "config": [
+ "FLOAT",
+ {
+ "default": 1
+ }
+ ]
+ }
+ },
+ {
+ "name": "added_prompt",
+ "type": "STRING",
+ "link": 77,
+ "widget": {
+ "name": "added_prompt",
+ "config": [
+ "STRING",
+ {
+ "default": "",
+ "multiline": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "partial_prompt",
+ "type": "PARTIAL_PROMPT",
+ "links": [
+ 10
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Build Prompt [Dream]"
+ },
+ "widgets_values": [
+ "house",
+ 1
+ ]
+ },
+ {
+ "id": 61,
+ "type": "PrimitiveNode",
+ "pos": [
+ 376,
+ 294
+ ],
+ "size": {
+ "0": 210,
+ "1": 82
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 80
+ ],
+ "widget": {
+ "name": "selected",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0
+ }
+ ]
+ },
+ "slot_index": 0
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ 624,
+ "randomize"
+ ]
+ },
+ {
+ "id": 12,
+ "type": "Build Prompt [Dream]",
+ "pos": [
+ 430,
+ -280
+ ],
+ "size": [
+ 245.1999969482422,
+ 108.295654296875
+ ],
+ "flags": {},
+ "order": 33,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "partial_prompt",
+ "type": "PARTIAL_PROMPT",
+ "link": 11
+ },
+ {
+ "name": "weight",
+ "type": "FLOAT",
+ "link": 21,
+ "widget": {
+ "name": "weight",
+ "config": [
+ "FLOAT",
+ {
+ "default": 1
+ }
+ ]
+ }
+ },
+ {
+ "name": "added_prompt",
+ "type": "STRING",
+ "link": 81,
+ "widget": {
+ "name": "added_prompt",
+ "config": [
+ "STRING",
+ {
+ "default": "",
+ "multiline": true
+ }
+ ]
+ },
+ "slot_index": 2
+ }
+ ],
+ "outputs": [
+ {
+ "name": "partial_prompt",
+ "type": "PARTIAL_PROMPT",
+ "links": [
+ 12
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Build Prompt [Dream]"
+ },
+ "widgets_values": [
+ "jungle",
+ 1
+ ]
+ },
+ {
+ "id": 11,
+ "type": "Build Prompt [Dream]",
+ "pos": [
+ 140,
+ -286
+ ],
+ "size": [
+ 245.1999969482422,
+ 108.295654296875
+ ],
+ "flags": {},
+ "order": 30,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "partial_prompt",
+ "type": "PARTIAL_PROMPT",
+ "link": 10
+ },
+ {
+ "name": "weight",
+ "type": "FLOAT",
+ "link": 20,
+ "widget": {
+ "name": "weight",
+ "config": [
+ "FLOAT",
+ {
+ "default": 1
+ }
+ ]
+ }
+ },
+ {
+ "name": "added_prompt",
+ "type": "STRING",
+ "link": 79,
+ "widget": {
+ "name": "added_prompt",
+ "config": [
+ "STRING",
+ {
+ "default": "",
+ "multiline": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "partial_prompt",
+ "type": "PARTIAL_PROMPT",
+ "links": [
+ 11
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Build Prompt [Dream]"
+ },
+ "widgets_values": [
+ "horse",
+ 1
+ ]
+ },
+ {
+ "id": 60,
+ "type": "String Tokenizer [Dream]",
+ "pos": [
+ 370,
+ 79
+ ],
+ "size": {
+ "0": 325.8482971191406,
+ "1": 157.50692749023438
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "selected",
+ "type": "INT",
+ "link": 80,
+ "widget": {
+ "name": "selected",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0
+ }
+ ]
+ },
+ "slot_index": 0
+ }
+ ],
+ "outputs": [
+ {
+ "name": "token",
+ "type": "STRING",
+ "links": [
+ 81
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "String Tokenizer [Dream]"
+ },
+ "widgets_values": [
+ "watercolor, anime, ink sketch, photo, oil painting, graffiti, glass mosaic, charcoal art, comic book art, impressionist, old photo, pixel art, ",
+ ",",
+ 1360
+ ]
+ },
+ {
+ "id": 63,
+ "type": "PrimitiveNode",
+ "pos": [
+ 755,
+ 286
+ ],
+ "size": {
+ "0": 210,
+ "1": 82
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 82
+ ],
+ "widget": {
+ "name": "selected",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0
+ }
+ ]
+ }
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ 1787,
+ "randomize"
+ ]
+ },
+ {
+ "id": 62,
+ "type": "String Tokenizer [Dream]",
+ "pos": [
+ 736,
+ 77
+ ],
+ "size": {
+ "0": 325.8482971191406,
+ "1": 157.50692749023438
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "selected",
+ "type": "INT",
+ "link": 82,
+ "widget": {
+ "name": "selected",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0
+ }
+ ]
+ },
+ "slot_index": 0
+ }
+ ],
+ "outputs": [
+ {
+ "name": "token",
+ "type": "STRING",
+ "links": [
+ 83
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "String Tokenizer [Dream]"
+ },
+ "widgets_values": [
+ "christmas, summer, winter, easter, halloween, superbowl, world cup, county fair, circus, market, celebration, birthday party, rave, horror movie, slapstick",
+ ",",
+ 207
+ ]
+ },
+ {
+ "id": 64,
+ "type": "String Tokenizer [Dream]",
+ "pos": [
+ 1119,
+ 68
+ ],
+ "size": {
+ "0": 325.8482971191406,
+ "1": 157.50692749023438
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "selected",
+ "type": "INT",
+ "link": 85,
+ "widget": {
+ "name": "selected",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0
+ }
+ ]
+ },
+ "slot_index": 0
+ }
+ ],
+ "outputs": [
+ {
+ "name": "token",
+ "type": "STRING",
+ "links": [
+ 84
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "String Tokenizer [Dream]"
+ },
+ "widgets_values": [
+ "airplane, car, school, banana, rock star, president, preacher, monk, train, sailboat, monster truck, roller coaster, shopping mall, rock concert, ",
+ ",",
+ 0
+ ]
+ },
+ {
+ "id": 58,
+ "type": "String Tokenizer [Dream]",
+ "pos": [
+ -12,
+ 87
+ ],
+ "size": {
+ "0": 325.8482971191406,
+ "1": 157.50692749023438
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "selected",
+ "type": "INT",
+ "link": 78,
+ "widget": {
+ "name": "selected",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0
+ }
+ ]
+ },
+ "slot_index": 0
+ }
+ ],
+ "outputs": [
+ {
+ "name": "token",
+ "type": "STRING",
+ "links": [
+ 79
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "String Tokenizer [Dream]"
+ },
+ "widgets_values": [
+ "horse, cat, dog, chicken, monkey, bird, elephant, pig, kitten, puppy, whale, goat, fish, tiger, teddybear, panda, rabbit",
+ ",",
+ 1886
+ ]
+ },
+ {
+ "id": 59,
+ "type": "PrimitiveNode",
+ "pos": [
+ -6,
+ 286
+ ],
+ "size": {
+ "0": 210,
+ "1": 82
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 78
+ ],
+ "widget": {
+ "name": "selected",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0
+ }
+ ]
+ }
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ 1576,
+ "randomize"
+ ]
+ },
+ {
+ "id": 55,
+ "type": "String Tokenizer [Dream]",
+ "pos": [
+ -372,
+ 84
+ ],
+ "size": [
+ 325.84829956054546,
+ 157.5069268798817
+ ],
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "selected",
+ "type": "INT",
+ "link": 76,
+ "widget": {
+ "name": "selected",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0
+ }
+ ]
+ },
+ "slot_index": 0
+ }
+ ],
+ "outputs": [
+ {
+ "name": "token",
+ "type": "STRING",
+ "links": [
+ 77
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "String Tokenizer [Dream]"
+ },
+ "widgets_values": [
+ "in a house, on a field, in a tree, in a shopping mall, in a car, on a river boat, in the forest, in the ocean, on a mountain",
+ ",",
+ 581
+ ]
+ },
+ {
+ "id": 57,
+ "type": "PrimitiveNode",
+ "pos": [
+ -359,
+ 286
+ ],
+ "size": {
+ "0": 210,
+ "1": 82
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 76
+ ],
+ "widget": {
+ "name": "selected",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0
+ }
+ ]
+ }
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ 584,
+ "randomize"
+ ]
+ },
+ {
+ "id": 69,
+ "type": "Reroute",
+ "pos": [
+ 2710.2738751686875,
+ -143.5705977615644
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 22,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 89
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 90,
+ 91
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 6,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 2887.9416937538945,
+ -120.19947392580968
+ ],
+ "size": {
+ "0": 422.84503173828125,
+ "1": 164.31304931640625
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 49,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 91
+ },
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 32,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "multiline": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 4
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 7,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 2885.9416937538945,
+ -80.19947392580966
+ ],
+ "size": {
+ "0": 425.27801513671875,
+ "1": 180.6060791015625
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 50,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 90
+ },
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 33,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "multiline": true
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 6
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "text, watermark"
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 30,
+ "type": "Finalize Prompt [Dream]",
+ "pos": [
+ 1291,
+ -275
+ ],
+ "size": {
+ "0": 315,
+ "1": 126
+ },
+ "flags": {},
+ "order": 41,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "partial_prompt",
+ "type": "PARTIAL_PROMPT",
+ "link": 75
+ }
+ ],
+ "outputs": [
+ {
+ "name": "positive",
+ "type": "STRING",
+ "links": [
+ 30,
+ 94
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "negative",
+ "type": "STRING",
+ "links": [
+ 31,
+ 95
+ ],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Finalize Prompt [Dream]"
+ },
+ "widgets_values": [
+ "raw",
+ 2,
+ 1
+ ]
+ },
+ {
+ "id": 73,
+ "type": "Log Entry Joiner [Dream]",
+ "pos": [
+ 2205,
+ -949
+ ],
+ "size": {
+ "0": 216.59999084472656,
+ "1": 86
+ },
+ "flags": {},
+ "order": 51,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "entry_0",
+ "type": "LOG_ENTRY",
+ "link": 97
+ },
+ {
+ "name": "entry_1",
+ "type": "LOG_ENTRY",
+ "link": 98
+ },
+ {
+ "name": "entry_2",
+ "type": "LOG_ENTRY",
+ "link": null
+ },
+ {
+ "name": "entry_3",
+ "type": "LOG_ENTRY",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "log_entry",
+ "type": "LOG_ENTRY",
+ "links": [
+ 102
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Log Entry Joiner [Dream]"
+ }
+ },
+ {
+ "id": 70,
+ "type": "Log Entry Joiner [Dream]",
+ "pos": [
+ 4078,
+ -952
+ ],
+ "size": {
+ "0": 216.59999084472656,
+ "1": 86
+ },
+ "flags": {},
+ "order": 55,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "entry_0",
+ "type": "LOG_ENTRY",
+ "link": 102
+ },
+ {
+ "name": "entry_1",
+ "type": "LOG_ENTRY",
+ "link": 93
+ },
+ {
+ "name": "entry_2",
+ "type": "LOG_ENTRY",
+ "link": 103
+ },
+ {
+ "name": "entry_3",
+ "type": "LOG_ENTRY",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "log_entry",
+ "type": "LOG_ENTRY",
+ "links": [
+ 104
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Log Entry Joiner [Dream]"
+ }
+ },
+ {
+ "id": 77,
+ "type": "Reroute",
+ "pos": [
+ -599.5009408776509,
+ -1151.2457658693525
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 15,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 107
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 108
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 78,
+ "type": "Reroute",
+ "pos": [
+ 5088.998468177342,
+ -835.3322583924374
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 21,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 108
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 109
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 21,
+ "type": "Laboratory [Dream]",
+ "pos": [
+ -224,
+ -946
+ ],
+ "size": {
+ "0": 315,
+ "1": 266
+ },
+ "flags": {},
+ "order": 27,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 41
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 20
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "log_entry",
+ "type": "LOG_ENTRY",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Laboratory [Dream]"
+ },
+ "widgets_values": [
+ "Random value 455046",
+ 925180296886693,
+ "randomize",
+ "every frame",
+ -0.8,
+ 1.2,
+ "random uniform",
+ 0.1
+ ]
+ },
+ {
+ "id": 23,
+ "type": "Laboratory [Dream]",
+ "pos": [
+ 353,
+ -955
+ ],
+ "size": {
+ "0": 315,
+ "1": 266
+ },
+ "flags": {},
+ "order": 34,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 45
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 22
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "log_entry",
+ "type": "LOG_ENTRY",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Laboratory [Dream]"
+ },
+ "widgets_values": [
+ "Random value 455046",
+ 290988309040769,
+ "randomize",
+ "every frame",
+ -0.8,
+ 1.2,
+ "random uniform",
+ 0.1
+ ]
+ },
+ {
+ "id": 22,
+ "type": "Laboratory [Dream]",
+ "pos": [
+ 78,
+ -614
+ ],
+ "size": {
+ "0": 315,
+ "1": 266
+ },
+ "flags": {},
+ "order": 31,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 43
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 21
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "log_entry",
+ "type": "LOG_ENTRY",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Laboratory [Dream]"
+ },
+ "widgets_values": [
+ "Random value 455046",
+ 268911751368403,
+ "randomize",
+ "every frame",
+ -0.8,
+ 1.2,
+ "random uniform",
+ 0.1
+ ]
+ },
+ {
+ "id": 24,
+ "type": "Laboratory [Dream]",
+ "pos": [
+ 658,
+ -631
+ ],
+ "size": {
+ "0": 315,
+ "1": 266
+ },
+ "flags": {},
+ "order": 37,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 47
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 23
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "log_entry",
+ "type": "LOG_ENTRY",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Laboratory [Dream]"
+ },
+ "widgets_values": [
+ "Random value 455046",
+ 350640887146024,
+ "randomize",
+ "every frame",
+ -0.8,
+ 1.2,
+ "random uniform",
+ 0.1
+ ]
+ },
+ {
+ "id": 37,
+ "type": "Frame Counter (Directory) [Dream]",
+ "pos": [
+ -1147,
+ -975
+ ],
+ "size": {
+ "0": 315,
+ "1": 154
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "directory_path",
+ "type": "STRING",
+ "link": 37,
+ "widget": {
+ "name": "directory_path",
+ "config": [
+ "STRING",
+ {
+ "default": "",
+ "multiline": false
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 38
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Frame Counter (Directory) [Dream]"
+ },
+ "widgets_values": [
+ "",
+ "*",
+ "numeric",
+ 1000,
+ 30
+ ]
+ },
+ {
+ "id": 36,
+ "type": "String Input [Dream]",
+ "pos": [
+ -1147,
+ -730
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 37,
+ 65,
+ 107
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "✍ Output Directory",
+ "properties": {
+ "Node name for S&R": "String Input [Dream]"
+ },
+ "widgets_values": [
+ "I:\\AI\\output\\ComfyUI"
+ ]
+ },
+ {
+ "id": 8,
+ "type": "VAEDecode",
+ "pos": [
+ 3956.941693753893,
+ -38.199473925809635
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 57,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 7
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 113
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 34,
+ 60
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 5,
+ "type": "EmptyLatentImage",
+ "pos": [
+ 2988.9416937538945,
+ 21.80052607419041
+ ],
+ "size": {
+ "0": 315,
+ "1": 106
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 2
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "EmptyLatentImage"
+ },
+ "widgets_values": [
+ 768,
+ 512,
+ 1
+ ]
+ },
+ {
+ "id": 53,
+ "type": "Reroute",
+ "pos": [
+ 3480,
+ -1080
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 47,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 67
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 68,
+ 100
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 47,
+ "type": "Reroute",
+ "pos": [
+ 2960,
+ -1080
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 42,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 58
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 67,
+ 99
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 46,
+ "type": "Reroute",
+ "pos": [
+ 2100,
+ -1080
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 40,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 114
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 58
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 43,
+ "type": "Reroute",
+ "pos": [
+ 780,
+ -1080
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 38,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 48
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 114
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 42,
+ "type": "Reroute",
+ "pos": [
+ 520,
+ -1080
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 35,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 46
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 47,
+ 48
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 41,
+ "type": "Reroute",
+ "pos": [
+ 200,
+ -1080
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 32,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 44
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 45,
+ 46
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 40,
+ "type": "Reroute",
+ "pos": [
+ -80,
+ -1080
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 28,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 42
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 43,
+ 44
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 39,
+ "type": "Reroute",
+ "pos": [
+ -420,
+ -1080
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 24,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 40
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 41,
+ 42
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 38,
+ "type": "Reroute",
+ "pos": [
+ -790,
+ -1080
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 19,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 38
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 39,
+ 40
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 79,
+ "type": "Reroute",
+ "pos": [
+ 4924.998468177342,
+ -785.3322583924377
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 56,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 110
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 111
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 20,
+ "type": "Laboratory [Dream]",
+ "pos": [
+ -453,
+ -621
+ ],
+ "size": {
+ "0": 315,
+ "1": 266
+ },
+ "flags": {},
+ "order": 23,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 39
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 19
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "log_entry",
+ "type": "LOG_ENTRY",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Laboratory [Dream]"
+ },
+ "widgets_values": [
+ "Random value 455046",
+ 1036088194386378,
+ "randomize",
+ "every frame",
+ -0.8,
+ 1.2,
+ "random uniform",
+ 0.1
+ ]
+ },
+ {
+ "id": 71,
+ "type": "String to Log Entry [Dream]",
+ "pos": [
+ 1726,
+ -965
+ ],
+ "size": [
+ 315,
+ 82
+ ],
+ "flags": {},
+ "order": 44,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 94,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "default": ""
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "log_entry",
+ "type": "LOG_ENTRY",
+ "links": [
+ 97
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "String to Log Entry [Dream]"
+ },
+ "widgets_values": [
+ "",
+ "Positive prompt"
+ ]
+ },
+ {
+ "id": 72,
+ "type": "String to Log Entry [Dream]",
+ "pos": [
+ 1727,
+ -832
+ ],
+ "size": [
+ 315,
+ 82
+ ],
+ "flags": {},
+ "order": 46,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 95,
+ "widget": {
+ "name": "text",
+ "config": [
+ "STRING",
+ {
+ "default": ""
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "log_entry",
+ "type": "LOG_ENTRY",
+ "links": [
+ 98
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "String to Log Entry [Dream]"
+ },
+ "widgets_values": [
+ "",
+ "Negative prompt"
+ ]
+ },
+ {
+ "id": 54,
+ "type": "Laboratory [Dream]",
+ "pos": [
+ 2666.9416937538945,
+ -521.1994739258095
+ ],
+ "size": {
+ "0": 315,
+ "1": 266
+ },
+ "flags": {},
+ "order": 48,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 99
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [],
+ "shape": 3
+ },
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 70
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "log_entry",
+ "type": "LOG_ENTRY",
+ "links": [
+ 93
+ ],
+ "shape": 3,
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Laboratory [Dream]"
+ },
+ "widgets_values": [
+ "KSampler Steps",
+ 633069612491609,
+ "randomize",
+ "every frame",
+ 15,
+ 50,
+ "random bell",
+ 0.1
+ ]
+ },
+ {
+ "id": 3,
+ "type": "KSampler",
+ "pos": [
+ 3518.941693753895,
+ -37.199473925809635
+ ],
+ "size": {
+ "0": 315,
+ "1": 262
+ },
+ "flags": {},
+ "order": 54,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 112
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 4
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 6
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 2
+ },
+ {
+ "name": "cfg",
+ "type": "FLOAT",
+ "link": 115,
+ "widget": {
+ "name": "cfg",
+ "config": [
+ "FLOAT",
+ {
+ "default": 8,
+ "min": 0,
+ "max": 100
+ }
+ ]
+ },
+ "slot_index": 4
+ },
+ {
+ "name": "steps",
+ "type": "INT",
+ "link": 70,
+ "widget": {
+ "name": "steps",
+ "config": [
+ "INT",
+ {
+ "default": 20,
+ "min": 1,
+ "max": 10000
+ }
+ ]
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 7
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 496064975436282,
+ "randomize",
+ 20,
+ 8,
+ "euler",
+ "normal",
+ 1
+ ]
+ },
+ {
+ "id": 35,
+ "type": "Laboratory [Dream]",
+ "pos": [
+ 3196.9416937538945,
+ -533.1994739258092
+ ],
+ "size": {
+ "0": 315,
+ "1": 266
+ },
+ "flags": {},
+ "order": 52,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 68
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 115
+ ],
+ "shape": 3
+ },
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "log_entry",
+ "type": "LOG_ENTRY",
+ "links": [
+ 103
+ ],
+ "shape": 3,
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Laboratory [Dream]"
+ },
+ "widgets_values": [
+ "KSampler Cfg",
+ 357990178944917,
+ "randomize",
+ "every frame",
+ 6,
+ 10,
+ "random uniform",
+ 0.1
+ ]
+ },
+ {
+ "id": 75,
+ "type": "Log Entry Joiner [Dream]",
+ "pos": [
+ 4744.998468177342,
+ -648.3322583924376
+ ],
+ "size": {
+ "0": 216.59999084472656,
+ "1": 86
+ },
+ "flags": {},
+ "order": 60,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "entry_0",
+ "type": "LOG_ENTRY",
+ "link": 104
+ },
+ {
+ "name": "entry_1",
+ "type": "LOG_ENTRY",
+ "link": 105
+ },
+ {
+ "name": "entry_2",
+ "type": "LOG_ENTRY",
+ "link": null
+ },
+ {
+ "name": "entry_3",
+ "type": "LOG_ENTRY",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "log_entry",
+ "type": "LOG_ENTRY",
+ "links": [
+ 106
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Log Entry Joiner [Dream]"
+ }
+ },
+ {
+ "id": 76,
+ "type": "Log File [Dream]",
+ "pos": [
+ 5097.998468177342,
+ -397.3322583924378
+ ],
+ "size": [
+ 315,
+ 314
+ ],
+ "flags": {},
+ "order": 61,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 111
+ },
+ {
+ "name": "entry_0",
+ "type": "LOG_ENTRY",
+ "link": 106
+ },
+ {
+ "name": "entry_1",
+ "type": "LOG_ENTRY",
+ "link": null
+ },
+ {
+ "name": "entry_2",
+ "type": "LOG_ENTRY",
+ "link": null
+ },
+ {
+ "name": "entry_3",
+ "type": "LOG_ENTRY",
+ "link": null
+ },
+ {
+ "name": "entry_4",
+ "type": "LOG_ENTRY",
+ "link": null
+ },
+ {
+ "name": "entry_5",
+ "type": "LOG_ENTRY",
+ "link": null
+ },
+ {
+ "name": "entry_6",
+ "type": "LOG_ENTRY",
+ "link": null
+ },
+ {
+ "name": "entry_7",
+ "type": "LOG_ENTRY",
+ "link": null
+ },
+ {
+ "name": "log_directory",
+ "type": "STRING",
+ "link": 109,
+ "widget": {
+ "name": "log_directory",
+ "config": [
+ "STRING",
+ {
+ "default": "I:\\AI\\ComfyUI\\ComfyUI\\output"
+ }
+ ]
+ }
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Log File [Dream]"
+ },
+ "widgets_values": [
+ "I:\\AI\\ComfyUI\\ComfyUI\\output",
+ "dreamlog.txt",
+ true,
+ true,
+ true
+ ]
+ },
+ {
+ "id": 48,
+ "type": "Image Sequence Saver [Dream]",
+ "pos": [
+ 4466.998468177342,
+ -343.33225839243767
+ ],
+ "size": {
+ "0": 315,
+ "1": 174
+ },
+ "flags": {},
+ "order": 59,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 101
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 60,
+ "slot_index": 1
+ },
+ {
+ "name": "directory_path",
+ "type": "STRING",
+ "link": 61,
+ "widget": {
+ "name": "directory_path",
+ "config": [
+ "STRING",
+ {
+ "default": "I:\\AI\\ComfyUI\\ComfyUI\\output",
+ "multiline": false
+ }
+ ]
+ },
+ "slot_index": 2
+ }
+ ],
+ "outputs": [
+ {
+ "name": "sequence",
+ "type": "ANIMATION_SEQUENCE",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "log_entry",
+ "type": "LOG_ENTRY",
+ "links": [
+ 105
+ ],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Image Sequence Saver [Dream]"
+ },
+ "widgets_values": [
+ "I:\\AI\\ComfyUI\\ComfyUI\\output",
+ "frame",
+ 5,
+ "stop output",
+ "png with embedded workflow"
+ ]
+ },
+ {
+ "id": 33,
+ "type": "PreviewImage",
+ "pos": [
+ 4443.998468177342,
+ -65.33225839243688
+ ],
+ "size": [
+ 815.909842426252,
+ 588.7536164327751
+ ],
+ "flags": {},
+ "order": 58,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 34
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 31,
+ "type": "Reroute",
+ "pos": [
+ 2356,
+ -285
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 43,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 30
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 32
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 32,
+ "type": "Reroute",
+ "pos": [
+ 2360,
+ -246
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 45,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 31
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 33
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 67,
+ "type": "Reroute",
+ "pos": [
+ 2490,
+ 674
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 87
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 89
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 50,
+ "type": "Reroute",
+ "pos": [
+ 3374,
+ 571
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 25,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 63,
+ "slot_index": 0
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 62
+ ]
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 66,
+ "type": "Reroute",
+ "pos": [
+ 3358,
+ 638
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 86
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 112
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 68,
+ "type": "Reroute",
+ "pos": [
+ 3720,
+ 712
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 18,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 88
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 113
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 49,
+ "type": "Reroute",
+ "pos": [
+ 4173,
+ 576
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 29,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 62,
+ "slot_index": 0
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 61
+ ]
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 74,
+ "type": "Reroute",
+ "pos": [
+ 4291,
+ -1063
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 53,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 100
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 101,
+ 110
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 4,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ -1011,
+ 649
+ ],
+ "size": {
+ "0": 315,
+ "1": 98
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 86
+ ],
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 87
+ ],
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 88
+ ],
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "public\\main\\512-SD1.5\\anything-v3-fp16-pruned.safetensors"
+ ]
+ },
+ {
+ "id": 65,
+ "type": "PrimitiveNode",
+ "pos": [
+ 1133,
+ 269
+ ],
+ "size": {
+ "0": 210,
+ "1": 82
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 85
+ ],
+ "widget": {
+ "name": "selected",
+ "config": [
+ "INT",
+ {
+ "default": 0,
+ "min": 0
+ }
+ ]
+ }
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ 0,
+ "randomize"
+ ]
+ }
+ ],
+ "links": [
+ [
+ 2,
+ 5,
+ 0,
+ 3,
+ 3,
+ "LATENT"
+ ],
+ [
+ 4,
+ 6,
+ 0,
+ 3,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 6,
+ 7,
+ 0,
+ 3,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 7,
+ 3,
+ 0,
+ 8,
+ 0,
+ "LATENT"
+ ],
+ [
+ 10,
+ 10,
+ 0,
+ 11,
+ 0,
+ "PARTIAL_PROMPT"
+ ],
+ [
+ 11,
+ 11,
+ 0,
+ 12,
+ 0,
+ "PARTIAL_PROMPT"
+ ],
+ [
+ 12,
+ 12,
+ 0,
+ 13,
+ 0,
+ "PARTIAL_PROMPT"
+ ],
+ [
+ 13,
+ 13,
+ 0,
+ 14,
+ 0,
+ "PARTIAL_PROMPT"
+ ],
+ [
+ 19,
+ 20,
+ 0,
+ 10,
+ 1,
+ "FLOAT"
+ ],
+ [
+ 20,
+ 21,
+ 0,
+ 11,
+ 1,
+ "FLOAT"
+ ],
+ [
+ 21,
+ 22,
+ 0,
+ 12,
+ 1,
+ "FLOAT"
+ ],
+ [
+ 22,
+ 23,
+ 0,
+ 13,
+ 1,
+ "FLOAT"
+ ],
+ [
+ 23,
+ 24,
+ 0,
+ 14,
+ 1,
+ "FLOAT"
+ ],
+ [
+ 30,
+ 30,
+ 0,
+ 31,
+ 0,
+ "*"
+ ],
+ [
+ 31,
+ 30,
+ 1,
+ 32,
+ 0,
+ "*"
+ ],
+ [
+ 32,
+ 31,
+ 0,
+ 6,
+ 1,
+ "STRING"
+ ],
+ [
+ 33,
+ 32,
+ 0,
+ 7,
+ 1,
+ "STRING"
+ ],
+ [
+ 34,
+ 8,
+ 0,
+ 33,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 37,
+ 36,
+ 0,
+ 37,
+ 0,
+ "STRING"
+ ],
+ [
+ 38,
+ 37,
+ 0,
+ 38,
+ 0,
+ "*"
+ ],
+ [
+ 39,
+ 38,
+ 0,
+ 20,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 40,
+ 38,
+ 0,
+ 39,
+ 0,
+ "*"
+ ],
+ [
+ 41,
+ 39,
+ 0,
+ 21,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 42,
+ 39,
+ 0,
+ 40,
+ 0,
+ "*"
+ ],
+ [
+ 43,
+ 40,
+ 0,
+ 22,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 44,
+ 40,
+ 0,
+ 41,
+ 0,
+ "*"
+ ],
+ [
+ 45,
+ 41,
+ 0,
+ 23,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 46,
+ 41,
+ 0,
+ 42,
+ 0,
+ "*"
+ ],
+ [
+ 47,
+ 42,
+ 0,
+ 24,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 48,
+ 42,
+ 0,
+ 43,
+ 0,
+ "*"
+ ],
+ [
+ 58,
+ 46,
+ 0,
+ 47,
+ 0,
+ "*"
+ ],
+ [
+ 60,
+ 8,
+ 0,
+ 48,
+ 1,
+ "IMAGE"
+ ],
+ [
+ 61,
+ 49,
+ 0,
+ 48,
+ 2,
+ "STRING"
+ ],
+ [
+ 62,
+ 50,
+ 0,
+ 49,
+ 0,
+ "*"
+ ],
+ [
+ 63,
+ 51,
+ 0,
+ 50,
+ 0,
+ "*"
+ ],
+ [
+ 64,
+ 52,
+ 0,
+ 51,
+ 0,
+ "*"
+ ],
+ [
+ 65,
+ 36,
+ 0,
+ 52,
+ 0,
+ "*"
+ ],
+ [
+ 67,
+ 47,
+ 0,
+ 53,
+ 0,
+ "*"
+ ],
+ [
+ 68,
+ 53,
+ 0,
+ 35,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 70,
+ 54,
+ 1,
+ 3,
+ 5,
+ "INT"
+ ],
+ [
+ 75,
+ 14,
+ 0,
+ 30,
+ 0,
+ "PARTIAL_PROMPT"
+ ],
+ [
+ 76,
+ 57,
+ 0,
+ 55,
+ 0,
+ "INT"
+ ],
+ [
+ 77,
+ 55,
+ 0,
+ 10,
+ 2,
+ "STRING"
+ ],
+ [
+ 78,
+ 59,
+ 0,
+ 58,
+ 0,
+ "INT"
+ ],
+ [
+ 79,
+ 58,
+ 0,
+ 11,
+ 2,
+ "STRING"
+ ],
+ [
+ 80,
+ 61,
+ 0,
+ 60,
+ 0,
+ "INT"
+ ],
+ [
+ 81,
+ 60,
+ 0,
+ 12,
+ 2,
+ "STRING"
+ ],
+ [
+ 82,
+ 63,
+ 0,
+ 62,
+ 0,
+ "INT"
+ ],
+ [
+ 83,
+ 62,
+ 0,
+ 13,
+ 2,
+ "STRING"
+ ],
+ [
+ 84,
+ 64,
+ 0,
+ 14,
+ 2,
+ "STRING"
+ ],
+ [
+ 85,
+ 65,
+ 0,
+ 64,
+ 0,
+ "INT"
+ ],
+ [
+ 86,
+ 4,
+ 0,
+ 66,
+ 0,
+ "*"
+ ],
+ [
+ 87,
+ 4,
+ 1,
+ 67,
+ 0,
+ "*"
+ ],
+ [
+ 88,
+ 4,
+ 2,
+ 68,
+ 0,
+ "*"
+ ],
+ [
+ 89,
+ 67,
+ 0,
+ 69,
+ 0,
+ "*"
+ ],
+ [
+ 90,
+ 69,
+ 0,
+ 7,
+ 0,
+ "CLIP"
+ ],
+ [
+ 91,
+ 69,
+ 0,
+ 6,
+ 0,
+ "CLIP"
+ ],
+ [
+ 93,
+ 54,
+ 2,
+ 70,
+ 1,
+ "LOG_ENTRY"
+ ],
+ [
+ 94,
+ 30,
+ 0,
+ 71,
+ 0,
+ "STRING"
+ ],
+ [
+ 95,
+ 30,
+ 1,
+ 72,
+ 0,
+ "STRING"
+ ],
+ [
+ 97,
+ 71,
+ 0,
+ 73,
+ 0,
+ "LOG_ENTRY"
+ ],
+ [
+ 98,
+ 72,
+ 0,
+ 73,
+ 1,
+ "LOG_ENTRY"
+ ],
+ [
+ 99,
+ 47,
+ 0,
+ 54,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 100,
+ 53,
+ 0,
+ 74,
+ 0,
+ "*"
+ ],
+ [
+ 101,
+ 74,
+ 0,
+ 48,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 102,
+ 73,
+ 0,
+ 70,
+ 0,
+ "LOG_ENTRY"
+ ],
+ [
+ 103,
+ 35,
+ 2,
+ 70,
+ 2,
+ "LOG_ENTRY"
+ ],
+ [
+ 104,
+ 70,
+ 0,
+ 75,
+ 0,
+ "LOG_ENTRY"
+ ],
+ [
+ 105,
+ 48,
+ 1,
+ 75,
+ 1,
+ "LOG_ENTRY"
+ ],
+ [
+ 106,
+ 75,
+ 0,
+ 76,
+ 1,
+ "LOG_ENTRY"
+ ],
+ [
+ 107,
+ 36,
+ 0,
+ 77,
+ 0,
+ "*"
+ ],
+ [
+ 108,
+ 77,
+ 0,
+ 78,
+ 0,
+ "*"
+ ],
+ [
+ 109,
+ 78,
+ 0,
+ 76,
+ 9,
+ "STRING"
+ ],
+ [
+ 110,
+ 74,
+ 0,
+ 79,
+ 0,
+ "*"
+ ],
+ [
+ 111,
+ 79,
+ 0,
+ 76,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 112,
+ 66,
+ 0,
+ 3,
+ 0,
+ "MODEL"
+ ],
+ [
+ 113,
+ 68,
+ 0,
+ 8,
+ 1,
+ "VAE"
+ ],
+ [
+ 114,
+ 43,
+ 0,
+ 46,
+ 0,
+ "*"
+ ],
+ [
+ 115,
+ 35,
+ 0,
+ 3,
+ 4,
+ "FLOAT"
+ ]
+ ],
+ "groups": [
+ {
+ "title": "Random prompt",
+ "bounding": [
+ -558,
+ -1027,
+ 2204,
+ 1424
+ ],
+ "color": "#3f789e"
+ },
+ {
+ "title": "KSampler",
+ "bounding": [
+ 2576,
+ -695,
+ 1655,
+ 990
+ ],
+ "color": "#3f789e"
+ },
+ {
+ "title": "Output",
+ "bounding": [
+ 4353,
+ -875,
+ 1182,
+ 1424
+ ],
+ "color": "#3f789e"
+ }
+ ],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui-dream-project/examples/motion-workflow-example.json b/custom_nodes/comfyui-dream-project/examples/motion-workflow-example.json
new file mode 100644
index 0000000000000000000000000000000000000000..1afa6c8a63e05f1dabecd0342129bc35aa4d90b4
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/examples/motion-workflow-example.json
@@ -0,0 +1,4983 @@
+{
+ "last_node_id": 423,
+ "last_link_id": 5467,
+ "nodes": [
+ {
+ "id": 77,
+ "type": "PrimitiveNode",
+ "pos": [
+ -2115.912856640875,
+ 447.2204328247986
+ ],
+ "size": {
+ "0": 450,
+ "1": 190
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 2312
+ ],
+ "widget": {
+ "name": "text"
+ }
+ }
+ ],
+ "title": "Negative",
+ "properties": {},
+ "widgets_values": [
+ "text, watermark, logo, letters, writing, frame, border, hands, frame, paper"
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 78,
+ "type": "PrimitiveNode",
+ "pos": [
+ -2119.0805839357795,
+ 220.45355818433413
+ ],
+ "size": {
+ "0": 460,
+ "1": 190
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 2309
+ ],
+ "slot_index": 0,
+ "widget": {
+ "name": "text"
+ }
+ }
+ ],
+ "title": "Positive",
+ "properties": {},
+ "widgets_values": [
+ "serene forest landscape, watercolor, detailed"
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 227,
+ "type": "Common Frame Dimensions [Dream]",
+ "pos": [
+ -2107.1983297494985,
+ 1324.7646814141895
+ ],
+ "size": {
+ "0": 360,
+ "1": 240
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "links": [
+ 5179
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "links": [
+ 5073
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "final_width",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "final_height",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Common Frame Dimensions [Dream]"
+ },
+ "widgets_values": [
+ "512",
+ "1:1",
+ "wide",
+ "1",
+ 64,
+ "ceil"
+ ],
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 326,
+ "type": "Note",
+ "pos": [
+ 41.927985591864186,
+ 320.26230796549316
+ ],
+ "size": {
+ "0": 370,
+ "1": 90
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "title": "Note on curves",
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Curves are deterministic. They transform the frame counter (including information such as framerate, frame index and total frame counter) into a single float value (and a rounded integer)."
+ ],
+ "color": "#568479",
+ "bgcolor": "#427065"
+ },
+ {
+ "id": 329,
+ "type": "Note",
+ "pos": [
+ 2160.935341001233,
+ 880.498221189961
+ ],
+ "size": {
+ "0": 370,
+ "1": 90
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "title": "Note on outpainting",
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "This is outpainting done using controlnet. This are other ways to do outpainting."
+ ],
+ "color": "#568479",
+ "bgcolor": "#427065"
+ },
+ {
+ "id": 330,
+ "type": "Note",
+ "pos": [
+ 960.9353410012387,
+ 560.4982211899617
+ ],
+ "size": {
+ "0": 310,
+ "1": 60
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "title": "Note on CN model",
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "You need an inpainting controlnet model here."
+ ],
+ "color": "#568479",
+ "bgcolor": "#427065"
+ },
+ {
+ "id": 332,
+ "type": "Note",
+ "pos": [
+ 3743.4799277760635,
+ 440.536019679167
+ ],
+ "size": {
+ "0": 590,
+ "1": 80
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "title": "Note on output",
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The sequence processing is triggered only after the last frame has been saved. We blend the frames slightly, introduce \"tweening\" frames and encode a mp4 video file. We choose to remove the images, so we could in theory continue generating to produce multiple different videos. The ffmpeg node will not overwrite the video file."
+ ],
+ "color": "#568479",
+ "bgcolor": "#427065"
+ },
+ {
+ "id": 228,
+ "type": "Reroute",
+ "pos": [
+ -766.5873422711185,
+ 863.8157107780454
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5179
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 5074
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 229,
+ "type": "Reroute",
+ "pos": [
+ -766.5873422711185,
+ 893.8157107780456
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 18,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5073
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 5075
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 201,
+ "type": "Reroute",
+ "pos": [
+ -1744.0427424682491,
+ 860.8396239607022
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 22,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 2977,
+ "pos": [
+ 37.5,
+ 0
+ ]
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": true
+ }
+ },
+ {
+ "id": 242,
+ "type": "Reroute",
+ "pos": [
+ 1400.935341001236,
+ 560.4982211899617
+ ],
+ "size": [
+ 140.8,
+ 26
+ ],
+ "flags": {},
+ "order": 54,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5376
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 5110
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 241,
+ "type": "Reroute",
+ "pos": [
+ 1400.935341001236,
+ 520.4982211899614
+ ],
+ "size": [
+ 140.8,
+ 26
+ ],
+ "flags": {},
+ "order": 52,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5375
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 5111
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 87,
+ "type": "Reroute",
+ "pos": [
+ -784.3996392822262,
+ 672.4008465576175
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 60,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5369
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 5080,
+ 5198
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": false,
+ "horizontal": false
+ },
+ "color": "#2a363b",
+ "bgcolor": "#3f5159"
+ },
+ {
+ "id": 231,
+ "type": "Frame Counter Offset [Dream]",
+ "pos": [
+ -674.3996392822264,
+ 742.4008465576175
+ ],
+ "size": {
+ "0": 342.5999755859375,
+ "1": 58
+ },
+ "flags": {},
+ "order": 64,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 5080
+ }
+ ],
+ "outputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 5081,
+ 5082
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Frame Counter Offset [Dream]"
+ },
+ "widgets_values": [
+ -1
+ ],
+ "color": "#2a363b",
+ "bgcolor": "#3f5159"
+ },
+ {
+ "id": 282,
+ "type": "Reroute",
+ "pos": [
+ 87.92452081298865,
+ 684.8108026123044
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 65,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5198
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 5199
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 285,
+ "type": "Reroute",
+ "pos": [
+ 3081.5470204862327,
+ 362.2550892786084
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 63,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5204
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 5205
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 58,
+ "type": "Reroute",
+ "pos": [
+ 1640.9353410012352,
+ 840.4982211899608
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 81,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 333
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "IMAGE",
+ "links": [
+ 5241
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": false,
+ "horizontal": false
+ },
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 124,
+ "type": "ControlNetApplyAdvanced",
+ "pos": [
+ 1680.9353410012357,
+ 560.4982211899617
+ ],
+ "size": {
+ "0": 315,
+ "1": 166
+ },
+ "flags": {},
+ "order": 82,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 5111
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 5110
+ },
+ {
+ "name": "control_net",
+ "type": "CONTROL_NET",
+ "link": 272
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 5296
+ }
+ ],
+ "outputs": [
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "links": [
+ 5301
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "links": [
+ 5302
+ ],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ControlNetApplyAdvanced"
+ },
+ "widgets_values": [
+ 1,
+ 0,
+ 1
+ ]
+ },
+ {
+ "id": 302,
+ "type": "VAEEncode",
+ "pos": [
+ 1910.935341001228,
+ 780.4982211899609
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 84,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "pixels",
+ "type": "IMAGE",
+ "link": 5241
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 5242
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 5303
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEEncode"
+ }
+ },
+ {
+ "id": 267,
+ "type": "KSampler",
+ "pos": [
+ 2270.935341001233,
+ 570.4982211899617
+ ],
+ "size": {
+ "0": 315,
+ "1": 262
+ },
+ "flags": {},
+ "order": 85,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 5436
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 5301
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 5302
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 5303
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 5159,
+ 5308
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 582926500345395,
+ "randomize",
+ 25,
+ 9,
+ "euler_ancestral",
+ "normal",
+ 0.804544677734375
+ ]
+ },
+ {
+ "id": 268,
+ "type": "Reroute",
+ "pos": [
+ 2620.935341001234,
+ 570.4982211899617
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 86,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5159
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 5293
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 265,
+ "type": "PreviewImage",
+ "pos": [
+ 3130.9159068821527,
+ 500.0091821650353
+ ],
+ "size": {
+ "0": 250,
+ "1": 370
+ },
+ "flags": {},
+ "order": 92,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 5152
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 364,
+ "type": "Int Input [Dream]",
+ "pos": [
+ -2097.1983297494985,
+ 1704.7646814141895
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 5350
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "✍ Framerate",
+ "properties": {
+ "Node name for S&R": "Int Input [Dream]"
+ },
+ "widgets_values": [
+ 15
+ ],
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 365,
+ "type": "Int Input [Dream]",
+ "pos": [
+ -2097.1983297494985,
+ 1604.7646814141895
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 5351
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "✍ Number of frames",
+ "properties": {
+ "Node name for S&R": "Int Input [Dream]"
+ },
+ "widgets_values": [
+ 30
+ ],
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 362,
+ "type": "String Input [Dream]",
+ "pos": [
+ -2097.1983297494985,
+ 1803.7646814141895
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 5346
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "✍ Output Directory",
+ "properties": {
+ "Node name for S&R": "String Input [Dream]"
+ },
+ "widgets_values": [
+ "I:\\AI\\output\\ComfyUI"
+ ],
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 333,
+ "type": "Note",
+ "pos": [
+ -2098.1983297494985,
+ 1907.7646814141895
+ ],
+ "size": {
+ "0": 408.5453796386719,
+ "1": 74.01783752441406
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "title": "Note on settings",
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Both framerate and total number of frames are important since they affect curves and sequence processing. Typically, queue an equal number of prompt executions as the total number of frames."
+ ],
+ "color": "#568479",
+ "bgcolor": "#427065"
+ },
+ {
+ "id": 171,
+ "type": "VAEDecode",
+ "pos": [
+ 3161.547020486233,
+ 412.2550892786084
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 90,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 5294
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 5205
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5152,
+ 5354
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 367,
+ "type": "Reroute",
+ "pos": [
+ 3403.212597957077,
+ 394.68859707729007
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 93,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5354
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5357
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 334,
+ "type": "Note",
+ "pos": [
+ -1376.5813007363186,
+ 888.406571589691
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "title": "Note on settings",
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "This node creates progression based on the files in the output directory."
+ ],
+ "color": "#568479",
+ "bgcolor": "#427065"
+ },
+ {
+ "id": 230,
+ "type": "Frame Counter (Directory) [Dream]",
+ "pos": [
+ -1374.2153335854396,
+ 697.9571941696488
+ ],
+ "size": {
+ "0": 320,
+ "1": 150
+ },
+ "flags": {},
+ "order": 26,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "total_frames",
+ "type": "INT",
+ "link": 5351,
+ "widget": {
+ "name": "total_frames"
+ },
+ "slot_index": 0
+ },
+ {
+ "name": "directory_path",
+ "type": "STRING",
+ "link": 5347,
+ "widget": {
+ "name": "directory_path"
+ }
+ },
+ {
+ "name": "frames_per_second",
+ "type": "INT",
+ "link": 5350,
+ "widget": {
+ "name": "frames_per_second"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 5176
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Frame Counter (Directory) [Dream]"
+ },
+ "widgets_values": [
+ "I:\\AI\\output\\ComfyUI",
+ "*",
+ "numeric",
+ 500,
+ 15
+ ],
+ "color": "#2a363b",
+ "bgcolor": "#3f5159"
+ },
+ {
+ "id": 363,
+ "type": "Reroute",
+ "pos": [
+ -1680,
+ 1838
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 19,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5346
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 5347,
+ 5348,
+ 5358
+ ],
+ "slot_index": 0
+ }
+ ],
+ "title": "Test",
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 236,
+ "type": "LoadImage",
+ "pos": [
+ -2106,
+ 679
+ ],
+ "size": {
+ "0": 430,
+ "1": 340
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5313
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "after (3).jpg",
+ "image"
+ ],
+ "color": "#2a363b",
+ "bgcolor": "#3f5159"
+ },
+ {
+ "id": 373,
+ "type": "Reroute",
+ "pos": [
+ -1540,
+ 197
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 23,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5365
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 5366,
+ 5367
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 292,
+ "type": "Linear Curve [Dream]",
+ "pos": [
+ -300.1999890136713,
+ 310.9999736785893
+ ],
+ "size": {
+ "0": 315,
+ "1": 102
+ },
+ "flags": {},
+ "order": 57,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 5220
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 5222
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": null,
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Linear Curve [Dream]"
+ },
+ "widgets_values": [
+ -0.5,
+ 0.5
+ ]
+ },
+ {
+ "id": 291,
+ "type": "Sine Curve [Dream]",
+ "pos": [
+ -300.1999890136713,
+ 465.99997367858884
+ ],
+ "size": {
+ "0": 315,
+ "1": 150
+ },
+ "flags": {},
+ "order": 58,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 5221
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 5223
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Sine Curve [Dream]"
+ },
+ "widgets_values": [
+ 0.3,
+ -0.3,
+ 2,
+ 0
+ ]
+ },
+ {
+ "id": 294,
+ "type": "Beat Curve [Dream]",
+ "pos": [
+ -632.199989013671,
+ 294.9999736785893
+ ],
+ "size": {
+ "0": 315,
+ "1": 318
+ },
+ "flags": {},
+ "order": 59,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 5224
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 5225
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Beat Curve [Dream]"
+ },
+ "widgets_values": [
+ 100,
+ 0,
+ 4,
+ -0.3,
+ 1,
+ "no",
+ 2,
+ 1,
+ 3,
+ 0,
+ 0
+ ]
+ },
+ {
+ "id": 293,
+ "type": "Reroute",
+ "pos": [
+ -806.1999890136706,
+ 358.9999736785893
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 48,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5368
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 5220,
+ 5221,
+ 5224,
+ 5369
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 7,
+ "type": "CLIPTextEncode",
+ "pos": [
+ -1256,
+ 9
+ ],
+ "size": {
+ "0": 425.27801513671875,
+ "1": 180.6060791015625
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 31,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 5367
+ },
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 2312,
+ "widget": {
+ "name": "text"
+ },
+ "slot_index": 1
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 5372
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "text, watermark, logo, letters, writing, frame, border, hands, frame, paper"
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 6,
+ "type": "CLIPTextEncode",
+ "pos": [
+ -1250,
+ -43
+ ],
+ "size": {
+ "0": 422.84503173828125,
+ "1": 164.31304931640625
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 30,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 5366
+ },
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 2309,
+ "widget": {
+ "name": "text"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 5371
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "serene forest landscape, watercolor, detailed"
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 375,
+ "type": "Reroute",
+ "pos": [
+ 300,
+ -70
+ ],
+ "size": [
+ 140.8,
+ 26
+ ],
+ "flags": {},
+ "order": 37,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5371
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 5373
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 368,
+ "type": "Reroute",
+ "pos": [
+ -1236,
+ 24
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 27,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5358
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 5359
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 369,
+ "type": "Reroute",
+ "pos": [
+ 334,
+ 26
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 35,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5359
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 5360
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 274,
+ "type": "Reroute",
+ "pos": [
+ -979,
+ 68
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 41,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5457
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 5368,
+ 5370
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#2a363b",
+ "bgcolor": "#3f5159",
+ "shape": 2
+ },
+ {
+ "id": 376,
+ "type": "Reroute",
+ "pos": [
+ 301,
+ -21
+ ],
+ "size": [
+ 140.8,
+ 26
+ ],
+ "flags": {},
+ "order": 38,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5372
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 5374
+ ]
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 386,
+ "type": "Reroute",
+ "pos": [
+ -1491,
+ -190
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 21,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5392
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 5389
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 382,
+ "type": "Reroute",
+ "pos": [
+ -1462,
+ -149
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 24,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5381
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 5382
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 186,
+ "type": "KSampler",
+ "pos": [
+ 2772.704072973985,
+ 438.14907683566435
+ ],
+ "size": {
+ "0": 320,
+ "1": 470
+ },
+ "flags": {},
+ "order": 88,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 5399
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 5387
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 5386
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 5293
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 5294
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 959601926573140,
+ "randomize",
+ 5,
+ 5,
+ "dpmpp_3m_sde",
+ "exponential",
+ 0.6
+ ]
+ },
+ {
+ "id": 284,
+ "type": "Reroute",
+ "pos": [
+ 2745.704072973985,
+ 371.1490768356643
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 56,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5395
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 5204
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 331,
+ "type": "Note",
+ "pos": [
+ 2810.704072973985,
+ 953.1490768356643
+ ],
+ "size": {
+ "0": 570,
+ "1": 60
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "title": "Note on full frame sampler",
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "This step is really mostly important if you zoom out as it reintroduces details."
+ ],
+ "color": "#568479",
+ "bgcolor": "#427065"
+ },
+ {
+ "id": 211,
+ "type": "ImageScale",
+ "pos": [
+ 14,
+ 800
+ ],
+ "size": {
+ "0": 320,
+ "1": 130
+ },
+ "flags": {},
+ "order": 69,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 5085
+ },
+ {
+ "name": "width",
+ "type": "INT",
+ "link": 5074,
+ "widget": {
+ "name": "width"
+ }
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "link": 5075,
+ "widget": {
+ "name": "height"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5088,
+ 5415
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ImageScale"
+ },
+ "widgets_values": [
+ "nearest-exact",
+ 512,
+ 512,
+ "disabled"
+ ]
+ },
+ {
+ "id": 119,
+ "type": "ControlNetLoader",
+ "pos": [
+ 982.0796921393085,
+ 673.1125436264036
+ ],
+ "size": {
+ "0": 400,
+ "1": 90
+ },
+ "flags": {
+ "collapsed": false
+ },
+ "order": 14,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "CONTROL_NET",
+ "type": "CONTROL_NET",
+ "links": [
+ 272
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ControlNetLoader"
+ },
+ "widgets_values": [
+ "SD1.5\\control_v11p_sd15_inpaint.pth"
+ ]
+ },
+ {
+ "id": 325,
+ "type": "InpaintPreprocessor",
+ "pos": [
+ 1164.0796921393064,
+ 841.1125436264033
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 78,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 5295
+ },
+ {
+ "name": "mask",
+ "type": "MASK",
+ "link": 5430
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5296
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "InpaintPreprocessor"
+ }
+ },
+ {
+ "id": 240,
+ "type": "Reroute",
+ "pos": [
+ 1666.6531151244042,
+ 940.0343845569474
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 47,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5426
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 5242
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 150,
+ "type": "Reroute",
+ "pos": [
+ 1202.0796921393064,
+ 909.1125436264033
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 76,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5289
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "IMAGE",
+ "links": [
+ 333
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": false,
+ "horizontal": false
+ },
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 403,
+ "type": "Reroute",
+ "pos": [
+ 924.0796921393085,
+ 936.1125436264036
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 40,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5433
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 5426
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 383,
+ "type": "Reroute",
+ "pos": [
+ 602,
+ -138
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 32,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5382
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 5393,
+ 5433
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 378,
+ "type": "Reroute",
+ "pos": [
+ 1275,
+ -11
+ ],
+ "size": [
+ 140.8,
+ 26
+ ],
+ "flags": {},
+ "order": 45,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5374,
+ "slot_index": 0
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 5376,
+ 5380
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 377,
+ "type": "Reroute",
+ "pos": [
+ 1383,
+ -68
+ ],
+ "size": [
+ 140.8,
+ 26
+ ],
+ "flags": {},
+ "order": 44,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5373,
+ "slot_index": 0
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 5375,
+ 5379
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 380,
+ "type": "Reroute",
+ "pos": [
+ 2416,
+ -69
+ ],
+ "size": [
+ 140.8,
+ 26
+ ],
+ "flags": {},
+ "order": 53,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5379
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 5387
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 381,
+ "type": "Reroute",
+ "pos": [
+ 2381,
+ -17
+ ],
+ "size": [
+ 140.8,
+ 26
+ ],
+ "flags": {},
+ "order": 55,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5380
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 5386
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 234,
+ "type": "Image Motion [Dream]",
+ "pos": [
+ 385,
+ 751
+ ],
+ "size": {
+ "0": 320,
+ "1": 360
+ },
+ "flags": {},
+ "order": 71,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 5088
+ },
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 5199
+ },
+ {
+ "name": "noise",
+ "type": "IMAGE",
+ "link": null
+ },
+ {
+ "name": "x_translation",
+ "type": "FLOAT",
+ "link": 5222,
+ "widget": {
+ "name": "x_translation"
+ }
+ },
+ {
+ "name": "y_translation",
+ "type": "FLOAT",
+ "link": 5223,
+ "widget": {
+ "name": "y_translation"
+ }
+ },
+ {
+ "name": "zoom",
+ "type": "FLOAT",
+ "link": 5225,
+ "widget": {
+ "name": "zoom"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 5439
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "mask1",
+ "type": "MASK",
+ "links": [
+ 5438
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "mask2",
+ "type": "MASK",
+ "links": [],
+ "shape": 3,
+ "slot_index": 2
+ },
+ {
+ "name": "mask3",
+ "type": "MASK",
+ "links": [],
+ "shape": 3,
+ "slot_index": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Image Motion [Dream]"
+ },
+ "widgets_values": [
+ -1.1272729492187497,
+ 15,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ },
+ {
+ "id": 279,
+ "type": "Reroute",
+ "pos": [
+ 3598,
+ 525
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 70,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5385
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 5195
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#2a363b",
+ "bgcolor": "#3f5159"
+ },
+ {
+ "id": 407,
+ "type": "Reroute",
+ "pos": [
+ 3608,
+ 569
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 62,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5441
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 5442
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 287,
+ "type": "Image Sequence Blend [Dream]",
+ "pos": [
+ 4100,
+ 560
+ ],
+ "size": {
+ "0": 315,
+ "1": 106
+ },
+ "flags": {},
+ "order": 97,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "sequence",
+ "type": "ANIMATION_SEQUENCE",
+ "link": 5440
+ }
+ ],
+ "outputs": [
+ {
+ "name": "sequence",
+ "type": "ANIMATION_SEQUENCE",
+ "links": [
+ 5210
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Image Sequence Blend [Dream]"
+ },
+ "widgets_values": [
+ 0.1,
+ 0.1,
+ 1
+ ],
+ "color": "#332922",
+ "bgcolor": "#593930"
+ },
+ {
+ "id": 233,
+ "type": "Image Sequence Saver [Dream]",
+ "pos": [
+ 3760,
+ 560
+ ],
+ "size": {
+ "0": 320,
+ "1": 170
+ },
+ "flags": {},
+ "order": 95,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 5195
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 5193
+ },
+ {
+ "name": "directory_path",
+ "type": "STRING",
+ "link": 5442,
+ "widget": {
+ "name": "directory_path"
+ },
+ "slot_index": 2
+ }
+ ],
+ "outputs": [
+ {
+ "name": "sequence",
+ "type": "ANIMATION_SEQUENCE",
+ "links": [
+ 5440
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "log_entry",
+ "type": "LOG_ENTRY",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Image Sequence Saver [Dream]"
+ },
+ "widgets_values": [
+ "I:\\AI\\output\\ComfyUI",
+ "frame",
+ 5,
+ "stop output",
+ "jpg"
+ ],
+ "color": "#332922",
+ "bgcolor": "#593930"
+ },
+ {
+ "id": 389,
+ "type": "Reroute",
+ "pos": [
+ 2480,
+ -180
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 51,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5402
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 5399
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 393,
+ "type": "Reroute",
+ "pos": [
+ 2070,
+ -180
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 43,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5437
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 5402
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 406,
+ "type": "Reroute",
+ "pos": [
+ 1840,
+ -180
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 36,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5434
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 5436,
+ 5437
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 387,
+ "type": "Reroute",
+ "pos": [
+ 510,
+ -180
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 29,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5389
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 5434
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 390,
+ "type": "Reroute",
+ "pos": [
+ 1551,
+ -135
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 39,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5393
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 5394
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 391,
+ "type": "Reroute",
+ "pos": [
+ 2534,
+ -139
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 46,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5394
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 5395
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 374,
+ "type": "Reroute",
+ "pos": [
+ 538,
+ 77
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 49,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5370
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 5378
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 370,
+ "type": "Reroute",
+ "pos": [
+ 2405,
+ 32
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 42,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5360
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 5397
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 379,
+ "type": "Reroute",
+ "pos": [
+ 2345,
+ 78
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 61,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5378
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 5383
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 392,
+ "type": "Reroute",
+ "pos": [
+ 3374,
+ 37
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 50,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5397
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 5441
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 384,
+ "type": "Reroute",
+ "pos": [
+ 2837,
+ 79
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 66,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5383
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 5384
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 385,
+ "type": "Reroute",
+ "pos": [
+ 3293,
+ 80
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 68,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5384
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 5385
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 232,
+ "type": "Image Sequence Loader [Dream]",
+ "pos": [
+ -294.3996392822261,
+ 772.4008465576175
+ ],
+ "size": {
+ "0": 320,
+ "1": 130
+ },
+ "flags": {},
+ "order": 67,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 5082
+ },
+ {
+ "name": "default_image",
+ "type": "IMAGE",
+ "link": 5444
+ },
+ {
+ "name": "directory_path",
+ "type": "STRING",
+ "link": 5348,
+ "widget": {
+ "name": "directory_path"
+ },
+ "slot_index": 2
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 5085
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "frame_name",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Image Sequence Loader [Dream]"
+ },
+ "widgets_values": [
+ "I:\\AI\\output\\ComfyUI",
+ "*",
+ "numeric"
+ ]
+ },
+ {
+ "id": 408,
+ "type": "Reroute",
+ "pos": [
+ -777,
+ 820
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 28,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5456
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5444
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 405,
+ "type": "Reroute",
+ "pos": [
+ 927.0796921393089,
+ 858.1125436264033
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 74,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5438
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": [
+ 5430,
+ 5447
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 237,
+ "type": "Reroute",
+ "pos": [
+ 926.0796921393085,
+ 791.1125436264036
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 73,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5439
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5289,
+ 5295,
+ 5449
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 327,
+ "type": "Reroute",
+ "pos": [
+ 2550.935341001234,
+ 920.498221189961
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 87,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5308,
+ "pos": [
+ 45.2,
+ 0
+ ]
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 5450
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": true
+ }
+ },
+ {
+ "id": 4,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ -2102.608581739303,
+ 1125.9695878411585
+ ],
+ "size": {
+ "0": 400,
+ "1": 100
+ },
+ "flags": {
+ "collapsed": false
+ },
+ "order": 15,
+ "mode": 0,
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 5392
+ ],
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 2977,
+ 5365
+ ],
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 5381,
+ 5451
+ ],
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "public\\main\\512-SD1.5\\Realistic_Vision_V5.0.safetensors"
+ ]
+ },
+ {
+ "id": 415,
+ "type": "Reroute",
+ "pos": [
+ -1416,
+ 2365
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 25,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5451
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 5452
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 338,
+ "type": "Reroute",
+ "pos": [
+ -1251,
+ 169
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 20,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5313
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5456
+ ],
+ "slot_index": 0
+ }
+ ],
+ "title": "Seed Image",
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#2a363b",
+ "bgcolor": "#3f5159"
+ },
+ {
+ "id": 273,
+ "type": "Reroute",
+ "pos": [
+ -1124.21533358544,
+ 636.9571941696493
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 34,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5176
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 5457
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#2a363b",
+ "bgcolor": "#3f5159"
+ },
+ {
+ "id": 328,
+ "type": "Note",
+ "pos": [
+ -512,
+ 962
+ ],
+ "size": {
+ "0": 370,
+ "1": 90
+ },
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "title": "Note on motion",
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "This group reads the previously rendered frame and performs a motion transformation on the image. The output of the image motion node is the transformed image and up to three masks to use for outpainting."
+ ],
+ "color": "#568479",
+ "bgcolor": "#427065"
+ },
+ {
+ "id": 411,
+ "type": "MaskToImage",
+ "pos": [
+ 1287,
+ 2084
+ ],
+ "size": {
+ "0": 210,
+ "1": 26
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 79,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "mask",
+ "type": "MASK",
+ "link": 5447
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5448
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "MaskToImage"
+ }
+ },
+ {
+ "id": 416,
+ "type": "Reroute",
+ "pos": [
+ 1658,
+ 2360
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 33,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5452
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 5453
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 414,
+ "type": "VAEDecode",
+ "pos": [
+ 1771,
+ 2095
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 89,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 5450
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 5453
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5454
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 278,
+ "type": "Reroute",
+ "pos": [
+ 3614,
+ 610
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 94,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5357
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5193,
+ 5460
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 420,
+ "type": "Reroute",
+ "pos": [
+ 3634.1977835692924,
+ 1253.5845951163976
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 96,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5460,
+ "pos": [
+ 41,
+ 0
+ ]
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5461
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": true
+ }
+ },
+ {
+ "id": 418,
+ "type": "PreviewImage",
+ "pos": [
+ 1970,
+ 2080
+ ],
+ "size": {
+ "0": 210,
+ "1": 246
+ },
+ "flags": {},
+ "order": 98,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 5461
+ }
+ ],
+ "title": "Saved Image",
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 417,
+ "type": "PreviewImage",
+ "pos": [
+ 1730,
+ 2080
+ ],
+ "size": {
+ "0": 210,
+ "1": 246
+ },
+ "flags": {},
+ "order": 91,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 5454
+ }
+ ],
+ "title": "After Inpainting",
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 413,
+ "type": "PreviewImage",
+ "pos": [
+ 1510,
+ 2080
+ ],
+ "size": {
+ "0": 210,
+ "1": 246
+ },
+ "flags": {},
+ "order": 77,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 5449
+ }
+ ],
+ "title": "Inpainting input",
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 412,
+ "type": "PreviewImage",
+ "pos": [
+ 1270,
+ 2080
+ ],
+ "size": {
+ "0": 210,
+ "1": 246
+ },
+ "flags": {},
+ "order": 83,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 5448
+ }
+ ],
+ "title": "Inpainting mask",
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 409,
+ "type": "PreviewImage",
+ "pos": [
+ 1046,
+ 2072
+ ],
+ "size": {
+ "0": 210,
+ "1": 246
+ },
+ "flags": {},
+ "order": 80,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 5459
+ }
+ ],
+ "title": "Previous Frame",
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 398,
+ "type": "Reroute",
+ "pos": [
+ 288,
+ 1073
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 72,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5415,
+ "pos": [
+ 41,
+ 0
+ ]
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5464
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": true
+ }
+ },
+ {
+ "id": 419,
+ "type": "Reroute",
+ "pos": [
+ 459,
+ 2070
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 75,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5464
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5459
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 281,
+ "type": "Image Sequence Tweening [Dream]",
+ "pos": [
+ 4440,
+ 560
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 99,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "sequence",
+ "type": "ANIMATION_SEQUENCE",
+ "link": 5210
+ }
+ ],
+ "outputs": [
+ {
+ "name": "sequence",
+ "type": "ANIMATION_SEQUENCE",
+ "links": [
+ 5467
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Image Sequence Tweening [Dream]"
+ },
+ "widgets_values": [
+ 2
+ ],
+ "color": "#332922",
+ "bgcolor": "#593930"
+ },
+ {
+ "id": 423,
+ "type": "FFMPEG Video Encoder [Dream]",
+ "pos": [
+ 4785,
+ 557
+ ],
+ "size": {
+ "0": 315,
+ "1": 106
+ },
+ "flags": {},
+ "order": 100,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "sequence",
+ "type": "ANIMATION_SEQUENCE",
+ "link": 5467
+ }
+ ],
+ "outputs": [
+ {
+ "name": "log_entry",
+ "type": "LOG_ENTRY",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "FFMPEG Video Encoder [Dream]"
+ },
+ "widgets_values": [
+ "video",
+ 1,
+ true
+ ]
+ }
+ ],
+ "links": [
+ [
+ 272,
+ 119,
+ 0,
+ 124,
+ 2,
+ "CONTROL_NET"
+ ],
+ [
+ 333,
+ 150,
+ 0,
+ 58,
+ 0,
+ "*"
+ ],
+ [
+ 2309,
+ 78,
+ 0,
+ 6,
+ 1,
+ "STRING"
+ ],
+ [
+ 2312,
+ 77,
+ 0,
+ 7,
+ 1,
+ "STRING"
+ ],
+ [
+ 2977,
+ 4,
+ 1,
+ 201,
+ 0,
+ "*"
+ ],
+ [
+ 5073,
+ 227,
+ 1,
+ 229,
+ 0,
+ "*"
+ ],
+ [
+ 5074,
+ 228,
+ 0,
+ 211,
+ 1,
+ "INT"
+ ],
+ [
+ 5075,
+ 229,
+ 0,
+ 211,
+ 2,
+ "INT"
+ ],
+ [
+ 5080,
+ 87,
+ 0,
+ 231,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 5081,
+ 231,
+ 0,
+ 82,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 5082,
+ 231,
+ 0,
+ 232,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 5085,
+ 232,
+ 0,
+ 211,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5088,
+ 211,
+ 0,
+ 234,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5110,
+ 242,
+ 0,
+ 124,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 5111,
+ 241,
+ 0,
+ 124,
+ 0,
+ "CONDITIONING"
+ ],
+ [
+ 5152,
+ 171,
+ 0,
+ 265,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5159,
+ 267,
+ 0,
+ 268,
+ 0,
+ "*"
+ ],
+ [
+ 5176,
+ 230,
+ 0,
+ 273,
+ 0,
+ "*"
+ ],
+ [
+ 5179,
+ 227,
+ 0,
+ 228,
+ 0,
+ "*"
+ ],
+ [
+ 5193,
+ 278,
+ 0,
+ 233,
+ 1,
+ "IMAGE"
+ ],
+ [
+ 5195,
+ 279,
+ 0,
+ 233,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 5198,
+ 87,
+ 0,
+ 282,
+ 0,
+ "*"
+ ],
+ [
+ 5199,
+ 282,
+ 0,
+ 234,
+ 1,
+ "FRAME_COUNTER"
+ ],
+ [
+ 5204,
+ 284,
+ 0,
+ 285,
+ 0,
+ "*"
+ ],
+ [
+ 5205,
+ 285,
+ 0,
+ 171,
+ 1,
+ "VAE"
+ ],
+ [
+ 5210,
+ 287,
+ 0,
+ 281,
+ 0,
+ "ANIMATION_SEQUENCE"
+ ],
+ [
+ 5220,
+ 293,
+ 0,
+ 292,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 5221,
+ 293,
+ 0,
+ 291,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 5222,
+ 292,
+ 0,
+ 234,
+ 3,
+ "FLOAT"
+ ],
+ [
+ 5223,
+ 291,
+ 0,
+ 234,
+ 4,
+ "FLOAT"
+ ],
+ [
+ 5224,
+ 293,
+ 0,
+ 294,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 5225,
+ 294,
+ 0,
+ 234,
+ 5,
+ "FLOAT"
+ ],
+ [
+ 5241,
+ 58,
+ 0,
+ 302,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5242,
+ 240,
+ 0,
+ 302,
+ 1,
+ "VAE"
+ ],
+ [
+ 5289,
+ 237,
+ 0,
+ 150,
+ 0,
+ "*"
+ ],
+ [
+ 5293,
+ 268,
+ 0,
+ 186,
+ 3,
+ "LATENT"
+ ],
+ [
+ 5294,
+ 186,
+ 0,
+ 171,
+ 0,
+ "LATENT"
+ ],
+ [
+ 5295,
+ 237,
+ 0,
+ 325,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5296,
+ 325,
+ 0,
+ 124,
+ 3,
+ "IMAGE"
+ ],
+ [
+ 5301,
+ 124,
+ 0,
+ 267,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 5302,
+ 124,
+ 1,
+ 267,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 5303,
+ 302,
+ 0,
+ 267,
+ 3,
+ "LATENT"
+ ],
+ [
+ 5308,
+ 267,
+ 0,
+ 327,
+ 0,
+ "*"
+ ],
+ [
+ 5313,
+ 236,
+ 0,
+ 338,
+ 0,
+ "*"
+ ],
+ [
+ 5346,
+ 362,
+ 0,
+ 363,
+ 0,
+ "*"
+ ],
+ [
+ 5347,
+ 363,
+ 0,
+ 230,
+ 1,
+ "STRING"
+ ],
+ [
+ 5348,
+ 363,
+ 0,
+ 232,
+ 2,
+ "STRING"
+ ],
+ [
+ 5350,
+ 364,
+ 0,
+ 230,
+ 2,
+ "INT"
+ ],
+ [
+ 5351,
+ 365,
+ 0,
+ 230,
+ 0,
+ "INT"
+ ],
+ [
+ 5354,
+ 171,
+ 0,
+ 367,
+ 0,
+ "*"
+ ],
+ [
+ 5357,
+ 367,
+ 0,
+ 278,
+ 0,
+ "*"
+ ],
+ [
+ 5358,
+ 363,
+ 0,
+ 368,
+ 0,
+ "*"
+ ],
+ [
+ 5359,
+ 368,
+ 0,
+ 369,
+ 0,
+ "*"
+ ],
+ [
+ 5360,
+ 369,
+ 0,
+ 370,
+ 0,
+ "*"
+ ],
+ [
+ 5365,
+ 4,
+ 1,
+ 373,
+ 0,
+ "*"
+ ],
+ [
+ 5366,
+ 373,
+ 0,
+ 6,
+ 0,
+ "CLIP"
+ ],
+ [
+ 5367,
+ 373,
+ 0,
+ 7,
+ 0,
+ "CLIP"
+ ],
+ [
+ 5368,
+ 274,
+ 0,
+ 293,
+ 0,
+ "*"
+ ],
+ [
+ 5369,
+ 293,
+ 0,
+ 87,
+ 0,
+ "*"
+ ],
+ [
+ 5370,
+ 274,
+ 0,
+ 374,
+ 0,
+ "*"
+ ],
+ [
+ 5371,
+ 6,
+ 0,
+ 375,
+ 0,
+ "*"
+ ],
+ [
+ 5372,
+ 7,
+ 0,
+ 376,
+ 0,
+ "*"
+ ],
+ [
+ 5373,
+ 375,
+ 0,
+ 377,
+ 0,
+ "*"
+ ],
+ [
+ 5374,
+ 376,
+ 0,
+ 378,
+ 0,
+ "*"
+ ],
+ [
+ 5375,
+ 377,
+ 0,
+ 241,
+ 0,
+ "*"
+ ],
+ [
+ 5376,
+ 378,
+ 0,
+ 242,
+ 0,
+ "*"
+ ],
+ [
+ 5378,
+ 374,
+ 0,
+ 379,
+ 0,
+ "*"
+ ],
+ [
+ 5379,
+ 377,
+ 0,
+ 380,
+ 0,
+ "*"
+ ],
+ [
+ 5380,
+ 378,
+ 0,
+ 381,
+ 0,
+ "*"
+ ],
+ [
+ 5381,
+ 4,
+ 2,
+ 382,
+ 0,
+ "*"
+ ],
+ [
+ 5382,
+ 382,
+ 0,
+ 383,
+ 0,
+ "*"
+ ],
+ [
+ 5383,
+ 379,
+ 0,
+ 384,
+ 0,
+ "*"
+ ],
+ [
+ 5384,
+ 384,
+ 0,
+ 385,
+ 0,
+ "*"
+ ],
+ [
+ 5385,
+ 385,
+ 0,
+ 279,
+ 0,
+ "*"
+ ],
+ [
+ 5386,
+ 381,
+ 0,
+ 186,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 5387,
+ 380,
+ 0,
+ 186,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 5389,
+ 386,
+ 0,
+ 387,
+ 0,
+ "*"
+ ],
+ [
+ 5392,
+ 4,
+ 0,
+ 386,
+ 0,
+ "*"
+ ],
+ [
+ 5393,
+ 383,
+ 0,
+ 390,
+ 0,
+ "*"
+ ],
+ [
+ 5394,
+ 390,
+ 0,
+ 391,
+ 0,
+ "*"
+ ],
+ [
+ 5395,
+ 391,
+ 0,
+ 284,
+ 0,
+ "*"
+ ],
+ [
+ 5397,
+ 370,
+ 0,
+ 392,
+ 0,
+ "*"
+ ],
+ [
+ 5399,
+ 389,
+ 0,
+ 186,
+ 0,
+ "MODEL"
+ ],
+ [
+ 5402,
+ 393,
+ 0,
+ 389,
+ 0,
+ "*"
+ ],
+ [
+ 5415,
+ 211,
+ 0,
+ 398,
+ 0,
+ "*"
+ ],
+ [
+ 5426,
+ 403,
+ 0,
+ 240,
+ 0,
+ "*"
+ ],
+ [
+ 5430,
+ 405,
+ 0,
+ 325,
+ 1,
+ "MASK"
+ ],
+ [
+ 5433,
+ 383,
+ 0,
+ 403,
+ 0,
+ "*"
+ ],
+ [
+ 5434,
+ 387,
+ 0,
+ 406,
+ 0,
+ "*"
+ ],
+ [
+ 5436,
+ 406,
+ 0,
+ 267,
+ 0,
+ "MODEL"
+ ],
+ [
+ 5437,
+ 406,
+ 0,
+ 393,
+ 0,
+ "*"
+ ],
+ [
+ 5438,
+ 234,
+ 1,
+ 405,
+ 0,
+ "*"
+ ],
+ [
+ 5439,
+ 234,
+ 0,
+ 237,
+ 0,
+ "*"
+ ],
+ [
+ 5440,
+ 233,
+ 0,
+ 287,
+ 0,
+ "ANIMATION_SEQUENCE"
+ ],
+ [
+ 5441,
+ 392,
+ 0,
+ 407,
+ 0,
+ "*"
+ ],
+ [
+ 5442,
+ 407,
+ 0,
+ 233,
+ 2,
+ "STRING"
+ ],
+ [
+ 5444,
+ 408,
+ 0,
+ 232,
+ 1,
+ "IMAGE"
+ ],
+ [
+ 5447,
+ 405,
+ 0,
+ 411,
+ 0,
+ "MASK"
+ ],
+ [
+ 5448,
+ 411,
+ 0,
+ 412,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5449,
+ 237,
+ 0,
+ 413,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5450,
+ 327,
+ 0,
+ 414,
+ 0,
+ "LATENT"
+ ],
+ [
+ 5451,
+ 4,
+ 2,
+ 415,
+ 0,
+ "*"
+ ],
+ [
+ 5452,
+ 415,
+ 0,
+ 416,
+ 0,
+ "*"
+ ],
+ [
+ 5453,
+ 416,
+ 0,
+ 414,
+ 1,
+ "VAE"
+ ],
+ [
+ 5454,
+ 414,
+ 0,
+ 417,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5456,
+ 338,
+ 0,
+ 408,
+ 0,
+ "*"
+ ],
+ [
+ 5457,
+ 273,
+ 0,
+ 274,
+ 0,
+ "*"
+ ],
+ [
+ 5459,
+ 419,
+ 0,
+ 409,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5460,
+ 278,
+ 0,
+ 420,
+ 0,
+ "*"
+ ],
+ [
+ 5461,
+ 420,
+ 0,
+ 418,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5464,
+ 398,
+ 0,
+ 419,
+ 0,
+ "*"
+ ],
+ [
+ 5467,
+ 281,
+ 0,
+ 423,
+ 0,
+ "ANIMATION_SEQUENCE"
+ ]
+ ],
+ "groups": [
+ {
+ "title": "Prompting",
+ "bounding": [
+ -2129,
+ 148,
+ 474,
+ 885
+ ],
+ "color": "#a1309b",
+ "font_size": 24
+ },
+ {
+ "title": "Settings",
+ "bounding": [
+ -2125,
+ 1259,
+ 482,
+ 742
+ ],
+ "color": "#b58b2a",
+ "font_size": 24
+ },
+ {
+ "title": "Inpainting/Outpainting",
+ "bounding": [
+ 950,
+ 483,
+ 1711,
+ 528
+ ],
+ "color": "#929054",
+ "font_size": 24
+ },
+ {
+ "title": "Prev Frame Move",
+ "bounding": [
+ -736,
+ 630,
+ 1450,
+ 456
+ ],
+ "color": "#3f789e",
+ "font_size": 24
+ },
+ {
+ "title": "Full frame sampler",
+ "bounding": [
+ 2764,
+ 318,
+ 691,
+ 700
+ ],
+ "color": "#88A",
+ "font_size": 24
+ },
+ {
+ "title": "Output",
+ "bounding": [
+ 3653,
+ 373,
+ 1458,
+ 381
+ ],
+ "color": "#b06634",
+ "font_size": 24
+ },
+ {
+ "title": "Animation Driver",
+ "bounding": [
+ -1385,
+ 592,
+ 336,
+ 373
+ ],
+ "color": "#3f789e",
+ "font_size": 24
+ },
+ {
+ "title": "Motion Control",
+ "bounding": [
+ -738,
+ 235,
+ 1451,
+ 392
+ ],
+ "color": "#ef75ff",
+ "font_size": 24
+ },
+ {
+ "title": "Model selection",
+ "bounding": [
+ -2128,
+ 1042,
+ 476,
+ 204
+ ],
+ "color": "#3f789e",
+ "font_size": 24
+ },
+ {
+ "title": "Previews",
+ "bounding": [
+ -2124,
+ 2011,
+ 6473,
+ 329
+ ],
+ "color": "#444",
+ "font_size": 24
+ }
+ ],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui-dream-project/examples/motion-workflow-with-color-coherence.json b/custom_nodes/comfyui-dream-project/examples/motion-workflow-with-color-coherence.json
new file mode 100644
index 0000000000000000000000000000000000000000..a6492346b17eb1cdb24b315569db1031fa96d96f
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/examples/motion-workflow-with-color-coherence.json
@@ -0,0 +1,6230 @@
+{
+ "last_node_id": 422,
+ "last_link_id": 5463,
+ "nodes": [
+ {
+ "id": 77,
+ "type": "PrimitiveNode",
+ "pos": [
+ -2115.912856640875,
+ 447.2204328247986
+ ],
+ "size": {
+ "0": 450,
+ "1": 190
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 2312
+ ],
+ "widget": {
+ "name": "text"
+ }
+ }
+ ],
+ "title": "Negative",
+ "properties": {},
+ "widgets_values": [
+ "text, watermark, logo, letters, writing, frame, border, hands, frame, paper"
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 78,
+ "type": "PrimitiveNode",
+ "pos": [
+ -2119.0805839357795,
+ 220.45355818433413
+ ],
+ "size": {
+ "0": 460,
+ "1": 190
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 2309
+ ],
+ "slot_index": 0,
+ "widget": {
+ "name": "text"
+ }
+ }
+ ],
+ "title": "Positive",
+ "properties": {},
+ "widgets_values": [
+ "serene forest landscape, watercolor, detailed"
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 227,
+ "type": "Common Frame Dimensions [Dream]",
+ "pos": [
+ -2107.1983297494985,
+ 1324.7646814141895
+ ],
+ "size": {
+ "0": 360,
+ "1": 240
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "links": [
+ 5179
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "links": [
+ 5073
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "final_width",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "final_height",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Common Frame Dimensions [Dream]"
+ },
+ "widgets_values": [
+ "512",
+ "1:1",
+ "wide",
+ "1",
+ 64,
+ "ceil"
+ ],
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 326,
+ "type": "Note",
+ "pos": [
+ 41.927985591864186,
+ 320.26230796549316
+ ],
+ "size": {
+ "0": 370,
+ "1": 90
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "title": "Note on curves",
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Curves are deterministic. They transform the frame counter (including information such as framerate, frame index and total frame counter) into a single float value (and a rounded integer)."
+ ],
+ "color": "#568479",
+ "bgcolor": "#427065"
+ },
+ {
+ "id": 329,
+ "type": "Note",
+ "pos": [
+ 2160.935341001233,
+ 880.498221189961
+ ],
+ "size": {
+ "0": 370,
+ "1": 90
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "title": "Note on outpainting",
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "This is outpainting done using controlnet. This are other ways to do outpainting."
+ ],
+ "color": "#568479",
+ "bgcolor": "#427065"
+ },
+ {
+ "id": 330,
+ "type": "Note",
+ "pos": [
+ 960.9353410012387,
+ 560.4982211899617
+ ],
+ "size": {
+ "0": 310,
+ "1": 60
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "title": "Note on CN model",
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "You need an inpainting controlnet model here."
+ ],
+ "color": "#568479",
+ "bgcolor": "#427065"
+ },
+ {
+ "id": 332,
+ "type": "Note",
+ "pos": [
+ 3743.4799277760635,
+ 440.536019679167
+ ],
+ "size": {
+ "0": 590,
+ "1": 80
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "title": "Note on output",
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "The sequence processing is triggered only after the last frame has been saved. We blend the frames slightly, introduce \"tweening\" frames and encode a mp4 video file. We choose to remove the images, so we could in theory continue generating to produce multiple different videos. The ffmpeg node will not overwrite the video file."
+ ],
+ "color": "#568479",
+ "bgcolor": "#427065"
+ },
+ {
+ "id": 228,
+ "type": "Reroute",
+ "pos": [
+ -766.5873422711185,
+ 863.8157107780454
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 19,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5179
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 5074
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 229,
+ "type": "Reroute",
+ "pos": [
+ -766.5873422711185,
+ 893.8157107780456
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 20,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5073
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 5075
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 201,
+ "type": "Reroute",
+ "pos": [
+ -1744.0427424682491,
+ 860.8396239607022
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 23,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 2977,
+ "pos": [
+ 37.5,
+ 0
+ ]
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": true
+ }
+ },
+ {
+ "id": 345,
+ "type": "Sample Image Area as Palette [Dream]",
+ "pos": [
+ -139.11143154552485,
+ 1294.2580203476157
+ ],
+ "size": {
+ "0": 315,
+ "1": 130
+ },
+ "flags": {},
+ "order": 54,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 5320
+ }
+ ],
+ "outputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "links": [
+ 5327
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Sample Image Area as Palette [Dream]"
+ },
+ "widgets_values": [
+ 256,
+ 984424700736240,
+ "randomize",
+ "center-right"
+ ],
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 346,
+ "type": "Sample Image Area as Palette [Dream]",
+ "pos": [
+ -139.11143154552485,
+ 1654.2580203476143
+ ],
+ "size": {
+ "0": 315,
+ "1": 130
+ },
+ "flags": {},
+ "order": 55,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 5321
+ }
+ ],
+ "outputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "links": [
+ 5331
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Sample Image Area as Palette [Dream]"
+ },
+ "widgets_values": [
+ 256,
+ 393866830673316,
+ "randomize",
+ "bottom-center"
+ ],
+ "color": "#323",
+ "bgcolor": "#535"
+ },
+ {
+ "id": 242,
+ "type": "Reroute",
+ "pos": [
+ 1400.935341001236,
+ 560.4982211899617
+ ],
+ "size": [
+ 140.8,
+ 26
+ ],
+ "flags": {},
+ "order": 62,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5376
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 5110
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 241,
+ "type": "Reroute",
+ "pos": [
+ 1400.935341001236,
+ 520.4982211899614
+ ],
+ "size": [
+ 140.8,
+ 26
+ ],
+ "flags": {},
+ "order": 60,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5375
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 5111
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 87,
+ "type": "Reroute",
+ "pos": [
+ -784.3996392822262,
+ 672.4008465576175
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 68,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5369
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 5080,
+ 5198
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": false,
+ "horizontal": false
+ },
+ "color": "#2a363b",
+ "bgcolor": "#3f5159"
+ },
+ {
+ "id": 231,
+ "type": "Frame Counter Offset [Dream]",
+ "pos": [
+ -674.3996392822264,
+ 742.4008465576175
+ ],
+ "size": {
+ "0": 342.5999755859375,
+ "1": 58
+ },
+ "flags": {},
+ "order": 72,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 5080
+ }
+ ],
+ "outputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 5081,
+ 5082
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Frame Counter Offset [Dream]"
+ },
+ "widgets_values": [
+ -1
+ ],
+ "color": "#2a363b",
+ "bgcolor": "#3f5159"
+ },
+ {
+ "id": 282,
+ "type": "Reroute",
+ "pos": [
+ 87.92452081298865,
+ 684.8108026123044
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 73,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5198
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 5199
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 285,
+ "type": "Reroute",
+ "pos": [
+ 3081.5470204862327,
+ 362.2550892786084
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 71,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5204
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 5205
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 337,
+ "type": "Sample Image Area as Palette [Dream]",
+ "pos": [
+ -139.11143154552485,
+ 1824.258020347618
+ ],
+ "size": {
+ "0": 315,
+ "1": 130
+ },
+ "flags": {},
+ "order": 83,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 5312
+ }
+ ],
+ "outputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "links": [
+ 5332
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Sample Image Area as Palette [Dream]"
+ },
+ "widgets_values": [
+ 256,
+ 912873459964733,
+ "randomize",
+ "bottom-center"
+ ],
+ "color": "#323",
+ "bgcolor": "#535"
+ },
+ {
+ "id": 341,
+ "type": "Sample Image Area as Palette [Dream]",
+ "pos": [
+ -139.11143154552485,
+ 1464.258020347614
+ ],
+ "size": {
+ "0": 315,
+ "1": 130
+ },
+ "flags": {},
+ "order": 86,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 5317
+ }
+ ],
+ "outputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "links": [
+ 5326
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Sample Image Area as Palette [Dream]"
+ },
+ "widgets_values": [
+ 256,
+ 257321647530942,
+ "randomize",
+ "center-right"
+ ],
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 350,
+ "type": "Palette Color Align [Dream]",
+ "pos": [
+ 250.88856845447202,
+ 1814.2580203476173
+ ],
+ "size": {
+ "0": 320,
+ "1": 80
+ },
+ "flags": {},
+ "order": 88,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "link": 5332
+ },
+ {
+ "name": "target_align",
+ "type": "RGB_PALETTE",
+ "link": 5331
+ },
+ {
+ "name": "alignment_factor",
+ "type": "FLOAT",
+ "link": 5334,
+ "widget": {
+ "name": "alignment_factor"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "links": [
+ 5340
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Palette Color Align [Dream]"
+ },
+ "widgets_values": [
+ 1.2000000000000002
+ ],
+ "color": "#323",
+ "bgcolor": "#535"
+ },
+ {
+ "id": 347,
+ "type": "Palette Color Align [Dream]",
+ "pos": [
+ 260.88856845447134,
+ 1354.2580203476148
+ ],
+ "size": {
+ "0": 320,
+ "1": 80
+ },
+ "flags": {},
+ "order": 89,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "link": 5322
+ },
+ {
+ "name": "target_align",
+ "type": "RGB_PALETTE",
+ "link": 5323
+ },
+ {
+ "name": "alignment_factor",
+ "type": "FLOAT",
+ "link": 5325,
+ "widget": {
+ "name": "alignment_factor"
+ },
+ "slot_index": 2
+ }
+ ],
+ "outputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "links": [
+ 5337
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Palette Color Align [Dream]"
+ },
+ "widgets_values": [
+ 1.2000000000000002
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 349,
+ "type": "Palette Color Align [Dream]",
+ "pos": [
+ 250.88856845447202,
+ 1664.2580203476143
+ ],
+ "size": {
+ "0": 320,
+ "1": 80
+ },
+ "flags": {},
+ "order": 90,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "link": 5330
+ },
+ {
+ "name": "target_align",
+ "type": "RGB_PALETTE",
+ "link": 5329
+ },
+ {
+ "name": "alignment_factor",
+ "type": "FLOAT",
+ "link": 5333,
+ "widget": {
+ "name": "alignment_factor"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "links": [
+ 5339
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Palette Color Align [Dream]"
+ },
+ "widgets_values": [
+ 1.2000000000000002
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 348,
+ "type": "Palette Color Align [Dream]",
+ "pos": [
+ 260.88856845447134,
+ 1504.2580203476136
+ ],
+ "size": {
+ "0": 320,
+ "1": 80
+ },
+ "flags": {},
+ "order": 91,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "link": 5326
+ },
+ {
+ "name": "target_align",
+ "type": "RGB_PALETTE",
+ "link": 5327
+ },
+ {
+ "name": "alignment_factor",
+ "type": "FLOAT",
+ "link": 5328,
+ "widget": {
+ "name": "alignment_factor"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "links": [
+ 5338
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Palette Color Align [Dream]"
+ },
+ "widgets_values": [
+ 1.2000000000000002
+ ],
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 58,
+ "type": "Reroute",
+ "pos": [
+ 1640.9353410012352,
+ 840.4982211899608
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 103,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 333
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "IMAGE",
+ "links": [
+ 5241
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": false,
+ "horizontal": false
+ },
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 124,
+ "type": "ControlNetApplyAdvanced",
+ "pos": [
+ 1680.9353410012357,
+ 560.4982211899617
+ ],
+ "size": {
+ "0": 315,
+ "1": 166
+ },
+ "flags": {},
+ "order": 104,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 5111
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 5110
+ },
+ {
+ "name": "control_net",
+ "type": "CONTROL_NET",
+ "link": 272
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 5296
+ }
+ ],
+ "outputs": [
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "links": [
+ 5301
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "links": [
+ 5302
+ ],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ControlNetApplyAdvanced"
+ },
+ "widgets_values": [
+ 1,
+ 0,
+ 1
+ ]
+ },
+ {
+ "id": 302,
+ "type": "VAEEncode",
+ "pos": [
+ 1910.935341001228,
+ 780.4982211899609
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 106,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "pixels",
+ "type": "IMAGE",
+ "link": 5241
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 5242
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 5303
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEEncode"
+ }
+ },
+ {
+ "id": 267,
+ "type": "KSampler",
+ "pos": [
+ 2270.935341001233,
+ 570.4982211899617
+ ],
+ "size": {
+ "0": 315,
+ "1": 262
+ },
+ "flags": {},
+ "order": 107,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 5436
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 5301
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 5302
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 5303
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 5159,
+ 5308
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 838611060522510,
+ "randomize",
+ 25,
+ 9,
+ "euler_ancestral",
+ "normal",
+ 0.804544677734375
+ ]
+ },
+ {
+ "id": 268,
+ "type": "Reroute",
+ "pos": [
+ 2620.935341001234,
+ 570.4982211899617
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 108,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5159
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 5293
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 265,
+ "type": "PreviewImage",
+ "pos": [
+ 3130.9159068821527,
+ 500.0091821650353
+ ],
+ "size": {
+ "0": 250,
+ "1": 370
+ },
+ "flags": {},
+ "order": 114,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 5152
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 364,
+ "type": "Int Input [Dream]",
+ "pos": [
+ -2097.1983297494985,
+ 1704.7646814141895
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 5350
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "✍ Framerate",
+ "properties": {
+ "Node name for S&R": "Int Input [Dream]"
+ },
+ "widgets_values": [
+ 15
+ ],
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 365,
+ "type": "Int Input [Dream]",
+ "pos": [
+ -2097.1983297494985,
+ 1604.7646814141895
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 5351
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "✍ Number of frames",
+ "properties": {
+ "Node name for S&R": "Int Input [Dream]"
+ },
+ "widgets_values": [
+ 30
+ ],
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 362,
+ "type": "String Input [Dream]",
+ "pos": [
+ -2097.1983297494985,
+ 1803.7646814141895
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 5346
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "✍ Output Directory",
+ "properties": {
+ "Node name for S&R": "String Input [Dream]"
+ },
+ "widgets_values": [
+ "I:\\AI\\output\\ComfyUI"
+ ],
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 333,
+ "type": "Note",
+ "pos": [
+ -2098.1983297494985,
+ 1907.7646814141895
+ ],
+ "size": {
+ "0": 408.5453796386719,
+ "1": 74.01783752441406
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "title": "Note on settings",
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Both framerate and total number of frames are important since they affect curves and sequence processing. Typically, queue an equal number of prompt executions as the total number of frames."
+ ],
+ "color": "#568479",
+ "bgcolor": "#427065"
+ },
+ {
+ "id": 171,
+ "type": "VAEDecode",
+ "pos": [
+ 3161.547020486233,
+ 412.2550892786084
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 112,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 5294
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 5205
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5152,
+ 5354
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 367,
+ "type": "Reroute",
+ "pos": [
+ 3403.212597957077,
+ 394.68859707729007
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 115,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5354
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5357
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 334,
+ "type": "Note",
+ "pos": [
+ -1376.5813007363186,
+ 888.406571589691
+ ],
+ "size": {
+ "0": 300,
+ "1": 60
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "title": "Note on settings",
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "This node creates progression based on the files in the output directory."
+ ],
+ "color": "#568479",
+ "bgcolor": "#427065"
+ },
+ {
+ "id": 230,
+ "type": "Frame Counter (Directory) [Dream]",
+ "pos": [
+ -1374.2153335854396,
+ 697.9571941696488
+ ],
+ "size": {
+ "0": 320,
+ "1": 150
+ },
+ "flags": {},
+ "order": 28,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "total_frames",
+ "type": "INT",
+ "link": 5351,
+ "widget": {
+ "name": "total_frames"
+ },
+ "slot_index": 0
+ },
+ {
+ "name": "directory_path",
+ "type": "STRING",
+ "link": 5347,
+ "widget": {
+ "name": "directory_path"
+ }
+ },
+ {
+ "name": "frames_per_second",
+ "type": "INT",
+ "link": 5350,
+ "widget": {
+ "name": "frames_per_second"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 5176
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Frame Counter (Directory) [Dream]"
+ },
+ "widgets_values": [
+ "I:\\AI\\output\\ComfyUI",
+ "*",
+ "numeric",
+ 500,
+ 15
+ ],
+ "color": "#2a363b",
+ "bgcolor": "#3f5159"
+ },
+ {
+ "id": 363,
+ "type": "Reroute",
+ "pos": [
+ -1680,
+ 1838
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 21,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5346
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 5347,
+ 5348,
+ 5358
+ ],
+ "slot_index": 0
+ }
+ ],
+ "title": "Test",
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 373,
+ "type": "Reroute",
+ "pos": [
+ -1540,
+ 197
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 24,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5365
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 5366,
+ 5367
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 292,
+ "type": "Linear Curve [Dream]",
+ "pos": [
+ -300.1999890136713,
+ 310.9999736785893
+ ],
+ "size": {
+ "0": 315,
+ "1": 102
+ },
+ "flags": {},
+ "order": 65,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 5220
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 5222
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": null,
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Linear Curve [Dream]"
+ },
+ "widgets_values": [
+ -0.5,
+ 0.5
+ ]
+ },
+ {
+ "id": 291,
+ "type": "Sine Curve [Dream]",
+ "pos": [
+ -300.1999890136713,
+ 465.99997367858884
+ ],
+ "size": {
+ "0": 315,
+ "1": 150
+ },
+ "flags": {},
+ "order": 66,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 5221
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 5223
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Sine Curve [Dream]"
+ },
+ "widgets_values": [
+ 0.3,
+ -0.3,
+ 2,
+ 0
+ ]
+ },
+ {
+ "id": 294,
+ "type": "Beat Curve [Dream]",
+ "pos": [
+ -632.199989013671,
+ 294.9999736785893
+ ],
+ "size": {
+ "0": 315,
+ "1": 318
+ },
+ "flags": {},
+ "order": 67,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 5224
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 5225
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Beat Curve [Dream]"
+ },
+ "widgets_values": [
+ 100,
+ 0,
+ 4,
+ -0.3,
+ 1,
+ "no",
+ 2,
+ 1,
+ 3,
+ 0,
+ 0
+ ]
+ },
+ {
+ "id": 352,
+ "type": "PrimitiveNode",
+ "pos": [
+ -804.1114315455187,
+ 1318.2580203476155
+ ],
+ "size": {
+ "0": 210,
+ "1": 82
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 5325,
+ 5328,
+ 5333,
+ 5334
+ ],
+ "slot_index": 0,
+ "widget": {
+ "name": "alignment_factor"
+ }
+ }
+ ],
+ "title": "Alignment Factor",
+ "properties": {},
+ "widgets_values": [
+ 1.2,
+ "fixed"
+ ],
+ "color": "#2a363b",
+ "bgcolor": "#3f5159"
+ },
+ {
+ "id": 293,
+ "type": "Reroute",
+ "pos": [
+ -806.1999890136706,
+ 358.9999736785893
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 56,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5368
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 5220,
+ 5221,
+ 5224,
+ 5369
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 344,
+ "type": "Sample Image Area as Palette [Dream]",
+ "pos": [
+ -581.1111678736465,
+ 1641.6580613937565
+ ],
+ "size": {
+ "0": 315,
+ "1": 130
+ },
+ "flags": {},
+ "order": 53,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 5319
+ }
+ ],
+ "outputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "links": [
+ 5329
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Sample Image Area as Palette [Dream]"
+ },
+ "widgets_values": [
+ 256,
+ 919440470904266,
+ "randomize",
+ "center-left"
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 342,
+ "type": "Sample Image Area as Palette [Dream]",
+ "pos": [
+ -578.1111678736465,
+ 1820.6580613937606
+ ],
+ "size": {
+ "0": 315,
+ "1": 130
+ },
+ "flags": {},
+ "order": 85,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 5316
+ }
+ ],
+ "outputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "links": [
+ 5330
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Sample Image Area as Palette [Dream]"
+ },
+ "widgets_values": [
+ 256,
+ 528123820881935,
+ "randomize",
+ "center-left"
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 7,
+ "type": "CLIPTextEncode",
+ "pos": [
+ -1256,
+ 9
+ ],
+ "size": {
+ "0": 425.27801513671875,
+ "1": 180.6060791015625
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 32,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 5367
+ },
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 2312,
+ "widget": {
+ "name": "text"
+ },
+ "slot_index": 1
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 5372
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "text, watermark, logo, letters, writing, frame, border, hands, frame, paper"
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 6,
+ "type": "CLIPTextEncode",
+ "pos": [
+ -1250,
+ -43
+ ],
+ "size": {
+ "0": 422.84503173828125,
+ "1": 164.31304931640625
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 31,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 5366
+ },
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 2309,
+ "widget": {
+ "name": "text"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 5371
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ "serene forest landscape, watercolor, detailed"
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 375,
+ "type": "Reroute",
+ "pos": [
+ 300,
+ -70
+ ],
+ "size": [
+ 140.8,
+ 26
+ ],
+ "flags": {},
+ "order": 40,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5371
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 5373
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 368,
+ "type": "Reroute",
+ "pos": [
+ -1236,
+ 24
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 29,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5358
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 5359
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 369,
+ "type": "Reroute",
+ "pos": [
+ 334,
+ 26
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 38,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5359
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 5360
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 274,
+ "type": "Reroute",
+ "pos": [
+ -979,
+ 68
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 45,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5457
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 5368,
+ 5370
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#2a363b",
+ "bgcolor": "#3f5159",
+ "shape": 2
+ },
+ {
+ "id": 376,
+ "type": "Reroute",
+ "pos": [
+ 301,
+ -21
+ ],
+ "size": [
+ 140.8,
+ 26
+ ],
+ "flags": {},
+ "order": 41,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5372
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 5374
+ ]
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 386,
+ "type": "Reroute",
+ "pos": [
+ -1491,
+ -190
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 22,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5392
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 5389
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 382,
+ "type": "Reroute",
+ "pos": [
+ -1462,
+ -149
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 25,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5381
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 5382
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 186,
+ "type": "KSampler",
+ "pos": [
+ 2772.704072973985,
+ 438.14907683566435
+ ],
+ "size": {
+ "0": 320,
+ "1": 470
+ },
+ "flags": {},
+ "order": 110,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 5399
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 5387
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 5386
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 5293
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 5294
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 700759510514483,
+ "randomize",
+ 5,
+ 5,
+ "dpmpp_3m_sde",
+ "exponential",
+ 0.6
+ ]
+ },
+ {
+ "id": 284,
+ "type": "Reroute",
+ "pos": [
+ 2745.704072973985,
+ 371.1490768356643
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 64,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5395
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 5204
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 331,
+ "type": "Note",
+ "pos": [
+ 2810.704072973985,
+ 953.1490768356643
+ ],
+ "size": {
+ "0": 570,
+ "1": 60
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "title": "Note on full frame sampler",
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "This step is really mostly important if you zoom out as it reintroduces details."
+ ],
+ "color": "#568479",
+ "bgcolor": "#427065"
+ },
+ {
+ "id": 339,
+ "type": "Reroute",
+ "pos": [
+ -753.0145320767159,
+ 1250.5620514226848
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 44,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5420
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5318,
+ 5319,
+ 5320,
+ 5321
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#8c8c8c",
+ "bgcolor": "#787878"
+ },
+ {
+ "id": 336,
+ "type": "Reroute",
+ "pos": [
+ -773.8466075929075,
+ 1518.6144670803217
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 81,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5421
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5312,
+ 5315,
+ 5316,
+ 5317
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 398,
+ "type": "Reroute",
+ "pos": [
+ 288,
+ 1073
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 79,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5415,
+ "pos": [
+ 41,
+ 0
+ ]
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5417
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": true
+ }
+ },
+ {
+ "id": 400,
+ "type": "Reroute",
+ "pos": [
+ -878,
+ 1115
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 35,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5419,
+ "pos": [
+ 41,
+ 0
+ ]
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5420
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": true
+ }
+ },
+ {
+ "id": 359,
+ "type": "Note",
+ "pos": [
+ 624.9741736417784,
+ 1294.1377389138133
+ ],
+ "size": {
+ "0": 250,
+ "1": 270
+ },
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "title": "Note on noise",
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "This section of the spaghetti samples the edges of the seed image and the last frame and produces noise that will slightly nudge the colors of the next frame towards the original seed image."
+ ],
+ "color": "#568479",
+ "bgcolor": "#427065"
+ },
+ {
+ "id": 402,
+ "type": "Reroute",
+ "pos": [
+ 176,
+ 1049
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 95,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5423
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5424
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 211,
+ "type": "ImageScale",
+ "pos": [
+ 14,
+ 800
+ ],
+ "size": {
+ "0": 320,
+ "1": 130
+ },
+ "flags": {},
+ "order": 77,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 5085
+ },
+ {
+ "name": "width",
+ "type": "INT",
+ "link": 5074,
+ "widget": {
+ "name": "width"
+ }
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "link": 5075,
+ "widget": {
+ "name": "height"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5088,
+ 5415
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ImageScale"
+ },
+ "widgets_values": [
+ "nearest-exact",
+ 512,
+ 512,
+ "disabled"
+ ]
+ },
+ {
+ "id": 343,
+ "type": "Sample Image Area as Palette [Dream]",
+ "pos": [
+ -556.0258263582216,
+ 1293.1377389138133
+ ],
+ "size": {
+ "0": 315,
+ "1": 130
+ },
+ "flags": {},
+ "order": 52,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 5318
+ }
+ ],
+ "outputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "links": [
+ 5323
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Sample Image Area as Palette [Dream]"
+ },
+ "widgets_values": [
+ 256,
+ 502577689536295,
+ "randomize",
+ "top-center"
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 340,
+ "type": "Sample Image Area as Palette [Dream]",
+ "pos": [
+ -560.0258263582216,
+ 1462.1377389138133
+ ],
+ "size": {
+ "0": 315,
+ "1": 130
+ },
+ "flags": {},
+ "order": 84,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 5315
+ }
+ ],
+ "outputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "links": [
+ 5322
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Sample Image Area as Palette [Dream]"
+ },
+ "widgets_values": [
+ 256,
+ 1125637252645999,
+ "randomize",
+ "top-center"
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 119,
+ "type": "ControlNetLoader",
+ "pos": [
+ 982.0796921393085,
+ 673.1125436264036
+ ],
+ "size": {
+ "0": 400,
+ "1": 90
+ },
+ "flags": {
+ "collapsed": false
+ },
+ "order": 15,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "CONTROL_NET",
+ "type": "CONTROL_NET",
+ "links": [
+ 272
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ControlNetLoader"
+ },
+ "widgets_values": [
+ "SD1.5\\control_v11p_sd15_inpaint.pth"
+ ]
+ },
+ {
+ "id": 325,
+ "type": "InpaintPreprocessor",
+ "pos": [
+ 1164.0796921393064,
+ 841.1125436264033
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 101,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 5295
+ },
+ {
+ "name": "mask",
+ "type": "MASK",
+ "link": 5430
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5296
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "InpaintPreprocessor"
+ }
+ },
+ {
+ "id": 240,
+ "type": "Reroute",
+ "pos": [
+ 1666.6531151244042,
+ 940.0343845569474
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 51,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5426
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 5242
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 150,
+ "type": "Reroute",
+ "pos": [
+ 1202.0796921393064,
+ 909.1125436264033
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 99,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5289
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "IMAGE",
+ "links": [
+ 333
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": false,
+ "horizontal": false
+ },
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 403,
+ "type": "Reroute",
+ "pos": [
+ 924.0796921393085,
+ 936.1125436264036
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 43,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5433
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 5426
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 383,
+ "type": "Reroute",
+ "pos": [
+ 602,
+ -138
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 33,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5382
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 5393,
+ 5433
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 378,
+ "type": "Reroute",
+ "pos": [
+ 1275,
+ -11
+ ],
+ "size": [
+ 140.8,
+ 26
+ ],
+ "flags": {},
+ "order": 49,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5374,
+ "slot_index": 0
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 5376,
+ 5380
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 377,
+ "type": "Reroute",
+ "pos": [
+ 1383,
+ -68
+ ],
+ "size": [
+ 140.8,
+ 26
+ ],
+ "flags": {},
+ "order": 48,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5373,
+ "slot_index": 0
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 5375,
+ 5379
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 380,
+ "type": "Reroute",
+ "pos": [
+ 2416,
+ -69
+ ],
+ "size": [
+ 140.8,
+ 26
+ ],
+ "flags": {},
+ "order": 61,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5379
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 5387
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 381,
+ "type": "Reroute",
+ "pos": [
+ 2381,
+ -17
+ ],
+ "size": [
+ 140.8,
+ 26
+ ],
+ "flags": {},
+ "order": 63,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5380
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 5386
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 234,
+ "type": "Image Motion [Dream]",
+ "pos": [
+ 385,
+ 751
+ ],
+ "size": {
+ "0": 320,
+ "1": 360
+ },
+ "flags": {},
+ "order": 96,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 5088
+ },
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 5199
+ },
+ {
+ "name": "noise",
+ "type": "IMAGE",
+ "link": 5424
+ },
+ {
+ "name": "x_translation",
+ "type": "FLOAT",
+ "link": 5222,
+ "widget": {
+ "name": "x_translation"
+ }
+ },
+ {
+ "name": "y_translation",
+ "type": "FLOAT",
+ "link": 5223,
+ "widget": {
+ "name": "y_translation"
+ }
+ },
+ {
+ "name": "zoom",
+ "type": "FLOAT",
+ "link": 5225,
+ "widget": {
+ "name": "zoom"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 5439
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "mask1",
+ "type": "MASK",
+ "links": [
+ 5438
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "mask2",
+ "type": "MASK",
+ "links": [],
+ "shape": 3,
+ "slot_index": 2
+ },
+ {
+ "name": "mask3",
+ "type": "MASK",
+ "links": [],
+ "shape": 3,
+ "slot_index": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Image Motion [Dream]"
+ },
+ "widgets_values": [
+ -1.1272729492187497,
+ 15,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ },
+ {
+ "id": 279,
+ "type": "Reroute",
+ "pos": [
+ 3598,
+ 525
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 78,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5385
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 5195
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#2a363b",
+ "bgcolor": "#3f5159"
+ },
+ {
+ "id": 407,
+ "type": "Reroute",
+ "pos": [
+ 3608,
+ 569
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 70,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5441
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 5442
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 287,
+ "type": "Image Sequence Blend [Dream]",
+ "pos": [
+ 4100,
+ 560
+ ],
+ "size": {
+ "0": 315,
+ "1": 106
+ },
+ "flags": {},
+ "order": 119,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "sequence",
+ "type": "ANIMATION_SEQUENCE",
+ "link": 5440
+ }
+ ],
+ "outputs": [
+ {
+ "name": "sequence",
+ "type": "ANIMATION_SEQUENCE",
+ "links": [
+ 5210
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Image Sequence Blend [Dream]"
+ },
+ "widgets_values": [
+ 0.1,
+ 0.1,
+ 1
+ ],
+ "color": "#332922",
+ "bgcolor": "#593930"
+ },
+ {
+ "id": 233,
+ "type": "Image Sequence Saver [Dream]",
+ "pos": [
+ 3760,
+ 560
+ ],
+ "size": {
+ "0": 320,
+ "1": 170
+ },
+ "flags": {},
+ "order": 117,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 5195
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 5193
+ },
+ {
+ "name": "directory_path",
+ "type": "STRING",
+ "link": 5442,
+ "widget": {
+ "name": "directory_path"
+ },
+ "slot_index": 2
+ }
+ ],
+ "outputs": [
+ {
+ "name": "sequence",
+ "type": "ANIMATION_SEQUENCE",
+ "links": [
+ 5440
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "log_entry",
+ "type": "LOG_ENTRY",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Image Sequence Saver [Dream]"
+ },
+ "widgets_values": [
+ "I:\\AI\\output\\ComfyUI",
+ "frame",
+ 5,
+ "stop output",
+ "jpg"
+ ],
+ "color": "#332922",
+ "bgcolor": "#593930"
+ },
+ {
+ "id": 389,
+ "type": "Reroute",
+ "pos": [
+ 2480,
+ -180
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 59,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5402
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 5399
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 393,
+ "type": "Reroute",
+ "pos": [
+ 2070,
+ -180
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 47,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5437
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 5402
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 406,
+ "type": "Reroute",
+ "pos": [
+ 1840,
+ -180
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 39,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5434
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 5436,
+ 5437
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 387,
+ "type": "Reroute",
+ "pos": [
+ 510,
+ -180
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 30,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5389
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 5434
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 390,
+ "type": "Reroute",
+ "pos": [
+ 1551,
+ -135
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 42,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5393
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 5394
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 391,
+ "type": "Reroute",
+ "pos": [
+ 2534,
+ -139
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 50,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5394
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 5395
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 374,
+ "type": "Reroute",
+ "pos": [
+ 538,
+ 77
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 57,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5370
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 5378
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 370,
+ "type": "Reroute",
+ "pos": [
+ 2405,
+ 32
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 46,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5360
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 5397
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 379,
+ "type": "Reroute",
+ "pos": [
+ 2345,
+ 78
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 69,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5378
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 5383
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 392,
+ "type": "Reroute",
+ "pos": [
+ 3374,
+ 37
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 58,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5397
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 5441
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 384,
+ "type": "Reroute",
+ "pos": [
+ 2837,
+ 79
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 74,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5383
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 5384
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 385,
+ "type": "Reroute",
+ "pos": [
+ 3293,
+ 80
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 76,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5384
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 5385
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 232,
+ "type": "Image Sequence Loader [Dream]",
+ "pos": [
+ -294.3996392822261,
+ 772.4008465576175
+ ],
+ "size": {
+ "0": 320,
+ "1": 130
+ },
+ "flags": {},
+ "order": 75,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 5082
+ },
+ {
+ "name": "default_image",
+ "type": "IMAGE",
+ "link": 5444
+ },
+ {
+ "name": "directory_path",
+ "type": "STRING",
+ "link": 5348,
+ "widget": {
+ "name": "directory_path"
+ },
+ "slot_index": 2
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 5085
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "frame_name",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Image Sequence Loader [Dream]"
+ },
+ "widgets_values": [
+ "I:\\AI\\output\\ComfyUI",
+ "*",
+ "numeric"
+ ]
+ },
+ {
+ "id": 408,
+ "type": "Reroute",
+ "pos": [
+ -777,
+ 820
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 36,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5456
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5444
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 401,
+ "type": "Reroute",
+ "pos": [
+ -44,
+ 1103
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 93,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5422
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5423
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 399,
+ "type": "Reroute",
+ "pos": [
+ -933,
+ 1273
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 80,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5417,
+ "pos": [
+ 41,
+ 0
+ ]
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5421,
+ 5458
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": true
+ }
+ },
+ {
+ "id": 354,
+ "type": "Noise from Area Palettes [Dream]",
+ "pos": [
+ 634.9741736417784,
+ 1602.1377389138133
+ ],
+ "size": {
+ "0": 342.5999755859375,
+ "1": 362
+ },
+ "flags": {},
+ "order": 92,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "top_left_palette",
+ "type": "RGB_PALETTE",
+ "link": null
+ },
+ {
+ "name": "top_center_palette",
+ "type": "RGB_PALETTE",
+ "link": 5337
+ },
+ {
+ "name": "top_right_palette",
+ "type": "RGB_PALETTE",
+ "link": null
+ },
+ {
+ "name": "center_left_palette",
+ "type": "RGB_PALETTE",
+ "link": 5339
+ },
+ {
+ "name": "center_palette",
+ "type": "RGB_PALETTE",
+ "link": null
+ },
+ {
+ "name": "center_right_palette",
+ "type": "RGB_PALETTE",
+ "link": 5338
+ },
+ {
+ "name": "bottom_left_palette",
+ "type": "RGB_PALETTE",
+ "link": null
+ },
+ {
+ "name": "bottom_center_palette",
+ "type": "RGB_PALETTE",
+ "link": 5340
+ },
+ {
+ "name": "bottom_right_palette",
+ "type": "RGB_PALETTE",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 5422,
+ 5446
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Noise from Area Palettes [Dream]"
+ },
+ "widgets_values": [
+ 0.18181762695312503,
+ 256,
+ 256,
+ 0.11818237304687501,
+ 0.5,
+ 855083604429355,
+ "randomize"
+ ],
+ "color": "#2a363b",
+ "bgcolor": "#3f5159"
+ },
+ {
+ "id": 405,
+ "type": "Reroute",
+ "pos": [
+ 927.0796921393089,
+ 858.1125436264033
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 98,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5438
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": [
+ 5430,
+ 5447
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 237,
+ "type": "Reroute",
+ "pos": [
+ 926.0796921393085,
+ 791.1125436264036
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 97,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5439
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5289,
+ 5295,
+ 5449
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 327,
+ "type": "Reroute",
+ "pos": [
+ 2550.935341001234,
+ 920.498221189961
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 109,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5308,
+ "pos": [
+ 45.2,
+ 0
+ ]
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 5450
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": true
+ }
+ },
+ {
+ "id": 4,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ -2102.608581739303,
+ 1125.9695878411585
+ ],
+ "size": {
+ "0": 400,
+ "1": 100
+ },
+ "flags": {
+ "collapsed": false
+ },
+ "order": 16,
+ "mode": 0,
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 5392
+ ],
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 2977,
+ 5365
+ ],
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 5381,
+ 5451
+ ],
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "public\\main\\512-SD1.5\\Realistic_Vision_V5.0.safetensors"
+ ]
+ },
+ {
+ "id": 415,
+ "type": "Reroute",
+ "pos": [
+ -1416,
+ 2365
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 26,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5451
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 5452
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 338,
+ "type": "Reroute",
+ "pos": [
+ -1251,
+ 169
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 27,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5313
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5419,
+ 5456
+ ],
+ "slot_index": 0
+ }
+ ],
+ "title": "Seed Image",
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#2a363b",
+ "bgcolor": "#3f5159"
+ },
+ {
+ "id": 273,
+ "type": "Reroute",
+ "pos": [
+ -1124.21533358544,
+ 636.9571941696493
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 37,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5176
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 5457
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#2a363b",
+ "bgcolor": "#3f5159"
+ },
+ {
+ "id": 328,
+ "type": "Note",
+ "pos": [
+ -512,
+ 962
+ ],
+ "size": {
+ "0": 370,
+ "1": 90
+ },
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "title": "Note on motion",
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "This group reads the previously rendered frame and performs a motion transformation on the image. The output of the image motion node is the transformed image and up to three masks to use for outpainting."
+ ],
+ "color": "#568479",
+ "bgcolor": "#427065"
+ },
+ {
+ "id": 419,
+ "type": "Reroute",
+ "pos": [
+ -785.8463153465063,
+ 2070.274048358372
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 82,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5458
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5459
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 411,
+ "type": "MaskToImage",
+ "pos": [
+ 1287,
+ 2084
+ ],
+ "size": {
+ "0": 210,
+ "1": 26
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 102,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "mask",
+ "type": "MASK",
+ "link": 5447
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5448
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "MaskToImage"
+ }
+ },
+ {
+ "id": 416,
+ "type": "Reroute",
+ "pos": [
+ 1658,
+ 2360
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 34,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5452
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 5453
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 414,
+ "type": "VAEDecode",
+ "pos": [
+ 1771,
+ 2095
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 111,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 5450
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 5453
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5454
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 278,
+ "type": "Reroute",
+ "pos": [
+ 3614,
+ 610
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 116,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5357
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5193,
+ 5460
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 420,
+ "type": "Reroute",
+ "pos": [
+ 3634.1977835692924,
+ 1253.5845951163976
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 118,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 5460,
+ "pos": [
+ 41,
+ 0
+ ]
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5461
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": true
+ }
+ },
+ {
+ "id": 418,
+ "type": "PreviewImage",
+ "pos": [
+ 1970,
+ 2080
+ ],
+ "size": {
+ "0": 210,
+ "1": 246
+ },
+ "flags": {},
+ "order": 120,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 5461
+ }
+ ],
+ "title": "Saved Image",
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 417,
+ "type": "PreviewImage",
+ "pos": [
+ 1730,
+ 2080
+ ],
+ "size": {
+ "0": 210,
+ "1": 246
+ },
+ "flags": {},
+ "order": 113,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 5454
+ }
+ ],
+ "title": "After Inpainting",
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 413,
+ "type": "PreviewImage",
+ "pos": [
+ 1510,
+ 2080
+ ],
+ "size": {
+ "0": 210,
+ "1": 246
+ },
+ "flags": {},
+ "order": 100,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 5449
+ }
+ ],
+ "title": "Inpainting input",
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 412,
+ "type": "PreviewImage",
+ "pos": [
+ 1270,
+ 2080
+ ],
+ "size": {
+ "0": 210,
+ "1": 246
+ },
+ "flags": {},
+ "order": 105,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 5448
+ }
+ ],
+ "title": "Inpainting mask",
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 410,
+ "type": "PreviewImage",
+ "pos": [
+ 1040,
+ 2080
+ ],
+ "size": {
+ "0": 210,
+ "1": 246
+ },
+ "flags": {},
+ "order": 94,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 5446
+ }
+ ],
+ "title": "Noise",
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 409,
+ "type": "PreviewImage",
+ "pos": [
+ 800,
+ 2080
+ ],
+ "size": {
+ "0": 210,
+ "1": 246
+ },
+ "flags": {},
+ "order": 87,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 5459
+ }
+ ],
+ "title": "Previous Frame",
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 281,
+ "type": "Image Sequence Tweening [Dream]",
+ "pos": [
+ 4440,
+ 560
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 121,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "sequence",
+ "type": "ANIMATION_SEQUENCE",
+ "link": 5210
+ }
+ ],
+ "outputs": [
+ {
+ "name": "sequence",
+ "type": "ANIMATION_SEQUENCE",
+ "links": [
+ 5463
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Image Sequence Tweening [Dream]"
+ },
+ "widgets_values": [
+ 2
+ ],
+ "color": "#332922",
+ "bgcolor": "#593930"
+ },
+ {
+ "id": 422,
+ "type": "FFMPEG Video Encoder [Dream]",
+ "pos": [
+ 4781,
+ 562
+ ],
+ "size": {
+ "0": 315,
+ "1": 106
+ },
+ "flags": {},
+ "order": 122,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "sequence",
+ "type": "ANIMATION_SEQUENCE",
+ "link": 5463
+ }
+ ],
+ "outputs": [
+ {
+ "name": "log_entry",
+ "type": "LOG_ENTRY",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "FFMPEG Video Encoder [Dream]"
+ },
+ "widgets_values": [
+ "video",
+ 1,
+ true
+ ]
+ },
+ {
+ "id": 236,
+ "type": "LoadImage",
+ "pos": [
+ -2106,
+ 679
+ ],
+ "size": {
+ "0": 430,
+ "1": 340
+ },
+ "flags": {},
+ "order": 18,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 5313
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "7159726-HSC00002-7 (1).jpg",
+ "image"
+ ],
+ "color": "#2a363b",
+ "bgcolor": "#3f5159"
+ }
+ ],
+ "links": [
+ [
+ 272,
+ 119,
+ 0,
+ 124,
+ 2,
+ "CONTROL_NET"
+ ],
+ [
+ 333,
+ 150,
+ 0,
+ 58,
+ 0,
+ "*"
+ ],
+ [
+ 2309,
+ 78,
+ 0,
+ 6,
+ 1,
+ "STRING"
+ ],
+ [
+ 2312,
+ 77,
+ 0,
+ 7,
+ 1,
+ "STRING"
+ ],
+ [
+ 2977,
+ 4,
+ 1,
+ 201,
+ 0,
+ "*"
+ ],
+ [
+ 5073,
+ 227,
+ 1,
+ 229,
+ 0,
+ "*"
+ ],
+ [
+ 5074,
+ 228,
+ 0,
+ 211,
+ 1,
+ "INT"
+ ],
+ [
+ 5075,
+ 229,
+ 0,
+ 211,
+ 2,
+ "INT"
+ ],
+ [
+ 5080,
+ 87,
+ 0,
+ 231,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 5081,
+ 231,
+ 0,
+ 82,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 5082,
+ 231,
+ 0,
+ 232,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 5085,
+ 232,
+ 0,
+ 211,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5088,
+ 211,
+ 0,
+ 234,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5110,
+ 242,
+ 0,
+ 124,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 5111,
+ 241,
+ 0,
+ 124,
+ 0,
+ "CONDITIONING"
+ ],
+ [
+ 5152,
+ 171,
+ 0,
+ 265,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5159,
+ 267,
+ 0,
+ 268,
+ 0,
+ "*"
+ ],
+ [
+ 5176,
+ 230,
+ 0,
+ 273,
+ 0,
+ "*"
+ ],
+ [
+ 5179,
+ 227,
+ 0,
+ 228,
+ 0,
+ "*"
+ ],
+ [
+ 5193,
+ 278,
+ 0,
+ 233,
+ 1,
+ "IMAGE"
+ ],
+ [
+ 5195,
+ 279,
+ 0,
+ 233,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 5198,
+ 87,
+ 0,
+ 282,
+ 0,
+ "*"
+ ],
+ [
+ 5199,
+ 282,
+ 0,
+ 234,
+ 1,
+ "FRAME_COUNTER"
+ ],
+ [
+ 5204,
+ 284,
+ 0,
+ 285,
+ 0,
+ "*"
+ ],
+ [
+ 5205,
+ 285,
+ 0,
+ 171,
+ 1,
+ "VAE"
+ ],
+ [
+ 5210,
+ 287,
+ 0,
+ 281,
+ 0,
+ "ANIMATION_SEQUENCE"
+ ],
+ [
+ 5220,
+ 293,
+ 0,
+ 292,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 5221,
+ 293,
+ 0,
+ 291,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 5222,
+ 292,
+ 0,
+ 234,
+ 3,
+ "FLOAT"
+ ],
+ [
+ 5223,
+ 291,
+ 0,
+ 234,
+ 4,
+ "FLOAT"
+ ],
+ [
+ 5224,
+ 293,
+ 0,
+ 294,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 5225,
+ 294,
+ 0,
+ 234,
+ 5,
+ "FLOAT"
+ ],
+ [
+ 5241,
+ 58,
+ 0,
+ 302,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5242,
+ 240,
+ 0,
+ 302,
+ 1,
+ "VAE"
+ ],
+ [
+ 5289,
+ 237,
+ 0,
+ 150,
+ 0,
+ "*"
+ ],
+ [
+ 5293,
+ 268,
+ 0,
+ 186,
+ 3,
+ "LATENT"
+ ],
+ [
+ 5294,
+ 186,
+ 0,
+ 171,
+ 0,
+ "LATENT"
+ ],
+ [
+ 5295,
+ 237,
+ 0,
+ 325,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5296,
+ 325,
+ 0,
+ 124,
+ 3,
+ "IMAGE"
+ ],
+ [
+ 5301,
+ 124,
+ 0,
+ 267,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 5302,
+ 124,
+ 1,
+ 267,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 5303,
+ 302,
+ 0,
+ 267,
+ 3,
+ "LATENT"
+ ],
+ [
+ 5308,
+ 267,
+ 0,
+ 327,
+ 0,
+ "*"
+ ],
+ [
+ 5312,
+ 336,
+ 0,
+ 337,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5313,
+ 236,
+ 0,
+ 338,
+ 0,
+ "*"
+ ],
+ [
+ 5315,
+ 336,
+ 0,
+ 340,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5316,
+ 336,
+ 0,
+ 342,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5317,
+ 336,
+ 0,
+ 341,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5318,
+ 339,
+ 0,
+ 343,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5319,
+ 339,
+ 0,
+ 344,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5320,
+ 339,
+ 0,
+ 345,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5321,
+ 339,
+ 0,
+ 346,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5322,
+ 340,
+ 0,
+ 347,
+ 0,
+ "RGB_PALETTE"
+ ],
+ [
+ 5323,
+ 343,
+ 0,
+ 347,
+ 1,
+ "RGB_PALETTE"
+ ],
+ [
+ 5325,
+ 352,
+ 0,
+ 347,
+ 2,
+ "FLOAT"
+ ],
+ [
+ 5326,
+ 341,
+ 0,
+ 348,
+ 0,
+ "RGB_PALETTE"
+ ],
+ [
+ 5327,
+ 345,
+ 0,
+ 348,
+ 1,
+ "RGB_PALETTE"
+ ],
+ [
+ 5328,
+ 352,
+ 0,
+ 348,
+ 2,
+ "FLOAT"
+ ],
+ [
+ 5329,
+ 344,
+ 0,
+ 349,
+ 1,
+ "RGB_PALETTE"
+ ],
+ [
+ 5330,
+ 342,
+ 0,
+ 349,
+ 0,
+ "RGB_PALETTE"
+ ],
+ [
+ 5331,
+ 346,
+ 0,
+ 350,
+ 1,
+ "RGB_PALETTE"
+ ],
+ [
+ 5332,
+ 337,
+ 0,
+ 350,
+ 0,
+ "RGB_PALETTE"
+ ],
+ [
+ 5333,
+ 352,
+ 0,
+ 349,
+ 2,
+ "FLOAT"
+ ],
+ [
+ 5334,
+ 352,
+ 0,
+ 350,
+ 2,
+ "FLOAT"
+ ],
+ [
+ 5337,
+ 347,
+ 0,
+ 354,
+ 1,
+ "RGB_PALETTE"
+ ],
+ [
+ 5338,
+ 348,
+ 0,
+ 354,
+ 5,
+ "RGB_PALETTE"
+ ],
+ [
+ 5339,
+ 349,
+ 0,
+ 354,
+ 3,
+ "RGB_PALETTE"
+ ],
+ [
+ 5340,
+ 350,
+ 0,
+ 354,
+ 7,
+ "RGB_PALETTE"
+ ],
+ [
+ 5346,
+ 362,
+ 0,
+ 363,
+ 0,
+ "*"
+ ],
+ [
+ 5347,
+ 363,
+ 0,
+ 230,
+ 1,
+ "STRING"
+ ],
+ [
+ 5348,
+ 363,
+ 0,
+ 232,
+ 2,
+ "STRING"
+ ],
+ [
+ 5350,
+ 364,
+ 0,
+ 230,
+ 2,
+ "INT"
+ ],
+ [
+ 5351,
+ 365,
+ 0,
+ 230,
+ 0,
+ "INT"
+ ],
+ [
+ 5354,
+ 171,
+ 0,
+ 367,
+ 0,
+ "*"
+ ],
+ [
+ 5357,
+ 367,
+ 0,
+ 278,
+ 0,
+ "*"
+ ],
+ [
+ 5358,
+ 363,
+ 0,
+ 368,
+ 0,
+ "*"
+ ],
+ [
+ 5359,
+ 368,
+ 0,
+ 369,
+ 0,
+ "*"
+ ],
+ [
+ 5360,
+ 369,
+ 0,
+ 370,
+ 0,
+ "*"
+ ],
+ [
+ 5365,
+ 4,
+ 1,
+ 373,
+ 0,
+ "*"
+ ],
+ [
+ 5366,
+ 373,
+ 0,
+ 6,
+ 0,
+ "CLIP"
+ ],
+ [
+ 5367,
+ 373,
+ 0,
+ 7,
+ 0,
+ "CLIP"
+ ],
+ [
+ 5368,
+ 274,
+ 0,
+ 293,
+ 0,
+ "*"
+ ],
+ [
+ 5369,
+ 293,
+ 0,
+ 87,
+ 0,
+ "*"
+ ],
+ [
+ 5370,
+ 274,
+ 0,
+ 374,
+ 0,
+ "*"
+ ],
+ [
+ 5371,
+ 6,
+ 0,
+ 375,
+ 0,
+ "*"
+ ],
+ [
+ 5372,
+ 7,
+ 0,
+ 376,
+ 0,
+ "*"
+ ],
+ [
+ 5373,
+ 375,
+ 0,
+ 377,
+ 0,
+ "*"
+ ],
+ [
+ 5374,
+ 376,
+ 0,
+ 378,
+ 0,
+ "*"
+ ],
+ [
+ 5375,
+ 377,
+ 0,
+ 241,
+ 0,
+ "*"
+ ],
+ [
+ 5376,
+ 378,
+ 0,
+ 242,
+ 0,
+ "*"
+ ],
+ [
+ 5378,
+ 374,
+ 0,
+ 379,
+ 0,
+ "*"
+ ],
+ [
+ 5379,
+ 377,
+ 0,
+ 380,
+ 0,
+ "*"
+ ],
+ [
+ 5380,
+ 378,
+ 0,
+ 381,
+ 0,
+ "*"
+ ],
+ [
+ 5381,
+ 4,
+ 2,
+ 382,
+ 0,
+ "*"
+ ],
+ [
+ 5382,
+ 382,
+ 0,
+ 383,
+ 0,
+ "*"
+ ],
+ [
+ 5383,
+ 379,
+ 0,
+ 384,
+ 0,
+ "*"
+ ],
+ [
+ 5384,
+ 384,
+ 0,
+ 385,
+ 0,
+ "*"
+ ],
+ [
+ 5385,
+ 385,
+ 0,
+ 279,
+ 0,
+ "*"
+ ],
+ [
+ 5386,
+ 381,
+ 0,
+ 186,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 5387,
+ 380,
+ 0,
+ 186,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 5389,
+ 386,
+ 0,
+ 387,
+ 0,
+ "*"
+ ],
+ [
+ 5392,
+ 4,
+ 0,
+ 386,
+ 0,
+ "*"
+ ],
+ [
+ 5393,
+ 383,
+ 0,
+ 390,
+ 0,
+ "*"
+ ],
+ [
+ 5394,
+ 390,
+ 0,
+ 391,
+ 0,
+ "*"
+ ],
+ [
+ 5395,
+ 391,
+ 0,
+ 284,
+ 0,
+ "*"
+ ],
+ [
+ 5397,
+ 370,
+ 0,
+ 392,
+ 0,
+ "*"
+ ],
+ [
+ 5399,
+ 389,
+ 0,
+ 186,
+ 0,
+ "MODEL"
+ ],
+ [
+ 5402,
+ 393,
+ 0,
+ 389,
+ 0,
+ "*"
+ ],
+ [
+ 5415,
+ 211,
+ 0,
+ 398,
+ 0,
+ "*"
+ ],
+ [
+ 5417,
+ 398,
+ 0,
+ 399,
+ 0,
+ "*"
+ ],
+ [
+ 5419,
+ 338,
+ 0,
+ 400,
+ 0,
+ "*"
+ ],
+ [
+ 5420,
+ 400,
+ 0,
+ 339,
+ 0,
+ "*"
+ ],
+ [
+ 5421,
+ 399,
+ 0,
+ 336,
+ 0,
+ "*"
+ ],
+ [
+ 5422,
+ 354,
+ 0,
+ 401,
+ 0,
+ "*"
+ ],
+ [
+ 5423,
+ 401,
+ 0,
+ 402,
+ 0,
+ "*"
+ ],
+ [
+ 5424,
+ 402,
+ 0,
+ 234,
+ 2,
+ "IMAGE"
+ ],
+ [
+ 5426,
+ 403,
+ 0,
+ 240,
+ 0,
+ "*"
+ ],
+ [
+ 5430,
+ 405,
+ 0,
+ 325,
+ 1,
+ "MASK"
+ ],
+ [
+ 5433,
+ 383,
+ 0,
+ 403,
+ 0,
+ "*"
+ ],
+ [
+ 5434,
+ 387,
+ 0,
+ 406,
+ 0,
+ "*"
+ ],
+ [
+ 5436,
+ 406,
+ 0,
+ 267,
+ 0,
+ "MODEL"
+ ],
+ [
+ 5437,
+ 406,
+ 0,
+ 393,
+ 0,
+ "*"
+ ],
+ [
+ 5438,
+ 234,
+ 1,
+ 405,
+ 0,
+ "*"
+ ],
+ [
+ 5439,
+ 234,
+ 0,
+ 237,
+ 0,
+ "*"
+ ],
+ [
+ 5440,
+ 233,
+ 0,
+ 287,
+ 0,
+ "ANIMATION_SEQUENCE"
+ ],
+ [
+ 5441,
+ 392,
+ 0,
+ 407,
+ 0,
+ "*"
+ ],
+ [
+ 5442,
+ 407,
+ 0,
+ 233,
+ 2,
+ "STRING"
+ ],
+ [
+ 5444,
+ 408,
+ 0,
+ 232,
+ 1,
+ "IMAGE"
+ ],
+ [
+ 5446,
+ 354,
+ 0,
+ 410,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5447,
+ 405,
+ 0,
+ 411,
+ 0,
+ "MASK"
+ ],
+ [
+ 5448,
+ 411,
+ 0,
+ 412,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5449,
+ 237,
+ 0,
+ 413,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5450,
+ 327,
+ 0,
+ 414,
+ 0,
+ "LATENT"
+ ],
+ [
+ 5451,
+ 4,
+ 2,
+ 415,
+ 0,
+ "*"
+ ],
+ [
+ 5452,
+ 415,
+ 0,
+ 416,
+ 0,
+ "*"
+ ],
+ [
+ 5453,
+ 416,
+ 0,
+ 414,
+ 1,
+ "VAE"
+ ],
+ [
+ 5454,
+ 414,
+ 0,
+ 417,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5456,
+ 338,
+ 0,
+ 408,
+ 0,
+ "*"
+ ],
+ [
+ 5457,
+ 273,
+ 0,
+ 274,
+ 0,
+ "*"
+ ],
+ [
+ 5458,
+ 399,
+ 0,
+ 419,
+ 0,
+ "*"
+ ],
+ [
+ 5459,
+ 419,
+ 0,
+ 409,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5460,
+ 278,
+ 0,
+ 420,
+ 0,
+ "*"
+ ],
+ [
+ 5461,
+ 420,
+ 0,
+ 418,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 5463,
+ 281,
+ 0,
+ 422,
+ 0,
+ "ANIMATION_SEQUENCE"
+ ]
+ ],
+ "groups": [
+ {
+ "title": "Prompting",
+ "bounding": [
+ -2129,
+ 148,
+ 474,
+ 885
+ ],
+ "color": "#a1309b",
+ "font_size": 24
+ },
+ {
+ "title": "Settings",
+ "bounding": [
+ -2125,
+ 1259,
+ 482,
+ 742
+ ],
+ "color": "#b58b2a",
+ "font_size": 24
+ },
+ {
+ "title": "Inpainting/Outpainting",
+ "bounding": [
+ 950,
+ 483,
+ 1711,
+ 528
+ ],
+ "color": "#929054",
+ "font_size": 24
+ },
+ {
+ "title": "Prev Frame Move",
+ "bounding": [
+ -736,
+ 630,
+ 1450,
+ 456
+ ],
+ "color": "#3f789e",
+ "font_size": 24
+ },
+ {
+ "title": "Full frame sampler",
+ "bounding": [
+ 2764,
+ 318,
+ 691,
+ 700
+ ],
+ "color": "#88A",
+ "font_size": 24
+ },
+ {
+ "title": "Output",
+ "bounding": [
+ 3653,
+ 373,
+ 1458,
+ 381
+ ],
+ "color": "#b06634",
+ "font_size": 24
+ },
+ {
+ "title": "Animation Driver",
+ "bounding": [
+ -1385,
+ 592,
+ 336,
+ 373
+ ],
+ "color": "#3f789e",
+ "font_size": 24
+ },
+ {
+ "title": "Motion Control",
+ "bounding": [
+ -738,
+ 235,
+ 1451,
+ 392
+ ],
+ "color": "#ef75ff",
+ "font_size": 24
+ },
+ {
+ "title": "Model selection",
+ "bounding": [
+ -2128,
+ 1042,
+ 476,
+ 204
+ ],
+ "color": "#3f789e",
+ "font_size": 24
+ },
+ {
+ "title": "Noise",
+ "bounding": [
+ -730,
+ 1213,
+ 1840,
+ 765
+ ],
+ "color": "#3f789e",
+ "font_size": 24
+ },
+ {
+ "title": "Previews",
+ "bounding": [
+ -2124,
+ 2011,
+ 6473,
+ 329
+ ],
+ "color": "#444",
+ "font_size": 24
+ }
+ ],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui-dream-project/examples/prompt-morphing.json b/custom_nodes/comfyui-dream-project/examples/prompt-morphing.json
new file mode 100644
index 0000000000000000000000000000000000000000..ee3eca8f1efabb09db6205c227314f2653fba287
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/examples/prompt-morphing.json
@@ -0,0 +1,7024 @@
+{
+ "last_node_id": 193,
+ "last_link_id": 341,
+ "nodes": [
+ {
+ "id": 62,
+ "type": "Reroute",
+ "pos": [
+ -310,
+ -670
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 24,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 91
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 96
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 57,
+ "type": "Build Prompt [Dream]",
+ "pos": [
+ 1290,
+ -310
+ ],
+ "size": {
+ "0": 279.916748046875,
+ "1": 100
+ },
+ "flags": {},
+ "order": 86,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "partial_prompt",
+ "type": "PARTIAL_PROMPT",
+ "link": 85
+ },
+ {
+ "name": "weight",
+ "type": "FLOAT",
+ "link": 289,
+ "widget": {
+ "name": "weight"
+ },
+ "slot_index": 1
+ }
+ ],
+ "outputs": [
+ {
+ "name": "partial_prompt",
+ "type": "PARTIAL_PROMPT",
+ "links": [
+ 87
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Build Prompt [Dream]"
+ },
+ "widgets_values": [
+ "sunny, spring",
+ 1
+ ]
+ },
+ {
+ "id": 26,
+ "type": "Build Prompt [Dream]",
+ "pos": [
+ 970,
+ -310
+ ],
+ "size": {
+ "0": 279.916748046875,
+ "1": 100
+ },
+ "flags": {},
+ "order": 83,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "partial_prompt",
+ "type": "PARTIAL_PROMPT",
+ "link": 37
+ },
+ {
+ "name": "weight",
+ "type": "FLOAT",
+ "link": 286,
+ "widget": {
+ "name": "weight"
+ },
+ "slot_index": 1
+ }
+ ],
+ "outputs": [
+ {
+ "name": "partial_prompt",
+ "type": "PARTIAL_PROMPT",
+ "links": [
+ 85
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Build Prompt [Dream]"
+ },
+ "widgets_values": [
+ "snow, winter",
+ 1
+ ]
+ },
+ {
+ "id": 25,
+ "type": "Build Prompt [Dream]",
+ "pos": [
+ 650,
+ -310
+ ],
+ "size": {
+ "0": 279.916748046875,
+ "1": 100
+ },
+ "flags": {},
+ "order": 76,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "partial_prompt",
+ "type": "PARTIAL_PROMPT",
+ "link": 36
+ },
+ {
+ "name": "weight",
+ "type": "FLOAT",
+ "link": 283,
+ "widget": {
+ "name": "weight"
+ },
+ "slot_index": 1
+ }
+ ],
+ "outputs": [
+ {
+ "name": "partial_prompt",
+ "type": "PARTIAL_PROMPT",
+ "links": [
+ 37
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Build Prompt [Dream]"
+ },
+ "widgets_values": [
+ "autumn, rain",
+ 1
+ ]
+ },
+ {
+ "id": 85,
+ "type": "Reroute",
+ "pos": [
+ 2234,
+ 424
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 133
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 179
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 84,
+ "type": "Reroute",
+ "pos": [
+ 2132,
+ 391
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 132
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 176
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 19,
+ "type": "Frame Counter Offset [Dream]",
+ "pos": [
+ 2762.9266748065575,
+ -73.37708387563696
+ ],
+ "size": {
+ "0": 342.5999755859375,
+ "1": 58
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 81,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 269
+ }
+ ],
+ "outputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 25
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Frame Counter Offset [Dream]"
+ },
+ "widgets_values": [
+ -1
+ ]
+ },
+ {
+ "id": 96,
+ "type": "Reroute",
+ "pos": [
+ -128,
+ 457
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 15,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 156
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 335
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 78,
+ "type": "String Input [Dream]",
+ "pos": [
+ -836,
+ 275
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 119,
+ 156
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "✍ Output Directory",
+ "properties": {
+ "Node name for S&R": "String Input [Dream]"
+ },
+ "widgets_values": [
+ "I:\\AI\\output\\ComfyUI"
+ ]
+ },
+ {
+ "id": 124,
+ "type": "Reroute",
+ "pos": [
+ 4551,
+ -297
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 70,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 214
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 215
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 103,
+ "type": "Sample Image as Palette [Dream]",
+ "pos": [
+ 4600,
+ -219
+ ],
+ "size": {
+ "0": 315,
+ "1": 106
+ },
+ "flags": {
+ "collapsed": false
+ },
+ "order": 77,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 215
+ }
+ ],
+ "outputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "links": [
+ 209
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Sample Image as Palette [Dream]"
+ },
+ "widgets_values": [
+ 1024,
+ 1023351604428941,
+ "randomize"
+ ]
+ },
+ {
+ "id": 125,
+ "type": "Reroute",
+ "pos": [
+ 4539,
+ 26
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 106,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 273
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 217,
+ 220
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 127,
+ "type": "Reroute",
+ "pos": [
+ 4923,
+ -116
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 111,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 220
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 221
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 104,
+ "type": "Sample Image as Palette [Dream]",
+ "pos": [
+ 4596,
+ 108
+ ],
+ "size": {
+ "0": 315,
+ "1": 106
+ },
+ "flags": {
+ "collapsed": false
+ },
+ "order": 110,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 217
+ }
+ ],
+ "outputs": [
+ {
+ "name": "palette",
+ "type": "RGB_PALETTE",
+ "links": [
+ 208
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Sample Image as Palette [Dream]"
+ },
+ "widgets_values": [
+ 1024,
+ 557841854168100,
+ "randomize"
+ ]
+ },
+ {
+ "id": 119,
+ "type": "Compare Palettes [Dream]",
+ "pos": [
+ 4732,
+ -28
+ ],
+ "size": {
+ "0": 292.20001220703125,
+ "1": 86
+ },
+ "flags": {},
+ "order": 113,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "a",
+ "type": "RGB_PALETTE",
+ "link": 208
+ },
+ {
+ "name": "b",
+ "type": "RGB_PALETTE",
+ "link": 209
+ }
+ ],
+ "outputs": [
+ {
+ "name": "brightness_multiplier",
+ "type": "FLOAT",
+ "links": [
+ 211
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "contrast_multiplier",
+ "type": "FLOAT",
+ "links": [
+ 210
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "red_multiplier",
+ "type": "FLOAT",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "green_multiplier",
+ "type": "FLOAT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Compare Palettes [Dream]"
+ }
+ },
+ {
+ "id": 118,
+ "type": "Reroute",
+ "pos": [
+ 5071,
+ 34
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 116,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 210
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 227
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 129,
+ "type": "Calculation [Dream]",
+ "pos": [
+ 5183,
+ 272
+ ],
+ "size": {
+ "0": 301.4544372558594,
+ "1": 232
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 119,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "r_float",
+ "type": "FLOAT",
+ "link": 224,
+ "widget": {
+ "name": "r_float"
+ }
+ },
+ {
+ "name": "s_float",
+ "type": "FLOAT",
+ "link": 223,
+ "widget": {
+ "name": "s_float"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 225
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Calculation [Dream]"
+ },
+ "widgets_values": [
+ "(r - 1)*s + 1",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ },
+ {
+ "id": 121,
+ "type": "Reroute",
+ "pos": [
+ 5107,
+ 156
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 115,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 211
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 224
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 131,
+ "type": "Reroute",
+ "pos": [
+ 5327,
+ 179
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 123,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 225
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 228
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 132,
+ "type": "Calculation [Dream]",
+ "pos": [
+ 5182,
+ 114
+ ],
+ "size": {
+ "0": 301.4544372558594,
+ "1": 232
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 120,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "r_float",
+ "type": "FLOAT",
+ "link": 227,
+ "widget": {
+ "name": "r_float"
+ }
+ },
+ {
+ "name": "s_float",
+ "type": "FLOAT",
+ "link": 226,
+ "widget": {
+ "name": "s_float"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 229
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Calculation [Dream]"
+ },
+ "widgets_values": [
+ "(r - 1)*s + 1",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ },
+ {
+ "id": 133,
+ "type": "Reroute",
+ "pos": [
+ 5068,
+ -49
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 124,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 229
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 230
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 122,
+ "type": "Image Contrast Adjustment [Dream]",
+ "pos": [
+ 4949,
+ -295
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 125,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 221
+ },
+ {
+ "name": "factor",
+ "type": "FLOAT",
+ "link": 230,
+ "widget": {
+ "name": "factor"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 222
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Image Contrast Adjustment [Dream]"
+ },
+ "widgets_values": [
+ 1
+ ]
+ },
+ {
+ "id": 128,
+ "type": "Image Brightness Adjustment [Dream]",
+ "pos": [
+ 5113,
+ -187
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 126,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 222
+ },
+ {
+ "name": "factor",
+ "type": "FLOAT",
+ "link": 228,
+ "widget": {
+ "name": "factor"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 235
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Image Brightness Adjustment [Dream]"
+ },
+ "widgets_values": [
+ 1
+ ]
+ },
+ {
+ "id": 89,
+ "type": "Reroute",
+ "pos": [
+ 4714,
+ -643
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 65,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 143
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 231
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 50,
+ "type": "MiDaS-DepthMapPreprocessor",
+ "pos": [
+ 3604.370798354893,
+ -255.81188496288598
+ ],
+ "size": {
+ "0": 315,
+ "1": 106
+ },
+ "flags": {},
+ "order": 63,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 247
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 74
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "MiDaS-DepthMapPreprocessor"
+ },
+ "widgets_values": [
+ 5.237730900441297,
+ 0.16363647460937536,
+ 512
+ ]
+ },
+ {
+ "id": 147,
+ "type": "Reroute",
+ "pos": [
+ 3806.370798354893,
+ -99.81188496288625
+ ],
+ "size": [
+ 140.8,
+ 26
+ ],
+ "flags": {},
+ "order": 108,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 252
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 254
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 88,
+ "type": "Reroute",
+ "pos": [
+ 3943,
+ -642
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 57,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 138
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 143
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 69,
+ "type": "Reroute",
+ "pos": [
+ 3357,
+ -637
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 50,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 103
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 138
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 68,
+ "type": "Reroute",
+ "pos": [
+ 1745,
+ -640
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 38,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 102
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 103
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 63,
+ "type": "Reroute",
+ "pos": [
+ -230,
+ -640
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 25,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 92
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 102
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 43,
+ "type": "Reroute",
+ "pos": [
+ -253,
+ -602
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 39,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 68
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 69,
+ 76
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 44,
+ "type": "Reroute",
+ "pos": [
+ 498,
+ -605
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 51,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 69
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 71
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 82,
+ "type": "Reroute",
+ "pos": [
+ 3402,
+ -603
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 80,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 130
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 155
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 80,
+ "type": "Reroute",
+ "pos": [
+ 4208,
+ -610
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 84,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 155
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 123
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 93,
+ "type": "Reroute",
+ "pos": [
+ 5686.218317189983,
+ -120.99836627792959
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 90,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 260,
+ "pos": [
+ 74.6,
+ 0
+ ]
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 152
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": true
+ }
+ },
+ {
+ "id": 79,
+ "type": "Reroute",
+ "pos": [
+ 5324,
+ -607
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 87,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 123
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 260,
+ 261
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 134,
+ "type": "Reroute",
+ "pos": [
+ 5798,
+ -637
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 72,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 231
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 232,
+ 263
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 149,
+ "type": "Reroute",
+ "pos": [
+ 6305,
+ -638
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 79,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 263
+ }
+ ],
+ "outputs": [
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 264
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 21,
+ "type": "VAEEncode",
+ "pos": [
+ 5892.055568991249,
+ -318.8922317182515
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 127,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "pixels",
+ "type": "IMAGE",
+ "link": 235
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 232
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 140
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEEncode"
+ }
+ },
+ {
+ "id": 64,
+ "type": "Reroute",
+ "pos": [
+ 981,
+ -714
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 36,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 93
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 97
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 65,
+ "type": "Reroute",
+ "pos": [
+ 1846,
+ -673
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 37,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 96
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 98,
+ 99
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 66,
+ "type": "Reroute",
+ "pos": [
+ 1845,
+ -714
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 49,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 97
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 100
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 92,
+ "type": "Reroute",
+ "pos": [
+ 4454,
+ -714
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 71,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 296
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 233
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 135,
+ "type": "Reroute",
+ "pos": [
+ 5910,
+ -714
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 78,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 233
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 234
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 61,
+ "type": "Reroute",
+ "pos": [
+ -313,
+ -714
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 23,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 90
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 93
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 47,
+ "type": "Reroute",
+ "pos": [
+ -249,
+ -565
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 22,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 70
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 72
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 49,
+ "type": "Reroute",
+ "pos": [
+ 1875,
+ -579
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 35,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 72
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 117
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 77,
+ "type": "Reroute",
+ "pos": [
+ 2697,
+ -568
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 48,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 117
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 161,
+ 246
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 100,
+ "type": "Reroute",
+ "pos": [
+ 3421,
+ -561
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 54,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 161
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 162,
+ 213
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 123,
+ "type": "Reroute",
+ "pos": [
+ 4412,
+ -572
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 62,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 213
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 214
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 90,
+ "type": "Reroute",
+ "pos": [
+ 4750,
+ -520
+ ],
+ "size": [
+ 140.8,
+ 26
+ ],
+ "flags": {},
+ "order": 121,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 145
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 147
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 91,
+ "type": "Reroute",
+ "pos": [
+ 4749,
+ -480
+ ],
+ "size": [
+ 140.8,
+ 26
+ ],
+ "flags": {},
+ "order": 122,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 146
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 148
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 151,
+ "type": "Image Sequence Blend [Dream]",
+ "pos": [
+ 7811.282361863094,
+ -318.08540210493265
+ ],
+ "size": {
+ "0": 315,
+ "1": 106
+ },
+ "flags": {},
+ "order": 134,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "sequence",
+ "type": "ANIMATION_SEQUENCE",
+ "link": 266
+ }
+ ],
+ "outputs": [
+ {
+ "name": "sequence",
+ "type": "ANIMATION_SEQUENCE",
+ "links": [
+ 267
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Image Sequence Blend [Dream]"
+ },
+ "widgets_values": [
+ 0.2,
+ 0.2,
+ 3
+ ]
+ },
+ {
+ "id": 72,
+ "type": "Reroute",
+ "pos": [
+ 4327,
+ -527
+ ],
+ "size": [
+ 140.8,
+ 26
+ ],
+ "flags": {},
+ "order": 117,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 141
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 145
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 73,
+ "type": "Reroute",
+ "pos": [
+ 4348,
+ -480
+ ],
+ "size": [
+ 140.8,
+ 26
+ ],
+ "flags": {},
+ "order": 118,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 142
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 146
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 70,
+ "type": "Reroute",
+ "pos": [
+ 2584,
+ -523
+ ],
+ "size": [
+ 140.8,
+ 26
+ ],
+ "flags": {},
+ "order": 104,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 104
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 251
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 71,
+ "type": "Reroute",
+ "pos": [
+ 2582,
+ -476
+ ],
+ "size": [
+ 140.8,
+ 26
+ ],
+ "flags": {},
+ "order": 105,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 105
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 252,
+ 268
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 152,
+ "type": "Reroute",
+ "pos": [
+ 3302,
+ -471
+ ],
+ "size": [
+ 140.8,
+ 26
+ ],
+ "flags": {},
+ "order": 109,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 268
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": null
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 146,
+ "type": "Reroute",
+ "pos": [
+ 3652,
+ -166
+ ],
+ "size": [
+ 140.8,
+ 26
+ ],
+ "flags": {},
+ "order": 107,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 251
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 253
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 112,
+ "type": "Reroute",
+ "pos": [
+ 2905,
+ 428
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 31,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 179
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 180
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 111,
+ "type": "Reroute",
+ "pos": [
+ 2678,
+ 389
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 30,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 176
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 177
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 126,
+ "type": "Reroute",
+ "pos": [
+ 6090,
+ 460
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 53,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 218
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 219
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 155,
+ "type": "Reroute",
+ "pos": [
+ 4438,
+ 429
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 102,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 272
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 273
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 143,
+ "type": "Reroute",
+ "pos": [
+ 3555,
+ -427
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 55,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 246,
+ "pos": [
+ 41,
+ 0
+ ]
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 247
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": true
+ }
+ },
+ {
+ "id": 52,
+ "type": "Reroute",
+ "pos": [
+ -360,
+ -190
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 52,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 76
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 83,
+ 278
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 24,
+ "type": "Build Prompt [Dream]",
+ "pos": [
+ 310,
+ -310
+ ],
+ "size": {
+ "0": 279.916748046875,
+ "1": 100
+ },
+ "flags": {},
+ "order": 69,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "partial_prompt",
+ "type": "PARTIAL_PROMPT",
+ "link": 81,
+ "slot_index": 0
+ },
+ {
+ "name": "weight",
+ "type": "FLOAT",
+ "link": 279,
+ "widget": {
+ "name": "weight"
+ },
+ "slot_index": 1
+ }
+ ],
+ "outputs": [
+ {
+ "name": "partial_prompt",
+ "type": "PARTIAL_PROMPT",
+ "links": [
+ 36
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Build Prompt [Dream]"
+ },
+ "widgets_values": [
+ "summer, green grass",
+ 1
+ ]
+ },
+ {
+ "id": 140,
+ "type": "Reroute",
+ "pos": [
+ 30,
+ 50
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 21,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 241,
+ "slot_index": 0
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 240,
+ 281
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 56,
+ "type": "Reroute",
+ "pos": [
+ -50,
+ -190
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 59,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 83
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 112,
+ 282
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 139,
+ "type": "Reroute",
+ "pos": [
+ 411,
+ 41
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 34,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 240,
+ "slot_index": 0
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 239,
+ 284
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 75,
+ "type": "Reroute",
+ "pos": [
+ 330,
+ -190
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 67,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 112
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 114,
+ 285
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 138,
+ "type": "Reroute",
+ "pos": [
+ 817,
+ 37
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 47,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 239,
+ "slot_index": 0
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 287
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 76,
+ "type": "Reroute",
+ "pos": [
+ 750,
+ -190
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 74,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 114
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 288
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 130,
+ "type": "Float Input [Dream]",
+ "pos": [
+ 4596,
+ 280
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 223,
+ 226
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "✍ Alignment factor",
+ "properties": {
+ "Node name for S&R": "Float Input [Dream]"
+ },
+ "widgets_values": [
+ 0.7
+ ]
+ },
+ {
+ "id": 7,
+ "type": "VAEDecode",
+ "pos": [
+ 6531.266744446639,
+ -328.62463277942487
+ ],
+ "size": {
+ "0": 210,
+ "1": 46
+ },
+ "flags": {},
+ "order": 129,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 7
+ },
+ {
+ "name": "vae",
+ "type": "VAE",
+ "link": 264,
+ "slot_index": 1
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 9,
+ 292
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VAEDecode"
+ }
+ },
+ {
+ "id": 163,
+ "type": "UpscaleModelLoader",
+ "pos": [
+ 6835.001486999512,
+ -334.6799815673828
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "UPSCALE_MODEL",
+ "type": "UPSCALE_MODEL",
+ "links": [
+ 294
+ ],
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "UpscaleModelLoader"
+ },
+ "widgets_values": [
+ "4x_foolhardy_Remacri.pth"
+ ]
+ },
+ {
+ "id": 164,
+ "type": "Note",
+ "pos": [
+ 6839,
+ -128
+ ],
+ "size": {
+ "0": 355.8685302734375,
+ "1": 59.4520263671875
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Using 4x_foolhardy_Remacri for upscale.\nhttps://huggingface.co/FacehugmanIII/4x_foolhardy_Remacri"
+ ],
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 67,
+ "type": "Reroute",
+ "pos": [
+ 3088,
+ -714
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 56,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 100
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 295
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 167,
+ "type": "Reroute",
+ "pos": [
+ 3566,
+ -712
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 64,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 295
+ }
+ ],
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 296
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 162,
+ "type": "ImageUpscaleWithModel",
+ "pos": [
+ 6849.001486999512,
+ -225.6799815673828
+ ],
+ "size": {
+ "0": 241.79998779296875,
+ "1": 46
+ },
+ "flags": {},
+ "order": 131,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "upscale_model",
+ "type": "UPSCALE_MODEL",
+ "link": 294,
+ "slot_index": 0
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 292
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 303
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ImageUpscaleWithModel"
+ }
+ },
+ {
+ "id": 98,
+ "type": "Reroute",
+ "pos": [
+ 7324,
+ 448
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 61,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 219
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 159
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 145,
+ "type": "ControlNetLoader",
+ "pos": [
+ 3614,
+ -381
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "CONTROL_NET",
+ "type": "CONTROL_NET",
+ "links": [
+ 305
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ControlNetLoader"
+ },
+ "widgets_values": [
+ "SD1.5\\control_v11f1p_sd15_depth.pth"
+ ]
+ },
+ {
+ "id": 87,
+ "type": "Reroute",
+ "pos": [
+ 3153,
+ 390
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 43,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 177
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 136
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 86,
+ "type": "Reroute",
+ "pos": [
+ 3157,
+ 427
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 44,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 180
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 137
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 171,
+ "type": "Reroute",
+ "pos": [
+ -104,
+ 501
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 18,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 309
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 310
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 172,
+ "type": "Reroute",
+ "pos": [
+ -102,
+ 538
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 19,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 308
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 311
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 173,
+ "type": "Reroute",
+ "pos": [
+ 3989.896920186283,
+ 499.7709166573809
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 32,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 310
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 312
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 174,
+ "type": "Reroute",
+ "pos": [
+ 4007,
+ 543
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 33,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 311
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 313
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 175,
+ "type": "Reroute",
+ "pos": [
+ 7134,
+ 482
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 45,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 312
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 314
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 176,
+ "type": "Reroute",
+ "pos": [
+ 7181,
+ 519
+ ],
+ "size": [
+ 75,
+ 26
+ ],
+ "flags": {},
+ "order": 46,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 313
+ }
+ ],
+ "outputs": [
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": [
+ 315
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 83,
+ "type": "Common Frame Dimensions [Dream]",
+ "pos": [
+ -830,
+ 387
+ ],
+ "size": {
+ "0": 315,
+ "1": 238
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "width",
+ "type": "INT",
+ "links": [
+ 132
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "links": [
+ 133
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "final_width",
+ "type": "INT",
+ "links": [
+ 309
+ ],
+ "shape": 3,
+ "slot_index": 2
+ },
+ {
+ "name": "final_height",
+ "type": "INT",
+ "links": [
+ 308
+ ],
+ "shape": 3,
+ "slot_index": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Common Frame Dimensions [Dream]"
+ },
+ "widgets_values": [
+ "1280",
+ "16:9",
+ "wide",
+ "2",
+ 64,
+ "ceil"
+ ]
+ },
+ {
+ "id": 170,
+ "type": "ImageScale",
+ "pos": [
+ 7168,
+ -225
+ ],
+ "size": {
+ "0": 315,
+ "1": 130
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 132,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 303
+ },
+ {
+ "name": "width",
+ "type": "INT",
+ "link": 314,
+ "widget": {
+ "name": "width"
+ }
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "link": 315,
+ "widget": {
+ "name": "height"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 304
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ImageScale"
+ },
+ "widgets_values": [
+ "nearest-exact",
+ 512,
+ 512,
+ "disabled"
+ ]
+ },
+ {
+ "id": 18,
+ "type": "Image Sequence Loader [Dream]",
+ "pos": [
+ 2974.4051691056047,
+ -29.907667959345808
+ ],
+ "size": {
+ "0": 315,
+ "1": 126
+ },
+ "flags": {},
+ "order": 85,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 25
+ },
+ {
+ "name": "default_image",
+ "type": "IMAGE",
+ "link": 162
+ },
+ {
+ "name": "directory_path",
+ "type": "STRING",
+ "link": 257,
+ "widget": {
+ "name": "directory_path"
+ },
+ "slot_index": 2
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 187
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "frame_name",
+ "type": "STRING",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Image Sequence Loader [Dream]"
+ },
+ "widgets_values": [
+ "I:\\AI\\output\\ComfyUI",
+ "*",
+ "numeric"
+ ]
+ },
+ {
+ "id": 154,
+ "type": "Reroute",
+ "pos": [
+ 3482,
+ 429
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 98,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 271
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 272,
+ 317
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 178,
+ "type": "LineArtPreprocessor",
+ "pos": [
+ 3606,
+ 104
+ ],
+ "size": {
+ "0": 315,
+ "1": 82
+ },
+ "flags": {},
+ "order": 103,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 317
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 318
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LineArtPreprocessor"
+ },
+ "widgets_values": [
+ "disable",
+ 512
+ ]
+ },
+ {
+ "id": 177,
+ "type": "ControlNetLoader",
+ "pos": [
+ 3593,
+ -2
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "CONTROL_NET",
+ "type": "CONTROL_NET",
+ "links": [
+ 316
+ ],
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ControlNetLoader"
+ },
+ "widgets_values": [
+ "SD1.5\\control_v11p_sd15_lineart.pth"
+ ]
+ },
+ {
+ "id": 34,
+ "type": "ImageScale",
+ "pos": [
+ 3337.8065851212295,
+ -1.1077146512402969
+ ],
+ "size": {
+ "0": 315,
+ "1": 130
+ },
+ "flags": {
+ "collapsed": true
+ },
+ "order": 88,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 187
+ },
+ {
+ "name": "width",
+ "type": "INT",
+ "link": 136,
+ "widget": {
+ "name": "width"
+ },
+ "slot_index": 1
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "link": 137,
+ "widget": {
+ "name": "height"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 270
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ImageScale"
+ },
+ "widgets_values": [
+ "bicubic",
+ 512,
+ 512,
+ "disabled"
+ ]
+ },
+ {
+ "id": 55,
+ "type": "Build Prompt [Dream]",
+ "pos": [
+ 1,
+ -313
+ ],
+ "size": {
+ "0": 297.6357116699219,
+ "1": 112.5521240234375
+ },
+ "flags": {},
+ "order": 20,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "partial_prompt",
+ "type": "PARTIAL_PROMPT",
+ "link": 80
+ }
+ ],
+ "outputs": [
+ {
+ "name": "partial_prompt",
+ "type": "PARTIAL_PROMPT",
+ "links": [
+ 81
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Build Prompt [Dream]"
+ },
+ "widgets_values": [
+ "mountain landscape, photo, professional, realistic",
+ 0.8
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 165,
+ "type": "Note",
+ "pos": [
+ 3869,
+ 215
+ ],
+ "size": {
+ "0": 442.48046875,
+ "1": 58
+ },
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "properties": {
+ "text": ""
+ },
+ "widgets_values": [
+ "Using control_v11f1p_sd15_depth.pth and control_v11f1p_sd15_lineart.pth\nhttps://huggingface.co/lllyasviel/ControlNet-v1-1/tree/main"
+ ],
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 157,
+ "type": "Triangle Event Curve [Dream]",
+ "pos": [
+ -257,
+ -118
+ ],
+ "size": {
+ "0": 315,
+ "1": 150
+ },
+ "flags": {},
+ "order": 60,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 278
+ },
+ {
+ "name": "width_seconds",
+ "type": "FLOAT",
+ "link": 280,
+ "widget": {
+ "name": "width_seconds"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 279
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Triangle Event Curve [Dream]"
+ },
+ "widgets_values": [
+ 1,
+ 0,
+ 1,
+ 2
+ ]
+ },
+ {
+ "id": 158,
+ "type": "Triangle Event Curve [Dream]",
+ "pos": [
+ 150,
+ -121
+ ],
+ "size": {
+ "0": 315,
+ "1": 150
+ },
+ "flags": {},
+ "order": 68,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 282
+ },
+ {
+ "name": "width_seconds",
+ "type": "FLOAT",
+ "link": 281,
+ "widget": {
+ "name": "width_seconds"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 283
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Triangle Event Curve [Dream]"
+ },
+ "widgets_values": [
+ 1,
+ 0,
+ 1,
+ 4
+ ]
+ },
+ {
+ "id": 159,
+ "type": "Triangle Event Curve [Dream]",
+ "pos": [
+ 544,
+ -123
+ ],
+ "size": {
+ "0": 315,
+ "1": 150
+ },
+ "flags": {},
+ "order": 75,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 285
+ },
+ {
+ "name": "width_seconds",
+ "type": "FLOAT",
+ "link": 284,
+ "widget": {
+ "name": "width_seconds"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 286
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Triangle Event Curve [Dream]"
+ },
+ "widgets_values": [
+ 1,
+ 0,
+ 1,
+ 6
+ ]
+ },
+ {
+ "id": 160,
+ "type": "Triangle Event Curve [Dream]",
+ "pos": [
+ 927,
+ -122
+ ],
+ "size": {
+ "0": 315,
+ "1": 150
+ },
+ "flags": {},
+ "order": 82,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 288
+ },
+ {
+ "name": "width_seconds",
+ "type": "FLOAT",
+ "link": 287,
+ "widget": {
+ "name": "width_seconds"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 289
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Triangle Event Curve [Dream]"
+ },
+ "widgets_values": [
+ 1,
+ 0,
+ 1,
+ 8
+ ]
+ },
+ {
+ "id": 39,
+ "type": "Build Prompt [Dream]",
+ "pos": [
+ -270,
+ -310
+ ],
+ "size": {
+ "0": 245.1999969482422,
+ "1": 100
+ },
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "partial_prompt",
+ "type": "PARTIAL_PROMPT",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "partial_prompt",
+ "type": "PARTIAL_PROMPT",
+ "links": [
+ 80
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Build Prompt [Dream]"
+ },
+ "widgets_values": [
+ "logo, text, watermark, signature, signed, frame, signed, pumpkin",
+ -0.8
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 46,
+ "type": "ControlNetApplyAdvanced",
+ "pos": [
+ 3974.370798354893,
+ -410.8118849628873
+ ],
+ "size": {
+ "0": 315,
+ "1": 166
+ },
+ "flags": {},
+ "order": 114,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 255
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 256
+ },
+ {
+ "name": "control_net",
+ "type": "CONTROL_NET",
+ "link": 305,
+ "slot_index": 2
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 74
+ }
+ ],
+ "outputs": [
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "links": [
+ 141
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "links": [
+ 142
+ ],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ControlNetApplyAdvanced"
+ },
+ "widgets_values": [
+ 0.8,
+ 0,
+ 1
+ ]
+ },
+ {
+ "id": 141,
+ "type": "Float Input [Dream]",
+ "pos": [
+ -308,
+ 71
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 9,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 241,
+ 280
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "title": "✍ Event Width",
+ "properties": {
+ "Node name for S&R": "Float Input [Dream]"
+ },
+ "widgets_values": [
+ 7
+ ]
+ },
+ {
+ "id": 94,
+ "type": "Beat Curve [Dream]",
+ "pos": [
+ 5588.415456647985,
+ -33.219014131323135
+ ],
+ "size": {
+ "0": 315,
+ "1": 318
+ },
+ "flags": {},
+ "order": 97,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 152
+ },
+ {
+ "name": "low_value",
+ "type": "FLOAT",
+ "link": 319,
+ "widget": {
+ "name": "low_value"
+ },
+ "slot_index": 1
+ },
+ {
+ "name": "high_value",
+ "type": "FLOAT",
+ "link": 320,
+ "widget": {
+ "name": "high_value"
+ },
+ "slot_index": 2
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 153
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "INT",
+ "type": "INT",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Beat Curve [Dream]"
+ },
+ "widgets_values": [
+ 60,
+ 0,
+ 4,
+ 0.475,
+ 0.7,
+ "no",
+ 1,
+ 1,
+ 0,
+ 0,
+ 0
+ ]
+ },
+ {
+ "id": 45,
+ "type": "LoadImage",
+ "pos": [
+ -827,
+ -99
+ ],
+ "size": {
+ "0": 315,
+ "1": 314
+ },
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 70
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "frame_00004.jpg",
+ "image"
+ ]
+ },
+ {
+ "id": 5,
+ "type": "CheckpointLoaderSimple",
+ "pos": [
+ -836,
+ -266
+ ],
+ "size": {
+ "0": 315,
+ "1": 98
+ },
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "MODEL",
+ "type": "MODEL",
+ "links": [
+ 90
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "CLIP",
+ "type": "CLIP",
+ "links": [
+ 91
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "VAE",
+ "type": "VAE",
+ "links": [
+ 92
+ ],
+ "shape": 3,
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CheckpointLoaderSimple"
+ },
+ "widgets_values": [
+ "public\\main\\768-SD2.1\\landscapeRealistic_v20768BetterRender.safetensors"
+ ]
+ },
+ {
+ "id": 6,
+ "type": "KSampler",
+ "pos": [
+ 6117,
+ -417
+ ],
+ "size": {
+ "0": 315,
+ "1": 262
+ },
+ "flags": {},
+ "order": 128,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MODEL",
+ "link": 234
+ },
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 147
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 148
+ },
+ {
+ "name": "latent_image",
+ "type": "LATENT",
+ "link": 140,
+ "slot_index": 3
+ },
+ {
+ "name": "denoise",
+ "type": "FLOAT",
+ "link": 153,
+ "widget": {
+ "name": "denoise"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "LATENT",
+ "type": "LATENT",
+ "links": [
+ 7
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "KSampler"
+ },
+ "widgets_values": [
+ 1051311018316524,
+ "randomize",
+ 30,
+ 7.5,
+ "euler",
+ "normal",
+ 0.8290911865234379
+ ]
+ },
+ {
+ "id": 8,
+ "type": "PreviewImage",
+ "pos": [
+ 6063,
+ -103
+ ],
+ "size": {
+ "0": 672.37939453125,
+ "1": 436.3568115234375
+ },
+ "flags": {},
+ "order": 130,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 9
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 148,
+ "type": "Reroute",
+ "pos": [
+ 7052,
+ -617
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 91,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 261
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 262
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 17,
+ "type": "Frame Counter (Directory) [Dream]",
+ "pos": [
+ -826,
+ -666
+ ],
+ "size": {
+ "0": 315,
+ "1": 154
+ },
+ "flags": {},
+ "order": 26,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "directory_path",
+ "type": "STRING",
+ "link": 119,
+ "widget": {
+ "name": "directory_path"
+ },
+ "slot_index": 0
+ },
+ {
+ "name": "total_frames",
+ "type": "INT",
+ "link": 154,
+ "widget": {
+ "name": "total_frames"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 68
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Frame Counter (Directory) [Dream]"
+ },
+ "widgets_values": [
+ "I:\\AI\\output\\ComfyUI",
+ "*",
+ "numeric",
+ 300,
+ 15
+ ]
+ },
+ {
+ "id": 95,
+ "type": "Frame Count Calculator [Dream]",
+ "pos": [
+ -1217,
+ -652
+ ],
+ "size": {
+ "0": 315,
+ "1": 154
+ },
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "TOTAL",
+ "type": "INT",
+ "links": [
+ 154
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Frame Count Calculator [Dream]"
+ },
+ "widgets_values": [
+ 0,
+ 0,
+ 10,
+ 0,
+ 15
+ ]
+ },
+ {
+ "id": 22,
+ "type": "Image Sequence Saver [Dream]",
+ "pos": [
+ 7448.356765125588,
+ -318.5521625401107
+ ],
+ "size": {
+ "0": 315,
+ "1": 174
+ },
+ "flags": {},
+ "order": 133,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 262
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 304
+ },
+ {
+ "name": "directory_path",
+ "type": "STRING",
+ "link": 159,
+ "widget": {
+ "name": "directory_path"
+ },
+ "slot_index": 2
+ }
+ ],
+ "outputs": [
+ {
+ "name": "sequence",
+ "type": "ANIMATION_SEQUENCE",
+ "links": [
+ 266
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "log_entry",
+ "type": "LOG_ENTRY",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Image Sequence Saver [Dream]"
+ },
+ "widgets_values": [
+ "I:\\AI\\output\\ComfyUI",
+ "frame",
+ 5,
+ "stop output",
+ "jpg"
+ ]
+ },
+ {
+ "id": 59,
+ "type": "Reroute",
+ "pos": [
+ 1986,
+ -317
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 93,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 88
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 94
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 60,
+ "type": "Reroute",
+ "pos": [
+ 1988,
+ -272
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 95,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 89
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 95
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ },
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 74,
+ "type": "Reroute",
+ "pos": [
+ 2671,
+ -608
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 73,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 333
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 130,
+ 269
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 191,
+ "type": "Reroute",
+ "pos": [
+ 2267,
+ -614
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 66,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 332
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 333
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 3,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 2179,
+ -346
+ ],
+ "size": {
+ "0": 210,
+ "1": 54
+ },
+ "flags": {},
+ "order": 99,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 98,
+ "slot_index": 0
+ },
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 94,
+ "widget": {
+ "name": "text"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 104
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ ""
+ ],
+ "color": "#232",
+ "bgcolor": "#353"
+ },
+ {
+ "id": 9,
+ "type": "CLIPTextEncode",
+ "pos": [
+ 2176,
+ -243
+ ],
+ "size": {
+ "0": 210,
+ "1": 54
+ },
+ "flags": {},
+ "order": 100,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "clip",
+ "type": "CLIP",
+ "link": 99,
+ "slot_index": 0
+ },
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 95,
+ "widget": {
+ "name": "text"
+ },
+ "slot_index": 1
+ }
+ ],
+ "outputs": [
+ {
+ "name": "CONDITIONING",
+ "type": "CONDITIONING",
+ "links": [
+ 105
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CLIPTextEncode"
+ },
+ "widgets_values": [
+ ""
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 97,
+ "type": "Reroute",
+ "pos": [
+ 2710,
+ 460
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 42,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 337
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 218,
+ 257
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 153,
+ "type": "Reroute",
+ "pos": [
+ 3463,
+ 179
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 92,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 270
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 271
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 181,
+ "type": "Reroute",
+ "pos": [
+ 1193,
+ 576
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 27,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 322,
+ "slot_index": 0
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 321
+ ]
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 183,
+ "type": "Reroute",
+ "pos": [
+ 1089,
+ 613
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 28,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 324,
+ "slot_index": 0
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 323
+ ]
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 180,
+ "type": "Reroute",
+ "pos": [
+ 5491,
+ 588
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 41,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 323,
+ "slot_index": 0
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 320
+ ]
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 179,
+ "type": "Reroute",
+ "pos": [
+ 5384,
+ 560
+ ],
+ "size": [
+ 82,
+ 26
+ ],
+ "flags": {},
+ "order": 40,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 321,
+ "slot_index": 0
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 319
+ ]
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 182,
+ "type": "Float Input [Dream]",
+ "pos": [
+ -828,
+ 712
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 322
+ ],
+ "shape": 3
+ }
+ ],
+ "title": "✍ Low Denoise",
+ "properties": {
+ "Node name for S&R": "Float Input [Dream]"
+ },
+ "widgets_values": [
+ 0.55
+ ]
+ },
+ {
+ "id": 142,
+ "type": "ControlNetApplyAdvanced",
+ "pos": [
+ 3986,
+ -3
+ ],
+ "size": {
+ "0": 315,
+ "1": 166
+ },
+ "flags": {},
+ "order": 112,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "link": 253
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "link": 254
+ },
+ {
+ "name": "control_net",
+ "type": "CONTROL_NET",
+ "link": 316,
+ "slot_index": 2
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 318
+ }
+ ],
+ "outputs": [
+ {
+ "name": "positive",
+ "type": "CONDITIONING",
+ "links": [
+ 255
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "negative",
+ "type": "CONDITIONING",
+ "links": [
+ 256
+ ],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ControlNetApplyAdvanced"
+ },
+ "widgets_values": [
+ 0.75,
+ 0,
+ 1
+ ]
+ },
+ {
+ "id": 184,
+ "type": "Float Input [Dream]",
+ "pos": [
+ -830,
+ 814
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "FLOAT",
+ "type": "FLOAT",
+ "links": [
+ 324
+ ],
+ "shape": 3
+ }
+ ],
+ "title": "✍ High Denoise",
+ "properties": {
+ "Node name for S&R": "Float Input [Dream]"
+ },
+ "widgets_values": [
+ 0.75
+ ]
+ },
+ {
+ "id": 38,
+ "type": "Finalize Prompt [Dream]",
+ "pos": [
+ 1658,
+ -311
+ ],
+ "size": {
+ "0": 315,
+ "1": 126
+ },
+ "flags": {},
+ "order": 89,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "partial_prompt",
+ "type": "PARTIAL_PROMPT",
+ "link": 87
+ }
+ ],
+ "outputs": [
+ {
+ "name": "positive",
+ "type": "STRING",
+ "links": [
+ 88,
+ 338
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "negative",
+ "type": "STRING",
+ "links": [
+ 89,
+ 339
+ ],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Finalize Prompt [Dream]"
+ },
+ "widgets_values": [
+ "by_abs_max",
+ 1.5,
+ 1.25
+ ]
+ },
+ {
+ "id": 189,
+ "type": "String to Log Entry [Dream]",
+ "pos": [
+ 1318,
+ -132
+ ],
+ "size": {
+ "0": 315,
+ "1": 82
+ },
+ "flags": {},
+ "order": 94,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 338,
+ "widget": {
+ "name": "text"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "log_entry",
+ "type": "LOG_ENTRY",
+ "links": [
+ 330
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "String to Log Entry [Dream]"
+ },
+ "widgets_values": [
+ "",
+ "Positive prompt"
+ ]
+ },
+ {
+ "id": 190,
+ "type": "String to Log Entry [Dream]",
+ "pos": [
+ 1240,
+ 7
+ ],
+ "size": {
+ "0": 315,
+ "1": 82
+ },
+ "flags": {},
+ "order": 96,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "text",
+ "type": "STRING",
+ "link": 339,
+ "widget": {
+ "name": "text"
+ }
+ }
+ ],
+ "outputs": [
+ {
+ "name": "log_entry",
+ "type": "LOG_ENTRY",
+ "links": [
+ 331
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "String to Log Entry [Dream]"
+ },
+ "widgets_values": [
+ "",
+ "Negative prompt"
+ ]
+ },
+ {
+ "id": 188,
+ "type": "Log File [Dream]",
+ "pos": [
+ 1676,
+ -142
+ ],
+ "size": {
+ "0": 315,
+ "1": 314
+ },
+ "flags": {},
+ "order": 101,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "frame_counter",
+ "type": "FRAME_COUNTER",
+ "link": 340
+ },
+ {
+ "name": "entry_0",
+ "type": "LOG_ENTRY",
+ "link": 330
+ },
+ {
+ "name": "entry_1",
+ "type": "LOG_ENTRY",
+ "link": 331
+ },
+ {
+ "name": "entry_2",
+ "type": "LOG_ENTRY",
+ "link": null
+ },
+ {
+ "name": "entry_3",
+ "type": "LOG_ENTRY",
+ "link": null
+ },
+ {
+ "name": "entry_4",
+ "type": "LOG_ENTRY",
+ "link": null
+ },
+ {
+ "name": "entry_5",
+ "type": "LOG_ENTRY",
+ "link": null
+ },
+ {
+ "name": "entry_6",
+ "type": "LOG_ENTRY",
+ "link": null
+ },
+ {
+ "name": "entry_7",
+ "type": "LOG_ENTRY",
+ "link": null
+ },
+ {
+ "name": "log_directory",
+ "type": "STRING",
+ "link": 336,
+ "widget": {
+ "name": "log_directory"
+ }
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Log File [Dream]"
+ },
+ "widgets_values": [
+ "I:\\AI\\ComfyUI\\ComfyUI\\output",
+ "dreamlog.txt",
+ true,
+ true,
+ true
+ ]
+ },
+ {
+ "id": 48,
+ "type": "Reroute",
+ "pos": [
+ 1361,
+ -612
+ ],
+ "size": [
+ 149.2,
+ 26
+ ],
+ "flags": {},
+ "order": 58,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 71
+ }
+ ],
+ "outputs": [
+ {
+ "name": "FRAME_COUNTER",
+ "type": "FRAME_COUNTER",
+ "links": [
+ 332,
+ 340
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 192,
+ "type": "Reroute",
+ "pos": [
+ 1512,
+ 462
+ ],
+ "size": [
+ 90.4,
+ 26
+ ],
+ "flags": {},
+ "order": 29,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "",
+ "type": "*",
+ "link": 335
+ }
+ ],
+ "outputs": [
+ {
+ "name": "STRING",
+ "type": "STRING",
+ "links": [
+ 336,
+ 337
+ ],
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "showOutputText": true,
+ "horizontal": false
+ }
+ },
+ {
+ "id": 37,
+ "type": "Image Sequence Tweening [Dream]",
+ "pos": [
+ 8156.282361863093,
+ -319.08540210493265
+ ],
+ "size": {
+ "0": 315,
+ "1": 58
+ },
+ "flags": {},
+ "order": 135,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "sequence",
+ "type": "ANIMATION_SEQUENCE",
+ "link": 267
+ }
+ ],
+ "outputs": [
+ {
+ "name": "sequence",
+ "type": "ANIMATION_SEQUENCE",
+ "links": [
+ 341
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Image Sequence Tweening [Dream]"
+ },
+ "widgets_values": [
+ 4
+ ]
+ },
+ {
+ "id": 193,
+ "type": "FFMPEG Video Encoder [Dream]",
+ "pos": [
+ 8506,
+ -319
+ ],
+ "size": {
+ "0": 315,
+ "1": 106
+ },
+ "flags": {},
+ "order": 136,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "sequence",
+ "type": "ANIMATION_SEQUENCE",
+ "link": 341
+ }
+ ],
+ "outputs": [
+ {
+ "name": "log_entry",
+ "type": "LOG_ENTRY",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "FFMPEG Video Encoder [Dream]"
+ },
+ "widgets_values": [
+ "video",
+ 1,
+ true
+ ]
+ }
+ ],
+ "links": [
+ [
+ 7,
+ 6,
+ 0,
+ 7,
+ 0,
+ "LATENT"
+ ],
+ [
+ 9,
+ 7,
+ 0,
+ 8,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 25,
+ 19,
+ 0,
+ 18,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 36,
+ 24,
+ 0,
+ 25,
+ 0,
+ "PARTIAL_PROMPT"
+ ],
+ [
+ 37,
+ 25,
+ 0,
+ 26,
+ 0,
+ "PARTIAL_PROMPT"
+ ],
+ [
+ 68,
+ 17,
+ 0,
+ 43,
+ 0,
+ "*"
+ ],
+ [
+ 69,
+ 43,
+ 0,
+ 44,
+ 0,
+ "*"
+ ],
+ [
+ 70,
+ 45,
+ 0,
+ 47,
+ 0,
+ "*"
+ ],
+ [
+ 71,
+ 44,
+ 0,
+ 48,
+ 0,
+ "*"
+ ],
+ [
+ 72,
+ 47,
+ 0,
+ 49,
+ 0,
+ "*"
+ ],
+ [
+ 74,
+ 50,
+ 0,
+ 46,
+ 3,
+ "IMAGE"
+ ],
+ [
+ 76,
+ 43,
+ 0,
+ 52,
+ 0,
+ "*"
+ ],
+ [
+ 80,
+ 39,
+ 0,
+ 55,
+ 0,
+ "PARTIAL_PROMPT"
+ ],
+ [
+ 81,
+ 55,
+ 0,
+ 24,
+ 0,
+ "PARTIAL_PROMPT"
+ ],
+ [
+ 83,
+ 52,
+ 0,
+ 56,
+ 0,
+ "*"
+ ],
+ [
+ 85,
+ 26,
+ 0,
+ 57,
+ 0,
+ "PARTIAL_PROMPT"
+ ],
+ [
+ 87,
+ 57,
+ 0,
+ 38,
+ 0,
+ "PARTIAL_PROMPT"
+ ],
+ [
+ 88,
+ 38,
+ 0,
+ 59,
+ 0,
+ "*"
+ ],
+ [
+ 89,
+ 38,
+ 1,
+ 60,
+ 0,
+ "*"
+ ],
+ [
+ 90,
+ 5,
+ 0,
+ 61,
+ 0,
+ "*"
+ ],
+ [
+ 91,
+ 5,
+ 1,
+ 62,
+ 0,
+ "*"
+ ],
+ [
+ 92,
+ 5,
+ 2,
+ 63,
+ 0,
+ "*"
+ ],
+ [
+ 93,
+ 61,
+ 0,
+ 64,
+ 0,
+ "*"
+ ],
+ [
+ 94,
+ 59,
+ 0,
+ 3,
+ 1,
+ "STRING"
+ ],
+ [
+ 95,
+ 60,
+ 0,
+ 9,
+ 1,
+ "STRING"
+ ],
+ [
+ 96,
+ 62,
+ 0,
+ 65,
+ 0,
+ "*"
+ ],
+ [
+ 97,
+ 64,
+ 0,
+ 66,
+ 0,
+ "*"
+ ],
+ [
+ 98,
+ 65,
+ 0,
+ 3,
+ 0,
+ "CLIP"
+ ],
+ [
+ 99,
+ 65,
+ 0,
+ 9,
+ 0,
+ "CLIP"
+ ],
+ [
+ 100,
+ 66,
+ 0,
+ 67,
+ 0,
+ "*"
+ ],
+ [
+ 102,
+ 63,
+ 0,
+ 68,
+ 0,
+ "*"
+ ],
+ [
+ 103,
+ 68,
+ 0,
+ 69,
+ 0,
+ "*"
+ ],
+ [
+ 104,
+ 3,
+ 0,
+ 70,
+ 0,
+ "*"
+ ],
+ [
+ 105,
+ 9,
+ 0,
+ 71,
+ 0,
+ "*"
+ ],
+ [
+ 112,
+ 56,
+ 0,
+ 75,
+ 0,
+ "*"
+ ],
+ [
+ 114,
+ 75,
+ 0,
+ 76,
+ 0,
+ "*"
+ ],
+ [
+ 117,
+ 49,
+ 0,
+ 77,
+ 0,
+ "*"
+ ],
+ [
+ 119,
+ 78,
+ 0,
+ 17,
+ 0,
+ "STRING"
+ ],
+ [
+ 123,
+ 80,
+ 0,
+ 79,
+ 0,
+ "*"
+ ],
+ [
+ 130,
+ 74,
+ 0,
+ 82,
+ 0,
+ "*"
+ ],
+ [
+ 132,
+ 83,
+ 0,
+ 84,
+ 0,
+ "*"
+ ],
+ [
+ 133,
+ 83,
+ 1,
+ 85,
+ 0,
+ "*"
+ ],
+ [
+ 136,
+ 87,
+ 0,
+ 34,
+ 1,
+ "INT"
+ ],
+ [
+ 137,
+ 86,
+ 0,
+ 34,
+ 2,
+ "INT"
+ ],
+ [
+ 138,
+ 69,
+ 0,
+ 88,
+ 0,
+ "*"
+ ],
+ [
+ 140,
+ 21,
+ 0,
+ 6,
+ 3,
+ "LATENT"
+ ],
+ [
+ 141,
+ 46,
+ 0,
+ 72,
+ 0,
+ "*"
+ ],
+ [
+ 142,
+ 46,
+ 1,
+ 73,
+ 0,
+ "*"
+ ],
+ [
+ 143,
+ 88,
+ 0,
+ 89,
+ 0,
+ "*"
+ ],
+ [
+ 145,
+ 72,
+ 0,
+ 90,
+ 0,
+ "*"
+ ],
+ [
+ 146,
+ 73,
+ 0,
+ 91,
+ 0,
+ "*"
+ ],
+ [
+ 147,
+ 90,
+ 0,
+ 6,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 148,
+ 91,
+ 0,
+ 6,
+ 2,
+ "CONDITIONING"
+ ],
+ [
+ 152,
+ 93,
+ 0,
+ 94,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 153,
+ 94,
+ 0,
+ 6,
+ 4,
+ "FLOAT"
+ ],
+ [
+ 154,
+ 95,
+ 0,
+ 17,
+ 1,
+ "INT"
+ ],
+ [
+ 155,
+ 82,
+ 0,
+ 80,
+ 0,
+ "*"
+ ],
+ [
+ 156,
+ 78,
+ 0,
+ 96,
+ 0,
+ "*"
+ ],
+ [
+ 159,
+ 98,
+ 0,
+ 22,
+ 2,
+ "STRING"
+ ],
+ [
+ 161,
+ 77,
+ 0,
+ 100,
+ 0,
+ "*"
+ ],
+ [
+ 162,
+ 100,
+ 0,
+ 18,
+ 1,
+ "IMAGE"
+ ],
+ [
+ 176,
+ 84,
+ 0,
+ 111,
+ 0,
+ "*"
+ ],
+ [
+ 177,
+ 111,
+ 0,
+ 87,
+ 0,
+ "*"
+ ],
+ [
+ 179,
+ 85,
+ 0,
+ 112,
+ 0,
+ "*"
+ ],
+ [
+ 180,
+ 112,
+ 0,
+ 86,
+ 0,
+ "*"
+ ],
+ [
+ 187,
+ 18,
+ 0,
+ 34,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 208,
+ 104,
+ 0,
+ 119,
+ 0,
+ "RGB_PALETTE"
+ ],
+ [
+ 209,
+ 103,
+ 0,
+ 119,
+ 1,
+ "RGB_PALETTE"
+ ],
+ [
+ 210,
+ 119,
+ 1,
+ 118,
+ 0,
+ "*"
+ ],
+ [
+ 211,
+ 119,
+ 0,
+ 121,
+ 0,
+ "*"
+ ],
+ [
+ 213,
+ 100,
+ 0,
+ 123,
+ 0,
+ "*"
+ ],
+ [
+ 214,
+ 123,
+ 0,
+ 124,
+ 0,
+ "*"
+ ],
+ [
+ 215,
+ 124,
+ 0,
+ 103,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 217,
+ 125,
+ 0,
+ 104,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 218,
+ 97,
+ 0,
+ 126,
+ 0,
+ "*"
+ ],
+ [
+ 219,
+ 126,
+ 0,
+ 98,
+ 0,
+ "*"
+ ],
+ [
+ 220,
+ 125,
+ 0,
+ 127,
+ 0,
+ "*"
+ ],
+ [
+ 221,
+ 127,
+ 0,
+ 122,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 222,
+ 122,
+ 0,
+ 128,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 223,
+ 130,
+ 0,
+ 129,
+ 1,
+ "FLOAT"
+ ],
+ [
+ 224,
+ 121,
+ 0,
+ 129,
+ 0,
+ "FLOAT"
+ ],
+ [
+ 225,
+ 129,
+ 0,
+ 131,
+ 0,
+ "*"
+ ],
+ [
+ 226,
+ 130,
+ 0,
+ 132,
+ 1,
+ "FLOAT"
+ ],
+ [
+ 227,
+ 118,
+ 0,
+ 132,
+ 0,
+ "FLOAT"
+ ],
+ [
+ 228,
+ 131,
+ 0,
+ 128,
+ 1,
+ "FLOAT"
+ ],
+ [
+ 229,
+ 132,
+ 0,
+ 133,
+ 0,
+ "*"
+ ],
+ [
+ 230,
+ 133,
+ 0,
+ 122,
+ 1,
+ "FLOAT"
+ ],
+ [
+ 231,
+ 89,
+ 0,
+ 134,
+ 0,
+ "*"
+ ],
+ [
+ 232,
+ 134,
+ 0,
+ 21,
+ 1,
+ "VAE"
+ ],
+ [
+ 233,
+ 92,
+ 0,
+ 135,
+ 0,
+ "*"
+ ],
+ [
+ 234,
+ 135,
+ 0,
+ 6,
+ 0,
+ "MODEL"
+ ],
+ [
+ 235,
+ 128,
+ 0,
+ 21,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 239,
+ 139,
+ 0,
+ 138,
+ 0,
+ "*"
+ ],
+ [
+ 240,
+ 140,
+ 0,
+ 139,
+ 0,
+ "*"
+ ],
+ [
+ 241,
+ 141,
+ 0,
+ 140,
+ 0,
+ "*"
+ ],
+ [
+ 246,
+ 77,
+ 0,
+ 143,
+ 0,
+ "*"
+ ],
+ [
+ 247,
+ 143,
+ 0,
+ 50,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 251,
+ 70,
+ 0,
+ 146,
+ 0,
+ "*"
+ ],
+ [
+ 252,
+ 71,
+ 0,
+ 147,
+ 0,
+ "*"
+ ],
+ [
+ 253,
+ 146,
+ 0,
+ 142,
+ 0,
+ "CONDITIONING"
+ ],
+ [
+ 254,
+ 147,
+ 0,
+ 142,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 255,
+ 142,
+ 0,
+ 46,
+ 0,
+ "CONDITIONING"
+ ],
+ [
+ 256,
+ 142,
+ 1,
+ 46,
+ 1,
+ "CONDITIONING"
+ ],
+ [
+ 257,
+ 97,
+ 0,
+ 18,
+ 2,
+ "STRING"
+ ],
+ [
+ 260,
+ 79,
+ 0,
+ 93,
+ 0,
+ "*"
+ ],
+ [
+ 261,
+ 79,
+ 0,
+ 148,
+ 0,
+ "*"
+ ],
+ [
+ 262,
+ 148,
+ 0,
+ 22,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 263,
+ 134,
+ 0,
+ 149,
+ 0,
+ "*"
+ ],
+ [
+ 264,
+ 149,
+ 0,
+ 7,
+ 1,
+ "VAE"
+ ],
+ [
+ 266,
+ 22,
+ 0,
+ 151,
+ 0,
+ "ANIMATION_SEQUENCE"
+ ],
+ [
+ 267,
+ 151,
+ 0,
+ 37,
+ 0,
+ "ANIMATION_SEQUENCE"
+ ],
+ [
+ 268,
+ 71,
+ 0,
+ 152,
+ 0,
+ "*"
+ ],
+ [
+ 269,
+ 74,
+ 0,
+ 19,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 270,
+ 34,
+ 0,
+ 153,
+ 0,
+ "*"
+ ],
+ [
+ 271,
+ 153,
+ 0,
+ 154,
+ 0,
+ "*"
+ ],
+ [
+ 272,
+ 154,
+ 0,
+ 155,
+ 0,
+ "*"
+ ],
+ [
+ 273,
+ 155,
+ 0,
+ 125,
+ 0,
+ "*"
+ ],
+ [
+ 278,
+ 52,
+ 0,
+ 157,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 279,
+ 157,
+ 0,
+ 24,
+ 1,
+ "FLOAT"
+ ],
+ [
+ 280,
+ 141,
+ 0,
+ 157,
+ 1,
+ "FLOAT"
+ ],
+ [
+ 281,
+ 140,
+ 0,
+ 158,
+ 1,
+ "FLOAT"
+ ],
+ [
+ 282,
+ 56,
+ 0,
+ 158,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 283,
+ 158,
+ 0,
+ 25,
+ 1,
+ "FLOAT"
+ ],
+ [
+ 284,
+ 139,
+ 0,
+ 159,
+ 1,
+ "FLOAT"
+ ],
+ [
+ 285,
+ 75,
+ 0,
+ 159,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 286,
+ 159,
+ 0,
+ 26,
+ 1,
+ "FLOAT"
+ ],
+ [
+ 287,
+ 138,
+ 0,
+ 160,
+ 1,
+ "FLOAT"
+ ],
+ [
+ 288,
+ 76,
+ 0,
+ 160,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 289,
+ 160,
+ 0,
+ 57,
+ 1,
+ "FLOAT"
+ ],
+ [
+ 292,
+ 7,
+ 0,
+ 162,
+ 1,
+ "IMAGE"
+ ],
+ [
+ 294,
+ 163,
+ 0,
+ 162,
+ 0,
+ "UPSCALE_MODEL"
+ ],
+ [
+ 295,
+ 67,
+ 0,
+ 167,
+ 0,
+ "*"
+ ],
+ [
+ 296,
+ 167,
+ 0,
+ 92,
+ 0,
+ "*"
+ ],
+ [
+ 303,
+ 162,
+ 0,
+ 170,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 304,
+ 170,
+ 0,
+ 22,
+ 1,
+ "IMAGE"
+ ],
+ [
+ 305,
+ 145,
+ 0,
+ 46,
+ 2,
+ "CONTROL_NET"
+ ],
+ [
+ 308,
+ 83,
+ 3,
+ 172,
+ 0,
+ "*"
+ ],
+ [
+ 309,
+ 83,
+ 2,
+ 171,
+ 0,
+ "*"
+ ],
+ [
+ 310,
+ 171,
+ 0,
+ 173,
+ 0,
+ "*"
+ ],
+ [
+ 311,
+ 172,
+ 0,
+ 174,
+ 0,
+ "*"
+ ],
+ [
+ 312,
+ 173,
+ 0,
+ 175,
+ 0,
+ "*"
+ ],
+ [
+ 313,
+ 174,
+ 0,
+ 176,
+ 0,
+ "*"
+ ],
+ [
+ 314,
+ 175,
+ 0,
+ 170,
+ 1,
+ "INT"
+ ],
+ [
+ 315,
+ 176,
+ 0,
+ 170,
+ 2,
+ "INT"
+ ],
+ [
+ 316,
+ 177,
+ 0,
+ 142,
+ 2,
+ "CONTROL_NET"
+ ],
+ [
+ 317,
+ 154,
+ 0,
+ 178,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 318,
+ 178,
+ 0,
+ 142,
+ 3,
+ "IMAGE"
+ ],
+ [
+ 319,
+ 179,
+ 0,
+ 94,
+ 1,
+ "FLOAT"
+ ],
+ [
+ 320,
+ 180,
+ 0,
+ 94,
+ 2,
+ "FLOAT"
+ ],
+ [
+ 321,
+ 181,
+ 0,
+ 179,
+ 0,
+ "*"
+ ],
+ [
+ 322,
+ 182,
+ 0,
+ 181,
+ 0,
+ "*"
+ ],
+ [
+ 323,
+ 183,
+ 0,
+ 180,
+ 0,
+ "*"
+ ],
+ [
+ 324,
+ 184,
+ 0,
+ 183,
+ 0,
+ "*"
+ ],
+ [
+ 330,
+ 189,
+ 0,
+ 188,
+ 1,
+ "LOG_ENTRY"
+ ],
+ [
+ 331,
+ 190,
+ 0,
+ 188,
+ 2,
+ "LOG_ENTRY"
+ ],
+ [
+ 332,
+ 48,
+ 0,
+ 191,
+ 0,
+ "*"
+ ],
+ [
+ 333,
+ 191,
+ 0,
+ 74,
+ 0,
+ "*"
+ ],
+ [
+ 335,
+ 96,
+ 0,
+ 192,
+ 0,
+ "*"
+ ],
+ [
+ 336,
+ 192,
+ 0,
+ 188,
+ 9,
+ "STRING"
+ ],
+ [
+ 337,
+ 192,
+ 0,
+ 97,
+ 0,
+ "*"
+ ],
+ [
+ 338,
+ 38,
+ 0,
+ 189,
+ 0,
+ "STRING"
+ ],
+ [
+ 339,
+ 38,
+ 1,
+ 190,
+ 0,
+ "STRING"
+ ],
+ [
+ 340,
+ 48,
+ 0,
+ 188,
+ 0,
+ "FRAME_COUNTER"
+ ],
+ [
+ 341,
+ 37,
+ 0,
+ 193,
+ 0,
+ "ANIMATION_SEQUENCE"
+ ]
+ ],
+ "groups": [
+ {
+ "title": "Prompt Morphing",
+ "bounding": [
+ -310,
+ -456,
+ 2356,
+ 646
+ ],
+ "color": "#b58b2a",
+ "font_size": 24
+ },
+ {
+ "title": "ControlNet",
+ "bounding": [
+ 3576,
+ -472,
+ 764,
+ 765
+ ],
+ "color": "#3f789e",
+ "font_size": 24
+ },
+ {
+ "title": "Previous Frame",
+ "bounding": [
+ 2804,
+ -188,
+ 748,
+ 385
+ ],
+ "color": "#a1309b",
+ "font_size": 24
+ },
+ {
+ "title": "Save",
+ "bounding": [
+ 7404,
+ -481,
+ 1434,
+ 353
+ ],
+ "color": "#3f789e",
+ "font_size": 24
+ },
+ {
+ "title": "Diffusion",
+ "bounding": [
+ 5989,
+ -490,
+ 805,
+ 853
+ ],
+ "color": "#88A",
+ "font_size": 24
+ },
+ {
+ "title": "Denoise curve",
+ "bounding": [
+ 5532,
+ -169,
+ 396,
+ 501
+ ],
+ "color": "#3f789e",
+ "font_size": 24
+ },
+ {
+ "title": "Align brightness/contrast",
+ "bounding": [
+ 4583,
+ -378,
+ 860,
+ 767
+ ],
+ "color": "#8AA",
+ "font_size": 24
+ },
+ {
+ "title": "Upscale",
+ "bounding": [
+ 6810,
+ -454,
+ 496,
+ 459
+ ],
+ "color": "#8A8",
+ "font_size": 24
+ },
+ {
+ "title": "Basic Settings",
+ "bounding": [
+ -1247,
+ -793,
+ 788,
+ 1706
+ ],
+ "color": "#88A",
+ "font_size": 24
+ }
+ ],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui-dream-project/examples/test_colors.png b/custom_nodes/comfyui-dream-project/examples/test_colors.png
new file mode 100644
index 0000000000000000000000000000000000000000..f9c63432cd96f0291a69fc34ead901e2e9c8a29e
Binary files /dev/null and b/custom_nodes/comfyui-dream-project/examples/test_colors.png differ
diff --git a/custom_nodes/comfyui-dream-project/image_processing.py b/custom_nodes/comfyui-dream-project/image_processing.py
new file mode 100644
index 0000000000000000000000000000000000000000..4e4da0325ff25b8cce6958c4520c41e5dc420a31
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/image_processing.py
@@ -0,0 +1,134 @@
+# -*- coding: utf-8 -*-
+import math
+
+import numpy
+import torch
+from PIL import Image, ImageDraw
+from PIL.Image import Resampling
+
+from .categories import *
+from .shared import ALWAYS_CHANGED_FLAG, convertTensorImageToPIL, DreamImageProcessor, \
+ DreamImage, DreamMask
+from .dreamtypes import SharedTypes, FrameCounter
+
+
+class DreamImageMotion:
+ NODE_NAME = "Image Motion"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "image": ("IMAGE",),
+ "zoom": ("FLOAT", {"default": 0.0, "min": -10, "max": 10, "step": 0.01}),
+ "mask_1_feather": ("INT", {"default": 0, "min": 0}),
+ "mask_1_overlap": ("INT", {"default": 0, "min": 0}),
+ "mask_2_feather": ("INT", {"default": 10, "min": 0}),
+ "mask_2_overlap": ("INT", {"default": 5, "min": 0}),
+ "mask_3_feather": ("INT", {"default": 15, "min": 0}),
+ "mask_3_overlap": ("INT", {"default": 5, "min": 0}),
+ "x_translation": ("FLOAT", {"default": 0.0, "min": -10, "max": 10, "step": 0.01}),
+ "y_translation": ("FLOAT", {"default": 0.0, "min": -10, "max": 10, "step": 0.01}),
+ } | SharedTypes.frame_counter,
+ "optional": {
+ "noise": ("IMAGE",),
+ "output_resize_width": ("INT", {"default": 0, "min": 0}),
+ "output_resize_height": ("INT", {"default": 0, "min": 0})
+ }
+ }
+
+ CATEGORY = NodeCategories.ANIMATION_TRANSFORMS
+ RETURN_TYPES = ("IMAGE", "MASK", "MASK", "MASK")
+ RETURN_NAMES = ("image", "mask1", "mask2", "mask3")
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return ALWAYS_CHANGED_FLAG
+
+ def _mk_PIL_image(self, size, color=None, mode="RGB") -> Image:
+ im = Image.new(mode=mode, size=size)
+ if color:
+ im.paste(color, (0, 0, size[0], size[1]))
+ return im
+
+ def _convertPILToMask(self, image):
+ return torch.from_numpy(numpy.array(image.convert("L")).astype(numpy.float32) / 255.0)
+
+ def _apply_feather(self, pil_image, area, feather):
+ feather = min((area[2] - area[0]) // 2 - 1, feather)
+ draw = ImageDraw.Draw(pil_image)
+ for i in range(1, feather + 1):
+ rect = [(area[0] + i - 1, area[1] + i - 1), (area[2] - i + 1, area[3] - i + 1)]
+ c = 255 - int(round(255.0 * (i / (feather + 1))))
+ draw.rectangle(rect, fill=None, outline=(c, c, c))
+ return pil_image
+
+ def _make_mask(self, width, height, selection_area, feather, overlap):
+ complete_area = self._mk_PIL_image((width, height), "white")
+ draw = ImageDraw.Draw(complete_area)
+ (left, top, right, bottom) = selection_area
+ area = (left + overlap, top + overlap, right - overlap - 1, bottom - overlap - 1)
+ draw.rectangle(area, fill="black", width=0)
+ return self._apply_feather(complete_area, area, feather)
+
+ def _make_resizer(self, output_resize_width, output_resize_height):
+ def bound(i):
+ return min(max(i, 1), 32767)
+
+ if output_resize_height and output_resize_width:
+ return lambda img: img.resize((bound(output_resize_width), bound(output_resize_height)), Resampling.NEAREST)
+ else:
+ return lambda img: img
+
+ def result(self, image: torch.Tensor, zoom, x_translation, y_translation, mask_1_feather, mask_1_overlap,
+ mask_2_feather, mask_2_overlap, mask_3_feather, mask_3_overlap, frame_counter: FrameCounter,
+ **other):
+ def _limit_range(f):
+ return max(-1.0, min(1.0, f))
+
+ def _motion(image: DreamImage, batch_counter, zoom, x_translation, y_translation, mask_1_overlap,
+ mask_2_overlap,
+ mask_3_overlap):
+ zoom = _limit_range(zoom / frame_counter.frames_per_second)
+ x_translation = _limit_range(x_translation / frame_counter.frames_per_second)
+ y_translation = _limit_range(y_translation / frame_counter.frames_per_second)
+ pil_image = image.pil_image
+ sz = self._make_resizer(other.get("output_resize_width", None), other.get("output_resize_height", None))
+ noise = other.get("noise", None)
+ multiplier = math.pow(2, zoom)
+ resized_image = pil_image.resize((round(pil_image.width * multiplier),
+ round(pil_image.height * multiplier)), Resampling.BILINEAR)
+
+ if noise is None:
+ base_image = self._mk_PIL_image(pil_image.size, "black")
+ else:
+ base_image = convertTensorImageToPIL(noise).resize(pil_image.size, Resampling.BILINEAR)
+
+ selection_offset = (round(x_translation * pil_image.width), round(y_translation * pil_image.height))
+ selection = ((pil_image.width - resized_image.width) // 2 + selection_offset[0],
+ (pil_image.height - resized_image.height) // 2 + selection_offset[1],
+ (pil_image.width - resized_image.width) // 2 + selection_offset[0] + resized_image.width,
+ (pil_image.height - resized_image.height) // 2 + selection_offset[1] + resized_image.height)
+ base_image.paste(resized_image, selection)
+
+ mask_1_overlap = min(pil_image.width // 3, min(mask_1_overlap, pil_image.height // 3))
+ mask_2_overlap = min(pil_image.width // 3, min(mask_2_overlap, pil_image.height // 3))
+ mask_3_overlap = min(pil_image.width // 3, min(mask_3_overlap, pil_image.height // 3))
+ mask1 = self._make_mask(pil_image.width, pil_image.height, selection, mask_1_feather, mask_1_overlap)
+ mask2 = self._make_mask(pil_image.width, pil_image.height, selection, mask_2_feather, mask_2_overlap)
+ mask3 = self._make_mask(pil_image.width, pil_image.height, selection, mask_3_feather, mask_3_overlap)
+
+ return (DreamImage(pil_image=sz(base_image)),
+ DreamMask(pil_image=sz(mask1)),
+ DreamMask(pil_image=sz(mask2)),
+ DreamMask(pil_image=sz(mask3)))
+
+ proc = DreamImageProcessor(image,
+ zoom=zoom,
+ x_translation=x_translation,
+ y_translation=y_translation,
+ mask_1_overlap=mask_1_overlap,
+ mask_2_overlap=mask_2_overlap,
+ mask_3_overlap=mask_3_overlap)
+ return proc.process(_motion)
diff --git a/custom_nodes/comfyui-dream-project/inputfields.py b/custom_nodes/comfyui-dream-project/inputfields.py
new file mode 100644
index 0000000000000000000000000000000000000000..50b3937eba568a71f2fd2e2cd737fcbbe07ebb29
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/inputfields.py
@@ -0,0 +1,100 @@
+from .categories import *
+from .shared import *
+
+class DreamInputText:
+ NODE_NAME = "Text Input"
+ ICON = "✍"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "value": ("STRING", {"default": "", "multiline": True}),
+ },
+ }
+
+ CATEGORY = NodeCategories.UTILS
+ RETURN_TYPES = ("STRING",)
+ RETURN_NAMES = ("STRING",)
+ FUNCTION = "noop"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return hashed_as_strings(*values)
+
+ def noop(self, value):
+ return (value,)
+
+class DreamInputString:
+ NODE_NAME = "String Input"
+ ICON = "✍"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "value": ("STRING", {"default": "", "multiline": False}),
+ },
+ }
+
+ CATEGORY = NodeCategories.UTILS
+ RETURN_TYPES = ("STRING",)
+ RETURN_NAMES = ("STRING",)
+ FUNCTION = "noop"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return hashed_as_strings(*values)
+
+ def noop(self, value):
+ return (value,)
+
+
+class DreamInputFloat:
+ NODE_NAME = "Float Input"
+ ICON = "✍"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "value": ("FLOAT", {"default": 0.0}),
+ },
+ }
+
+ CATEGORY = NodeCategories.UTILS
+ RETURN_TYPES = ("FLOAT",)
+ RETURN_NAMES = ("FLOAT",)
+ FUNCTION = "noop"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return hashed_as_strings(*values)
+
+ def noop(self, value):
+ return (value,)
+
+
+class DreamInputInt:
+ NODE_NAME = "Int Input"
+ ICON = "✍"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "value": ("INT", {"default": 0}),
+ },
+ }
+
+ CATEGORY = NodeCategories.UTILS
+ RETURN_TYPES = ("INT",)
+ RETURN_NAMES = ("INT",)
+ FUNCTION = "noop"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return hashed_as_strings(*values)
+
+ def noop(self, value):
+ return (value,)
diff --git a/custom_nodes/comfyui-dream-project/install.py b/custom_nodes/comfyui-dream-project/install.py
new file mode 100644
index 0000000000000000000000000000000000000000..28e6daa194d85857dbbccfc162340cdc7a93cd77
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/install.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+from .shared import DreamConfig
+
+
+def setup_default_config():
+ DreamConfig()
+
+
+def run_install():
+ setup_default_config()
+
+
+if __name__ == "__main__":
+ run_install()
diff --git a/custom_nodes/comfyui-dream-project/laboratory.py b/custom_nodes/comfyui-dream-project/laboratory.py
new file mode 100644
index 0000000000000000000000000000000000000000..ebeae2c9120bd2627aaab8e8d0f591e7429f6dbb
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/laboratory.py
@@ -0,0 +1,100 @@
+# -*- coding: utf-8 -*-
+
+# -*- coding: utf-8 -*-
+
+import json
+
+from .categories import *
+from .shared import ALWAYS_CHANGED_FLAG, DreamStateFile
+from .dreamtypes import *
+
+_laboratory_state = DreamStateFile("laboratory")
+
+
+class DreamLaboratory:
+ NODE_NAME = "Laboratory"
+ ICON = "🧪"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": SharedTypes.frame_counter | {
+ "key": ("STRING", {"default": "Random value " + str(random.randint(0, 1000000))}),
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
+ "renew_policy": (["every frame", "first frame"],),
+ "min_value": ("FLOAT", {"default": 0.0}),
+ "max_value": ("FLOAT", {"default": 1.0}),
+ "mode": (["random uniform", "random bell", "ladder", "random walk"],),
+ },
+ "optional": {
+ "step_size": ("FLOAT", {"default": 0.1}),
+ },
+ }
+
+ CATEGORY = NodeCategories.UTILS
+ RETURN_TYPES = ("FLOAT", "INT", LogEntry.ID)
+ RETURN_NAMES = ("FLOAT", "INT", "log_entry")
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return ALWAYS_CHANGED_FLAG
+
+ def _generate(self, seed, last_value, min_value, max_value, mode, step_size):
+ rnd = random.Random()
+ rnd.seed(seed)
+
+ def jsonify(v: float):
+ return json.loads(json.dumps(v))
+
+ if mode == "random uniform":
+ return jsonify(self._mode_uniform(rnd, last_value, min_value, max_value, step_size))
+ elif mode == "random bell":
+ return jsonify(self._mode_bell(rnd, last_value, min_value, max_value, step_size))
+ elif mode == "ladder":
+ return jsonify(self._mode_ladder(rnd, last_value, min_value, max_value, step_size))
+ else:
+ return jsonify(self._mode_walk(rnd, last_value, min_value, max_value, step_size))
+
+ def _mode_uniform(self, rnd: random.Random, last_value: float, min_value: float, max_value: float, step_size):
+ return rnd.random() * (max_value - min_value) + min_value
+
+ def _mode_bell(self, rnd: random.Random, last_value: float, min_value: float, max_value: float, step_size):
+ s = 0.0
+ for i in range(3):
+ s += rnd.random() * (max_value - min_value) + min_value
+ return s / 3.0
+
+ def _mode_ladder(self, rnd: random.Random, last_value: float, min_value: float, max_value: float, step_size):
+ if last_value is None:
+ last_value = min_value - step_size
+ next_value = last_value + step_size
+ if next_value > max_value:
+ d = abs(max_value - min_value)
+ next_value = (next_value - min_value) % d + min_value
+ return next_value
+
+ def _mode_walk(self, rnd: random.Random, last_value: float, min_value: float, max_value: float, step_size):
+ if last_value is None:
+ last_value = (max_value - min_value) * 0.5
+ if rnd.random() >= 0.5:
+ return min(max_value, last_value + step_size)
+ else:
+ return max(min_value, last_value - step_size)
+
+ def result(self, key, frame_counter: FrameCounter, seed, renew_policy, min_value, max_value, mode, **values):
+ if min_value > max_value:
+ t = max_value
+ max_value = min_value
+ min_value = t
+ step_size = values.get("step_size", abs(max_value - min_value) * 0.1)
+ last_value = _laboratory_state.get_section("values").get(key, None)
+
+ if (last_value is None) or (renew_policy == "every frame") or frame_counter.is_first_frame:
+ v = _laboratory_state.get_section("values") \
+ .update(key, 0, lambda old: self._generate(seed, last_value, min_value, max_value, mode, step_size))
+ return v, round(v), LogEntry.new(
+ "Laboratory generated new value for '{}': {} ({})".format(key, v, round(v)))
+ else:
+ return last_value, round(last_value), LogEntry.new("Laboratory reused value for '{}': {} ({})"
+ .format(key, last_value, round(last_value)))
diff --git a/custom_nodes/comfyui-dream-project/license.txt b/custom_nodes/comfyui-dream-project/license.txt
new file mode 100644
index 0000000000000000000000000000000000000000..604c43003ef2d5c157a53a4c86d46935220c32e4
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/license.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Morgan Johansson/Dream Project
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/custom_nodes/comfyui-dream-project/loaders.py b/custom_nodes/comfyui-dream-project/loaders.py
new file mode 100644
index 0000000000000000000000000000000000000000..d25dfe111275cde15938137c77c288a7c5cc0900
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/loaders.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+from .categories import NodeCategories
+from .shared import ALWAYS_CHANGED_FLAG, list_images_in_directory, DreamImage
+from .dreamtypes import SharedTypes, FrameCounter
+import os
+
+
+class DreamImageSequenceInputWithDefaultFallback:
+ NODE_NAME = "Image Sequence Loader"
+ ICON = "💾"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": SharedTypes.frame_counter | {
+ "directory_path": ("STRING", {"default": '', "multiline": False}),
+ "pattern": ("STRING", {"default": '*', "multiline": False}),
+ "indexing": (["numeric", "alphabetic order"],)
+ },
+ "optional": {
+ "default_image": ("IMAGE", {"default": None})
+ }
+ }
+
+ CATEGORY = NodeCategories.IMAGE_ANIMATION
+ RETURN_TYPES = ("IMAGE","STRING")
+ RETURN_NAMES = ("image","frame_name")
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return ALWAYS_CHANGED_FLAG
+
+ def result(self, frame_counter: FrameCounter, directory_path, pattern, indexing, **other):
+ default_image = other.get("default_image", None)
+ entries = list_images_in_directory(directory_path, pattern, indexing == "alphabetic order")
+ entry = entries.get(frame_counter.current_frame, None)
+ if not entry:
+ return (default_image, "")
+ else:
+ image_names = [os.path.basename(file_path) for file_path in entry]
+ images = map(lambda f: DreamImage(file_path=f), entry)
+ return (DreamImage.join_to_tensor_data(images), image_names[0])
diff --git a/custom_nodes/comfyui-dream-project/node_list.json b/custom_nodes/comfyui-dream-project/node_list.json
new file mode 100644
index 0000000000000000000000000000000000000000..1fa445b1471e8483181bf2852afa55c3b404e4bd
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/node_list.json
@@ -0,0 +1,59 @@
+{
+ "Analyze Palette [Dream]": "Output brightness, contrast, red, green and blue averages of a palette",
+ "Beat Curve [Dream]": "Beat pattern curve with impulses at specified beats of a measure",
+ "Big Float Switch [Dream]": "Switch for up to 10 inputs",
+ "Big Image Switch [Dream]": "Switch for up to 10 inputs",
+ "Big Int Switch [Dream]": "Switch for up to 10 inputs",
+ "Big Latent Switch [Dream]": "Switch for up to 10 inputs",
+ "Big Palette Switch [Dream]": "Switch for up to 10 inputs",
+ "Big Text Switch [Dream]": "Switch for up to 10 inputs",
+ "Boolean To Float [Dream]": "Converts a boolean value to two different float values",
+ "Boolean To Int [Dream]": "Converts a boolean value to two different int values",
+ "Build Prompt [Dream]": "Weighted text prompt builder utility",
+ "CSV Curve [Dream]": "CSV input curve where first column is frame or second and second column is value",
+ "CSV Generator [Dream]": "CSV output, mainly for debugging purposes",
+ "Calculation [Dream]": "Mathematical calculation node",
+ "Common Frame Dimensions [Dream]": "Utility for calculating good width/height based on common video dimensions",
+ "Compare Palettes [Dream]": "Analyses two palettes producing the factor for each color channel",
+ "FFMPEG Video Encoder [Dream]": "Post processing for animation sequences calling FFMPEG to generate video file",
+ "File Count [Dream]": "Finds the number of files in a directory matching specified patterns",
+ "Finalize Prompt [Dream]": "Used in conjunction with 'Build Prompt'",
+ "Float Input [Dream]": "Float input (until primitive routing issues are solved)",
+ "Float to Log Entry [Dream]": "Logging for float values",
+ "Frame Count Calculator [Dream]": "Simple utility to calculate number of frames based on duration and framerate",
+ "Frame Counter (Directory) [Dream]": "Directory backed frame counter, for output directories",
+ "Frame Counter (Simple) [Dream]": "Integer value used as frame counter",
+ "Frame Counter Info [Dream]": "Extracts information from the frame counter",
+ "Frame Counter Offset [Dream]": "Adds an offset to a frame counter",
+ "Frame Counter Time Offset [Dream]": "Adds an offset to a frame counter in seconds",
+ "Image Brightness Adjustment [Dream]": "Adjusts the brightness of an image by a factor",
+ "Image Color Shift [Dream]": "Adjust the colors (or brightness) of an image",
+ "Image Contrast Adjustment [Dream]": "Adjusts the contrast of an image by a factor",
+ "Image Motion [Dream]": "Node supporting zooming in/out and translating an image",
+ "Image Sequence Blend [Dream]": "Post processing for animation sequences blending frame for a smoother blurred effect",
+ "Image Sequence Loader [Dream]": "Loads a frame from a directory of images",
+ "Image Sequence Saver [Dream]": "Saves a frame to a directory",
+ "Image Sequence Tweening [Dream]": "Post processing for animation sequences generating blended in-between frames",
+ "Int Input [Dream]": "Integer input (until primitive routing issues are solved)",
+ "Int to Log Entry [Dream]": "Logging for int values",
+ "Laboratory [Dream]": "Super-charged number generator for experimenting with ComfyUI",
+ "Linear Curve [Dream]": "Linear interpolation between two value over the full animation",
+ "Log Entry Joiner [Dream]": "Merges multiple log entries (reduces noodling)",
+ "Log File [Dream]": "Logging node for output to file",
+ "Noise from Area Palettes [Dream]": "Generates noise based on the colors of up to nine different palettes",
+ "Noise from Palette [Dream]": "Generates noise based on the colors in a palette",
+ "Palette Color Align [Dream]": "Shifts the colors of one palette towards another target palette",
+ "Palette Color Shift [Dream]": "Multiplies the color values in a palette",
+ "Sample Image Area as Palette [Dream]": "Samples a palette from an image based on pre-defined areas",
+ "Sample Image as Palette [Dream]": "Randomly samples pixel values to build a palette from an image",
+ "Saw Curve [Dream]": "Saw wave curve",
+ "Sine Curve [Dream]": "Simple sine wave curve",
+ "Smooth Event Curve [Dream]": "Single event/peak curve with a slight bell-shape",
+ "String Input [Dream]": "String input (until primitive routing issues are solved)",
+ "String Tokenizer [Dream]": "Extract individual words or phrases from a text as tokens",
+ "String to Log Entry [Dream]": "Use any string as a log entry",
+ "Text Input [Dream]": "Multiline string input (until primitive routing issues are solved)",
+ "Triangle Curve [Dream]": "Triangle wave curve",
+ "Triangle Event Curve [Dream]": "Single event/peak curve with triangular shape",
+ "WAV Curve [Dream]": "WAV audio file as a curve"
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui-dream-project/noise.py b/custom_nodes/comfyui-dream-project/noise.py
new file mode 100644
index 0000000000000000000000000000000000000000..6e01ee38576407299cdcd04597f9a46a064d9f89
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/noise.py
@@ -0,0 +1,163 @@
+# -*- coding: utf-8 -*-
+import math
+
+from .categories import NodeCategories
+from .shared import *
+from .dreamtypes import *
+
+
+def _generate_noise(image: DreamImage, color_function, rng: random.Random, block_size, blur_amount,
+ density) -> DreamImage:
+ w = block_size[0]
+ h = block_size[1]
+ blur_radius = round(max(image.width, image.height) * blur_amount * 0.25)
+ if w <= (image.width // 128) or h <= (image.height // 128):
+ return image
+ max_placements = round(density * (image.width * image.height))
+ num = min(max_placements, round((image.width * image.height * 2) / (w * h)))
+ for i in range(num):
+ x = rng.randint(-w + 1, image.width - 1)
+ y = rng.randint(-h + 1, image.height - 1)
+ image.color_area(x, y, w, h, color_function(x + (w >> 1), y + (h >> 1)))
+ image = image.blur(blur_radius)
+ return _generate_noise(image, color_function, rng, (w >> 1, h >> 1), blur_amount, density)
+
+
+class DreamNoiseFromPalette:
+ NODE_NAME = "Noise from Palette"
+ ICON = "🌫"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": SharedTypes.palette | {
+ "width": ("INT", {"default": 512, "min": 1, "max": 8192}),
+ "height": ("INT", {"default": 512, "min": 1, "max": 8192}),
+ "blur_amount": ("FLOAT", {"default": 0.3, "min": 0, "max": 1.0, "step": 0.05}),
+ "density": ("FLOAT", {"default": 0.5, "min": 0.1, "max": 1.0, "step": 0.025}),
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff})
+ },
+ }
+
+ CATEGORY = NodeCategories.IMAGE_GENERATE
+ RETURN_TYPES = ("IMAGE",)
+ RETURN_NAMES = ("image",)
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return ALWAYS_CHANGED_FLAG
+
+ def result(self, palette: Tuple[RGBPalette], width, height, seed, blur_amount, density):
+ outputs = list()
+ rng = random.Random()
+ for p in palette:
+ seed += 1
+ color_iterator = p.random_iteration(seed)
+ image = DreamImage(pil_image=Image.new("RGB", (width, height), color=next(color_iterator)))
+ image = _generate_noise(image, lambda x, y: next(color_iterator), rng,
+ (image.width >> 1, image.height >> 1), blur_amount, density)
+ outputs.append(image)
+
+ return (DreamImage.join_to_tensor_data(outputs),)
+
+
+class DreamNoiseFromAreaPalettes:
+ NODE_NAME = "Noise from Area Palettes"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "optional": {
+ "top_left_palette": (RGBPalette.ID,),
+ "top_center_palette": (RGBPalette.ID,),
+ "top_right_palette": (RGBPalette.ID,),
+ "center_left_palette": (RGBPalette.ID,),
+ "center_palette": (RGBPalette.ID,),
+ "center_right_palette": (RGBPalette.ID,),
+ "bottom_left_palette": (RGBPalette.ID,),
+ "bottom_center_palette": (RGBPalette.ID,),
+ "bottom_right_palette": (RGBPalette.ID,),
+ },
+ "required": {
+ "area_sharpness": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.05}),
+ "width": ("INT", {"default": 512, "min": 1, "max": 8192}),
+ "height": ("INT", {"default": 512, "min": 1, "max": 8192}),
+ "blur_amount": ("FLOAT", {"default": 0.3, "min": 0, "max": 1.0, "step": 0.05}),
+ "density": ("FLOAT", {"default": 0.5, "min": 0.1, "max": 1.0, "step": 0.025}),
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
+ },
+ }
+
+ CATEGORY = NodeCategories.IMAGE_GENERATE
+ ICON = "🌫"
+ RETURN_TYPES = ("IMAGE",)
+ RETURN_NAMES = ("image",)
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return ALWAYS_CHANGED_FLAG
+
+ def _area_coordinates(self, width, height):
+ dx = width / 6
+ dy = height / 6
+ return {
+ "top_left_palette": (dx, dy),
+ "top_center_palette": (dx * 3, dy),
+ "top_right_palette": (dx * 5, dy),
+ "center_left_palette": (dx, dy * 3),
+ "center_palette": (dx * 3, dy * 3),
+ "center_right_palette": (dx * 5, dy * 3),
+ "bottom_left_palette": (dx * 1, dy * 5),
+ "bottom_center_palette": (dx * 3, dy * 5),
+ "bottom_right_palette": (dx * 5, dy * 5),
+ }
+
+ def _pick_random_area(self, active_coordinates, x, y, rng, area_sharpness):
+ def _dst(x1, y1, x2, y2):
+ a = x1 - x2
+ b = y1 - y2
+ return math.sqrt(a * a + b * b)
+
+ distances = list(map(lambda item: (item[0], _dst(item[1][0], item[1][1], x, y)), active_coordinates))
+ areas_by_weight = list(
+ map(lambda item: (math.pow((1.0 / max(1, item[1])), 0.5 + 4.5 * area_sharpness), item[0]), distances))
+ return pick_random_by_weight(areas_by_weight, rng)
+
+ def _setup_initial_colors(self, image: DreamImage, color_func):
+ w = image.width
+ h = image.height
+ wpart = round(w / 3)
+ hpart = round(h / 3)
+ for i in range(3):
+ for j in range(3):
+ image.color_area(wpart * i, hpart * j, w, h,
+ color_func(wpart * i + w // 2, hpart * j + h // 2))
+
+ def result(self, width, height, seed, blur_amount, density, area_sharpness, **palettes):
+ outputs = list()
+ rng = random.Random()
+ coordinates = self._area_coordinates(width, height)
+ active_palettes = list(filter(lambda pair: pair[1] is not None and len(pair[1]) > 0, palettes.items()))
+ active_coordinates = list(map(lambda item: (item[0], coordinates[item[0]]), active_palettes))
+
+ n = max(list(map(len, palettes.values())) + [0])
+ for b in range(n):
+ batch_palettes = dict(map(lambda item: (item[0], item[1][b].random_iteration(seed)), active_palettes))
+
+ def _color_func(x, y):
+ name = self._pick_random_area(active_coordinates, x, y, rng, area_sharpness)
+ rgb = batch_palettes[name]
+ return next(rgb)
+
+ image = DreamImage(pil_image=Image.new("RGB", (width, height)))
+ self._setup_initial_colors(image, _color_func)
+ image = _generate_noise(image, _color_func, rng, (round(image.width / 3), round(image.height / 3)),
+ blur_amount, density)
+ outputs.append(image)
+
+ if not outputs:
+ outputs.append(DreamImage(pil_image=Image.new("RGB", (width, height))))
+
+ return (DreamImage.join_to_tensor_data(outputs),)
diff --git a/custom_nodes/comfyui-dream-project/output.py b/custom_nodes/comfyui-dream-project/output.py
new file mode 100644
index 0000000000000000000000000000000000000000..af092c0e3612683c4e9398d73755857b578b1ef8
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/output.py
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+import json
+import os
+
+import folder_paths as comfy_paths
+from PIL.PngImagePlugin import PngInfo
+
+from .categories import NodeCategories
+from .shared import hashed_as_strings, DreamImageProcessor, DreamImage, \
+ list_images_in_directory, DreamConfig
+from .dreamtypes import SharedTypes, FrameCounter, AnimationSequence, LogEntry
+
+CONFIG = DreamConfig()
+
+
+def _save_png(pil_image, filepath, embed_info, prompt, extra_pnginfo):
+ info = PngInfo()
+ if extra_pnginfo is not None:
+ for item in extra_pnginfo:
+ info.add_text(item, json.dumps(extra_pnginfo[item]))
+ if prompt is not None:
+ info.add_text("prompt", json.dumps(prompt))
+ if embed_info:
+ pil_image.save(filepath, pnginfo=info, optimize=True)
+ else:
+ pil_image.save(filepath, optimize=True)
+
+
+def _save_jpg(pil_image, filepath, quality):
+ pil_image.save(filepath, quality=quality, optimize=True)
+
+
+class DreamImageSequenceOutput:
+ NODE_NAME = "Image Sequence Saver"
+ ICON = "💾"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": SharedTypes.frame_counter | {
+ "image": ("IMAGE",),
+ "directory_path": ("STRING", {"default": comfy_paths.output_directory, "multiline": False}),
+ "prefix": ("STRING", {"default": 'frame', "multiline": False}),
+ "digits": ("INT", {"default": 5}),
+ "at_end": (["stop output", "raise error", "keep going"],),
+ "filetype": (['png with embedded workflow', "png", 'jpg'],),
+ },
+ "hidden": {
+ "prompt": "PROMPT",
+ "extra_pnginfo": "EXTRA_PNGINFO"
+ },
+ }
+
+ CATEGORY = NodeCategories.IMAGE_ANIMATION
+ RETURN_TYPES = (AnimationSequence.ID, LogEntry.ID)
+ OUTPUT_NODE = True
+ RETURN_NAMES = ("sequence", "log_entry")
+ FUNCTION = "save"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return hashed_as_strings(*values)
+
+ def _get_new_filename(self, current_frame, prefix, digits, filetype):
+ return prefix + "_" + str(current_frame).zfill(digits) + "." + filetype.split(" ")[0]
+
+ def _save_single_image(self, dream_image: DreamImage, batch_counter, frame_counter: FrameCounter,
+ directory_path,
+ prefix, digits, filetype, prompt, extra_pnginfo, at_end, logger):
+
+ if at_end == "stop output" and frame_counter.is_after_last_frame:
+ logger("Reached end of animation - not saving output!")
+ return ()
+ if at_end == "raise error" and frame_counter.is_after_last_frame:
+ logger("Reached end of animation - raising error to stop processing!")
+ raise Exception("Reached end of animation!")
+ filename = self._get_new_filename(frame_counter.current_frame, prefix, digits, filetype)
+ if batch_counter >= 0:
+ filepath = os.path.join(directory_path, "batch_" + (str(batch_counter).zfill(4)), filename)
+ else:
+ filepath = os.path.join(directory_path, filename)
+ save_dir = os.path.dirname(filepath)
+ if not os.path.isdir(save_dir):
+ os.makedirs(save_dir)
+ if filetype.startswith("png"):
+ dream_image.save_png(filepath, filetype == 'png with embedded workflow', prompt, extra_pnginfo)
+ elif filetype == "jpg":
+ dream_image.save_jpg(filepath, int(CONFIG.get("encoding.jpeg_quality", 95)))
+ logger("Saved {} in {}".format(filename, os.path.abspath(save_dir)))
+ return ()
+
+ def _generate_animation_sequence(self, filetype, directory_path, frame_counter):
+ if filetype.startswith("png"):
+ pattern = "*.png"
+ else:
+ pattern = "*.jpg"
+ frames = list_images_in_directory(directory_path, pattern, False)
+ return AnimationSequence(frame_counter, frames)
+
+ def save(self, image, **args):
+ log_texts = list()
+ logger = lambda s: log_texts.append(s)
+ if not args.get("directory_path", ""):
+ args["directory_path"] = comfy_paths.output_directory
+ args["logger"] = logger
+ proc = DreamImageProcessor(image, **args)
+ proc.process(self._save_single_image)
+ frame_counter: FrameCounter = args["frame_counter"]
+ log_entry = LogEntry([])
+ for text in log_texts:
+ log_entry = log_entry.add(text)
+ if frame_counter.is_final_frame:
+ return (self._generate_animation_sequence(args["filetype"], args["directory_path"],
+ frame_counter), log_entry)
+ else:
+ return (AnimationSequence(frame_counter), log_entry)
diff --git a/custom_nodes/comfyui-dream-project/prompting.py b/custom_nodes/comfyui-dream-project/prompting.py
new file mode 100644
index 0000000000000000000000000000000000000000..b725eaf37765584ddd666deb63c1d104114ecafe
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/prompting.py
@@ -0,0 +1,69 @@
+from .categories import NodeCategories
+from .shared import hashed_as_strings
+from .dreamtypes import PartialPrompt
+
+
+class DreamWeightedPromptBuilder:
+ NODE_NAME = "Build Prompt"
+ ICON = "⚖"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "optional": {
+ "partial_prompt": (PartialPrompt.ID,)
+ },
+ "required": {
+ "added_prompt": ("STRING", {"default": "", "multiline": True}),
+ "weight": ("FLOAT", {"default": 1.0}),
+ },
+ }
+
+ CATEGORY = NodeCategories.CONDITIONING
+ RETURN_TYPES = (PartialPrompt.ID,)
+ RETURN_NAMES = ("partial_prompt",)
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return hashed_as_strings(*values)
+
+ def result(self, added_prompt, weight, **args):
+ input = args.get("partial_prompt", PartialPrompt())
+ p = input.add(added_prompt, weight)
+ return (p,)
+
+
+class DreamPromptFinalizer:
+ NODE_NAME = "Finalize Prompt"
+ ICON = "🗫"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "partial_prompt": (PartialPrompt.ID,),
+ "adjustment": (["raw", "by_abs_max", "by_abs_sum"],),
+ "clamp": ("FLOAT", {"default": 2.0, "min": 0.1, "step": 0.1}),
+ "adjustment_reference": ("FLOAT", {"default": 1.0, "min": 0.1}),
+ },
+ }
+
+ CATEGORY = NodeCategories.CONDITIONING
+ RETURN_TYPES = ("STRING", "STRING")
+ RETURN_NAMES = ("positive", "negative")
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return hashed_as_strings(*values)
+
+ def result(self, partial_prompt: PartialPrompt, adjustment, adjustment_reference, clamp):
+ if adjustment == "raw" or partial_prompt.is_empty():
+ return partial_prompt.finalize(clamp)
+ elif adjustment == "by_abs_sum":
+ f = adjustment_reference / partial_prompt.abs_sum()
+ return partial_prompt.scaled_by(f).finalize(clamp)
+ else:
+ f = adjustment_reference / partial_prompt.abs_max()
+ return partial_prompt.scaled_by(f).finalize(clamp)
diff --git a/custom_nodes/comfyui-dream-project/readme.md b/custom_nodes/comfyui-dream-project/readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..67efe98d13a5f1ded9e99906d5b6d4b1b9ec2013
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/readme.md
@@ -0,0 +1,336 @@
+# Dream Project Animation Nodes for ComfyUI
+
+This repository contains various nodes for supporting Deforum-style animation generation with ComfyUI. I created these
+for my own use (producing videos for my "Alt Key Project" music -
+[youtube channel](https://www.youtube.com/channel/UC4cKvJ4hia7zULxeCc-7OcQ)), but I think they should be generic enough
+and useful to many ComfyUI users.
+
+I have demonstrated the use of these custom nodes in this [youtube video](https://youtu.be/pZ6Li3qF-Kk).
+
+# Notice!
+
+This custom node pack is currently not being updated. Stable Diffusion video generation is moving towards a different
+workflow with AnimateDiff and Stable Video Diffusion. I decided to not try to update this node pack, but I am instead
+creating a separate custom node pack here:
+
+[github](https://github.com/alt-key-project/comfyui-dream-video-batches)
+
+This new node pack will be getting my attention from now on (at least as long as stable diffusion video generation is done mostly
+in batches).
+
+## Installation
+
+### Simple option
+
+You can install Dream Project Animation Nodes using the ComfyUI Manager.
+
+### Manual option
+
+Run within (ComfyUI)/custom_nodes/ folder:
+
+* git clone https://github.com/alt-key-project/comfyui-dream-project.git
+* cd comfyui-dream-project
+
+Then, if you are using the python embedded in ComfyUI:
+* (ComfyUI)/python_embedded/python.exe -s -m pip install -r requirements.txt
+
+With your system-wide python:
+* pip install -r requirements.txt
+
+Finally:
+* Start ComfyUI.
+
+After startup, a configuration file 'config.json' should have been created in the 'comfyui-dream-project' directory.
+Specifically check that the path of ffmpeg works in your system (add full path to the command if needed).
+
+## Upgrade
+
+When upgrading, it is good to re-run the pip install command as specified in the install section. This will install any
+new dependencies.
+
+## Configuration
+
+### debug
+
+Setting this to true will enable some trace-level logging.
+
+### ffmpeg.file_extension
+
+Sets the output file extension and with that the envelope used.
+
+### ffmpeg.path
+
+Path to the ffmpeg executable or just the command if ffmpeg is in PATH.
+
+### ffmpeg.arguments
+
+The arguments sent to FFMPEG. A few of the values are provided by the node:
+
+* %FPS% the target framerate
+* %FRAMES% a frame ionput file
+* %OUTPUT% output video file path
+
+### encoding.jpeg__quality
+
+Sets the encoding quality of jpeg images.
+
+### ui.top_category
+
+Sets the name of the top level category on the menu. Set to empty string "" to remove the top level. If the top level
+is removed you may also want to disable the category icons to get nodes into existing category folders.
+
+### prepend_icon_to_category / append_icon_to_category
+
+Flags to add a icon before and/or after the category name at each level.
+
+### prepend_icon_icon_to_node / append_icon_icon_to_node
+
+Flags to add an icon before and/or after the node name.
+
+### ui.category_icons
+
+Each key defines a unicode symbol as an icon used for the specified category.
+
+### mpeg_coder.bitrate_factor
+
+This factor allows changing the bitrate to better fit the required quality and codec. A value of 1 is typically
+suitable for H.265.
+
+### mpeg_coder.codec_name
+
+Codec names as specified by ffmpeg. Some common options include "libx264", "libx264" and "mpeg2video".
+
+### mpeg_coder.encoding_threads
+
+Increasing the number of encoding threads in mpegCoder will generally reduce the overall encoding time, but it will also
+increase the load on the computer.
+
+### mpeg_coder.file_extension
+
+Sets the output file extension and with that the envelope used.
+
+### mpeg_coder.max_b_frame
+
+Sets the max-b-frames parameter for as specified in ffmpeg.
+
+## Concepts used
+
+These are some concepts used in nodes:
+
+### Frame Counter
+
+The frame counter is an abstraction that keeps track of where we are in the animation - what frame is rendered
+and how does the current frame fit into the current animation.
+
+### Curves
+
+A curve is simply a node that produces a value based on the frame counter (changing over time).
+
+### Palette
+
+A palette is a collection of color values.
+
+### Sequence
+
+A sequence is a full set of animation frames and a corresponding timeline for these frames. The sequence is
+created by the 'Image Sequence Saver' node and it may be used to trigger post processing tasks such as generating the
+video file using ffmpeg. These nodes should be seen as a convenience and they are severely limited. Never put sequence
+nodes in parallel - they will not work as intended!
+
+## The nodes
+### Analyze Palette [Dream]
+Output brightness, red, green and blue averages of a palette. Useful to control other processing.
+
+### Beat Curve [Dream]
+Beat pattern curve with impulses at specified beats of a measure.
+
+### Big *** Switch [Dream]
+Switch nodes for different type for up to ten inputs.
+
+### Boolean To Float/Int [Dream]
+Converts a boolean value to two different numeric values.
+
+### Build Prompt [Dream] (and Finalize Prompt [Dream])
+Weighted text prompt builder utility. Chain any number of these nodes and terminate with 'Finalize Prompt'.
+
+### Calculation [Dream]
+Mathematical calculation node. Exposes most of the mathematical functions in the python
+[math module](https://docs.python.org/3/library/math.html), mathematical operators as well as round, abs, int,
+float, max and min.
+
+### Compare Palettes [Dream]
+Analyses two palettes and produces the quotient for each individual channel (b/a) and brightness.
+
+### CSV Curve [Dream]
+CSV input curve where first column is frame or second and second column is value.
+
+### CSV Generator [Dream]
+CSV output, mainly for debugging purposes. First column is frame number and second is value.
+Recreates file at frame 0 (removing and existing content in the file).
+
+### Common Frame Dimensions [Dream]
+Utility for calculating good width/height based on common video dimensions.
+
+### Video Encoder (FFMPEG) [Dream]
+Post processing for animation sequences calling FFMPEG to generate video files.
+
+### File Count [Dream]
+Finds the number of files in a directory matching specified patterns.
+
+### Float/Int/string to Log Entry [Dream]
+Logging for float/int/string values.
+
+### Frame Count Calculator [Dream]
+Simple utility to calculate number of frames based on time and framerate.
+
+### Frame Counter (Directory) [Dream]
+Directory backed frame counter, for output directories.
+
+### Frame Counter (Simple) [Dream]
+Integer value used as frame counter. Useful for testing or if an auto-incrementing primitive is used as a frame
+counter.
+
+### Frame Counter Info [Dream]
+Extracts information from the frame counter.
+
+### Frame Counter Offset [Dream]
+Adds an offset (in frames) to a frame counter.
+
+### Frame Counter Time Offset [Dream]
+Adds an offset in seconds to a frame counter.
+
+### Image Brightness Adjustment [Dream]
+Adjusts the brightness of an image by a factor.
+
+### Image Color Shift [Dream]
+Allows changing the colors of an image with a multiplier for each channel (RGB).
+
+### Image Contrast Adjustment [Dream]
+Adjusts the contrast of an image by a factor.
+
+### Image Motion [Dream]
+Node supporting zooming in/out and translating an image.
+
+### Image Sequence Blend [Dream]
+Post processing for animation sequences blending frame for a smoother blurred effect.
+
+### Image Sequence Loader [Dream]
+Loads a frame from a directory of images.
+
+### Image Sequence Saver [Dream]
+Saves a frame to a directory.
+
+### Image Sequence Tweening [Dream]
+Post processing for animation sequences generating blended in-between frames.
+
+### Laboratory [Dream]
+Super-charged number generator for experimenting with ComfyUI.
+
+### Log Entry Joiner [Dream]
+Merges multiple log entries (reduces noodling).
+
+### Log File [Dream]
+The text logging facility for the Dream Project Animation nodes.
+
+### Linear Curve [Dream]
+Linear interpolation between two values over the full animation.
+
+### Noise from Area Palettes [Dream]
+Generates noise based on the colors of up to nine different palettes, each connected to position/area of the
+image. Although the palettes are optional, at least one palette should be provided.
+
+### Noise from Palette [Dream]
+Generates noise based on the colors in a palette.
+
+### Palette Color Align [Dream]
+Shifts the colors of one palette towards another target palette. If the alignment factor
+is 0.5 the result is nearly an average of the two palettes. At 0 no alignment is done and at 1 we get a close
+alignment to the target. Above one we will overshoot the alignment.
+
+### Palette Color Shift [Dream]
+Multiplies the color values in a palette to shift the color balance or brightness.
+
+### Sample Image Area as Palette [Dream]
+Randomly samples a palette from an image based on pre-defined areas. The image is separated into nine rectangular areas
+of equal size and each node may sample one of these.
+
+### Sample Image as Palette [Dream]
+Randomly samples pixels from a source image to build a palette from it.
+
+### Saw Curve [Dream]
+Saw wave curve.
+
+### Sine Curve [Dream]
+Simple sine wave curve.
+
+### Smooth Event Curve [Dream]
+Single event/peak curve with a slight bell-shape.
+
+### String Tokenizer [Dream]
+Splits a text into tokens by a separator and returns one of the tokens based on a given index.
+
+### Triangle Curve [Dream]
+Triangle wave curve.
+
+### Triangle Event Curve [Dream]
+Single event/peak curve with triangular shape.
+
+### WAV Curve [Dream]
+Use an uncompressed WAV audio file as a curve.
+
+### Other custom nodes
+
+Many of the nodes found in 'WAS Node Suite' are useful the Dream Project Animation nodes - I suggest you install those
+custom nodes as well!
+
+## Examples
+
+### Image Motion with Curves
+
+This example should be a starting point for anyone wanting to build with the Dream Project Animation nodes.
+
+[motion-workflow-example](examples/motion-workflow-example.json)
+
+### Image Motion with Color Coherence
+
+Same as above but with added color coherence through palettes.
+
+[motion-workflow-with-color-coherence](examples/motion-workflow-with-color-coherence.json)
+
+### Area Sampled Noise
+
+This flow demonstrates sampling image areas into palettes and generating noise for these areas.
+
+[area-sampled-noise](examples/area-sampled-noise.json)
+
+### Prompt Morphing
+
+This flow demonstrates prompt building with weights based on curves and brightness and contrast control.
+
+[prompt-morphing](examples/prompt-morphing.json)
+
+### Laboratory
+
+This flow demonstrates use of the Laboratory and Logging nodes.
+
+[laboratory](examples/laboratory.json)
+
+## Known issues
+
+### FFMPEG
+
+The call to FFMPEG currently in the default configuration (in config.json) does not seem to work for everyone. The good
+news is that you can change the arguments to whatever works for you - the node-supplied parameters (that probably all need to be in the call)
+are:
+
+* -i %FRAMES% (the input file listing frames)
+* -r %FPS% (sets the frame rate)
+* %OUTPUT% (the path to the video file)
+
+If possible, I will change the default configuration to one that more versions/builds of ffmpeg will accept. Do let me
+know what arguments are causing issues for you!
+
+### Framerate is not always right with mpegCoder encoding node
+
+The mpegCoder library will always use variable frame rate encoding if it is available in the output format. With most
+outputs this means that your actual framerate will differ slightly from the requested one.
diff --git a/custom_nodes/comfyui-dream-project/requirements.txt b/custom_nodes/comfyui-dream-project/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..cad039a7a69429da7516c9ccb053df9ae72a5ba7
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/requirements.txt
@@ -0,0 +1,6 @@
+imageio
+pilgram
+scipy
+numpy<1.24>=1.18
+torchvision
+evalidate
diff --git a/custom_nodes/comfyui-dream-project/seq_processing.py b/custom_nodes/comfyui-dream-project/seq_processing.py
new file mode 100644
index 0000000000000000000000000000000000000000..4ba1ad332d09e4f1cad1a6e5fa652e28714dc1e3
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/seq_processing.py
@@ -0,0 +1,343 @@
+# -*- coding: utf-8 -*-
+import os
+import shutil
+import subprocess
+import tempfile
+from functools import lru_cache
+
+from PIL import Image as PilImage
+
+from .categories import NodeCategories
+from .err import on_error
+from .shared import DreamConfig
+#from .shared import MpegEncoderUtility
+from .dreamtypes import *
+
+CONFIG = DreamConfig()
+
+
+@lru_cache(5)
+def _load_image_cached(filename):
+ return PilImage.open(filename)
+
+
+class TempFileSet:
+ def __init__(self):
+ self._files = dict()
+
+ def add(self, temppath, finalpath):
+ self._files[temppath] = finalpath
+
+ def remove(self):
+ for f in self._files.keys():
+ os.unlink(f)
+
+ def finalize(self):
+ for a, b in self._files.items():
+ shutil.move(a, b)
+ self._files = dict()
+
+
+class AnimationSeqProcessor:
+ def __init__(self, sequence: AnimationSequence):
+ self._sequence = sequence
+ self._input_cache = {}
+ self._inputs = {}
+ self._output_dirs = {}
+ for b in self._sequence.batches:
+ self._inputs[b] = list(self._sequence.get_image_files_of_batch(b))
+ self._output_dirs[b] = os.path.dirname(os.path.abspath(self._inputs[b][0]))
+ self._ext = os.path.splitext(self._inputs[0][0])[1].lower()
+ self._length = len(self._inputs[0])
+
+ def _load_input(self, batch_id, index) -> DreamImage:
+ files = self._inputs[batch_id]
+ index = min(max(0, index), len(files) - 1)
+ filename = files[index]
+ return DreamImage(pil_image=_load_image_cached(filename))
+
+ def _process_single_batch(self, batch_id, indices, index_offsets: List[int], fun, output_dir) -> List[str]:
+ all_indices = list(indices)
+ last_index = max(all_indices)
+ workset = TempFileSet()
+ rnd = random.randint(0, 1000000)
+ result_files = list()
+ try:
+ for index in all_indices:
+ images = list(map(lambda offset: self._load_input(batch_id, index + offset), index_offsets))
+
+ result: Dict[int, DreamImage] = fun(index, last_index, images)
+ for (result_index, img) in result.items():
+ filepath = os.path.join(output_dir,
+ "tmp_" + str(rnd) + "_" + (str(result_index).zfill(8)) + self._ext)
+ filepath_final = os.path.join(output_dir, "seq_" + (str(result_index).zfill(8)) + self._ext)
+ if self._ext == ".png":
+ img.save_png(filepath)
+ else:
+ img.save_jpg(filepath, quality=CONFIG.get("encoding.jpeg_quality", 98))
+ workset.add(filepath, filepath_final)
+ result_files.append(filepath_final)
+ # all done with batch - remove input files
+ for oldfile in self._inputs[batch_id]:
+ os.unlink(oldfile)
+ workset.finalize()
+ return result_files
+ finally:
+ workset.remove()
+
+ def process(self, index_offsets: List[int], fun):
+ results = dict()
+ new_length = 0
+ for batch_id in self._sequence.batches:
+ resulting_filenames = self._process_single_batch(batch_id, range(len(self._inputs[batch_id])),
+ index_offsets, fun,
+ self._output_dirs[batch_id])
+ for (index, filename) in enumerate(resulting_filenames):
+ l = results.get(index, [])
+ l.append(filename)
+ results[index] = l
+ new_length = len(resulting_filenames)
+ new_fps = self._sequence.frame_counter.frames_per_second * (float(new_length) / self._length)
+ counter = FrameCounter(new_length - 1, new_length, new_fps)
+ return AnimationSequence(counter, results)
+
+
+def _ffmpeg(config, filenames, fps, output):
+ fps = float(fps)
+ duration = 1.0 / fps
+ tmp = tempfile.NamedTemporaryFile(delete=False, mode="wb")
+ tempfilepath = tmp.name
+ try:
+ for filename in filenames:
+ filename = filename.replace("\\", "/")
+ tmp.write(f"file '{filename}'\n".encode())
+ tmp.write(f"duration {duration}\n".encode())
+ finally:
+ tmp.close()
+
+ try:
+ cmd = [config.get("ffmpeg.path", "ffmpeg")]
+ cmd.extend(config.get("ffmpeg.arguments"))
+ replacements = {"%FPS%": str(fps), "%FRAMES%": tempfilepath, "%OUTPUT%": output}
+
+ for (key, value) in replacements.items():
+ cmd = list(map(lambda s: s.replace(key, value), cmd))
+
+ subprocess.check_output(cmd, shell=True)
+ finally:
+ os.unlink(tempfilepath)
+
+
+def _make_video_filename(name, file_ext):
+ (b, _) = os.path.splitext(name)
+ return b + "." + file_ext.strip(".")
+
+#
+# class DreamVideoEncoderMpegCoder:
+# NODE_NAME = "Video Encoder (mpegCoder)"
+# ICON = "🎬"
+# CATEGORY = NodeCategories.ANIMATION_POSTPROCESSING
+# RETURN_TYPES = (LogEntry.ID,)
+# RETURN_NAMES = ("log_entry",)
+# OUTPUT_NODE = True
+# FUNCTION = "encode"
+#
+# @classmethod
+# def INPUT_TYPES(cls):
+# return {
+# "required": SharedTypes.sequence | {
+# "name": ("STRING", {"default": 'video', "multiline": False}),
+# "framerate_factor": ("FLOAT", {"default": 1.0, "min": 0.01, "max": 100.0}),
+# "remove_images": ("BOOLEAN", {"default": True})
+# },
+# }
+#
+# def _find_free_filename(self, filename, defaultdir):
+# if os.path.basename(filename) == filename:
+# filename = os.path.join(defaultdir, filename)
+# n = 1
+# tested = filename
+# while os.path.exists(tested):
+# n += 1
+# (b, ext) = os.path.splitext(filename)
+# tested = b + "_" + str(n) + ext
+# return tested
+#
+# def encode(self, sequence, name, framerate_factor, remove_images):
+# if not sequence.is_defined:
+# return (LogEntry([]),)
+# config = DreamConfig()
+# filename = _make_video_filename(name, config.get("mpeg_coder.file_extension", "mp4"))
+# log_entry = LogEntry([])
+# for batch_num in sequence.batches:
+# try:
+# images = list(sequence.get_image_files_of_batch(batch_num))
+# filename = self._find_free_filename(filename, os.path.dirname(images[0]))
+# first_image = DreamImage.from_file(images[0])
+# enc = MpegEncoderUtility(video_path=filename,
+# bit_rate_factor=float(config.get("mpeg_coder.bitrate_factor", 1.0)),
+# encoding_threads=int(config.get("mpeg_coder.encoding_threads", 4)),
+# max_b_frame=int(config.get("mpeg_coder.max_b_frame", 2)),
+# width=first_image.width,
+# height=first_image.height,
+# files=images,
+# fps=sequence.fps * framerate_factor,
+# codec_name=config.get("mpeg_coder.codec_name", "libx265"))
+# enc.encode()
+# log_entry = log_entry.add("Generated video '{}'".format(filename))
+# if remove_images:
+# for imagepath in images:
+# if os.path.isfile(imagepath):
+# os.unlink(imagepath)
+# except Exception as e:
+# on_error(self.__class__, str(e))
+# return (log_entry,)
+#
+
+class DreamVideoEncoder:
+ NODE_NAME = "FFMPEG Video Encoder"
+ DISPLAY_NAME = "Video Encoder (FFMPEG)"
+ ICON = "🎬"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": SharedTypes.sequence | {
+ "name": ("STRING", {"default": 'video', "multiline": False}),
+ "framerate_factor": ("FLOAT", {"default": 1.0, "min": 0.01, "max": 100.0}),
+ "remove_images": ("BOOLEAN", {"default": True})
+ },
+ }
+
+ CATEGORY = NodeCategories.ANIMATION_POSTPROCESSING
+ RETURN_TYPES = (LogEntry.ID,)
+ RETURN_NAMES = ("log_entry",)
+ OUTPUT_NODE = True
+ FUNCTION = "encode"
+
+ @classmethod
+ def IS_CHANGED(cls, sequence: AnimationSequence, **kwargs):
+ return sequence.is_defined
+
+ def _find_free_filename(self, filename, defaultdir):
+ if os.path.basename(filename) == filename:
+ filename = os.path.join(defaultdir, filename)
+ n = 1
+ tested = filename
+ while os.path.exists(tested):
+ n += 1
+ (b, ext) = os.path.splitext(filename)
+ tested = b + "_" + str(n) + ext
+ return tested
+
+ def generate_video(self, files, fps, filename, config):
+ filename = self._find_free_filename(filename, os.path.dirname(files[0]))
+ _ffmpeg(config, files, fps, filename)
+ return filename
+
+ def encode(self, sequence: AnimationSequence, name: str, remove_images, framerate_factor):
+ if not sequence.is_defined:
+ return (LogEntry([]),)
+
+ config = DreamConfig()
+ filename = _make_video_filename(name, config.get("ffmpeg.file_extension", "mp4"))
+ log_entry = LogEntry([])
+ for batch_num in sequence.batches:
+ try:
+ images = list(sequence.get_image_files_of_batch(batch_num))
+ actual_filename = self.generate_video(images, sequence.fps * framerate_factor, filename, config)
+
+ log_entry = log_entry.add("Generated video '{}'".format(actual_filename))
+ if remove_images:
+ for imagepath in images:
+ if os.path.isfile(imagepath):
+ os.unlink(imagepath)
+ except Exception as e:
+ on_error(self.__class__, str(e))
+ return (log_entry,)
+
+
+class DreamSequenceTweening:
+ NODE_NAME = "Image Sequence Tweening"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": SharedTypes.sequence | {
+ "multiplier": ("INT", {"default": 2, "min": 2, "max": 10}),
+ },
+ }
+
+ CATEGORY = NodeCategories.ANIMATION_POSTPROCESSING
+ RETURN_TYPES = (AnimationSequence.ID,)
+ RETURN_NAMES = ("sequence",)
+ OUTPUT_NODE = False
+ FUNCTION = "process"
+
+ @classmethod
+ def IS_CHANGED(cls, sequence: AnimationSequence, **kwargs):
+ return sequence.is_defined
+
+ def process(self, sequence: AnimationSequence, multiplier):
+ if not sequence.is_defined:
+ return (sequence,)
+
+ def _generate_extra_frames(input_index, last_index, images):
+ results = {}
+ if input_index == last_index:
+ # special case
+ for i in range(multiplier):
+ results[input_index * multiplier + i] = images[0]
+ return results
+
+ # normal case
+ current_frame = images[0]
+ next_frame = images[1]
+ for i in range(multiplier):
+ alpha = float(i + 1) / multiplier
+ results[multiplier * input_index + i] = current_frame.blend(next_frame, 1.0 - alpha, alpha)
+ return results
+
+ proc = AnimationSeqProcessor(sequence)
+ return (proc.process([0, 1], _generate_extra_frames),)
+
+
+class DreamSequenceBlend:
+ NODE_NAME = "Image Sequence Blend"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": SharedTypes.sequence | {
+ "fade_in": ("FLOAT", {"default": 0.1, "min": 0.01, "max": 0.5}),
+ "fade_out": ("FLOAT", {"default": 0.1, "min": 0.01, "max": 0.5}),
+ "iterations": ("INT", {"default": 1, "min": 1, "max": 10}),
+ },
+ }
+
+ CATEGORY = NodeCategories.ANIMATION_POSTPROCESSING
+ RETURN_TYPES = (AnimationSequence.ID,)
+ RETURN_NAMES = ("sequence",)
+ OUTPUT_NODE = False
+ FUNCTION = "process"
+
+ @classmethod
+ def IS_CHANGED(cls, sequence: AnimationSequence, **kwargs):
+ return sequence.is_defined
+
+ def process(self, sequence: AnimationSequence, fade_in, fade_out, iterations):
+ if not sequence.is_defined:
+ return (sequence,)
+
+ current_sequence = sequence
+ for i in range(iterations):
+ proc = AnimationSeqProcessor(current_sequence)
+
+ def _blur(index: int, last_index: int, images: List[DreamImage]):
+ pre_frame = images[0].blend(images[1], fade_in, 1.0)
+ post_frame = images[2].blend(images[1], fade_out, 1.0)
+ return {index: pre_frame.blend(post_frame)}
+
+ current_sequence = proc.process([-1, 0, 1], _blur)
+
+ return (current_sequence,)
diff --git a/custom_nodes/comfyui-dream-project/shared.py b/custom_nodes/comfyui-dream-project/shared.py
new file mode 100644
index 0000000000000000000000000000000000000000..24bde244e6208ff15bae931f9127dec674e2ee96
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/shared.py
@@ -0,0 +1,418 @@
+# -*- coding: utf-8 -*-
+
+import hashlib
+import json
+import os
+import random
+import time
+
+import folder_paths as comfy_paths
+import glob
+import numpy
+import torch
+from PIL import Image, ImageFilter, ImageEnhance
+from PIL.ImageDraw import ImageDraw
+from PIL.PngImagePlugin import PngInfo
+from typing import Dict, Tuple, List
+
+from .dreamlogger import DreamLog
+from .embedded_config import EMBEDDED_CONFIGURATION
+
+NODE_FILE = os.path.abspath(__file__)
+DREAM_NODES_SOURCE_ROOT = os.path.dirname(NODE_FILE)
+TEMP_PATH = os.path.join(os.path.abspath(comfy_paths.temp_directory), "Dream_Anim")
+ALWAYS_CHANGED_FLAG = float("NaN")
+
+
+def convertTensorImageToPIL(tensor_image) -> Image:
+ return Image.fromarray(numpy.clip(255. * tensor_image.cpu().numpy().squeeze(), 0, 255).astype(numpy.uint8))
+
+
+def convertFromPILToTensorImage(pil_image):
+ return torch.from_numpy(numpy.array(pil_image).astype(numpy.float32) / 255.0).unsqueeze(0)
+
+
+def _replace_pil_image(data):
+ if isinstance(data, Image.Image):
+ return DreamImage(pil_image=data)
+ else:
+ return data
+
+
+_config_data = None
+
+
+class DreamConfig:
+ FILEPATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config.json")
+ DEFAULT_CONFIG = EMBEDDED_CONFIGURATION
+
+ def __init__(self):
+ global _config_data
+ if not os.path.isfile(DreamConfig.FILEPATH):
+ self._data = DreamConfig.DEFAULT_CONFIG
+ self._save()
+ if _config_data is None:
+ with open(DreamConfig.FILEPATH, encoding="utf-8") as f:
+ self._data = json.load(f)
+ if self._merge_with_defaults(self._data, DreamConfig.DEFAULT_CONFIG):
+ self._save()
+ _config_data = self._data
+ else:
+ self._data = _config_data
+
+ def _save(self):
+ with open(DreamConfig.FILEPATH, "w", encoding="utf-8") as f:
+ json.dump(self._data, f, indent=2)
+
+ def _merge_with_defaults(self, config: dict, default_config: dict) -> bool:
+ changed = False
+ for key in default_config.keys():
+ if key not in config:
+ changed = True
+ config[key] = default_config[key]
+ elif isinstance(default_config[key], dict):
+ changed = changed or self._merge_with_defaults(config[key], default_config[key])
+ return changed
+
+ def get(self, key: str, default=None):
+ key = key.split(".")
+ d = self._data
+ for part in key:
+ d = d.get(part, {})
+ if isinstance(d, dict) and not d:
+ return default
+ else:
+ return d
+
+
+def get_logger():
+ config = DreamConfig()
+ return DreamLog(config.get("debug", False))
+
+
+class DreamImageProcessor:
+ def __init__(self, inputs: torch.Tensor, **extra_args):
+ self._images_in_batch = [convertTensorImageToPIL(tensor) for tensor in inputs]
+ self._extra_args = extra_args
+ self.is_batch = len(self._images_in_batch) > 1
+
+ def process_PIL(self, fun):
+ def _wrap(dream_image):
+ pil_outputs = fun(dream_image.pil_image)
+ return list(map(_replace_pil_image, pil_outputs))
+
+ return self.process(_wrap)
+
+ def process(self, fun):
+ output = []
+ batch_counter = 0 if self.is_batch else -1
+ for pil_image in self._images_in_batch:
+ exec_result = fun(DreamImage(pil_image=pil_image), batch_counter, **self._extra_args)
+ exec_result = list(map(_replace_pil_image, exec_result))
+ if not output:
+ output = [list() for i in range(len(exec_result))]
+ for i in range(len(exec_result)):
+ output[i].append(exec_result[i].create_tensor_image())
+ if batch_counter >= 0:
+ batch_counter += 1
+ return tuple(map(lambda l: torch.cat(l, dim=0), output))
+
+
+def pick_random_by_weight(data: List[Tuple[float, object]], rng: random.Random):
+ total_weight = sum(map(lambda item: item[0], data))
+ r = rng.random()
+ for (weight, obj) in data:
+ r -= weight / total_weight
+ if r <= 0:
+ return obj
+ return data[0][1]
+
+
+class DreamImage:
+ @classmethod
+ def join_to_tensor_data(cls, images):
+ l = list(map(lambda i: i.create_tensor_image(), images))
+ return torch.cat(l, dim=0)
+
+ def __init__(self, tensor_image=None, pil_image=None, file_path=None, with_alpha=False):
+ if pil_image is not None:
+ self.pil_image = pil_image
+ elif tensor_image is not None:
+ self.pil_image = convertTensorImageToPIL(tensor_image)
+ else:
+ self.pil_image = Image.open(file_path)
+ if with_alpha and self.pil_image.mode != "RGBA":
+ self.pil_image = self.pil_image.convert("RGBA")
+ else:
+ if self.pil_image.mode not in ("RGB", "RGBA"):
+ self.pil_image = self.pil_image.convert("RGB")
+ self.width = self.pil_image.width
+ self.height = self.pil_image.height
+ self.size = self.pil_image.size
+ self._draw = ImageDraw(self.pil_image)
+
+ def change_brightness(self, factor):
+ enhancer = ImageEnhance.Brightness(self.pil_image)
+ return DreamImage(pil_image=enhancer.enhance(factor))
+
+ def change_contrast(self, factor):
+ enhancer = ImageEnhance.Contrast(self.pil_image)
+ return DreamImage(pil_image=enhancer.enhance(factor))
+
+ def numpy_array(self):
+ return numpy.array(self.pil_image)
+
+ def _renew(self, pil_image):
+ self.pil_image = pil_image
+ self._draw = ImageDraw(self.pil_image)
+
+ def __iter__(self):
+ class _Pixels:
+ def __init__(self, image: DreamImage):
+ self.x = 0
+ self.y = 0
+ self._img = image
+
+ def __next__(self) -> Tuple[int, int, int, int]:
+ if self.x >= self._img.width:
+ self.y += 1
+ self.x = 1
+ if self.y >= self._img.height:
+ raise StopIteration
+ p = self._img.get_pixel(self.x, self.y)
+ self.x += 1
+ return (p, self.x, self.y)
+
+ return _Pixels(self)
+
+ def convert(self, mode="RGB"):
+ if self.pil_image.mode == mode:
+ return self
+ return DreamImage(pil_image=self.pil_image.convert(mode))
+
+ def create_tensor_image(self):
+ return convertFromPILToTensorImage(self.pil_image)
+
+ def blend(self, other, weight_self: float = 0.5, weight_other: float = 0.5):
+ alpha = 1.0 - weight_self / (weight_other + weight_self)
+ return DreamImage(pil_image=Image.blend(self.pil_image, other.pil_image, alpha))
+
+ def color_area(self, x, y, w, h, col):
+ self._draw.rectangle((x, y, x + w - 1, y + h - 1), fill=col, outline=col)
+
+ def blur(self, amount):
+ return DreamImage(pil_image=self.pil_image.filter(ImageFilter.GaussianBlur(amount)))
+
+ def adjust_colors(self, red_factor=1.0, green_factor=1.0, blue_factor=1.0):
+ # newRed = 1.1*oldRed + 0*oldGreen + 0*oldBlue + constant
+ # newGreen = 0*oldRed + 0.9*OldGreen + 0*OldBlue + constant
+ # newBlue = 0*oldRed + 0*OldGreen + 1*OldBlue + constant
+ matrix = (red_factor, 0, 0, 0,
+ 0, green_factor, 0, 0,
+ 0, 0, blue_factor, 0)
+ return DreamImage(pil_image=self.pil_image.convert("RGB", matrix))
+
+ def get_pixel(self, x, y):
+ p = self.pil_image.getpixel((x, y))
+ if len(p) == 4:
+ return p
+ else:
+ return (p[0], p[1], p[2], 255)
+
+ def set_pixel(self, x, y, pixelvalue):
+ if len(pixelvalue) == 4:
+ self.pil_image.putpixel((x, y), pixelvalue)
+ else:
+ self.pil_image.putpixel((x, y), (pixelvalue[0], pixelvalue[1], pixelvalue[2], 255))
+
+ def save_png(self, filepath, embed_info=False, prompt=None, extra_pnginfo=None):
+ info = PngInfo()
+ print(filepath)
+ if extra_pnginfo is not None:
+ for item in extra_pnginfo:
+ info.add_text(item, json.dumps(extra_pnginfo[item]))
+ if prompt is not None:
+ info.add_text("prompt", json.dumps(prompt))
+ if embed_info:
+ self.pil_image.save(filepath, pnginfo=info, optimize=True)
+ else:
+ self.pil_image.save(filepath, optimize=True)
+
+ def save_jpg(self, filepath, quality=98):
+ self.pil_image.save(filepath, quality=quality, optimize=True)
+
+ @classmethod
+ def from_file(cls, file_path):
+ return DreamImage(pil_image=Image.open(file_path))
+
+
+class DreamMask:
+ def __init__(self, tensor_image=None, pil_image=None):
+ if pil_image:
+ self.pil_image = pil_image
+ else:
+ self.pil_image = convertTensorImageToPIL(tensor_image)
+ if self.pil_image.mode != "L":
+ self.pil_image = self.pil_image.convert("L")
+
+ def create_tensor_image(self):
+ return torch.from_numpy(numpy.array(self.pil_image).astype(numpy.float32) / 255.0)
+
+
+def list_images_in_directory(directory_path: str, pattern: str, alphabetic_index: bool) -> Dict[int, List[str]]:
+ if not os.path.isdir(directory_path):
+ return {}
+ dirs_to_search = [directory_path]
+ if os.path.isdir(os.path.join(directory_path, "batch_0001")):
+ dirs_to_search = list()
+ for i in range(10000):
+ dirpath = os.path.join(directory_path, "batch_" + (str(i).zfill(4)))
+ if not os.path.isdir(dirpath):
+ break
+ else:
+ dirs_to_search.append(dirpath)
+
+ def _num_from_filename(fn):
+ (text, _) = os.path.splitext(fn)
+ token = text.split("_")[-1]
+ if token.isdigit():
+ return int(token)
+ else:
+ return -1
+
+ result = dict()
+ for search_path in dirs_to_search:
+ files = []
+ for file_name in glob.glob(os.path.join(search_path, pattern), recursive=False):
+ if file_name.lower().endswith(('.jpeg', '.jpg', '.png', '.tiff', '.gif', '.bmp', '.webp')):
+ files.append(os.path.abspath(file_name))
+
+ if alphabetic_index:
+ files.sort()
+ for idx, item in enumerate(files):
+ lst = result.get(idx, [])
+ lst.append(item)
+ result[idx] = lst
+ else:
+ for filepath in files:
+ idx = _num_from_filename(os.path.basename(filepath))
+ lst = result.get(idx, [])
+ lst.append(filepath)
+ result[idx] = lst
+ return result
+
+
+class DreamStateStore:
+ def __init__(self, name, read_fun, write_fun):
+ self._read = read_fun
+ self._write = write_fun
+ self._name = name
+
+ def _as_key(self, k):
+ return self._name + "_" + k
+
+ def get(self, key, default):
+ v = self[key]
+ if v is None:
+ return default
+ else:
+ return v
+
+ def update(self, key, default, f):
+ prev = self.get(key, default)
+ v = f(prev)
+ self[key] = v
+ return v
+
+ def __getitem__(self, item):
+ return self._read(self._as_key(item))
+
+ def __setitem__(self, key, value):
+ return self._write(self._as_key(key), value)
+
+
+class DreamStateFile:
+ def __init__(self, state_collection_name="state"):
+ self._filepath = os.path.join(TEMP_PATH, state_collection_name+".json")
+ self._dirname = os.path.dirname(self._filepath)
+ if not os.path.isdir(self._dirname):
+ os.makedirs(self._dirname)
+ if not os.path.isfile(self._filepath):
+ self._data = {}
+ else:
+ with open(self._filepath, encoding="utf-8") as f:
+ self._data = json.load(f)
+
+ def get_section(self, name: str) -> DreamStateStore:
+ return DreamStateStore(name, self._read, self._write)
+
+ def _read(self, key):
+ return self._data.get(key, None)
+
+ def _write(self, key, value):
+ previous = self._data.get(key, None)
+ if value is None:
+ if key in self._data:
+ del self._data[key]
+ else:
+ self._data[key] = value
+ with open(self._filepath, "w", encoding="utf-8") as f:
+ json.dump(self._data, f)
+ return previous
+
+
+def hashed_as_strings(*items):
+ tokens = "|".join(list(map(str, items)))
+ m = hashlib.sha256()
+ m.update(tokens.encode(encoding="utf-8"))
+ return m.digest().hex()
+#
+#
+# class MpegEncoderUtility:
+# def __init__(self, video_path: str, bit_rate_factor: float, width: int, height: int, files: List[str],
+# fps: float, encoding_threads: int, codec_name, max_b_frame):
+# import mpegCoder
+# self._files = files
+# self._logger = get_logger()
+# self._enc = mpegCoder.MpegEncoder()
+# bit_rate = self._calculate_bit_rate(width, height, fps, bit_rate_factor)
+# self._logger.info("Bitrate " + str(bit_rate))
+# self._enc.setParameter(
+# videoPath=video_path, codecName=codec_name,
+# nthread=encoding_threads, bitRate=bit_rate, width=width, height=height, widthSrc=width,
+# heightSrc=height,
+# GOPSize=len(files), maxBframe=max_b_frame, frameRate=self._fps_to_tuple(fps))
+#
+# def _calculate_bit_rate(self, width: int, height: int, fps: float, bit_rate_factor: float):
+# bits_per_pixel_base = 0.5
+# return round(max(10, float(width * height * fps * bits_per_pixel_base * bit_rate_factor * 0.001)))
+#
+# def encode(self):
+# if not self._enc.FFmpegSetup():
+# raise Exception("Failed to setup MPEG Encoder - check parameters!")
+# try:
+# t = time.time()
+#
+# for filepath in self._files:
+# self._logger.debug("Encoding frame {}", filepath)
+# image = DreamImage.from_file(filepath).convert("RGB")
+# self._enc.EncodeFrame(image.numpy_array())
+# self._enc.FFmpegClose()
+# self._logger.info("Completed video encoding of {n} frames in {t} seconds", n=len(self._files),
+# t=round(time.time() - t))
+# finally:
+# self._enc.clear()
+#
+# def _fps_to_tuple(self, fps: float):
+# def _is_almost_int(f: float):
+# return abs(f - int(f)) < 0.001
+#
+# a = fps
+# b = 1
+# while not _is_almost_int(a) and b < 100:
+# a /= 10
+# b *= 10
+# a = round(a)
+# b = round(b)
+# self._logger.info("Video specified as {fps} fps - encoder framerate {a}/{b}", fps=fps, a=a, b=b)
+# return (a, b)
diff --git a/custom_nodes/comfyui-dream-project/switches.py b/custom_nodes/comfyui-dream-project/switches.py
new file mode 100644
index 0000000000000000000000000000000000000000..d860f43b63d0f167b5319bf1889ef101c3f0b339
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/switches.py
@@ -0,0 +1,212 @@
+from .categories import NodeCategories
+from .err import *
+from .shared import ALWAYS_CHANGED_FLAG, hashed_as_strings
+from .dreamtypes import RGBPalette
+
+
+def _generate_switch_input(type: str):
+ d = dict()
+ for i in range(10):
+ d["input_" + str(i)] = (type,)
+ return {
+ "required": {
+ "select": ("INT", {"defualt": 0, "min": 0, "max": 9}),
+ "on_missing": (["previous", "next"],)
+ },
+ "optional": d
+ }
+
+
+def _do_pick(cls, select, on_missing, **args):
+ direction = 1
+ if on_missing == "previous":
+ direction = -1
+ if len(args) == 0:
+ on_error(cls, "No inputs provided!")
+ while args.get("input_" + str(select), None) is None:
+ select = (select + direction) % 10
+ return args["input_" + str(select)],
+
+
+class DreamBigImageSwitch:
+ _switch_type = "IMAGE"
+ NODE_NAME = "Big Image Switch"
+ ICON = "⭆"
+ CATEGORY = NodeCategories.UTILS_SWITCHES
+ RETURN_TYPES = (_switch_type,)
+ RETURN_NAMES = ("selected",)
+ FUNCTION = "pick"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return _generate_switch_input(cls._switch_type)
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return ALWAYS_CHANGED_FLAG
+
+ def pick(self, select, on_missing, **args):
+ return _do_pick(self.__class__, select, on_missing, **args)
+
+
+class DreamBigLatentSwitch:
+ _switch_type = "LATENT"
+ NODE_NAME = "Big Latent Switch"
+ ICON = "⭆"
+ CATEGORY = NodeCategories.UTILS_SWITCHES
+ RETURN_TYPES = (_switch_type,)
+ RETURN_NAMES = ("selected",)
+ FUNCTION = "pick"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return _generate_switch_input(cls._switch_type)
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return ALWAYS_CHANGED_FLAG
+
+ def pick(self, select, on_missing, **args):
+ return _do_pick(self.__class__, select, on_missing, **args)
+
+
+class DreamBigTextSwitch:
+ _switch_type = "STRING"
+ NODE_NAME = "Big Text Switch"
+ ICON = "⭆"
+ CATEGORY = NodeCategories.UTILS_SWITCHES
+ RETURN_TYPES = (_switch_type,)
+ RETURN_NAMES = ("selected",)
+ FUNCTION = "pick"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return _generate_switch_input(cls._switch_type)
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return hashed_as_strings(values)
+
+ def pick(self, select, on_missing, **args):
+ return _do_pick(self.__class__, select, on_missing, **args)
+
+
+class DreamBigPaletteSwitch:
+ _switch_type = RGBPalette.ID
+ NODE_NAME = "Big Palette Switch"
+ ICON = "⭆"
+ CATEGORY = NodeCategories.UTILS_SWITCHES
+ RETURN_TYPES = (_switch_type,)
+ RETURN_NAMES = ("selected",)
+ FUNCTION = "pick"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return _generate_switch_input(cls._switch_type)
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return ALWAYS_CHANGED_FLAG
+
+ def pick(self, select, on_missing, **args):
+ return _do_pick(self.__class__, select, on_missing, **args)
+
+
+class DreamBigFloatSwitch:
+ _switch_type = "FLOAT"
+ NODE_NAME = "Big Float Switch"
+ ICON = "⭆"
+ CATEGORY = NodeCategories.UTILS_SWITCHES
+ RETURN_TYPES = (_switch_type,)
+ RETURN_NAMES = ("selected",)
+ FUNCTION = "pick"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return _generate_switch_input(cls._switch_type)
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return hashed_as_strings(values)
+
+ def pick(self, select, on_missing, **args):
+ return _do_pick(self.__class__, select, on_missing, **args)
+
+
+class DreamBigIntSwitch:
+ _switch_type = "INT"
+ NODE_NAME = "Big Int Switch"
+ ICON = "⭆"
+ CATEGORY = NodeCategories.UTILS_SWITCHES
+ RETURN_TYPES = (_switch_type,)
+ RETURN_NAMES = ("selected",)
+ FUNCTION = "pick"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return _generate_switch_input(cls._switch_type)
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return hashed_as_strings(values)
+
+ def pick(self, select, on_missing, **args):
+ return _do_pick(self.__class__, select, on_missing, **args)
+
+
+class DreamBoolToFloat:
+ NODE_NAME = "Boolean To Float"
+ ICON = "⬖"
+ CATEGORY = NodeCategories.UTILS_SWITCHES
+ RETURN_TYPES = ("FLOAT",)
+ RETURN_NAMES = ("result",)
+ FUNCTION = "pick"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "boolean": ("BOOLEAN", {"default": False}),
+ "on_true": ("FLOAT", {"default": 1.0}),
+ "on_false": ("FLOAT", {"default": 0.0})
+ }
+ }
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return hashed_as_strings(values)
+
+ def pick(self, boolean, on_true, on_false):
+ if boolean:
+ return (on_true,)
+ else:
+ return (on_false,)
+
+
+class DreamBoolToInt:
+ NODE_NAME = "Boolean To Int"
+ ICON = "⬖"
+ CATEGORY = NodeCategories.UTILS_SWITCHES
+ RETURN_TYPES = ("INT",)
+ RETURN_NAMES = ("result",)
+ FUNCTION = "pick"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "boolean": ("BOOLEAN", {"default": False}),
+ "on_true": ("INT", {"default": 1}),
+ "on_false": ("INT", {"default": 0})
+ }
+ }
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return hashed_as_strings(values)
+
+ def pick(self, boolean, on_true, on_false):
+ if boolean:
+ return (on_true,)
+ else:
+ return (on_false,)
diff --git a/custom_nodes/comfyui-dream-project/uninstall.py b/custom_nodes/comfyui-dream-project/uninstall.py
new file mode 100644
index 0000000000000000000000000000000000000000..4cef258f451485e3de90af270dd7416b8e7919a7
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/uninstall.py
@@ -0,0 +1,7 @@
+# -*- coding: utf-8 -*-
+def run_uninstall():
+ pass
+
+
+if __name__ == "__main__":
+ run_uninstall()
diff --git a/custom_nodes/comfyui-dream-project/utility.py b/custom_nodes/comfyui-dream-project/utility.py
new file mode 100644
index 0000000000000000000000000000000000000000..5a0e79a3c70d7b23c74315a786665737cbeaf42c
--- /dev/null
+++ b/custom_nodes/comfyui-dream-project/utility.py
@@ -0,0 +1,266 @@
+# -*- coding: utf-8 -*-
+import datetime
+import math
+import os
+
+import folder_paths as comfy_paths
+
+from .categories import NodeCategories
+from .shared import hashed_as_strings, DreamStateFile
+from .dreamtypes import LogEntry, SharedTypes, FrameCounter
+
+_logfile_state = DreamStateFile("logging")
+
+
+class DreamJoinLog:
+ NODE_NAME = "Log Entry Joiner"
+ ICON = "🗎"
+ CATEGORY = NodeCategories.UTILS
+ RETURN_TYPES = (LogEntry.ID,)
+ RETURN_NAMES = ("log_entry",)
+ FUNCTION = "convert"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "optional": {
+ "entry_0": (LogEntry.ID,),
+ "entry_1": (LogEntry.ID,),
+ "entry_2": (LogEntry.ID,),
+ "entry_3": (LogEntry.ID,),
+ }
+ }
+
+ def convert(self, **values):
+ entry = LogEntry([])
+ for i in range(4):
+ txt = values.get("entry_" + str(i), None)
+ if txt:
+ entry = entry.merge(txt)
+ return (entry,)
+
+
+class DreamFloatToLog:
+ NODE_NAME = "Float to Log Entry"
+ ICON = "🗎"
+ CATEGORY = NodeCategories.UTILS
+ RETURN_TYPES = (LogEntry.ID,)
+ RETURN_NAMES = ("log_entry",)
+ FUNCTION = "convert"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "value": ("FLOAT", {"default": 0}),
+ "label": ("STRING", {"default": ""}),
+ },
+ }
+
+ def convert(self, label, value):
+ return (LogEntry.new(label + ": " + str(value)),)
+
+
+class DreamIntToLog:
+ NODE_NAME = "Int to Log Entry"
+ ICON = "🗎"
+ CATEGORY = NodeCategories.UTILS
+ RETURN_TYPES = (LogEntry.ID,)
+ RETURN_NAMES = ("log_entry",)
+ FUNCTION = "convert"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "value": ("INT", {"default": 0}),
+ "label": ("STRING", {"default": ""}),
+ },
+ }
+
+ def convert(self, label, value):
+ return (LogEntry.new(label + ": " + str(value)),)
+
+
+class DreamStringToLog:
+ NODE_NAME = "String to Log Entry"
+ ICON = "🗎"
+ OUTPUT_NODE = True
+ CATEGORY = NodeCategories.UTILS
+ RETURN_TYPES = (LogEntry.ID,)
+ RETURN_NAMES = ("log_entry",)
+ FUNCTION = "convert"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "text": ("STRING", {"default": ""}),
+ },
+ "optional": {
+ "label": ("STRING", {"default": ""}),
+ }
+ }
+
+ def convert(self, text, **values):
+ label = values.get("label", "")
+ if label:
+ return (LogEntry.new(label + ": " + text),)
+ else:
+ return (LogEntry.new(text),)
+
+
+class DreamStringTokenizer:
+ NODE_NAME = "String Tokenizer"
+ ICON = "🪙"
+ OUTPUT_NODE = True
+ CATEGORY = NodeCategories.UTILS
+ RETURN_TYPES = ("STRING",)
+ RETURN_NAMES = ("token",)
+ FUNCTION = "exec"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "text": ("STRING", {"default": "", "multiline": True}),
+ "separator": ("STRING", {"default": ","}),
+ "selected": ("INT", {"default": 0, "min": 0})
+ },
+ }
+
+ def exec(self, text: str, separator: str, selected: int):
+ if separator is None or separator == "":
+ separator = " "
+ parts = text.split(sep=separator)
+ return (parts[abs(selected) % len(parts)].strip(),)
+
+
+class DreamLogFile:
+ NODE_NAME = "Log File"
+ ICON = "🗎"
+ OUTPUT_NODE = True
+ CATEGORY = NodeCategories.UTILS
+ RETURN_TYPES = ()
+ RETURN_NAMES = ()
+ FUNCTION = "write"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": SharedTypes.frame_counter | {
+ "log_directory": ("STRING", {"default": comfy_paths.output_directory}),
+ "log_filename": ("STRING", {"default": "dreamlog.txt"}),
+ "stdout": ("BOOLEAN", {"default": True}),
+ "active": ("BOOLEAN", {"default": True}),
+ "clock_has_24_hours": ("BOOLEAN", {"default": True}),
+ },
+ "optional": {
+ "entry_0": (LogEntry.ID,),
+ "entry_1": (LogEntry.ID,),
+ "entry_2": (LogEntry.ID,),
+ "entry_3": (LogEntry.ID,),
+ "entry_4": (LogEntry.ID,),
+ "entry_5": (LogEntry.ID,),
+ "entry_6": (LogEntry.ID,),
+ "entry_7": (LogEntry.ID,),
+ },
+ }
+
+ def _path_to_log_file(self, log_directory, logfile):
+ if os.path.isabs(logfile):
+ return os.path.normpath(os.path.abspath(logfile))
+ elif os.path.isabs(log_directory):
+ return os.path.normpath(os.path.abspath(os.path.join(log_directory, logfile)))
+ elif log_directory:
+ return os.path.normpath(os.path.abspath(os.path.join(comfy_paths.output_directory, log_directory, logfile)))
+ else:
+ return os.path.normpath(os.path.abspath(os.path.join(comfy_paths.output_directory, logfile)))
+
+ def _get_tm_format(self, clock_has_24_hours):
+ if clock_has_24_hours:
+ return "%a %H:%M:%S"
+ else:
+ return "%a %I:%M:%S %p"
+
+ def write(self, frame_counter: FrameCounter, log_directory, log_filename, stdout, active, clock_has_24_hours,
+ **entries):
+ if not active:
+ return ()
+ log_entry = None
+ for i in range(8):
+ e = entries.get("entry_" + str(i), None)
+ if e is not None:
+ if log_entry is None:
+ log_entry = e
+ else:
+ log_entry = log_entry.merge(e)
+ log_file_path = self._path_to_log_file(log_directory, log_filename)
+ ts = _logfile_state.get_section("timestamps").get(log_file_path, 0)
+ output_text = list()
+ last_t = 0
+ for (t, text) in log_entry.get_filtered_entries(ts):
+ dt = datetime.datetime.fromtimestamp(t)
+ output_text.append("[frame {}/{} (~{}%), timestamp {}]\n{}".format(frame_counter.current_frame + 1,
+ frame_counter.total_frames,
+ round(frame_counter.progress * 100),
+ dt.strftime(self._get_tm_format(
+ clock_has_24_hours)), text.rstrip()))
+ output_text.append("---")
+ last_t = max(t, last_t)
+ output_text = "\n".join(output_text) + "\n"
+ if stdout:
+ print(output_text)
+ with open(log_file_path, "a", encoding="utf-8") as f:
+ f.write(output_text)
+ _logfile_state.get_section("timestamps").update(log_file_path, 0, lambda _: last_t)
+ return ()
+
+
+def _align_num(n: int, alignment: int, type: str):
+ if alignment <= 1:
+ return n
+ if type == "ceil":
+ return int(math.ceil(float(n) / alignment)) * alignment
+ elif type == "floor":
+ return int(math.floor(float(n) / alignment)) * alignment
+ else:
+ return int(round(float(n) / alignment)) * alignment
+
+
+class DreamFrameDimensions:
+ NODE_NAME = "Common Frame Dimensions"
+ ICON = "⌗"
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "size": (["3840", "1920", "1440", "1280", "768", "720", "640", "512"],),
+ "aspect_ratio": (["16:9", "16:10", "4:3", "1:1", "5:4", "3:2", "21:9", "14:9"],),
+ "orientation": (["wide", "tall"],),
+ "divisor": (["8", "4", "2", "1"],),
+ "alignment": ("INT", {"default": 64, "min": 1, "max": 512}),
+ "alignment_type": (["ceil", "floor", "nearest"],),
+ },
+ }
+
+ CATEGORY = NodeCategories.UTILS
+ RETURN_TYPES = ("INT", "INT", "INT", "INT")
+ RETURN_NAMES = ("width", "height", "final_width", "final_height")
+ FUNCTION = "result"
+
+ @classmethod
+ def IS_CHANGED(cls, *values):
+ return hashed_as_strings(*values)
+
+ def result(self, size, aspect_ratio, orientation, divisor, alignment, alignment_type):
+ ratio = tuple(map(int, aspect_ratio.split(":")))
+ final_width = int(size)
+ final_height = int(round((float(final_width) * ratio[1]) / ratio[0]))
+ width = _align_num(int(round(final_width / float(divisor))), alignment, alignment_type)
+ height = _align_num(int(round((float(width) * ratio[1]) / ratio[0])), alignment, alignment_type)
+ if orientation == "wide":
+ return (width, height, final_width, final_height)
+ else:
+ return (height, width, final_height, final_width)
diff --git a/custom_nodes/comfyui_controlnet_aux/.gitignore b/custom_nodes/comfyui_controlnet_aux/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..cb63911f1d78422454a3bcf52660ac59cad088aa
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/.gitignore
@@ -0,0 +1,182 @@
+# Initially taken from Github's Python gitignore file
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# tests and logs
+tests/fixtures/cached_*_text.txt
+logs/
+lightning_logs/
+lang_code_data/
+tests/outputs
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# vscode
+.vs
+.vscode
+
+# Pycharm
+.idea
+
+# TF code
+tensorflow_code
+
+# Models
+proc_data
+
+# examples
+runs
+/runs_old
+/wandb
+/examples/runs
+/examples/**/*.args
+/examples/rag/sweep
+
+# data
+/data
+serialization_dir
+
+# emacs
+*.*~
+debug.env
+
+# vim
+.*.swp
+
+#ctags
+tags
+
+# pre-commit
+.pre-commit*
+
+# .lock
+*.lock
+
+# DS_Store (MacOS)
+.DS_Store
+# RL pipelines may produce mp4 outputs
+*.mp4
+
+# dependencies
+/transformers
+
+# ruff
+.ruff_cache
+
+wandb
+
+ckpts/
+
+test.ipynb
+config.yaml
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/LICENSE.txt b/custom_nodes/comfyui_controlnet_aux/LICENSE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/LICENSE.txt
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/custom_nodes/comfyui_controlnet_aux/README.md b/custom_nodes/comfyui_controlnet_aux/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..46bd9feb1e7da6f63e6c4dbca5ec693779bc57e1
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/README.md
@@ -0,0 +1,233 @@
+# ComfyUI's ControlNet Auxiliary Preprocessors
+
+This is a rework of [comfyui_controlnet_preprocessors](https://github.com/Fannovel16/comfy_controlnet_preprocessors) based on [ControlNet auxiliary models by 🤗](https://github.com/patrickvonplaten/controlnet_aux). I think the old repo isn't good enough to maintain.
+
+YOU NEED TO REMOVE `comfyui_controlnet_preprocessors` BEFORE USING THIS REPO. THESE TWO CONFLICT WITH EACH OTHER.
+
+All old workflows still can be used with custom nodes in this repo but the version option won't do anything. Almost all v1 preprocessors are replaced by v1.1 except those doesn't apppear in v1.1.
+
+You don't need to care about the differences between v1 and v1.1 lol.
+
+The code is copy-pasted from the respective folders in https://github.com/lllyasviel/ControlNet/tree/main/annotator and connected to [the 🤗 Hub](https://huggingface.co/lllyasviel/Annotators).
+
+All credit & copyright goes to https://github.com/lllyasviel.
+
+# Updates
+* `AIO Aux Preprocessor` intergrating all loadable aux preprocessors as dropdown options. Easy to copy, paste and get the preprocessor faster.
+* Added OpenPose-format JSON output from OpenPose Preprocessor and DWPose Preprocessor. Checks [here](#faces-and-poses).
+* Fixed wrong model path when downloading DWPose.
+* Make hint images less blurry.
+* Added `resolution` option, `PixelPerfectResolution` and `HintImageEnchance` nodes (TODO: Documentation).
+* Added `RAFT Optical Flow Embedder` for TemporalNet2 (TODO: Workflow example).
+* Fixed opencv's conflicts between this extension, [ReActor](https://github.com/Gourieff/comfyui-reactor-node) and Roop. Thanks `Gourieff` for [the solution](https://github.com/Fannovel16/comfyui_controlnet_aux/issues/7#issuecomment-1734319075)!
+* RAFT is removed as the code behind it doesn't match what what the original code does
+* Changed `lineart`'s display name from `Normal Lineart` to `Realistic Lineart`. This change won't affect old workflows
+* Added support for `onnxruntime` to speed-up DWPose (see the Q&A)
+* Fixed TypeError: expected size to be one of int or Tuple[int] or Tuple[int, int] or Tuple[int, int, int], but got size with types [, ]: [Issue](https://github.com/Fannovel16/comfyui_controlnet_aux/issues/2), [PR](https://github.com/Fannovel16/comfyui_controlnet_aux/pull/71))
+* Fixed ImageGenResolutionFromImage mishape (https://github.com/Fannovel16/comfyui_controlnet_aux/pull/74)
+* Fixed LeRes and MiDaS's incomatipility with MPS device
+* Fixed checking DWPose onnxruntime session multiple times: https://github.com/Fannovel16/comfyui_controlnet_aux/issues/89)
+* Added `Anime Face Segmentor` (in `ControlNet Preprocessors/Semantic Segmentation`) for [ControlNet AnimeFaceSegmentV2](https://huggingface.co/bdsqlsz/qinglong_controlnet-lllite#animefacesegmentv2). Checks [here](#anime-face-segmentor)
+* Change download functions and fix [download error](https://github.com/Fannovel16/comfyui_controlnet_aux/issues/39): [PR](https://github.com/Fannovel16/comfyui_controlnet_aux/pull/96)
+* Caching DWPose Onnxruntime during the first use of DWPose node instead of ComfyUI startup
+* Added alternative YOLOX models for faster speed when using DWPose
+* Added alternative DWPose models
+* Implemented the preprocessor for [AnimalPose ControlNet](https://github.com/abehonest/ControlNet_AnimalPose/tree/main). Check [Animal Pose AP-10K](#animal-pose-ap-10k)
+* Added YOLO-NAS models which are drop-in replacements of YOLOX
+* Fixed Openpose Face/Hands no longer detecting: https://github.com/Fannovel16/comfyui_controlnet_aux/issues/54
+* Added TorchScript implementation of DWPose and AnimalPose
+* Added TorchScript implementation of DensePose from [Colab notebook](https://colab.research.google.com/drive/16hcaaKs210ivpxjoyGNuvEXZD4eqOOSQ) which doesn't require detectron2. [Example](#densepose). Ps/r: Currently doesn't work
+# Q&A:
+## Why some nodes doesn't appear after I installed this repo?
+
+This repo has a new mechanism which will skip any custom node can't be imported. If you meet this case, please create a issue on [Issues tab](https://github.com/Fannovel16/comfyui_controlnet_aux/issues) with the log from the command line.
+
+## DWPose/AnimalPose only uses CPU so it's so slow. How can I make it use GPU?
+There are two ways to speed-up DWPose: using TorchScript checkpoints (.torchscript.pt) checkpoints or ONNXRuntime (.onnx). TorchScript way is little bit slower than ONNXRuntime but doesn't require any additional library and still way way faster than CPU.
+
+A torchscript bbox detector is compatiable with an onnx pose estimator and vice versa.
+### TorchScript
+Set `bbox_detector` and `pose_estimator` according to this picture. You can try other bbox detector endings with `.torchscript.pt` to reduce bbox detection time if input images are ideal.
+
+### ONNXRuntime
+If onnxruntime is installed successfully and the checkpoint used endings with `.onnx`, it will replace default cv2 backend to take advantage of GPU. Note that if you are using NVidia card, this method currently can only works on CUDA 11.8 (ComfyUI_windows_portable_nvidia_cu118_or_cpu.7z) unless you compile onnxruntime yourself.
+
+1. Know your onnxruntime build:
+* * NVidia/AMD GPU: `onnxruntime-gpu`
+* * DirectML: `onnxruntime-directml`
+* * OpenVINO: `onnxruntime-openvino`
+
+Note that if this is your first time using ComfyUI, please test if it can run on your device before doing next steps.
+
+2. Add it into `requirements.txt`
+
+3. Run `install.bat` or pip command mentioned in Installation
+
+
+# Installation:
+## Using ComfyUI Manager (recommended):
+Install [ComfyUI Manager](https://github.com/ltdrdata/ComfyUI-Manager) and do steps introduced there to install this repo.
+
+## Alternative:
+If you're running on Linux, or non-admin account on windows you'll want to ensure `/ComfyUI/custom_nodes` and `comfyui_controlnet_aux` has write permissions.
+
+There is now a **install.bat** you can run to install to portable if detected. Otherwise it will default to system and assume you followed ConfyUI's manual installation steps.
+
+If you can't run **install.bat** (e.g. you are a Linux user). Open the CMD/Shell and do the following:
+ - Navigate to your `/ComfyUI/custom_nodes/` folder
+ - Run `git clone https://github.com/Fannovel16/comfyui_controlnet_aux/`
+ - Navigate to your `comfyui_controlnet_aux` folder
+ - Portable/venv:
+ - Run `path/to/ComfUI/python_embeded/python.exe -s -m pip install -r requirements.txt`
+ - With system python
+ - Run `pip install -r requirements.txt`
+ - Start ComfyUI
+
+# Nodes
+Please note that this repo only supports preprocessors making hint images (e.g. stickman, canny edge, etc).
+All preprocessors except Inpaint are intergrated into `AIO Aux Preprocessor` node.
+This node allow you to quickly get the preprocessor but a preprocessor's own threshold parameters won't be able to set.
+You need to use its node directly to set thresholds.
+
+## Line Extractors
+* Binary Lines
+* Canny Edge
+* HED Lines
+* Realistic Lineart (formerly Normal Lineart)
+* Anime Lineart
+* Manga Lineart
+* M-LSD Lines
+* PiDiNet Lines
+* Scribble Lines
+* Scribble XDoG Lines
+
+## Normal and Depth Map
+* LeReS - Depth Map
+* MiDaS - Normal Map
+* MiDaS - Depth Map
+* BAE - Normal Map
+* Zoe - Depth Map
+
+## Faces and Poses
+* DWPose Pose Estimation
+* OpenPose Pose Estimation
+* MediaPipe Face Mesh
+* Animal Pose Estimation
+
+An array of [OpenPose-format JSON](https://github.com/CMU-Perceptual-Computing-Lab/openpose/blob/master/doc/02_output.md#json-output-format) corresponsding to each frame in an IMAGE batch can be gotten from DWPose and OpenPose using `app.nodeOutputs` on the UI or `/history` API endpoint. JSON output from AnimalPose uses a kinda similar format to OpenPose JSON:
+```
+[
+ {
+ "version": "ap10k",
+ "animals": [
+ [[x1, y1, 1], [x2, y2, 1],..., [x17, y17, 1]],
+ [[x1, y1, 1], [x2, y2, 1],..., [x17, y17, 1]],
+ ...
+ ],
+ "canvas_height": 512,
+ "canvas_width": 768
+ },
+ ...
+]
+```
+
+For extension developers (e.g. Openpose editor):
+```js
+const poseNodes = app.graph._nodes.filter(node => ["OpenposePreprocessor", "DWPreprocessor", "AnimalPosePreprocessor"].includes(node.type))
+for (const poseNode of poseNodes) {
+ const openposeResults = JSON.parse(app.nodeOutputs[poseNode.id].openpose_json[0])
+ console.log(openposeResults) //An array containing Openpose JSON for each frame
+}
+```
+
+For API users:
+Javascript
+```js
+import fetch from "node-fetch" //Remember to add "type": "module" to "package.json"
+async function main() {
+ const promptId = '792c1905-ecfe-41f4-8114-83e6a4a09a9f' //Too lazy to POST /queue
+ let history = await fetch(`http://127.0.0.1:8188/history/${promptId}`).then(re => re.json())
+ history = history[promptId]
+ const nodeOutputs = Object.values(history.outputs).filter(output => output.openpose_json)
+ for (const nodeOutput of nodeOutputs) {
+ const openposeResults = JSON.parse(nodeOutput.openpose_json[0])
+ console.log(openposeResults) //An array containing Openpose JSON for each frame
+ }
+}
+main()
+```
+
+Python
+```py
+import json, urllib.request
+
+server_address = "127.0.0.1:8188"
+prompt_id = '' #Too lazy to POST /queue
+
+def get_history(prompt_id):
+ with urllib.request.urlopen("http://{}/history/{}".format(server_address, prompt_id)) as response:
+ return json.loads(response.read())
+
+history = get_history(prompt_id)[prompt_id]
+for o in history['outputs']:
+ for node_id in history['outputs']:
+ node_output = history['outputs'][node_id]
+ if 'openpose_json' in node_output:
+ print(json.loads(node_output['openpose_json'][0])) #An list containing Openpose JSON for each frame
+```
+## Semantic Segmentation
+* OneFormer ADE20K Segmentor
+* UniFormer Segmentor
+* OneFormer COCO Segmentor
+
+## T2IAdapter-only
+* Color Pallete
+* Content Shuffle
+
+# Examples
+> A picture is worth a thousand words
+
+Credit to https://huggingface.co/thibaud/controlnet-sd21. You can get the same kind of results from preprocessor nodes of this repo.
+## Line Extractors
+### Canny Edge
+
+### HED Lines
+
+### Realistic Lineart
+
+### Scribble/Fake Scribble
+
+
+## Normal and Depth Map
+### Depth (idk the preprocessor they use)
+
+## Zoe - Depth Map
+
+## BAE - Normal Map
+
+
+## Faces and Poses
+### OpenPose
+
+
+
+### Animal Pose (AP-10K)
+
+
+### DensePose
+
+
+## Semantic Segmantation
+### OneFormer ADE20K Segmentor
+
+
+### Anime Face Segmentor
+
+
+## T2IAdapter-only
+### Color Pallete for T2I-Adapter
+
+
+# Testing workflow
+https://github.com/Fannovel16/comfyui_controlnet_aux/blob/master/tests/test_cn_aux_full.json
+
diff --git a/custom_nodes/comfyui_controlnet_aux/__init__.py b/custom_nodes/comfyui_controlnet_aux/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1370c96a091a76c98a1b1a7c7f87151324a652d2
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/__init__.py
@@ -0,0 +1,122 @@
+import sys, os
+from .utils import here, create_node_input_types
+from pathlib import Path
+import threading
+import traceback
+import warnings
+import importlib
+from .log import log, blue_text, cyan_text, get_summary, get_label
+from .hint_image_enchance import NODE_CLASS_MAPPINGS as HIE_NODE_CLASS_MAPPINGS
+from .hint_image_enchance import NODE_DISPLAY_NAME_MAPPINGS as HIE_NODE_DISPLAY_NAME_MAPPINGS
+#Ref: https://github.com/comfyanonymous/ComfyUI/blob/76d53c4622fc06372975ed2a43ad345935b8a551/nodes.py#L17
+sys.path.insert(0, str(Path(here, "src").resolve()))
+for pkg_name in ["controlnet_aux", "custom_mmpkg"]:
+ sys.path.append(str(Path(here, "src", pkg_name).resolve()))
+
+#Enable CPU fallback for ops not being supported by MPS like upsample_bicubic2d.out
+#https://github.com/pytorch/pytorch/issues/77764
+#https://github.com/Fannovel16/comfyui_controlnet_aux/issues/2#issuecomment-1763579485
+os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = '1'
+
+
+def load_nodes():
+ shorted_errors = []
+ full_error_messages = []
+ node_class_mappings = {}
+ node_display_name_mappings = {}
+
+ for filename in (here / "node_wrappers").iterdir():
+
+ module_name = filename.stem
+ try:
+ module = importlib.import_module(
+ f".node_wrappers.{module_name}", package=__package__
+ )
+ node_class_mappings.update(getattr(module, "NODE_CLASS_MAPPINGS"))
+ if hasattr(module, "NODE_DISPLAY_NAME_MAPPINGS"):
+ node_display_name_mappings.update(getattr(module, "NODE_DISPLAY_NAME_MAPPINGS"))
+
+ log.debug(f"Imported {module_name} nodes")
+
+ except AttributeError:
+ pass # wip nodes
+ except Exception:
+ error_message = traceback.format_exc()
+ full_error_messages.append(error_message)
+ error_message = error_message.splitlines()[-1]
+ shorted_errors.append(
+ f"Failed to import module {module_name} because {error_message}"
+ )
+
+ if len(shorted_errors) > 0:
+ full_err_log = '\n\n'.join(full_error_messages)
+ print(f"\n\nFull error log from comfyui_controlnet_aux: \n{full_err_log}\n\n")
+ log.info(
+ f"Some nodes failed to load:\n\t"
+ + "\n\t".join(shorted_errors)
+ + "\n\n"
+ + "Check that you properly installed the dependencies.\n"
+ + "If you think this is a bug, please report it on the github page (https://github.com/Fannovel16/comfyui_controlnet_aux/issues)"
+ )
+ return node_class_mappings, node_display_name_mappings
+
+AUX_NODE_MAPPINGS, AUX_DISPLAY_NAME_MAPPINGS = load_nodes()
+
+AIO_NOT_SUPPORTED = ["InpaintPreprocessor"]
+#For nodes not mapping image to image
+
+class AIO_Preprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ auxs = list(AUX_NODE_MAPPINGS.keys())
+ for name in AIO_NOT_SUPPORTED:
+ if name in auxs: auxs.remove(name)
+
+ return create_node_input_types(
+ preprocessor=(auxs, {"default": "CannyEdgePreprocessor"})
+ )
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "execute"
+
+ CATEGORY = "ControlNet Preprocessors"
+
+ def execute(self, preprocessor, image, resolution=512):
+ aux_class = AUX_NODE_MAPPINGS[preprocessor]
+ input_types = aux_class.INPUT_TYPES()
+ input_types = {
+ **input_types["required"],
+ **(input_types["optional"] if "optional" in input_types else {})
+ }
+ params = {}
+ for name, input_type in input_types.items():
+ if name == "image":
+ params[name] = image
+ continue
+
+ if name == "resolution":
+ params[name] = resolution
+ continue
+
+ if len(input_type) == 2 and ("default" in input_type[1]):
+ params[name] = input_type[1]["default"]
+ continue
+
+ default_values = { "INT": 0, "FLOAT": 0.0 }
+ if input_type[0] in default_values:
+ params[name] = default_values[input_type[0]]
+
+ return getattr(aux_class(), aux_class.FUNCTION)(**params)
+
+
+NODE_CLASS_MAPPINGS = {
+ **AUX_NODE_MAPPINGS,
+ "AIO_Preprocessor": AIO_Preprocessor,
+ **HIE_NODE_CLASS_MAPPINGS
+}
+
+NODE_DISPLAY_NAME_MAPPINGS = {
+ **AUX_DISPLAY_NAME_MAPPINGS,
+ "AIO_Preprocessor": "AIO Aux Preprocessor",
+ **HIE_NODE_DISPLAY_NAME_MAPPINGS
+}
diff --git a/custom_nodes/comfyui_controlnet_aux/__pycache__/__init__.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0ea7e440a0a68ad070609ff4dd72a66ab02cdb46
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/__pycache__/__init__.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/__pycache__/__init__.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5eee383ec9550409263dcc945d4451302406d6f3
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/__pycache__/__init__.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/__pycache__/hint_image_enchance.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/__pycache__/hint_image_enchance.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..6ccaba1a1e64889975c8e2d17857526ffdd0345b
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/__pycache__/hint_image_enchance.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/__pycache__/hint_image_enchance.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/__pycache__/hint_image_enchance.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..9051533c941cd9d35ffd124ff6d8ccc5efa13a31
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/__pycache__/hint_image_enchance.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/__pycache__/log.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/__pycache__/log.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..53df5f6ab4eee3ead8b0da00a30d56ab2358b4c1
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/__pycache__/log.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/__pycache__/log.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/__pycache__/log.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..26abfc3f79d349593215c173d1a98ecca365c3bf
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/__pycache__/log.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/__pycache__/lvminthin.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/__pycache__/lvminthin.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..92d9e62689ef290fdd78b6f2c0f9ca3861dafb4a
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/__pycache__/lvminthin.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/__pycache__/lvminthin.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/__pycache__/lvminthin.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d79b17e4c03efb5bd3c9f22a21797e52d6db1196
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/__pycache__/lvminthin.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/__pycache__/utils.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/__pycache__/utils.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..3770041f709845e2ec75ecf7490e3deadd4a1c2b
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/__pycache__/utils.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/__pycache__/utils.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/__pycache__/utils.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f9f49f5893696dee3c65d172ceb7e6dd114f7359
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/__pycache__/utils.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/ckpts/lllyasviel/Annotators/ControlNetHED.pth b/custom_nodes/comfyui_controlnet_aux/ckpts/lllyasviel/Annotators/ControlNetHED.pth
new file mode 100644
index 0000000000000000000000000000000000000000..e0edbff99b09b7241441fb1f9f25187e0f1ff5c9
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/ckpts/lllyasviel/Annotators/ControlNetHED.pth
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5ca93762ffd68a29fee1af9d495bf6aab80ae86f08905fb35472a083a4c7a8fa
+size 29444406
diff --git a/custom_nodes/comfyui_controlnet_aux/ckpts/lllyasviel/Annotators/body_pose_model.pth b/custom_nodes/comfyui_controlnet_aux/ckpts/lllyasviel/Annotators/body_pose_model.pth
new file mode 100644
index 0000000000000000000000000000000000000000..9acb77e68f31906a8875f1daef2f3f7ef94acb1e
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/ckpts/lllyasviel/Annotators/body_pose_model.pth
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:25a948c16078b0f08e236bda51a385d855ef4c153598947c28c0d47ed94bb746
+size 209267595
diff --git a/custom_nodes/comfyui_controlnet_aux/ckpts/lllyasviel/Annotators/facenet.pth b/custom_nodes/comfyui_controlnet_aux/ckpts/lllyasviel/Annotators/facenet.pth
new file mode 100644
index 0000000000000000000000000000000000000000..ccfac27ffec2f25eb02dad5f52512872eb3b53e1
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/ckpts/lllyasviel/Annotators/facenet.pth
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8beb52e548624ffcc4aed12af7aee7dcbfaeea420c75609fee999fe7add79d43
+size 153718792
diff --git a/custom_nodes/comfyui_controlnet_aux/ckpts/lllyasviel/Annotators/hand_pose_model.pth b/custom_nodes/comfyui_controlnet_aux/ckpts/lllyasviel/Annotators/hand_pose_model.pth
new file mode 100644
index 0000000000000000000000000000000000000000..f23ccf3413cc8ac8581a82338a3037bc10d573f0
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/ckpts/lllyasviel/Annotators/hand_pose_model.pth
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b76b00d1750901abd07b9f9d8c98cc3385b8fe834a26d4b4f0aad439e75fc600
+size 147341049
diff --git a/custom_nodes/comfyui_controlnet_aux/config.example.yaml b/custom_nodes/comfyui_controlnet_aux/config.example.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..5128a63c040e1d85aac22c57153860727761ff1e
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/config.example.yaml
@@ -0,0 +1,15 @@
+# this is an example for config.yaml file, you can rename it to config.yaml if you want to use it
+# ###############################################################################################
+# you can also use absolute paths like: "/root/ComfyUI/custom_nodes/comfyui_controlnet_aux/ckpts" or "D:\\comfyui\\custom_nodes\\comfyui_controlnet_aux\\ckpts"
+annotator_ckpts_path: "./ckpts"
+# ###############################################################################################
+# if you already have downloaded ckpts via huggingface hub into default cache path like: ~/.cache/huggingface/hub, you can set this True to use symlinks to save space
+USE_SYMLINKS: False
+# ###############################################################################################
+# EP_list is a list of execution providers for onnxruntime, if one of them is not available or not working well, you can delete that provider from here(config.yaml)
+# you can find all available providers here: https://onnxruntime.ai/docs/execution-providers
+# for example, if you have CUDA installed, you can set it to: ["CUDAExecutionProvider", "CPUExecutionProvider"]
+# empty list or only keep ["CPUExecutionProvider"] means you use cv2.dnn.readNetFromONNX to load onnx models
+# if your onnx models can only run on the CPU or have other issues, we recommend using pt model instead.
+# default value is ["CUDAExecutionProvider", "DirectMLExecutionProvider", "OpenVINOExecutionProvider", "ROCMExecutionProvider", "CPUExecutionProvider"]
+EP_list: ["CUDAExecutionProvider", "DirectMLExecutionProvider", "OpenVINOExecutionProvider", "ROCMExecutionProvider", "CPUExecutionProvider"]
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/dev_interface.py b/custom_nodes/comfyui_controlnet_aux/dev_interface.py
new file mode 100644
index 0000000000000000000000000000000000000000..77413c867c0df9c0c1397061ac837b39bf98098d
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/dev_interface.py
@@ -0,0 +1,6 @@
+from pathlib import Path
+from utils import here
+import sys
+sys.path.append(str(Path(here, "src")))
+
+from controlnet_aux import *
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/example_animal_pose.png b/custom_nodes/comfyui_controlnet_aux/example_animal_pose.png
new file mode 100644
index 0000000000000000000000000000000000000000..11443aff62ef27bfe924be12c7b23a666fa00ff4
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/example_animal_pose.png differ
diff --git a/custom_nodes/comfyui_controlnet_aux/example_anime_face_segmentor.png b/custom_nodes/comfyui_controlnet_aux/example_anime_face_segmentor.png
new file mode 100644
index 0000000000000000000000000000000000000000..047d07b2fc65a1a10d72d489d8bd73fb586403a4
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/example_anime_face_segmentor.png differ
diff --git a/custom_nodes/comfyui_controlnet_aux/example_densepose.png b/custom_nodes/comfyui_controlnet_aux/example_densepose.png
new file mode 100644
index 0000000000000000000000000000000000000000..1d971ea70922e273d2d19c669a43319cf6ef2e9c
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/example_densepose.png differ
diff --git a/custom_nodes/comfyui_controlnet_aux/example_onnx.png b/custom_nodes/comfyui_controlnet_aux/example_onnx.png
new file mode 100644
index 0000000000000000000000000000000000000000..f3f9ad5a45e2ce33b03883446b55fb487c059a00
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/example_onnx.png differ
diff --git a/custom_nodes/comfyui_controlnet_aux/example_torchscript.png b/custom_nodes/comfyui_controlnet_aux/example_torchscript.png
new file mode 100644
index 0000000000000000000000000000000000000000..0a685f9cea265c5bc2057f567da0d93614f8ce9a
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/example_torchscript.png differ
diff --git a/custom_nodes/comfyui_controlnet_aux/hint_image_enchance.py b/custom_nodes/comfyui_controlnet_aux/hint_image_enchance.py
new file mode 100644
index 0000000000000000000000000000000000000000..cb2a06974d2e23e53218706248d6340ed99de61a
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/hint_image_enchance.py
@@ -0,0 +1,233 @@
+from .log import log
+from .utils import ResizeMode, safe_numpy
+import numpy as np
+import torch
+import cv2
+from .utils import get_unique_axis0
+from .lvminthin import nake_nms, lvmin_thin
+
+MAX_IMAGEGEN_RESOLUTION = 8192 #https://github.com/comfyanonymous/ComfyUI/blob/c910b4a01ca58b04e5d4ab4c747680b996ada02b/nodes.py#L42
+RESIZE_MODES = [ResizeMode.RESIZE.value, ResizeMode.INNER_FIT.value, ResizeMode.OUTER_FIT.value]
+
+#Port from https://github.com/Mikubill/sd-webui-controlnet/blob/e67e017731aad05796b9615dc6eadce911298ea1/internal_controlnet/external_code.py#L89
+class PixelPerfectResolution:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "original_image": ("IMAGE", ),
+ "image_gen_width": ("INT", {"default": 512, "min": 64, "max": MAX_IMAGEGEN_RESOLUTION, "step": 8}),
+ "image_gen_height": ("INT", {"default": 512, "min": 64, "max": MAX_IMAGEGEN_RESOLUTION, "step": 8}),
+ #https://github.com/comfyanonymous/ComfyUI/blob/c910b4a01ca58b04e5d4ab4c747680b996ada02b/nodes.py#L854
+ "resize_mode": (RESIZE_MODES, {"default": ResizeMode.RESIZE.value})
+ }
+ }
+
+ RETURN_TYPES = ("INT",)
+ RETURN_NAMES = ("RESOLUTION (INT)", )
+ FUNCTION = "execute"
+
+ CATEGORY = "ControlNet Preprocessors"
+
+ def execute(self, original_image, image_gen_width, image_gen_height, resize_mode):
+ _, raw_H, raw_W, _ = original_image.shape
+
+ k0 = float(image_gen_height) / float(raw_H)
+ k1 = float(image_gen_width) / float(raw_W)
+
+ if resize_mode == ResizeMode.OUTER_FIT.value:
+ estimation = min(k0, k1) * float(min(raw_H, raw_W))
+ else:
+ estimation = max(k0, k1) * float(min(raw_H, raw_W))
+
+ log.debug(f"Pixel Perfect Computation:")
+ log.debug(f"resize_mode = {resize_mode}")
+ log.debug(f"raw_H = {raw_H}")
+ log.debug(f"raw_W = {raw_W}")
+ log.debug(f"target_H = {image_gen_height}")
+ log.debug(f"target_W = {image_gen_width}")
+ log.debug(f"estimation = {estimation}")
+
+ return (int(np.round(estimation)), )
+
+class HintImageEnchance:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "hint_image": ("IMAGE", ),
+ "image_gen_width": ("INT", {"default": 512, "min": 64, "max": MAX_IMAGEGEN_RESOLUTION, "step": 8}),
+ "image_gen_height": ("INT", {"default": 512, "min": 64, "max": MAX_IMAGEGEN_RESOLUTION, "step": 8}),
+ #https://github.com/comfyanonymous/ComfyUI/blob/c910b4a01ca58b04e5d4ab4c747680b996ada02b/nodes.py#L854
+ "resize_mode": (RESIZE_MODES, {"default": ResizeMode.RESIZE.value})
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "execute"
+
+ CATEGORY = "ControlNet Preprocessors"
+ def execute(self, hint_image, image_gen_width, image_gen_height, resize_mode):
+ outs = []
+ for single_hint_image in hint_image:
+ np_hint_image = np.asarray(single_hint_image * 255., dtype=np.uint8)
+
+ if resize_mode == ResizeMode.RESIZE.value:
+ np_hint_image = self.execute_resize(np_hint_image, image_gen_width, image_gen_height)
+ elif resize_mode == ResizeMode.OUTER_FIT.value:
+ np_hint_image = self.execute_outer_fit(np_hint_image, image_gen_width, image_gen_height)
+ else:
+ np_hint_image = self.execute_inner_fit(np_hint_image, image_gen_width, image_gen_height)
+
+ outs.append(torch.from_numpy(np_hint_image.astype(np.float32) / 255.0))
+
+ return (torch.stack(outs, dim=0),)
+
+ def execute_resize(self, detected_map, w, h):
+ detected_map = self.high_quality_resize(detected_map, (w, h))
+ detected_map = safe_numpy(detected_map)
+ return detected_map
+
+ def execute_outer_fit(self, detected_map, w, h):
+ old_h, old_w, _ = detected_map.shape
+ old_w = float(old_w)
+ old_h = float(old_h)
+ k0 = float(h) / old_h
+ k1 = float(w) / old_w
+ safeint = lambda x: int(np.round(x))
+ k = min(k0, k1)
+
+ borders = np.concatenate([detected_map[0, :, :], detected_map[-1, :, :], detected_map[:, 0, :], detected_map[:, -1, :]], axis=0)
+ high_quality_border_color = np.median(borders, axis=0).astype(detected_map.dtype)
+ if len(high_quality_border_color) == 4:
+ # Inpaint hijack
+ high_quality_border_color[3] = 255
+ high_quality_background = np.tile(high_quality_border_color[None, None], [h, w, 1])
+ detected_map = self.high_quality_resize(detected_map, (safeint(old_w * k), safeint(old_h * k)))
+ new_h, new_w, _ = detected_map.shape
+ pad_h = max(0, (h - new_h) // 2)
+ pad_w = max(0, (w - new_w) // 2)
+ high_quality_background[pad_h:pad_h + new_h, pad_w:pad_w + new_w] = detected_map
+ detected_map = high_quality_background
+ detected_map = safe_numpy(detected_map)
+ return detected_map
+
+ def execute_inner_fit(self, detected_map, w, h):
+ old_h, old_w, _ = detected_map.shape
+ old_w = float(old_w)
+ old_h = float(old_h)
+ k0 = float(h) / old_h
+ k1 = float(w) / old_w
+ safeint = lambda x: int(np.round(x))
+ k = max(k0, k1)
+
+ detected_map = self.high_quality_resize(detected_map, (safeint(old_w * k), safeint(old_h * k)))
+ new_h, new_w, _ = detected_map.shape
+ pad_h = max(0, (new_h - h) // 2)
+ pad_w = max(0, (new_w - w) // 2)
+ detected_map = detected_map[pad_h:pad_h+h, pad_w:pad_w+w]
+ detected_map = safe_numpy(detected_map)
+ return detected_map
+
+ def high_quality_resize(self, x, size):
+ # Written by lvmin
+ # Super high-quality control map up-scaling, considering binary, seg, and one-pixel edges
+
+ inpaint_mask = None
+ if x.ndim == 3 and x.shape[2] == 4:
+ inpaint_mask = x[:, :, 3]
+ x = x[:, :, 0:3]
+
+ if x.shape[0] != size[1] or x.shape[1] != size[0]:
+ new_size_is_smaller = (size[0] * size[1]) < (x.shape[0] * x.shape[1])
+ new_size_is_bigger = (size[0] * size[1]) > (x.shape[0] * x.shape[1])
+ unique_color_count = len(get_unique_axis0(x.reshape(-1, x.shape[2])))
+ is_one_pixel_edge = False
+ is_binary = False
+ if unique_color_count == 2:
+ is_binary = np.min(x) < 16 and np.max(x) > 240
+ if is_binary:
+ xc = x
+ xc = cv2.erode(xc, np.ones(shape=(3, 3), dtype=np.uint8), iterations=1)
+ xc = cv2.dilate(xc, np.ones(shape=(3, 3), dtype=np.uint8), iterations=1)
+ one_pixel_edge_count = np.where(xc < x)[0].shape[0]
+ all_edge_count = np.where(x > 127)[0].shape[0]
+ is_one_pixel_edge = one_pixel_edge_count * 2 > all_edge_count
+
+ if 2 < unique_color_count < 200:
+ interpolation = cv2.INTER_NEAREST
+ elif new_size_is_smaller:
+ interpolation = cv2.INTER_AREA
+ else:
+ interpolation = cv2.INTER_CUBIC # Must be CUBIC because we now use nms. NEVER CHANGE THIS
+
+ y = cv2.resize(x, size, interpolation=interpolation)
+ if inpaint_mask is not None:
+ inpaint_mask = cv2.resize(inpaint_mask, size, interpolation=interpolation)
+
+ if is_binary:
+ y = np.mean(y.astype(np.float32), axis=2).clip(0, 255).astype(np.uint8)
+ if is_one_pixel_edge:
+ y = nake_nms(y)
+ _, y = cv2.threshold(y, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
+ y = lvmin_thin(y, prunings=new_size_is_bigger)
+ else:
+ _, y = cv2.threshold(y, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
+ y = np.stack([y] * 3, axis=2)
+ else:
+ y = x
+
+ if inpaint_mask is not None:
+ inpaint_mask = (inpaint_mask > 127).astype(np.float32) * 255.0
+ inpaint_mask = inpaint_mask[:, :, None].clip(0, 255).astype(np.uint8)
+ y = np.concatenate([y, inpaint_mask], axis=2)
+
+ return y
+
+
+class ImageGenResolutionFromLatent:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": { "latent": ("LATENT", ) }
+ }
+
+ RETURN_TYPES = ("INT", "INT")
+ RETURN_NAMES = ("IMAGE_GEN_WIDTH (INT)", "IMAGE_GEN_HEIGHT (INT)")
+ FUNCTION = "execute"
+
+ CATEGORY = "ControlNet Preprocessors"
+
+ def execute(self, latent):
+ _, _, H, W = latent["samples"].shape
+ return (W * 8, H * 8)
+
+class ImageGenResolutionFromImage:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": { "image": ("IMAGE", ) }
+ }
+
+ RETURN_TYPES = ("INT", "INT")
+ RETURN_NAMES = ("IMAGE_GEN_WIDTH (INT)", "IMAGE_GEN_HEIGHT (INT)")
+ FUNCTION = "execute"
+
+ CATEGORY = "ControlNet Preprocessors"
+
+ def execute(self, image):
+ _, H, W, _ = image.shape
+ return (W, H)
+
+NODE_CLASS_MAPPINGS = {
+ "PixelPerfectResolution": PixelPerfectResolution,
+ "ImageGenResolutionFromImage": ImageGenResolutionFromImage,
+ "ImageGenResolutionFromLatent": ImageGenResolutionFromLatent,
+ "HintImageEnchance": HintImageEnchance
+}
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "PixelPerfectResolution": "Pixel Perfect Resolution",
+ "ImageGenResolutionFromImage": "Generation Resolution From Image",
+ "ImageGenResolutionFromLatent": "Generation Resolution From Latent",
+ "HintImageEnchance": "Enchance And Resize Hint Images"
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/install.bat b/custom_nodes/comfyui_controlnet_aux/install.bat
new file mode 100644
index 0000000000000000000000000000000000000000..c36a67448534a5febc2a83d4ebef4cfa49fa6deb
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/install.bat
@@ -0,0 +1,20 @@
+@echo off
+
+set "requirements_txt=%~dp0\requirements.txt"
+set "python_exec=..\..\..\python_embeded\python.exe"
+
+echo Installing ComfyUI's ControlNet Auxiliary Preprocessors..
+
+if exist "%python_exec%" (
+ echo Installing with ComfyUI Portable
+ for /f "delims=" %%i in (%requirements_txt%) do (
+ %python_exec% -s -m pip install "%%i"
+ )
+) else (
+ echo Installing with system Python
+ for /f "delims=" %%i in (%requirements_txt%) do (
+ pip install "%%i"
+ )
+)
+
+pause
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/log.py b/custom_nodes/comfyui_controlnet_aux/log.py
new file mode 100644
index 0000000000000000000000000000000000000000..2978c6dc770c78feddc2d0dece7c0b6a91ed23f0
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/log.py
@@ -0,0 +1,80 @@
+#Cre: https://github.com/melMass/comfy_mtb/blob/main/log.py
+import logging
+import re
+import os
+
+base_log_level = logging.INFO
+
+
+# Custom object that discards the output
+class NullWriter:
+ def write(self, text):
+ pass
+
+
+class Formatter(logging.Formatter):
+ grey = "\x1b[38;20m"
+ cyan = "\x1b[36;20m"
+ purple = "\x1b[35;20m"
+ yellow = "\x1b[33;20m"
+ red = "\x1b[31;20m"
+ bold_red = "\x1b[31;1m"
+ reset = "\x1b[0m"
+ # format = "%(asctime)s - [%(name)s] - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)"
+ format = "[%(name)s] | %(levelname)s -> %(message)s"
+
+ FORMATS = {
+ logging.DEBUG: purple + format + reset,
+ logging.INFO: cyan + format + reset,
+ logging.WARNING: yellow + format + reset,
+ logging.ERROR: red + format + reset,
+ logging.CRITICAL: bold_red + format + reset,
+ }
+
+ def format(self, record):
+ log_fmt = self.FORMATS.get(record.levelno)
+ formatter = logging.Formatter(log_fmt)
+ return formatter.format(record)
+
+
+def mklog(name, level=base_log_level):
+ logger = logging.getLogger(name)
+ logger.setLevel(level)
+
+ for handler in logger.handlers:
+ logger.removeHandler(handler)
+
+ ch = logging.StreamHandler()
+ ch.setLevel(level)
+ ch.setFormatter(Formatter())
+ logger.addHandler(ch)
+
+ # Disable log propagation
+ logger.propagate = False
+
+ return logger
+
+
+# - The main app logger
+log = mklog(__package__, base_log_level)
+
+
+def log_user(arg):
+ print("\033[34mComfyUI ControlNet AUX:\033[0m {arg}")
+
+
+def get_summary(docstring):
+ return docstring.strip().split("\n\n", 1)[0]
+
+
+def blue_text(text):
+ return f"\033[94m{text}\033[0m"
+
+
+def cyan_text(text):
+ return f"\033[96m{text}\033[0m"
+
+
+def get_label(label):
+ words = re.findall(r"(?:^|[A-Z])[a-z]*", label)
+ return " ".join(words).strip()
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/lvminthin.py b/custom_nodes/comfyui_controlnet_aux/lvminthin.py
new file mode 100644
index 0000000000000000000000000000000000000000..eebe0fb6d4967d0f6c38c0117ce4775d46d0be08
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/lvminthin.py
@@ -0,0 +1,87 @@
+# High Quality Edge Thinning using Pure Python
+# Written by Lvmin Zhang
+# 2023 April
+# Stanford University
+# If you use this, please Cite "High Quality Edge Thinning using Pure Python", Lvmin Zhang, In Mikubill/sd-webui-controlnet.
+
+
+import cv2
+import numpy as np
+
+
+lvmin_kernels_raw = [
+ np.array([
+ [-1, -1, -1],
+ [0, 1, 0],
+ [1, 1, 1]
+ ], dtype=np.int32),
+ np.array([
+ [0, -1, -1],
+ [1, 1, -1],
+ [0, 1, 0]
+ ], dtype=np.int32)
+]
+
+lvmin_kernels = []
+lvmin_kernels += [np.rot90(x, k=0, axes=(0, 1)) for x in lvmin_kernels_raw]
+lvmin_kernels += [np.rot90(x, k=1, axes=(0, 1)) for x in lvmin_kernels_raw]
+lvmin_kernels += [np.rot90(x, k=2, axes=(0, 1)) for x in lvmin_kernels_raw]
+lvmin_kernels += [np.rot90(x, k=3, axes=(0, 1)) for x in lvmin_kernels_raw]
+
+lvmin_prunings_raw = [
+ np.array([
+ [-1, -1, -1],
+ [-1, 1, -1],
+ [0, 0, -1]
+ ], dtype=np.int32),
+ np.array([
+ [-1, -1, -1],
+ [-1, 1, -1],
+ [-1, 0, 0]
+ ], dtype=np.int32)
+]
+
+lvmin_prunings = []
+lvmin_prunings += [np.rot90(x, k=0, axes=(0, 1)) for x in lvmin_prunings_raw]
+lvmin_prunings += [np.rot90(x, k=1, axes=(0, 1)) for x in lvmin_prunings_raw]
+lvmin_prunings += [np.rot90(x, k=2, axes=(0, 1)) for x in lvmin_prunings_raw]
+lvmin_prunings += [np.rot90(x, k=3, axes=(0, 1)) for x in lvmin_prunings_raw]
+
+
+def remove_pattern(x, kernel):
+ objects = cv2.morphologyEx(x, cv2.MORPH_HITMISS, kernel)
+ objects = np.where(objects > 127)
+ x[objects] = 0
+ return x, objects[0].shape[0] > 0
+
+
+def thin_one_time(x, kernels):
+ y = x
+ is_done = True
+ for k in kernels:
+ y, has_update = remove_pattern(y, k)
+ if has_update:
+ is_done = False
+ return y, is_done
+
+
+def lvmin_thin(x, prunings=True):
+ y = x
+ for i in range(32):
+ y, is_done = thin_one_time(y, lvmin_kernels)
+ if is_done:
+ break
+ if prunings:
+ y, _ = thin_one_time(y, lvmin_prunings)
+ return y
+
+
+def nake_nms(x):
+ f1 = np.array([[0, 0, 0], [1, 1, 1], [0, 0, 0]], dtype=np.uint8)
+ f2 = np.array([[0, 1, 0], [0, 1, 0], [0, 1, 0]], dtype=np.uint8)
+ f3 = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype=np.uint8)
+ f4 = np.array([[0, 0, 1], [0, 1, 0], [1, 0, 0]], dtype=np.uint8)
+ y = np.zeros_like(x)
+ for f in [f1, f2, f3, f4]:
+ np.putmask(y, cv2.dilate(x, kernel=f) == x, x)
+ return y
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/anime_face_segment.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/anime_face_segment.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..01a2f8a05c499aa8cdc08e146fdcb6b30704d2fb
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/anime_face_segment.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/anime_face_segment.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/anime_face_segment.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2f4baae6f92594f3b1dcaec5f2e363ed0a9a062e
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/anime_face_segment.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/binary.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/binary.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..54420ba6ec37f1dc7277329db29409bace4cb2dd
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/binary.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/binary.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/binary.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..6a99c48ad5d3ae76547902c30fafa7eca8cd55e2
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/binary.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/canny.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/canny.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5289a3645b6ffcc9fe63f9d394abde211b04613e
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/canny.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/canny.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/canny.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1d71a78eac3c94631fd01b0a4f2b5e3444587c9e
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/canny.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/color.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/color.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..df29b847473308652670ce64129d6cf22ae9125b
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/color.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/color.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/color.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4f072cfce91d1d557dc825ef606ebb03cba38715
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/color.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/densepose.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/densepose.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c80ac7d5129bf89f939917d6af477d6f0138b857
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/densepose.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/dwpose.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/dwpose.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..deef9aef48512dedd335ee665b9e580ede9a8981
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/dwpose.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/dwpose.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/dwpose.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..cafbd8ed8f4a5253920f7320042c4b0645cb8645
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/dwpose.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/hed.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/hed.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ab454d42454d929e02ecd91324f6555177934c95
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/hed.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/hed.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/hed.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..90638a5cc29acf3a6be22b6d87a3118c5e6cbd42
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/hed.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/inpaint.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/inpaint.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a23c4d3a08f2734d683087474c43f89e46ad9e0c
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/inpaint.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/inpaint.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/inpaint.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..aa5788495bf062aa4eb241603369892fce283513
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/inpaint.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/leres.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/leres.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..9a1c39de990a2130638c8223276b2e9eabe198e2
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/leres.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/leres.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/leres.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..50b42076e6bfdb49274e8f90f606d8a3e95a6be5
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/leres.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/lineart.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/lineart.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..223ef432f53ac7922d41979211de34703fc6ca9d
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/lineart.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/lineart.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/lineart.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..99049bba3fab2fa7dbdfa38771d1cc8d5646c2db
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/lineart.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/lineart_anime.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/lineart_anime.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..14b9bd170e459ab533177ac01723f9f40c04a3db
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/lineart_anime.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/lineart_anime.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/lineart_anime.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..eda64ed324319c2f940605d9f297e1c0c65aa218
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/lineart_anime.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/manga_line.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/manga_line.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ecf9a42e79330d705b9d01edcd3806f37585147f
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/manga_line.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/manga_line.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/manga_line.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..6ddd3dfa456a21e9ddcb3f666effdb5235e1305b
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/manga_line.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/mediapipe_face.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/mediapipe_face.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..301da8758070d3e83a4172af95bc6f3ed04e6711
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/mediapipe_face.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/mediapipe_face.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/mediapipe_face.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..cc21692ab5995f3cae9240b955e42370fd7b988f
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/mediapipe_face.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/midas.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/midas.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..fd8688573204881a1637e4ced26f8e4b8dafe81a
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/midas.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/midas.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/midas.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c08d00bfaeada986d42918af63ad43e0a3f494b2
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/midas.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/mlsd.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/mlsd.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..fb54b5e9701688f59e9afba445f5dc774811ecfd
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/mlsd.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/mlsd.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/mlsd.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..cc824cc66064a1e471944272c563c60d5eb3b907
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/mlsd.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/normalbae.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/normalbae.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4dd0aad0c19d7dcfffa7ce42db076e8985073dc8
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/normalbae.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/normalbae.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/normalbae.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0bb3091389594567212c5a0991fc34269ff68219
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/normalbae.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/oneformer.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/oneformer.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7a56dcf5b2121d59f9d5f3e63a6ef789b76af12c
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/oneformer.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/oneformer.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/oneformer.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..db06aacc72f194d2deaba5de2352f553f574c2ea
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/oneformer.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/openpose.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/openpose.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8adf9bd72a228225100e7808d28070634d07d640
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/openpose.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/openpose.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/openpose.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8f82ab1da06d2306bc449a55fc8c4bf88d7f284f
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/openpose.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/pidinet.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/pidinet.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d62c71fd9cdb0feb1037075ce95907a3b362631a
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/pidinet.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/pidinet.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/pidinet.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e60203717e3e6d45666ef015d37f14f14f452ca6
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/pidinet.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/scribble.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/scribble.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..81bd2a2d222410526e857a30d9d3f438200274c8
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/scribble.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/scribble.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/scribble.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..9b6250c03fd2702c08b4767cc9ea8309edea6aa0
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/scribble.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/segment_anything.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/segment_anything.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7046548afd24723174171855e7f4da90861180db
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/segment_anything.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/segment_anything.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/segment_anything.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1fb769416741d58c8be7d63f2558354aaa517cf9
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/segment_anything.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/shuffle.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/shuffle.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b7594789d80c9e9a30f6d5c4104af96e466d293b
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/shuffle.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/shuffle.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/shuffle.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..49d5b723b89b2725095ef07bfe6d2f3a89f12791
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/shuffle.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/tile.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/tile.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2b7e9494a076e8d78522f1bbbfad625afaab48cf
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/tile.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/tile.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/tile.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8aec8c3d703e8aea2c00fc12f8cbaccb46634ba9
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/tile.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/uniformer.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/uniformer.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ee3fd574af82ba65338667ee32de2bb20cbf7743
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/uniformer.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/uniformer.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/uniformer.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ff01b13d97ebaff60af7482cc48f21ab9c6c95a3
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/uniformer.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/zoe.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/zoe.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..769c67e733545eced1f2dc13ecb035435994f488
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/zoe.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/zoe.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/zoe.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0bcb71fcd36da38ac62927e9ada40a6e00e8fb89
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/node_wrappers/__pycache__/zoe.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/anime_face_segment.py b/custom_nodes/comfyui_controlnet_aux/node_wrappers/anime_face_segment.py
new file mode 100644
index 0000000000000000000000000000000000000000..82056794d2440d08f9fcf2c131f9d0b58eb93293
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/node_wrappers/anime_face_segment.py
@@ -0,0 +1,48 @@
+from ..utils import common_annotator_call, annotator_ckpts_path, ANIFACESEG_MODEL_NAME, create_node_input_types
+import comfy.model_management as model_management
+import torch
+from einops import rearrange
+
+class AnimeFace_SemSegPreprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "image": ("IMAGE",)
+ },
+ "optional": {
+ #This preprocessor is only trained on 512x resolution
+ #https://github.com/siyeong0/Anime-Face-Segmentation/blob/main/predict.py#L25
+ "remove_background_using_abg": ("BOOLEAN", {"default": True}),
+ "resolution": ("INT", {"default": 512, "min": 512, "max": 512, "step": 64})
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "MASK")
+ RETURN_NAMES = ("IMAGE", "ABG_CHARACTER_MASK (MASK)")
+ FUNCTION = "execute"
+
+ CATEGORY = "ControlNet Preprocessors/Semantic Segmentation"
+
+ def execute(self, image, remove_background_using_abg=True, resolution=512, **kwargs):
+ from controlnet_aux.anime_face_segment import AnimeFaceSegmentor
+
+ model = AnimeFaceSegmentor.from_pretrained(ANIFACESEG_MODEL_NAME, cache_dir=annotator_ckpts_path).to(model_management.get_torch_device())
+ if remove_background_using_abg:
+ out_image_with_mask = common_annotator_call(model, image, resolution=resolution, remove_background=True)
+ out_image = out_image_with_mask[..., :3]
+ mask = out_image_with_mask[..., 3:]
+ mask = rearrange(mask, "n h w c -> n c h w")
+ else:
+ out_image = common_annotator_call(model, image, resolution=resolution, remove_background=False)
+ N, H, W, C = out_image.shape
+ mask = torch.ones(N, C, H, W)
+ del model
+ return (out_image, mask)
+
+NODE_CLASS_MAPPINGS = {
+ "AnimeFace_SemSegPreprocessor": AnimeFace_SemSegPreprocessor
+}
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "AnimeFace_SemSegPreprocessor": "Anime Face Segmentor"
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/binary.py b/custom_nodes/comfyui_controlnet_aux/node_wrappers/binary.py
new file mode 100644
index 0000000000000000000000000000000000000000..9787081d802f6621c901c894dcfd2f93c04cebd7
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/node_wrappers/binary.py
@@ -0,0 +1,28 @@
+from ..utils import common_annotator_call, annotator_ckpts_path, HF_MODEL_NAME, create_node_input_types
+import comfy.model_management as model_management
+
+class Binary_Preprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types(
+ bin_threshold=("INT", {"default": 100, "min": 0, "max": 255, "step": 1})
+ )
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "execute"
+
+ CATEGORY = "ControlNet Preprocessors/Line Extractors"
+
+ def execute(self, image, bin_threshold, resolution=512, **kwargs):
+ from controlnet_aux.binary import BinaryDetector
+
+ return (common_annotator_call(BinaryDetector(), image, bin_threshold=bin_threshold, resolution=resolution), )
+
+
+
+NODE_CLASS_MAPPINGS = {
+ "BinaryPreprocessor": Binary_Preprocessor
+}
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "BinaryPreprocessor": "Binary Lines"
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/canny.py b/custom_nodes/comfyui_controlnet_aux/node_wrappers/canny.py
new file mode 100644
index 0000000000000000000000000000000000000000..d76c723402dc1a200a47bdc84e396c1316e11252
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/node_wrappers/canny.py
@@ -0,0 +1,29 @@
+from ..utils import common_annotator_call, annotator_ckpts_path, HF_MODEL_NAME, create_node_input_types
+import comfy.model_management as model_management
+
+class Canny_Edge_Preprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types(
+ low_threshold=("INT", {"default": 100, "min": 0, "max": 255, "step": 1}),
+ high_threshold=("INT", {"default": 200, "min": 0, "max": 255, "step": 1})
+ )
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "execute"
+
+ CATEGORY = "ControlNet Preprocessors/Line Extractors"
+
+ def execute(self, image, low_threshold, high_threshold, resolution=512, **kwargs):
+ from controlnet_aux.canny import CannyDetector
+
+ return (common_annotator_call(CannyDetector(), image, low_threshold=low_threshold, high_threshold=high_threshold, resolution=resolution), )
+
+
+
+NODE_CLASS_MAPPINGS = {
+ "CannyEdgePreprocessor": Canny_Edge_Preprocessor
+}
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "CannyEdgePreprocessor": "Canny Edge"
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/color.py b/custom_nodes/comfyui_controlnet_aux/node_wrappers/color.py
new file mode 100644
index 0000000000000000000000000000000000000000..41aee91a1f6a9e5c6268bb8237b6ea595e8e0270
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/node_wrappers/color.py
@@ -0,0 +1,26 @@
+from ..utils import common_annotator_call, annotator_ckpts_path, HF_MODEL_NAME, create_node_input_types
+import comfy.model_management as model_management
+
+class Color_Preprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types()
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "execute"
+
+ CATEGORY = "ControlNet Preprocessors/T2IAdapter-only"
+
+ def execute(self, image, resolution=512, **kwargs):
+ from controlnet_aux.color import ColorDetector
+
+ return (common_annotator_call(ColorDetector(), image, resolution=resolution), )
+
+
+
+NODE_CLASS_MAPPINGS = {
+ "ColorPreprocessor": Color_Preprocessor
+}
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "ColorPreprocessor": "Color Pallete"
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/densepose.py b/custom_nodes/comfyui_controlnet_aux/node_wrappers/densepose.py
new file mode 100644
index 0000000000000000000000000000000000000000..b0b6bd153d604d05635cc38d0e823fd55d99c4cd
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/node_wrappers/densepose.py
@@ -0,0 +1,31 @@
+from ..utils import common_annotator_call, annotator_ckpts_path, HF_MODEL_NAME, create_node_input_types
+import comfy.model_management as model_management
+
+class DensePose_Preprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types(
+ model=(["densepose_r50_fpn_dl.torchscript", "densepose_r101_fpn_dl.torchscript"], {"default": "densepose_r50_fpn_dl.torchscript"}),
+ cmap=(["Viridis (MagicAnimate)", "Parula (CivitAI)"], {"default": "Viridis (MagicAnimate)"})
+ )
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "execute"
+
+ CATEGORY = "ControlNet Preprocessors/Faces and Poses"
+
+ def execute(self, image, model, cmap, resolution=512):
+ from controlnet_aux.densepose import DenseposeDetector
+ return (common_annotator_call(
+ DenseposeDetector.from_pretrained("hr16/DensePose-TorchScript-with-hint-image", model).to(model_management.get_torch_device()),
+ image,
+ cmap="viridis" if "Viridis" in cmap else "parula",
+ resolution=resolution), )
+
+
+NODE_CLASS_MAPPINGS = {
+ "DensePosePreprocessor": DensePose_Preprocessor
+}
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "DensePosePreprocessor": "DensePose Estimation"
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/dwpose.py b/custom_nodes/comfyui_controlnet_aux/node_wrappers/dwpose.py
new file mode 100644
index 0000000000000000000000000000000000000000..39f0e4d49335f581928a23a4dde24ae15506a057
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/node_wrappers/dwpose.py
@@ -0,0 +1,155 @@
+from ..utils import common_annotator_call, annotator_ckpts_path, HF_MODEL_NAME, DWPOSE_MODEL_NAME, create_node_input_types
+import comfy.model_management as model_management
+import numpy as np
+import warnings
+from controlnet_aux.dwpose import DwposeDetector, AnimalposeDetector
+import os
+import json
+
+#Trigger startup caching for onnxruntime
+GPU_PROVIDERS = ["CUDAExecutionProvider", "DirectMLExecutionProvider", "OpenVINOExecutionProvider", "ROCMExecutionProvider"]
+def check_ort_gpu():
+ try:
+ import onnxruntime as ort
+ for provider in GPU_PROVIDERS:
+ if provider in ort.get_available_providers():
+ return True
+ return False
+ except:
+ return False
+
+if not os.environ.get("DWPOSE_ONNXRT_CHECKED"):
+ if check_ort_gpu():
+ print("DWPose: Onnxruntime with acceleration providers detected")
+ else:
+ warnings.warn("DWPose: Onnxruntime not found or doesn't come with acceleration providers, switch to OpenCV with CPU device. DWPose might run very slowly")
+ os.environ['AUX_ORT_PROVIDERS'] = ''
+ os.environ["DWPOSE_ONNXRT_CHECKED"] = '1'
+
+class DWPose_Preprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ input_types = create_node_input_types(
+ detect_hand=(["enable", "disable"], {"default": "enable"}),
+ detect_body=(["enable", "disable"], {"default": "enable"}),
+ detect_face=(["enable", "disable"], {"default": "enable"})
+ )
+ input_types["optional"] = {
+ **input_types["optional"],
+ "bbox_detector": (
+ ["yolox_l.torchscript.pt", "yolox_m.torchscript.pt", "yolox_s.torchscript.pt", "yolo_nas_l_fp16.onnx", "yolo_nas_m_fp16.onnx", "yolo_nas_s_fp16.onnx", "yolox_l.onnx", "yolox_m.onnx", "yolox_s.onnx"],
+ {"default": "yolox_l.onnx"}
+ ),
+ "pose_estimator": (["dw-ll_ucoco_384_bs5.torchscript.pt", "dw-ll_ucoco_384.onnx", "dw-ll_ucoco.onnx", "dw-mm_ucoco.onnx", "dw-ss_ucoco.onnx"], {"default": "dw-ll_ucoco_384.onnx"})
+ }
+ return input_types
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "estimate_pose"
+
+ CATEGORY = "ControlNet Preprocessors/Faces and Poses"
+
+ def estimate_pose(self, image, detect_hand, detect_body, detect_face, resolution=512, bbox_detector="yolox_l.onnx", pose_estimator="dw-ll_ucoco_384.onnx", **kwargs):
+ if bbox_detector == "yolox_l.onnx":
+ yolo_repo = DWPOSE_MODEL_NAME
+ elif "yolox" in bbox_detector:
+ yolo_repo = "hr16/yolox-onnx"
+ elif "yolo_nas" in bbox_detector:
+ yolo_repo = "hr16/yolo-nas-fp16"
+ else:
+ raise NotImplementedError(f"Download mechanism for {bbox_detector}")
+
+ if pose_estimator == "dw-ll_ucoco_384.onnx":
+ pose_repo = DWPOSE_MODEL_NAME
+ elif pose_estimator.endswith(".onnx"):
+ pose_repo = "hr16/UnJIT-DWPose"
+ elif pose_estimator.endswith(".torchscript.pt"):
+ pose_repo = "hr16/DWPose-TorchScript-BatchSize5"
+ else:
+ raise NotImplementedError(f"Download mechanism for {pose_estimator}")
+
+ model = DwposeDetector.from_pretrained(
+ pose_repo,
+ yolo_repo,
+ cache_dir=annotator_ckpts_path, det_filename=bbox_detector, pose_filename=pose_estimator,
+ torchscript_device=model_management.get_torch_device()
+ )
+ detect_hand = detect_hand == "enable"
+ detect_body = detect_body == "enable"
+ detect_face = detect_face == "enable"
+ self.openpose_dicts = []
+ def func(image, **kwargs):
+ pose_img, openpose_dict = model(image, **kwargs)
+ self.openpose_dicts.append(openpose_dict)
+ return pose_img
+
+ out = common_annotator_call(func, image, include_hand=detect_hand, include_face=detect_face, include_body=detect_body, image_and_json=True, resolution=resolution)
+ del model
+ return {
+ 'ui': { "openpose_json": [json.dumps(self.openpose_dicts, indent=4)] },
+ "result": (out, )
+ }
+
+class AnimalPose_Preprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types(
+ bbox_detector = (
+ ["yolox_l.torchscript.pt", "yolox_m.torchscript.pt", "yolox_s.torchscript.pt", "yolo_nas_l_fp16.onnx", "yolo_nas_m_fp16.onnx", "yolo_nas_s_fp16.onnx", "yolox_l.onnx", "yolox_m.onnx", "yolox_s.onnx"],
+ {"default": "yolox_l.onnx"}
+ ),
+ pose_estimator = (["rtmpose-m_ap10k_256_bs5.torchscript.pt", "rtmpose-m_ap10k_256.onnx"], {"default": "rtmpose-m_ap10k_256.onnx"})
+ )
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "estimate_pose"
+
+ CATEGORY = "ControlNet Preprocessors/Faces and Poses"
+
+ def estimate_pose(self, image, resolution=512, bbox_detector="yolox_l.onnx", pose_estimator="rtmpose-m_ap10k_256.onnx", **kwargs):
+ if bbox_detector == "yolox_l.onnx":
+ yolo_repo = DWPOSE_MODEL_NAME
+ elif "yolox" in bbox_detector:
+ yolo_repo = "hr16/yolox-onnx"
+ elif "yolo_nas" in bbox_detector:
+ yolo_repo = "hr16/yolo-nas-fp16"
+ else:
+ raise NotImplementedError(f"Download mechanism for {bbox_detector}")
+
+ if pose_estimator == "dw-ll_ucoco_384.onnx":
+ pose_repo = DWPOSE_MODEL_NAME
+ elif pose_estimator.endswith(".onnx"):
+ pose_repo = "hr16/UnJIT-DWPose"
+ elif pose_estimator.endswith(".torchscript.pt"):
+ pose_repo = "hr16/DWPose-TorchScript-BatchSize5"
+ else:
+ raise NotImplementedError(f"Download mechanism for {pose_estimator}")
+
+ model = AnimalposeDetector.from_pretrained(
+ pose_repo,
+ yolo_repo,
+ cache_dir=annotator_ckpts_path, det_filename=bbox_detector, pose_filename=pose_estimator,
+ torchscript_device=model_management.get_torch_device()
+ )
+
+ self.openpose_dicts = []
+ def func(image, **kwargs):
+ pose_img, openpose_dict = model(image, **kwargs)
+ self.openpose_dicts.append(openpose_dict)
+ return pose_img
+
+ out = common_annotator_call(func, image, image_and_json=True, resolution=resolution)
+ del model
+ return {
+ 'ui': { "openpose_json": [json.dumps(self.openpose_dicts, indent=4)] },
+ "result": (out, )
+ }
+
+NODE_CLASS_MAPPINGS = {
+ "DWPreprocessor": DWPose_Preprocessor,
+ "AnimalPosePreprocessor": AnimalPose_Preprocessor
+}
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "DWPreprocessor": "DWPose Estimation",
+ "AnimalPosePreprocessor": "Animal Pose Estimation (AP10K)"
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/hed.py b/custom_nodes/comfyui_controlnet_aux/node_wrappers/hed.py
new file mode 100644
index 0000000000000000000000000000000000000000..8e93e30664af1ab5547f06b89ab4da9b45c0b404
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/node_wrappers/hed.py
@@ -0,0 +1,51 @@
+from ..utils import common_annotator_call, annotator_ckpts_path, HF_MODEL_NAME, create_node_input_types
+import comfy.model_management as model_management
+
+class HED_Preprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types(
+ safe=(["enable", "disable"], {"default": "enable"})
+ )
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "execute"
+
+ CATEGORY = "ControlNet Preprocessors/Line Extractors"
+
+ def execute(self, image, resolution=512, **kwargs):
+ from controlnet_aux.hed import HEDdetector
+
+ model = HEDdetector.from_pretrained(HF_MODEL_NAME, cache_dir=annotator_ckpts_path).to(model_management.get_torch_device())
+ out = common_annotator_call(model, image, resolution=resolution, safe = kwargs["safe"] == "enable")
+ del model
+ return (out, )
+
+class Fake_Scribble_Preprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types(
+ safe=(["enable", "disable"], {"default": "enable"})
+ )
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "execute"
+
+ CATEGORY = "ControlNet Preprocessors/Line Extractors"
+
+ def execute(self, image, resolution=512, **kwargs):
+ from controlnet_aux.hed import HEDdetector
+
+ model = HEDdetector.from_pretrained(HF_MODEL_NAME, cache_dir=annotator_ckpts_path).to(model_management.get_torch_device())
+ out = common_annotator_call(model, image, resolution=resolution, scribble=True, safe=kwargs["safe"]=="enable")
+ del model
+ return (out, )
+
+NODE_CLASS_MAPPINGS = {
+ "HEDPreprocessor": HED_Preprocessor,
+ "FakeScribblePreprocessor": Fake_Scribble_Preprocessor
+}
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "HEDPreprocessor": "HED Lines",
+ "FakeScribblePreprocessor": "Fake Scribble Lines (aka scribble_hed)"
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/inpaint.py b/custom_nodes/comfyui_controlnet_aux/node_wrappers/inpaint.py
new file mode 100644
index 0000000000000000000000000000000000000000..2127b1dde4d436950c36ef990ef6aadaa7f72877
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/node_wrappers/inpaint.py
@@ -0,0 +1,24 @@
+import torch
+
+class InpaintPreprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": { "image": ("IMAGE",), "mask": ("MASK",)}}
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "preprocess"
+
+ CATEGORY = "ControlNet Preprocessors/others"
+
+ def preprocess(self, image, mask):
+ mask = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), size=(image.shape[1], image.shape[2]), mode="bilinear")
+ mask = mask.movedim(1,-1).expand((-1,-1,-1,3))
+ image = image.clone()
+ image[mask > 0.5] = -1.0 # set as masked pixel
+ return (image,)
+
+NODE_CLASS_MAPPINGS = {
+ "InpaintPreprocessor": InpaintPreprocessor
+}
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "InpaintPreprocessor": "Inpaint Preprocessor"
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/leres.py b/custom_nodes/comfyui_controlnet_aux/node_wrappers/leres.py
new file mode 100644
index 0000000000000000000000000000000000000000..9032f19329aa83e7f57f7a3097b0bd2766dba1ef
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/node_wrappers/leres.py
@@ -0,0 +1,31 @@
+from ..utils import common_annotator_call, annotator_ckpts_path, HF_MODEL_NAME, create_node_input_types
+import comfy.model_management as model_management
+
+class LERES_Depth_Map_Preprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types(
+ rm_nearest=("FLOAT", {"default": 0.0, "min": 0.0, "max": 100, "step": 0.1}),
+ rm_background=("FLOAT", {"default": 0.0, "min": 0.0, "max": 100, "step": 0.1}),
+ boost=(["enable", "disable"], {"default": "disable"})
+ )
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "execute"
+
+ CATEGORY = "ControlNet Preprocessors/Normal and Depth Map"
+
+ def execute(self, image, rm_nearest, rm_background, resolution=512, **kwargs):
+ from controlnet_aux.leres import LeresDetector
+
+ model = LeresDetector.from_pretrained(HF_MODEL_NAME, cache_dir=annotator_ckpts_path).to(model_management.get_torch_device())
+ out = common_annotator_call(model, image, resolution=resolution, thr_a=rm_nearest, thr_b=rm_background, boost=kwargs["boost"] == "enable")
+ del model
+ return (out, )
+
+NODE_CLASS_MAPPINGS = {
+ "LeReS-DepthMapPreprocessor": LERES_Depth_Map_Preprocessor
+}
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "LeReS-DepthMapPreprocessor": "LeReS - Depth Map (enable boost for leres++)"
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/lineart.py b/custom_nodes/comfyui_controlnet_aux/node_wrappers/lineart.py
new file mode 100644
index 0000000000000000000000000000000000000000..c00a46bee20eeb3cade762019c84b9f5ddca0b70
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/node_wrappers/lineart.py
@@ -0,0 +1,29 @@
+from ..utils import common_annotator_call, annotator_ckpts_path, HF_MODEL_NAME, create_node_input_types
+import comfy.model_management as model_management
+
+class LineArt_Preprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types(
+ coarse=(["disable", "enable"], {"default": "disable"})
+ )
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "execute"
+
+ CATEGORY = "ControlNet Preprocessors/Line Extractors"
+
+ def execute(self, image, resolution=512, **kwargs):
+ from controlnet_aux.lineart import LineartDetector
+
+ model = LineartDetector.from_pretrained(HF_MODEL_NAME, cache_dir=annotator_ckpts_path).to(model_management.get_torch_device())
+ out = common_annotator_call(model, image, resolution=resolution, coarse = kwargs["coarse"] == "enable")
+ del model
+ return (out, )
+
+NODE_CLASS_MAPPINGS = {
+ "LineArtPreprocessor": LineArt_Preprocessor
+}
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "LineArtPreprocessor": "Realistic Lineart"
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/lineart_anime.py b/custom_nodes/comfyui_controlnet_aux/node_wrappers/lineart_anime.py
new file mode 100644
index 0000000000000000000000000000000000000000..417853376bfbaa1c582aa2fd3ee69f36ecc382f9
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/node_wrappers/lineart_anime.py
@@ -0,0 +1,27 @@
+from ..utils import common_annotator_call, annotator_ckpts_path, HF_MODEL_NAME, create_node_input_types
+import comfy.model_management as model_management
+
+class AnimeLineArt_Preprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types()
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "execute"
+
+ CATEGORY = "ControlNet Preprocessors/Line Extractors"
+
+ def execute(self, image, resolution=512, **kwargs):
+ from controlnet_aux.lineart_anime import LineartAnimeDetector
+
+ model = LineartAnimeDetector.from_pretrained(HF_MODEL_NAME, cache_dir=annotator_ckpts_path).to(model_management.get_torch_device())
+ out = common_annotator_call(model, image, resolution=resolution)
+ del model
+ return (out, )
+
+NODE_CLASS_MAPPINGS = {
+ "AnimeLineArtPreprocessor": AnimeLineArt_Preprocessor
+}
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "AnimeLineArtPreprocessor": "Anime Lineart"
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/manga_line.py b/custom_nodes/comfyui_controlnet_aux/node_wrappers/manga_line.py
new file mode 100644
index 0000000000000000000000000000000000000000..b6a3fb4bdda75ff04fd7bf06b2dd97712f01b89f
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/node_wrappers/manga_line.py
@@ -0,0 +1,27 @@
+from ..utils import common_annotator_call, annotator_ckpts_path, HF_MODEL_NAME, create_node_input_types
+import comfy.model_management as model_management
+
+class Manga2Anime_LineArt_Preprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types()
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "execute"
+
+ CATEGORY = "ControlNet Preprocessors/Line Extractors"
+
+ def execute(self, image, resolution=512, **kwargs):
+ from controlnet_aux.manga_line import LineartMangaDetector
+
+ model = LineartMangaDetector.from_pretrained(HF_MODEL_NAME, cache_dir=annotator_ckpts_path).to(model_management.get_torch_device())
+ out = common_annotator_call(model, image, resolution=resolution)
+ del model
+ return (out, )
+
+NODE_CLASS_MAPPINGS = {
+ "Manga2Anime_LineArt_Preprocessor": Manga2Anime_LineArt_Preprocessor
+}
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "Manga2Anime_LineArt_Preprocessor": "Manga Lineart (aka lineart_anime_denoise)"
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/mediapipe_face.py b/custom_nodes/comfyui_controlnet_aux/node_wrappers/mediapipe_face.py
new file mode 100644
index 0000000000000000000000000000000000000000..1015f724570bc2081939af584f2fe6b33d5d3c5c
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/node_wrappers/mediapipe_face.py
@@ -0,0 +1,57 @@
+from ..utils import common_annotator_call, annotator_ckpts_path, HF_MODEL_NAME, DWPOSE_MODEL_NAME, create_node_input_types
+import comfy.model_management as model_management
+import os, sys
+import subprocess, threading
+
+#Ref: https://github.com/ltdrdata/ComfyUI-Manager/blob/284e90dc8296a2e1e4f14b4b2d10fba2f52f0e53/__init__.py#L14
+def handle_stream(stream, prefix):
+ for line in stream:
+ print(prefix, line, end="")
+
+
+def run_script(cmd, cwd='.'):
+ process = subprocess.Popen(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1)
+
+ stdout_thread = threading.Thread(target=handle_stream, args=(process.stdout, ""))
+ stderr_thread = threading.Thread(target=handle_stream, args=(process.stderr, "[!]"))
+
+ stdout_thread.start()
+ stderr_thread.start()
+
+ stdout_thread.join()
+ stderr_thread.join()
+
+ return process.wait()
+
+class Media_Pipe_Face_Mesh_Preprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types(
+ max_faces=("INT", {"default": 10, "min": 1, "max": 50, "step": 1}), #Which image has more than 50 detectable faces?
+ min_confidence=("FLOAT", {"default": 0.5, "min": 0.01, "max": 1.0, "step": 0.01})
+ )
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "detect"
+
+ CATEGORY = "ControlNet Preprocessors/Faces and Poses"
+
+ def detect(self, image, max_faces, min_confidence, resolution=512):
+ try:
+ import mediapipe
+ except ImportError:
+ run_script([sys.executable, '-s', '-m', 'pip', 'install', 'mediapipe'])
+ run_script([sys.executable, '-s', '-m', 'pip', 'install', '--upgrade', 'protobuf'])
+
+ #Ref: https://github.com/Fannovel16/comfy_controlnet_preprocessors/issues/70#issuecomment-1677967369
+ from controlnet_aux.mediapipe_face import MediapipeFaceDetector
+
+ return (common_annotator_call(MediapipeFaceDetector(), image, max_faces=max_faces, min_confidence=min_confidence, resolution=resolution), )
+
+NODE_CLASS_MAPPINGS = {
+ "MediaPipe-FaceMeshPreprocessor": Media_Pipe_Face_Mesh_Preprocessor
+}
+
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "MediaPipe-FaceMeshPreprocessor": "MediaPipe Face Mesh"
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/midas.py b/custom_nodes/comfyui_controlnet_aux/node_wrappers/midas.py
new file mode 100644
index 0000000000000000000000000000000000000000..49bc6b2f247c12bb2977ac58d834771e6138389a
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/node_wrappers/midas.py
@@ -0,0 +1,57 @@
+from ..utils import common_annotator_call, annotator_ckpts_path, HF_MODEL_NAME, create_node_input_types
+import comfy.model_management as model_management
+import numpy as np
+
+class MIDAS_Normal_Map_Preprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types(
+ a = ("FLOAT", {"default": np.pi * 2.0, "min": 0.0, "max": np.pi * 5.0, "step": 0.05}),
+ bg_threshold = ("FLOAT", {"default": 0.1, "min": 0, "max": 1, "step": 0.05})
+ )
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "execute"
+
+ CATEGORY = "ControlNet Preprocessors/Normal and Depth Map"
+
+ def execute(self, image, a, bg_threshold, resolution=512, **kwargs):
+ from controlnet_aux.midas import MidasDetector
+
+ model = MidasDetector.from_pretrained(HF_MODEL_NAME, cache_dir=annotator_ckpts_path).to(model_management.get_torch_device())
+ #Dirty hack :))
+ cb = lambda image, **kargs: model(image, **kargs)[1]
+ out = common_annotator_call(cb, image, resolution=resolution, a=a, bg_th=bg_threshold, depth_and_normal=True)
+ del model
+ return (out, )
+
+class MIDAS_Depth_Map_Preprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types(
+ a = ("FLOAT", {"default": np.pi * 2.0, "min": 0.0, "max": np.pi * 5.0, "step": 0.05}),
+ bg_threshold = ("FLOAT", {"default": 0.1, "min": 0, "max": 1, "step": 0.05})
+ )
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "execute"
+
+ CATEGORY = "ControlNet Preprocessors/Normal and Depth Map"
+
+ def execute(self, image, a, bg_threshold, resolution=512, **kwargs):
+ from controlnet_aux.midas import MidasDetector
+
+ # Ref: https://github.com/lllyasviel/ControlNet/blob/main/gradio_depth2image.py
+ model = MidasDetector.from_pretrained(HF_MODEL_NAME, cache_dir=annotator_ckpts_path).to(model_management.get_torch_device())
+ out = common_annotator_call(model, image, resolution=resolution, a=a, bg_th=bg_threshold)
+ del model
+ return (out, )
+
+NODE_CLASS_MAPPINGS = {
+ "MiDaS-NormalMapPreprocessor": MIDAS_Normal_Map_Preprocessor,
+ "MiDaS-DepthMapPreprocessor": MIDAS_Depth_Map_Preprocessor
+}
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "MiDaS-NormalMapPreprocessor": "MiDaS - Normal Map",
+ "MiDaS-DepthMapPreprocessor": "MiDaS - Depth Map"
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/mlsd.py b/custom_nodes/comfyui_controlnet_aux/node_wrappers/mlsd.py
new file mode 100644
index 0000000000000000000000000000000000000000..8e6792d2aceccee751a65ecbd7885939762680e0
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/node_wrappers/mlsd.py
@@ -0,0 +1,30 @@
+from ..utils import common_annotator_call, annotator_ckpts_path, HF_MODEL_NAME, create_node_input_types
+import comfy.model_management as model_management
+import numpy as np
+
+class MLSD_Preprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types(
+ score_threshold = ("FLOAT", {"default": 0.1, "min": 0.01, "max": 2.0, "step": 0.01}),
+ dist_threshold = ("FLOAT", {"default": 0.1, "min": 0.01, "max": 20.0, "step": 0.01})
+ )
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "execute"
+
+ CATEGORY = "ControlNet Preprocessors/Line Extractors"
+
+ def execute(self, image, score_threshold, dist_threshold, resolution=512, **kwargs):
+ from controlnet_aux.mlsd import MLSDdetector
+
+ model = MLSDdetector.from_pretrained(HF_MODEL_NAME, cache_dir=annotator_ckpts_path).to(model_management.get_torch_device())
+ out = common_annotator_call(model, image, resolution=resolution, thr_v=score_threshold, thr_d=dist_threshold)
+ return (out, )
+
+NODE_CLASS_MAPPINGS = {
+ "M-LSDPreprocessor": MLSD_Preprocessor
+}
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "M-LSDPreprocessor": "M-LSD Lines"
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/normalbae.py b/custom_nodes/comfyui_controlnet_aux/node_wrappers/normalbae.py
new file mode 100644
index 0000000000000000000000000000000000000000..151a41bb9c1830502bfd48f5192a49cc322f8ebf
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/node_wrappers/normalbae.py
@@ -0,0 +1,27 @@
+from ..utils import common_annotator_call, annotator_ckpts_path, HF_MODEL_NAME, create_node_input_types
+import comfy.model_management as model_management
+
+class BAE_Normal_Map_Preprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types()
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "execute"
+
+ CATEGORY = "ControlNet Preprocessors/Normal and Depth Map"
+
+ def execute(self, image, resolution=512, **kwargs):
+ from controlnet_aux.normalbae import NormalBaeDetector
+
+ model = NormalBaeDetector.from_pretrained(HF_MODEL_NAME, cache_dir=annotator_ckpts_path).to(model_management.get_torch_device())
+ out = common_annotator_call(model, image, resolution=resolution)
+ del model
+ return (out,)
+
+NODE_CLASS_MAPPINGS = {
+ "BAE-NormalMapPreprocessor": BAE_Normal_Map_Preprocessor
+}
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "BAE-NormalMapPreprocessor": "BAE - Normal Map"
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/oneformer.py b/custom_nodes/comfyui_controlnet_aux/node_wrappers/oneformer.py
new file mode 100644
index 0000000000000000000000000000000000000000..3fe349e0ce01ffd4f22302499e4577c7b006c2df
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/node_wrappers/oneformer.py
@@ -0,0 +1,50 @@
+from ..utils import common_annotator_call, annotator_ckpts_path, HF_MODEL_NAME, create_node_input_types
+import comfy.model_management as model_management
+
+class OneFormer_COCO_SemSegPreprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types()
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "semantic_segmentate"
+
+ CATEGORY = "ControlNet Preprocessors/Semantic Segmentation"
+
+ def semantic_segmentate(self, image, resolution=512):
+ from controlnet_aux.oneformer import OneformerSegmentor
+
+ model = OneformerSegmentor.from_pretrained(HF_MODEL_NAME, "150_16_swin_l_oneformer_coco_100ep.pth", cache_dir=annotator_ckpts_path)
+ model = model.to(model_management.get_torch_device())
+ out = common_annotator_call(model, image, resolution=resolution)
+ del model
+ return (out,)
+
+class OneFormer_ADE20K_SemSegPreprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types()
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "semantic_segmentate"
+
+ CATEGORY = "ControlNet Preprocessors/Semantic Segmentation"
+
+ def semantic_segmentate(self, image, resolution=512):
+ from controlnet_aux.oneformer import OneformerSegmentor
+
+ model = OneformerSegmentor.from_pretrained(HF_MODEL_NAME, "250_16_swin_l_oneformer_ade20k_160k.pth", cache_dir=annotator_ckpts_path)
+ model = model.to(model_management.get_torch_device())
+ out = common_annotator_call(model, image, resolution=resolution)
+ del model
+ return (out,)
+
+NODE_CLASS_MAPPINGS = {
+ "OneFormer-COCO-SemSegPreprocessor": OneFormer_COCO_SemSegPreprocessor,
+ "OneFormer-ADE20K-SemSegPreprocessor": OneFormer_ADE20K_SemSegPreprocessor
+}
+
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "OneFormer-COCO-SemSegPreprocessor": "OneFormer COCO Segmentor",
+ "OneFormer-ADE20K-SemSegPreprocessor": "OneFormer ADE20K Segmentor"
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/openpose.py b/custom_nodes/comfyui_controlnet_aux/node_wrappers/openpose.py
new file mode 100644
index 0000000000000000000000000000000000000000..ed3a648ba4b73a0234b4f5382e3b1aeeaa76a615
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/node_wrappers/openpose.py
@@ -0,0 +1,46 @@
+from ..utils import common_annotator_call, annotator_ckpts_path, HF_MODEL_NAME, DWPOSE_MODEL_NAME, create_node_input_types
+import comfy.model_management as model_management
+
+class OpenPose_Preprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types(
+ detect_hand = (["enable", "disable"], {"default": "enable"}),
+ detect_body = (["enable", "disable"], {"default": "enable"}),
+ detect_face = (["enable", "disable"], {"default": "enable"})
+ )
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "estimate_pose"
+
+ CATEGORY = "ControlNet Preprocessors/Faces and Poses"
+
+ def estimate_pose(self, image, detect_hand, detect_body, detect_face, resolution=512, **kwargs):
+ from controlnet_aux.open_pose import OpenposeDetector
+
+ detect_hand = detect_hand == "enable"
+ detect_body = detect_body == "enable"
+ detect_face = detect_face == "enable"
+
+
+ self.openpose_json = None
+ model = OpenposeDetector.from_pretrained(HF_MODEL_NAME, cache_dir=annotator_ckpts_path).to(model_management.get_torch_device())
+
+ def cb(image, **kwargs):
+ result = model(image, **kwargs)
+ self.openpose_json = result[1]
+ return result[0]
+
+ out = common_annotator_call(cb, image, include_hand=detect_hand, include_face=detect_face, include_body=detect_body, image_and_json=True, resolution=resolution)
+ del model
+ return {
+ 'ui': { "openpose_json": [self.openpose_json] },
+ "result": (out, )
+ }
+
+NODE_CLASS_MAPPINGS = {
+ "OpenposePreprocessor": OpenPose_Preprocessor,
+}
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "OpenposePreprocessor": "OpenPose Pose Recognition",
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/pidinet.py b/custom_nodes/comfyui_controlnet_aux/node_wrappers/pidinet.py
new file mode 100644
index 0000000000000000000000000000000000000000..12b588275597ba0af93a6beb2b47dd1a81f8eb58
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/node_wrappers/pidinet.py
@@ -0,0 +1,29 @@
+from ..utils import common_annotator_call, annotator_ckpts_path, HF_MODEL_NAME, create_node_input_types
+import comfy.model_management as model_management
+
+class PIDINET_Preprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types(
+ safe=(["enable", "disable"], {"default": "enable"})
+ )
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "execute"
+
+ CATEGORY = "ControlNet Preprocessors/Line Extractors"
+
+ def execute(self, image, safe, resolution=512, **kwargs):
+ from controlnet_aux.pidi import PidiNetDetector
+
+ model = PidiNetDetector.from_pretrained(HF_MODEL_NAME, cache_dir=annotator_ckpts_path).to(model_management.get_torch_device())
+ out = common_annotator_call(model, image, resolution=resolution, safe = safe == "enable")
+ del model
+ return (out, )
+
+NODE_CLASS_MAPPINGS = {
+ "PiDiNetPreprocessor": PIDINET_Preprocessor,
+}
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "PiDiNetPreprocessor": "PiDiNet Lines"
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/scribble.py b/custom_nodes/comfyui_controlnet_aux/node_wrappers/scribble.py
new file mode 100644
index 0000000000000000000000000000000000000000..fa93539c8f69e6e5ba7c5e767456aa55520641ad
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/node_wrappers/scribble.py
@@ -0,0 +1,45 @@
+from ..utils import common_annotator_call, annotator_ckpts_path, HF_MODEL_NAME, create_node_input_types
+import comfy.model_management as model_management
+
+class Scribble_Preprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types()
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "execute"
+
+ CATEGORY = "ControlNet Preprocessors/Line Extractors"
+
+ def execute(self, image, resolution=512, **kwargs):
+ from controlnet_aux.scribble import ScribbleDetector
+
+ model = ScribbleDetector()
+ return (common_annotator_call(model, image, resolution=resolution), )
+
+class Scribble_XDoG_Preprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types(
+ threshold = ("INT", {"default": 32, "min": 1, "max": 64, "step": 64})
+ )
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "execute"
+
+ CATEGORY = "ControlNet Preprocessors/Line Extractors"
+
+ def execute(self, image, resolution=512, **kwargs):
+ from controlnet_aux.scribble import ScribbleXDog_Detector
+
+ model = ScribbleXDog_Detector()
+ return (common_annotator_call(model, image, resolution=resolution), )
+
+NODE_CLASS_MAPPINGS = {
+ "ScribblePreprocessor": Scribble_Preprocessor,
+ "Scribble_XDoG_Preprocessor": Scribble_XDoG_Preprocessor
+}
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "ScribblePreprocessor": "Scribble Lines",
+ "Scribble_XDoG_Preprocessor": "Scribble XDoG Lines"
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/segment_anything.py b/custom_nodes/comfyui_controlnet_aux/node_wrappers/segment_anything.py
new file mode 100644
index 0000000000000000000000000000000000000000..cb61f2e4462f199702880b16d155c64bfa216afe
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/node_wrappers/segment_anything.py
@@ -0,0 +1,27 @@
+from ..utils import common_annotator_call, annotator_ckpts_path, HF_MODEL_NAME, create_node_input_types
+import comfy.model_management as model_management
+
+class SAM_Preprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types()
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "execute"
+
+ CATEGORY = "ControlNet Preprocessors/others"
+
+ def execute(self, image, resolution=512, **kwargs):
+ from controlnet_aux.sam import SamDetector
+
+ mobile_sam = SamDetector.from_pretrained("dhkim2810/MobileSAM", model_type="vit_t", filename="mobile_sam.pt", cache_dir=annotator_ckpts_path).to(model_management.get_torch_device())
+ out = common_annotator_call(mobile_sam, image, resolution=resolution)
+ del mobile_sam
+ return (out, )
+
+NODE_CLASS_MAPPINGS = {
+ "SAMPreprocessor": SAM_Preprocessor
+}
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "SAMPreprocessor": "SAM Segmentor"
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/shuffle.py b/custom_nodes/comfyui_controlnet_aux/node_wrappers/shuffle.py
new file mode 100644
index 0000000000000000000000000000000000000000..f66f25a14fed300c0121ee8342b1ec0438136b30
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/node_wrappers/shuffle.py
@@ -0,0 +1,24 @@
+from ..utils import common_annotator_call, annotator_ckpts_path, HF_MODEL_NAME, create_node_input_types
+import comfy.model_management as model_management
+
+class Shuffle_Preprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types()
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "preprocess"
+
+ CATEGORY = "ControlNet Preprocessors/T2IAdapter-only"
+
+ def preprocess(self, image, resolution=512):
+ from controlnet_aux.shuffle import ContentShuffleDetector
+
+ return (common_annotator_call(ContentShuffleDetector(), image, resolution=resolution), )
+
+NODE_CLASS_MAPPINGS = {
+ "ShufflePreprocessor": Shuffle_Preprocessor
+}
+
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "ShufflePreprocessor": "Content Shuffle"
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/tile.py b/custom_nodes/comfyui_controlnet_aux/node_wrappers/tile.py
new file mode 100644
index 0000000000000000000000000000000000000000..01ae86ccc6211390634d2684fa263665ee56cb3d
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/node_wrappers/tile.py
@@ -0,0 +1,29 @@
+from ..utils import common_annotator_call, create_node_input_types
+
+
+class Tile_Preprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types(
+ pyrUp_iters = ("INT", {"default": 3, "min": 1, "max": 10, "step": 1})
+ )
+
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "execute"
+
+ CATEGORY = "ControlNet Preprocessors/others"
+
+ def execute(self, image, pyrUp_iters, resolution=512, **kwargs):
+ from controlnet_aux.tile import TileDetector
+
+ return (common_annotator_call(TileDetector(), image, pyrUp_iters=pyrUp_iters, resolution=resolution),)
+
+
+NODE_CLASS_MAPPINGS = {
+ "TilePreprocessor": Tile_Preprocessor,
+}
+
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "TilePreprocessor": "Tile"
+}
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/uniformer.py b/custom_nodes/comfyui_controlnet_aux/node_wrappers/uniformer.py
new file mode 100644
index 0000000000000000000000000000000000000000..3e1d3c1de8e4216695c18f3d3121cfe7d72dfd1e
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/node_wrappers/uniformer.py
@@ -0,0 +1,29 @@
+from ..utils import common_annotator_call, annotator_ckpts_path, HF_MODEL_NAME, create_node_input_types
+import comfy.model_management as model_management
+
+class Uniformer_SemSegPreprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types()
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "semantic_segmentate"
+
+ CATEGORY = "ControlNet Preprocessors/Semantic Segmentation"
+
+ def semantic_segmentate(self, image, resolution=512):
+ from controlnet_aux.uniformer import UniformerSegmentor
+
+ model = UniformerSegmentor.from_pretrained(HF_MODEL_NAME, cache_dir=annotator_ckpts_path).to(model_management.get_torch_device())
+ out = common_annotator_call(model, image, resolution=resolution)
+ del model
+ return (out, )
+
+NODE_CLASS_MAPPINGS = {
+ "UniFormer-SemSegPreprocessor": Uniformer_SemSegPreprocessor,
+ "SemSegPreprocessor": Uniformer_SemSegPreprocessor,
+}
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "UniFormer-SemSegPreprocessor": "UniFormer Segmentor",
+ "SemSegPreprocessor": "Semantic Segmentor (legacy, alias for UniFormer)",
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/node_wrappers/zoe.py b/custom_nodes/comfyui_controlnet_aux/node_wrappers/zoe.py
new file mode 100644
index 0000000000000000000000000000000000000000..b8e18db00a2a387c5d80ec4e6708b5073cb12927
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/node_wrappers/zoe.py
@@ -0,0 +1,27 @@
+from ..utils import common_annotator_call, annotator_ckpts_path, HF_MODEL_NAME, create_node_input_types
+import comfy.model_management as model_management
+
+class Zoe_Depth_Map_Preprocessor:
+ @classmethod
+ def INPUT_TYPES(s):
+ return create_node_input_types()
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "execute"
+
+ CATEGORY = "ControlNet Preprocessors/Normal and Depth Map"
+
+ def execute(self, image, resolution=512, **kwargs):
+ from controlnet_aux.zoe import ZoeDetector
+
+ model = ZoeDetector.from_pretrained(HF_MODEL_NAME, cache_dir=annotator_ckpts_path).to(model_management.get_torch_device())
+ out = common_annotator_call(model, image, resolution=resolution)
+ del model
+ return (out, )
+
+NODE_CLASS_MAPPINGS = {
+ "Zoe-DepthMapPreprocessor": Zoe_Depth_Map_Preprocessor
+}
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "Zoe-DepthMapPreprocessor": "Zoe - Depth Map"
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/requirements.txt b/custom_nodes/comfyui_controlnet_aux/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..98eab96783fc719a099a3b477f149c4f7263a008
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/requirements.txt
@@ -0,0 +1,20 @@
+torch
+importlib_metadata
+huggingface_hub
+scipy
+opencv-python>=4.7.0.72
+filelock
+numpy
+Pillow
+einops
+torchvision
+pyyaml
+scikit-image
+python-dateutil
+mediapipe
+svglib
+fvcore
+yapf
+omegaconf
+ftfy
+addict
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..33e7a7f594ef441479257c788e4c0d6e08657fc8
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/__init__.py
@@ -0,0 +1 @@
+#Dummy file ensuring this package will be recognized
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..33e7a7f594ef441479257c788e4c0d6e08657fc8
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/__init__.py
@@ -0,0 +1 @@
+#Dummy file ensuring this package will be recognized
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/__pycache__/__init__.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..02afcd848830bb0ef9af77a727196e4ebdf79151
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/__pycache__/__init__.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/__pycache__/__init__.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a07f4ad6236ba5440409cc22d48ad6a35640c63e
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/__pycache__/__init__.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/__pycache__/util.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/__pycache__/util.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..99e8cf09aed18506155e8d47023105f4279857b8
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/__pycache__/util.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/__pycache__/util.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/__pycache__/util.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..77ce0e8dc2353614ae72c70080d0086c9d54c453
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/__pycache__/util.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/anime_face_segment/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/anime_face_segment/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..736c4ba7f5a8c17422366f3a324b8f6c1460ef5c
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/anime_face_segment/__init__.py
@@ -0,0 +1,67 @@
+from .network import UNet
+from .util import seg2img
+import torch
+import os
+import cv2
+from controlnet_aux.util import HWC3, resize_image_with_pad, common_input_validate, annotator_ckpts_path, custom_hf_download
+from huggingface_hub import hf_hub_download
+from PIL import Image
+from einops import rearrange
+from .anime_segmentation import AnimeSegmentation
+import numpy as np
+
+class AnimeFaceSegmentor:
+ def __init__(self, model, seg_model):
+ self.model = model
+ self.seg_model = seg_model
+
+ @classmethod
+ def from_pretrained(cls, pretrained_model_or_path=None, filename=None, seg_filename=None, cache_dir=annotator_ckpts_path):
+ filename = filename or "UNet.pth"
+ seg_filename = seg_filename or "isnetis.ckpt"
+ model_path = custom_hf_download(pretrained_model_or_path, filename, subfolder="Annotators", cache_dir=cache_dir)
+ seg_model_path = custom_hf_download("skytnt/anime-seg", seg_filename, cache_dir=cache_dir)
+
+ model = UNet()
+ ckpt = torch.load(model_path)
+ model.load_state_dict(ckpt)
+ model.eval()
+
+ seg_model = AnimeSegmentation(seg_model_path)
+ seg_model.net.eval()
+ return cls(model, seg_model)
+
+ def to(self, device):
+ self.model.to(device)
+ self.seg_model.net.to(device)
+ return self
+
+ def __call__(self, input_image, detect_resolution=512, output_type="pil", upscale_method="INTER_CUBIC", remove_background=True, **kwargs):
+ input_image, output_type = common_input_validate(input_image, output_type, **kwargs)
+ input_image, remove_pad = resize_image_with_pad(input_image, detect_resolution, upscale_method)
+ device = next(iter(self.model.parameters())).device
+
+ with torch.no_grad():
+ if remove_background:
+ print(input_image.shape)
+ mask, input_image = self.seg_model(input_image, 0) #Don't resize image as it is resized
+ image_feed = torch.from_numpy(input_image).float().to(device)
+ image_feed = rearrange(image_feed, 'h w c -> 1 c h w')
+ image_feed = image_feed / 255
+ seg = self.model(image_feed).squeeze(dim=0)
+ result = seg2img(seg.cpu().detach().numpy())
+
+ detected_map = HWC3(result)
+ detected_map = remove_pad(detected_map)
+ if remove_background:
+ mask = remove_pad(mask)
+ H, W, C = detected_map.shape
+ tmp = np.zeros([H, W, C + 1])
+ tmp[:,:,:C] = detected_map
+ tmp[:,:,3:] = mask
+ detected_map = tmp
+
+ if output_type == "pil":
+ detected_map = Image.fromarray(detected_map[..., :3])
+
+ return detected_map
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/anime_face_segment/anime_segmentation.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/anime_face_segment/anime_segmentation.py
new file mode 100644
index 0000000000000000000000000000000000000000..ac4ab9c0effe4a84b3a76fc1b2cffd373109a035
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/anime_face_segment/anime_segmentation.py
@@ -0,0 +1,58 @@
+#https://github.com/SkyTNT/anime-segmentation/tree/main
+#Only adapt isnet_is (https://huggingface.co/skytnt/anime-seg/blob/main/isnetis.ckpt)
+import torch.nn as nn
+import torch
+from .isnet import ISNetDIS
+import numpy as np
+import cv2
+from comfy.model_management import get_torch_device
+DEVICE = get_torch_device()
+
+class AnimeSegmentation:
+ def __init__(self, ckpt_path):
+ super(AnimeSegmentation).__init__()
+ sd = torch.load(ckpt_path, map_location="cpu")
+ self.net = ISNetDIS()
+ #gt_encoder isn't used during inference
+ self.net.load_state_dict({k.replace("net.", ''):v for k, v in sd.items() if k.startswith("net.")})
+ self.net = self.net.to(DEVICE)
+ self.net.eval()
+
+ def get_mask(self, input_img, s=640):
+ input_img = (input_img / 255).astype(np.float32)
+ if s == 0:
+ img_input = np.transpose(input_img, (2, 0, 1))
+ img_input = img_input[np.newaxis, :]
+ tmpImg = torch.from_numpy(img_input).float().to(DEVICE)
+ with torch.no_grad():
+ pred = self.net(tmpImg)[0][0].sigmoid() #https://github.com/SkyTNT/anime-segmentation/blob/main/train.py#L92C20-L92C47
+ pred = pred.cpu().numpy()[0]
+ pred = np.transpose(pred, (1, 2, 0))
+ #pred = pred[:, :, np.newaxis]
+ return pred
+
+ h, w = h0, w0 = input_img.shape[:-1]
+ h, w = (s, int(s * w / h)) if h > w else (int(s * h / w), s)
+ ph, pw = s - h, s - w
+ img_input = np.zeros([s, s, 3], dtype=np.float32)
+ img_input[ph // 2:ph // 2 + h, pw // 2:pw // 2 + w] = cv2.resize(input_img, (w, h))
+ img_input = np.transpose(img_input, (2, 0, 1))
+ img_input = img_input[np.newaxis, :]
+ tmpImg = torch.from_numpy(img_input).float().to(DEVICE)
+ with torch.no_grad():
+ pred = self.net(tmpImg)[0][0].sigmoid() #https://github.com/SkyTNT/anime-segmentation/blob/main/train.py#L92C20-L92C47
+ pred = pred.cpu().numpy()[0]
+ pred = np.transpose(pred, (1, 2, 0))
+ pred = pred[ph // 2:ph // 2 + h, pw // 2:pw // 2 + w]
+ #pred = cv2.resize(pred, (w0, h0))[:, :, np.newaxis]
+ pred = cv2.resize(pred, (w0, h0))
+ return pred
+
+ def __call__(self, np_img, img_size):
+ mask = self.get_mask(np_img, int(img_size))
+ np_img = (mask * np_img + 255 * (1 - mask)).astype(np.uint8)
+ mask = (mask * 255).astype(np.uint8)
+ #np_img = np.concatenate([np_img, mask], axis=2, dtype=np.uint8)
+ #mask = mask.repeat(3, axis=2)
+ return mask, np_img
+
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/anime_face_segment/isnet.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/anime_face_segment/isnet.py
new file mode 100644
index 0000000000000000000000000000000000000000..8a0a504ecadf426358c8accee62d3f35129b0a11
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/anime_face_segment/isnet.py
@@ -0,0 +1,619 @@
+# Codes are borrowed from
+# https://github.com/xuebinqin/DIS/blob/main/IS-Net/models/isnet.py
+
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+from torchvision import models
+
+bce_loss = nn.BCEWithLogitsLoss(reduction="mean")
+
+
+def muti_loss_fusion(preds, target):
+ loss0 = 0.0
+ loss = 0.0
+
+ for i in range(0, len(preds)):
+ if preds[i].shape[2] != target.shape[2] or preds[i].shape[3] != target.shape[3]:
+ tmp_target = F.interpolate(
+ target, size=preds[i].size()[2:], mode="bilinear", align_corners=True
+ )
+ loss = loss + bce_loss(preds[i], tmp_target)
+ else:
+ loss = loss + bce_loss(preds[i], target)
+ if i == 0:
+ loss0 = loss
+ return loss0, loss
+
+
+fea_loss = nn.MSELoss(reduction="mean")
+kl_loss = nn.KLDivLoss(reduction="mean")
+l1_loss = nn.L1Loss(reduction="mean")
+smooth_l1_loss = nn.SmoothL1Loss(reduction="mean")
+
+
+def muti_loss_fusion_kl(preds, target, dfs, fs, mode="MSE"):
+ loss0 = 0.0
+ loss = 0.0
+
+ for i in range(0, len(preds)):
+ if preds[i].shape[2] != target.shape[2] or preds[i].shape[3] != target.shape[3]:
+ tmp_target = F.interpolate(
+ target, size=preds[i].size()[2:], mode="bilinear", align_corners=True
+ )
+ loss = loss + bce_loss(preds[i], tmp_target)
+ else:
+ loss = loss + bce_loss(preds[i], target)
+ if i == 0:
+ loss0 = loss
+
+ for i in range(0, len(dfs)):
+ df = dfs[i]
+ fs_i = fs[i]
+ if mode == "MSE":
+ loss = loss + fea_loss(
+ df, fs_i
+ ) ### add the mse loss of features as additional constraints
+ elif mode == "KL":
+ loss = loss + kl_loss(F.log_softmax(df, dim=1), F.softmax(fs_i, dim=1))
+ elif mode == "MAE":
+ loss = loss + l1_loss(df, fs_i)
+ elif mode == "SmoothL1":
+ loss = loss + smooth_l1_loss(df, fs_i)
+
+ return loss0, loss
+
+
+class REBNCONV(nn.Module):
+ def __init__(self, in_ch=3, out_ch=3, dirate=1, stride=1):
+ super(REBNCONV, self).__init__()
+
+ self.conv_s1 = nn.Conv2d(
+ in_ch, out_ch, 3, padding=1 * dirate, dilation=1 * dirate, stride=stride
+ )
+ self.bn_s1 = nn.BatchNorm2d(out_ch)
+ self.relu_s1 = nn.ReLU(inplace=True)
+
+ def forward(self, x):
+ hx = x
+ xout = self.relu_s1(self.bn_s1(self.conv_s1(hx)))
+
+ return xout
+
+
+## upsample tensor 'src' to have the same spatial size with tensor 'tar'
+def _upsample_like(src, tar):
+ src = F.interpolate(src, size=tar.shape[2:], mode="bilinear", align_corners=False)
+
+ return src
+
+
+### RSU-7 ###
+class RSU7(nn.Module):
+ def __init__(self, in_ch=3, mid_ch=12, out_ch=3, img_size=512):
+ super(RSU7, self).__init__()
+
+ self.in_ch = in_ch
+ self.mid_ch = mid_ch
+ self.out_ch = out_ch
+
+ self.rebnconvin = REBNCONV(in_ch, out_ch, dirate=1) ## 1 -> 1/2
+
+ self.rebnconv1 = REBNCONV(out_ch, mid_ch, dirate=1)
+ self.pool1 = nn.MaxPool2d(2, stride=2, ceil_mode=True)
+
+ self.rebnconv2 = REBNCONV(mid_ch, mid_ch, dirate=1)
+ self.pool2 = nn.MaxPool2d(2, stride=2, ceil_mode=True)
+
+ self.rebnconv3 = REBNCONV(mid_ch, mid_ch, dirate=1)
+ self.pool3 = nn.MaxPool2d(2, stride=2, ceil_mode=True)
+
+ self.rebnconv4 = REBNCONV(mid_ch, mid_ch, dirate=1)
+ self.pool4 = nn.MaxPool2d(2, stride=2, ceil_mode=True)
+
+ self.rebnconv5 = REBNCONV(mid_ch, mid_ch, dirate=1)
+ self.pool5 = nn.MaxPool2d(2, stride=2, ceil_mode=True)
+
+ self.rebnconv6 = REBNCONV(mid_ch, mid_ch, dirate=1)
+
+ self.rebnconv7 = REBNCONV(mid_ch, mid_ch, dirate=2)
+
+ self.rebnconv6d = REBNCONV(mid_ch * 2, mid_ch, dirate=1)
+ self.rebnconv5d = REBNCONV(mid_ch * 2, mid_ch, dirate=1)
+ self.rebnconv4d = REBNCONV(mid_ch * 2, mid_ch, dirate=1)
+ self.rebnconv3d = REBNCONV(mid_ch * 2, mid_ch, dirate=1)
+ self.rebnconv2d = REBNCONV(mid_ch * 2, mid_ch, dirate=1)
+ self.rebnconv1d = REBNCONV(mid_ch * 2, out_ch, dirate=1)
+
+ def forward(self, x):
+ b, c, h, w = x.shape
+
+ hx = x
+ hxin = self.rebnconvin(hx)
+
+ hx1 = self.rebnconv1(hxin)
+ hx = self.pool1(hx1)
+
+ hx2 = self.rebnconv2(hx)
+ hx = self.pool2(hx2)
+
+ hx3 = self.rebnconv3(hx)
+ hx = self.pool3(hx3)
+
+ hx4 = self.rebnconv4(hx)
+ hx = self.pool4(hx4)
+
+ hx5 = self.rebnconv5(hx)
+ hx = self.pool5(hx5)
+
+ hx6 = self.rebnconv6(hx)
+
+ hx7 = self.rebnconv7(hx6)
+
+ hx6d = self.rebnconv6d(torch.cat((hx7, hx6), 1))
+ hx6dup = _upsample_like(hx6d, hx5)
+
+ hx5d = self.rebnconv5d(torch.cat((hx6dup, hx5), 1))
+ hx5dup = _upsample_like(hx5d, hx4)
+
+ hx4d = self.rebnconv4d(torch.cat((hx5dup, hx4), 1))
+ hx4dup = _upsample_like(hx4d, hx3)
+
+ hx3d = self.rebnconv3d(torch.cat((hx4dup, hx3), 1))
+ hx3dup = _upsample_like(hx3d, hx2)
+
+ hx2d = self.rebnconv2d(torch.cat((hx3dup, hx2), 1))
+ hx2dup = _upsample_like(hx2d, hx1)
+
+ hx1d = self.rebnconv1d(torch.cat((hx2dup, hx1), 1))
+
+ return hx1d + hxin
+
+
+### RSU-6 ###
+class RSU6(nn.Module):
+ def __init__(self, in_ch=3, mid_ch=12, out_ch=3):
+ super(RSU6, self).__init__()
+
+ self.rebnconvin = REBNCONV(in_ch, out_ch, dirate=1)
+
+ self.rebnconv1 = REBNCONV(out_ch, mid_ch, dirate=1)
+ self.pool1 = nn.MaxPool2d(2, stride=2, ceil_mode=True)
+
+ self.rebnconv2 = REBNCONV(mid_ch, mid_ch, dirate=1)
+ self.pool2 = nn.MaxPool2d(2, stride=2, ceil_mode=True)
+
+ self.rebnconv3 = REBNCONV(mid_ch, mid_ch, dirate=1)
+ self.pool3 = nn.MaxPool2d(2, stride=2, ceil_mode=True)
+
+ self.rebnconv4 = REBNCONV(mid_ch, mid_ch, dirate=1)
+ self.pool4 = nn.MaxPool2d(2, stride=2, ceil_mode=True)
+
+ self.rebnconv5 = REBNCONV(mid_ch, mid_ch, dirate=1)
+
+ self.rebnconv6 = REBNCONV(mid_ch, mid_ch, dirate=2)
+
+ self.rebnconv5d = REBNCONV(mid_ch * 2, mid_ch, dirate=1)
+ self.rebnconv4d = REBNCONV(mid_ch * 2, mid_ch, dirate=1)
+ self.rebnconv3d = REBNCONV(mid_ch * 2, mid_ch, dirate=1)
+ self.rebnconv2d = REBNCONV(mid_ch * 2, mid_ch, dirate=1)
+ self.rebnconv1d = REBNCONV(mid_ch * 2, out_ch, dirate=1)
+
+ def forward(self, x):
+ hx = x
+
+ hxin = self.rebnconvin(hx)
+
+ hx1 = self.rebnconv1(hxin)
+ hx = self.pool1(hx1)
+
+ hx2 = self.rebnconv2(hx)
+ hx = self.pool2(hx2)
+
+ hx3 = self.rebnconv3(hx)
+ hx = self.pool3(hx3)
+
+ hx4 = self.rebnconv4(hx)
+ hx = self.pool4(hx4)
+
+ hx5 = self.rebnconv5(hx)
+
+ hx6 = self.rebnconv6(hx5)
+
+ hx5d = self.rebnconv5d(torch.cat((hx6, hx5), 1))
+ hx5dup = _upsample_like(hx5d, hx4)
+
+ hx4d = self.rebnconv4d(torch.cat((hx5dup, hx4), 1))
+ hx4dup = _upsample_like(hx4d, hx3)
+
+ hx3d = self.rebnconv3d(torch.cat((hx4dup, hx3), 1))
+ hx3dup = _upsample_like(hx3d, hx2)
+
+ hx2d = self.rebnconv2d(torch.cat((hx3dup, hx2), 1))
+ hx2dup = _upsample_like(hx2d, hx1)
+
+ hx1d = self.rebnconv1d(torch.cat((hx2dup, hx1), 1))
+
+ return hx1d + hxin
+
+
+### RSU-5 ###
+class RSU5(nn.Module):
+ def __init__(self, in_ch=3, mid_ch=12, out_ch=3):
+ super(RSU5, self).__init__()
+
+ self.rebnconvin = REBNCONV(in_ch, out_ch, dirate=1)
+
+ self.rebnconv1 = REBNCONV(out_ch, mid_ch, dirate=1)
+ self.pool1 = nn.MaxPool2d(2, stride=2, ceil_mode=True)
+
+ self.rebnconv2 = REBNCONV(mid_ch, mid_ch, dirate=1)
+ self.pool2 = nn.MaxPool2d(2, stride=2, ceil_mode=True)
+
+ self.rebnconv3 = REBNCONV(mid_ch, mid_ch, dirate=1)
+ self.pool3 = nn.MaxPool2d(2, stride=2, ceil_mode=True)
+
+ self.rebnconv4 = REBNCONV(mid_ch, mid_ch, dirate=1)
+
+ self.rebnconv5 = REBNCONV(mid_ch, mid_ch, dirate=2)
+
+ self.rebnconv4d = REBNCONV(mid_ch * 2, mid_ch, dirate=1)
+ self.rebnconv3d = REBNCONV(mid_ch * 2, mid_ch, dirate=1)
+ self.rebnconv2d = REBNCONV(mid_ch * 2, mid_ch, dirate=1)
+ self.rebnconv1d = REBNCONV(mid_ch * 2, out_ch, dirate=1)
+
+ def forward(self, x):
+ hx = x
+
+ hxin = self.rebnconvin(hx)
+
+ hx1 = self.rebnconv1(hxin)
+ hx = self.pool1(hx1)
+
+ hx2 = self.rebnconv2(hx)
+ hx = self.pool2(hx2)
+
+ hx3 = self.rebnconv3(hx)
+ hx = self.pool3(hx3)
+
+ hx4 = self.rebnconv4(hx)
+
+ hx5 = self.rebnconv5(hx4)
+
+ hx4d = self.rebnconv4d(torch.cat((hx5, hx4), 1))
+ hx4dup = _upsample_like(hx4d, hx3)
+
+ hx3d = self.rebnconv3d(torch.cat((hx4dup, hx3), 1))
+ hx3dup = _upsample_like(hx3d, hx2)
+
+ hx2d = self.rebnconv2d(torch.cat((hx3dup, hx2), 1))
+ hx2dup = _upsample_like(hx2d, hx1)
+
+ hx1d = self.rebnconv1d(torch.cat((hx2dup, hx1), 1))
+
+ return hx1d + hxin
+
+
+### RSU-4 ###
+class RSU4(nn.Module):
+ def __init__(self, in_ch=3, mid_ch=12, out_ch=3):
+ super(RSU4, self).__init__()
+
+ self.rebnconvin = REBNCONV(in_ch, out_ch, dirate=1)
+
+ self.rebnconv1 = REBNCONV(out_ch, mid_ch, dirate=1)
+ self.pool1 = nn.MaxPool2d(2, stride=2, ceil_mode=True)
+
+ self.rebnconv2 = REBNCONV(mid_ch, mid_ch, dirate=1)
+ self.pool2 = nn.MaxPool2d(2, stride=2, ceil_mode=True)
+
+ self.rebnconv3 = REBNCONV(mid_ch, mid_ch, dirate=1)
+
+ self.rebnconv4 = REBNCONV(mid_ch, mid_ch, dirate=2)
+
+ self.rebnconv3d = REBNCONV(mid_ch * 2, mid_ch, dirate=1)
+ self.rebnconv2d = REBNCONV(mid_ch * 2, mid_ch, dirate=1)
+ self.rebnconv1d = REBNCONV(mid_ch * 2, out_ch, dirate=1)
+
+ def forward(self, x):
+ hx = x
+
+ hxin = self.rebnconvin(hx)
+
+ hx1 = self.rebnconv1(hxin)
+ hx = self.pool1(hx1)
+
+ hx2 = self.rebnconv2(hx)
+ hx = self.pool2(hx2)
+
+ hx3 = self.rebnconv3(hx)
+
+ hx4 = self.rebnconv4(hx3)
+
+ hx3d = self.rebnconv3d(torch.cat((hx4, hx3), 1))
+ hx3dup = _upsample_like(hx3d, hx2)
+
+ hx2d = self.rebnconv2d(torch.cat((hx3dup, hx2), 1))
+ hx2dup = _upsample_like(hx2d, hx1)
+
+ hx1d = self.rebnconv1d(torch.cat((hx2dup, hx1), 1))
+
+ return hx1d + hxin
+
+
+### RSU-4F ###
+class RSU4F(nn.Module):
+ def __init__(self, in_ch=3, mid_ch=12, out_ch=3):
+ super(RSU4F, self).__init__()
+
+ self.rebnconvin = REBNCONV(in_ch, out_ch, dirate=1)
+
+ self.rebnconv1 = REBNCONV(out_ch, mid_ch, dirate=1)
+ self.rebnconv2 = REBNCONV(mid_ch, mid_ch, dirate=2)
+ self.rebnconv3 = REBNCONV(mid_ch, mid_ch, dirate=4)
+
+ self.rebnconv4 = REBNCONV(mid_ch, mid_ch, dirate=8)
+
+ self.rebnconv3d = REBNCONV(mid_ch * 2, mid_ch, dirate=4)
+ self.rebnconv2d = REBNCONV(mid_ch * 2, mid_ch, dirate=2)
+ self.rebnconv1d = REBNCONV(mid_ch * 2, out_ch, dirate=1)
+
+ def forward(self, x):
+ hx = x
+
+ hxin = self.rebnconvin(hx)
+
+ hx1 = self.rebnconv1(hxin)
+ hx2 = self.rebnconv2(hx1)
+ hx3 = self.rebnconv3(hx2)
+
+ hx4 = self.rebnconv4(hx3)
+
+ hx3d = self.rebnconv3d(torch.cat((hx4, hx3), 1))
+ hx2d = self.rebnconv2d(torch.cat((hx3d, hx2), 1))
+ hx1d = self.rebnconv1d(torch.cat((hx2d, hx1), 1))
+
+ return hx1d + hxin
+
+
+class myrebnconv(nn.Module):
+ def __init__(
+ self,
+ in_ch=3,
+ out_ch=1,
+ kernel_size=3,
+ stride=1,
+ padding=1,
+ dilation=1,
+ groups=1,
+ ):
+ super(myrebnconv, self).__init__()
+
+ self.conv = nn.Conv2d(
+ in_ch,
+ out_ch,
+ kernel_size=kernel_size,
+ stride=stride,
+ padding=padding,
+ dilation=dilation,
+ groups=groups,
+ )
+ self.bn = nn.BatchNorm2d(out_ch)
+ self.rl = nn.ReLU(inplace=True)
+
+ def forward(self, x):
+ return self.rl(self.bn(self.conv(x)))
+
+
+class ISNetGTEncoder(nn.Module):
+ def __init__(self, in_ch=1, out_ch=1):
+ super(ISNetGTEncoder, self).__init__()
+
+ self.conv_in = myrebnconv(
+ in_ch, 16, 3, stride=2, padding=1
+ ) # nn.Conv2d(in_ch,64,3,stride=2,padding=1)
+
+ self.stage1 = RSU7(16, 16, 64)
+ self.pool12 = nn.MaxPool2d(2, stride=2, ceil_mode=True)
+
+ self.stage2 = RSU6(64, 16, 64)
+ self.pool23 = nn.MaxPool2d(2, stride=2, ceil_mode=True)
+
+ self.stage3 = RSU5(64, 32, 128)
+ self.pool34 = nn.MaxPool2d(2, stride=2, ceil_mode=True)
+
+ self.stage4 = RSU4(128, 32, 256)
+ self.pool45 = nn.MaxPool2d(2, stride=2, ceil_mode=True)
+
+ self.stage5 = RSU4F(256, 64, 512)
+ self.pool56 = nn.MaxPool2d(2, stride=2, ceil_mode=True)
+
+ self.stage6 = RSU4F(512, 64, 512)
+
+ self.side1 = nn.Conv2d(64, out_ch, 3, padding=1)
+ self.side2 = nn.Conv2d(64, out_ch, 3, padding=1)
+ self.side3 = nn.Conv2d(128, out_ch, 3, padding=1)
+ self.side4 = nn.Conv2d(256, out_ch, 3, padding=1)
+ self.side5 = nn.Conv2d(512, out_ch, 3, padding=1)
+ self.side6 = nn.Conv2d(512, out_ch, 3, padding=1)
+
+ @staticmethod
+ def compute_loss(args):
+ preds, targets = args
+ return muti_loss_fusion(preds, targets)
+
+ def forward(self, x):
+ hx = x
+
+ hxin = self.conv_in(hx)
+ # hx = self.pool_in(hxin)
+
+ # stage 1
+ hx1 = self.stage1(hxin)
+ hx = self.pool12(hx1)
+
+ # stage 2
+ hx2 = self.stage2(hx)
+ hx = self.pool23(hx2)
+
+ # stage 3
+ hx3 = self.stage3(hx)
+ hx = self.pool34(hx3)
+
+ # stage 4
+ hx4 = self.stage4(hx)
+ hx = self.pool45(hx4)
+
+ # stage 5
+ hx5 = self.stage5(hx)
+ hx = self.pool56(hx5)
+
+ # stage 6
+ hx6 = self.stage6(hx)
+
+ # side output
+ d1 = self.side1(hx1)
+ d1 = _upsample_like(d1, x)
+
+ d2 = self.side2(hx2)
+ d2 = _upsample_like(d2, x)
+
+ d3 = self.side3(hx3)
+ d3 = _upsample_like(d3, x)
+
+ d4 = self.side4(hx4)
+ d4 = _upsample_like(d4, x)
+
+ d5 = self.side5(hx5)
+ d5 = _upsample_like(d5, x)
+
+ d6 = self.side6(hx6)
+ d6 = _upsample_like(d6, x)
+
+ # d0 = self.outconv(torch.cat((d1,d2,d3,d4,d5,d6),1))
+
+ # return [torch.sigmoid(d1), torch.sigmoid(d2), torch.sigmoid(d3), torch.sigmoid(d4), torch.sigmoid(d5), torch.sigmoid(d6)], [hx1, hx2, hx3, hx4, hx5, hx6]
+ return [d1, d2, d3, d4, d5, d6], [hx1, hx2, hx3, hx4, hx5, hx6]
+
+
+class ISNetDIS(nn.Module):
+ def __init__(self, in_ch=3, out_ch=1):
+ super(ISNetDIS, self).__init__()
+
+ self.conv_in = nn.Conv2d(in_ch, 64, 3, stride=2, padding=1)
+ self.pool_in = nn.MaxPool2d(2, stride=2, ceil_mode=True)
+
+ self.stage1 = RSU7(64, 32, 64)
+ self.pool12 = nn.MaxPool2d(2, stride=2, ceil_mode=True)
+
+ self.stage2 = RSU6(64, 32, 128)
+ self.pool23 = nn.MaxPool2d(2, stride=2, ceil_mode=True)
+
+ self.stage3 = RSU5(128, 64, 256)
+ self.pool34 = nn.MaxPool2d(2, stride=2, ceil_mode=True)
+
+ self.stage4 = RSU4(256, 128, 512)
+ self.pool45 = nn.MaxPool2d(2, stride=2, ceil_mode=True)
+
+ self.stage5 = RSU4F(512, 256, 512)
+ self.pool56 = nn.MaxPool2d(2, stride=2, ceil_mode=True)
+
+ self.stage6 = RSU4F(512, 256, 512)
+
+ # decoder
+ self.stage5d = RSU4F(1024, 256, 512)
+ self.stage4d = RSU4(1024, 128, 256)
+ self.stage3d = RSU5(512, 64, 128)
+ self.stage2d = RSU6(256, 32, 64)
+ self.stage1d = RSU7(128, 16, 64)
+
+ self.side1 = nn.Conv2d(64, out_ch, 3, padding=1)
+ self.side2 = nn.Conv2d(64, out_ch, 3, padding=1)
+ self.side3 = nn.Conv2d(128, out_ch, 3, padding=1)
+ self.side4 = nn.Conv2d(256, out_ch, 3, padding=1)
+ self.side5 = nn.Conv2d(512, out_ch, 3, padding=1)
+ self.side6 = nn.Conv2d(512, out_ch, 3, padding=1)
+
+ # self.outconv = nn.Conv2d(6*out_ch,out_ch,1)
+
+ @staticmethod
+ def compute_loss_kl(preds, targets, dfs, fs, mode="MSE"):
+ return muti_loss_fusion_kl(preds, targets, dfs, fs, mode=mode)
+
+ @staticmethod
+ def compute_loss(args):
+ if len(args) == 3:
+ ds, dfs, labels = args
+ return muti_loss_fusion(ds, labels)
+ else:
+ ds, dfs, labels, fs = args
+ return muti_loss_fusion_kl(ds, labels, dfs, fs, mode="MSE")
+
+ def forward(self, x):
+ hx = x
+
+ hxin = self.conv_in(hx)
+ hx = self.pool_in(hxin)
+
+ # stage 1
+ hx1 = self.stage1(hxin)
+ hx = self.pool12(hx1)
+
+ # stage 2
+ hx2 = self.stage2(hx)
+ hx = self.pool23(hx2)
+
+ # stage 3
+ hx3 = self.stage3(hx)
+ hx = self.pool34(hx3)
+
+ # stage 4
+ hx4 = self.stage4(hx)
+ hx = self.pool45(hx4)
+
+ # stage 5
+ hx5 = self.stage5(hx)
+ hx = self.pool56(hx5)
+
+ # stage 6
+ hx6 = self.stage6(hx)
+ hx6up = _upsample_like(hx6, hx5)
+
+ # -------------------- decoder --------------------
+ hx5d = self.stage5d(torch.cat((hx6up, hx5), 1))
+ hx5dup = _upsample_like(hx5d, hx4)
+
+ hx4d = self.stage4d(torch.cat((hx5dup, hx4), 1))
+ hx4dup = _upsample_like(hx4d, hx3)
+
+ hx3d = self.stage3d(torch.cat((hx4dup, hx3), 1))
+ hx3dup = _upsample_like(hx3d, hx2)
+
+ hx2d = self.stage2d(torch.cat((hx3dup, hx2), 1))
+ hx2dup = _upsample_like(hx2d, hx1)
+
+ hx1d = self.stage1d(torch.cat((hx2dup, hx1), 1))
+
+ # side output
+ d1 = self.side1(hx1d)
+ d1 = _upsample_like(d1, x)
+
+ d2 = self.side2(hx2d)
+ d2 = _upsample_like(d2, x)
+
+ d3 = self.side3(hx3d)
+ d3 = _upsample_like(d3, x)
+
+ d4 = self.side4(hx4d)
+ d4 = _upsample_like(d4, x)
+
+ d5 = self.side5(hx5d)
+ d5 = _upsample_like(d5, x)
+
+ d6 = self.side6(hx6)
+ d6 = _upsample_like(d6, x)
+
+ # d0 = self.outconv(torch.cat((d1,d2,d3,d4,d5,d6),1))
+
+ # return [torch.sigmoid(d1), torch.sigmoid(d2), torch.sigmoid(d3), torch.sigmoid(d4), torch.sigmoid(d5), torch.sigmoid(d6)], [hx1d, hx2d, hx3d, hx4d, hx5d, hx6]
+ return [d1, d2, d3, d4, d5, d6], [hx1d, hx2d, hx3d, hx4d, hx5d, hx6]
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/anime_face_segment/network.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/anime_face_segment/network.py
new file mode 100644
index 0000000000000000000000000000000000000000..2ab2a44e98dc67f9dbdb281c573470d35ede96be
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/anime_face_segment/network.py
@@ -0,0 +1,98 @@
+#https://github.com/siyeong0/Anime-Face-Segmentation/blob/main/network.py
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+import torchvision
+from torchvision.models import MobileNet_V2_Weights
+
+class UNet(nn.Module):
+ def __init__(self):
+ super(UNet, self).__init__()
+ self.NUM_SEG_CLASSES = 7 # Background, hair, face, eye, mouth, skin, clothes
+
+ mobilenet_v2 = torchvision.models.mobilenet_v2(weights=MobileNet_V2_Weights.IMAGENET1K_V1)
+ mob_blocks = mobilenet_v2.features
+
+ # Encoder
+ self.en_block0 = nn.Sequential( # in_ch=3 out_ch=16
+ mob_blocks[0],
+ mob_blocks[1]
+ )
+ self.en_block1 = nn.Sequential( # in_ch=16 out_ch=24
+ mob_blocks[2],
+ mob_blocks[3],
+ )
+ self.en_block2 = nn.Sequential( # in_ch=24 out_ch=32
+ mob_blocks[4],
+ mob_blocks[5],
+ mob_blocks[6],
+ )
+ self.en_block3 = nn.Sequential( # in_ch=32 out_ch=96
+ mob_blocks[7],
+ mob_blocks[8],
+ mob_blocks[9],
+ mob_blocks[10],
+ mob_blocks[11],
+ mob_blocks[12],
+ mob_blocks[13],
+ )
+ self.en_block4 = nn.Sequential( # in_ch=96 out_ch=160
+ mob_blocks[14],
+ mob_blocks[15],
+ mob_blocks[16],
+ )
+
+ # Decoder
+ self.de_block4 = nn.Sequential( # in_ch=160 out_ch=96
+ nn.UpsamplingNearest2d(scale_factor=2),
+ nn.Conv2d(160, 96, kernel_size=3, padding=1),
+ nn.InstanceNorm2d(96),
+ nn.LeakyReLU(0.1),
+ nn.Dropout(p=0.2)
+ )
+ self.de_block3 = nn.Sequential( # in_ch=96x2 out_ch=32
+ nn.UpsamplingNearest2d(scale_factor=2),
+ nn.Conv2d(96*2, 32, kernel_size=3, padding=1),
+ nn.InstanceNorm2d(32),
+ nn.LeakyReLU(0.1),
+ nn.Dropout(p=0.2)
+ )
+ self.de_block2 = nn.Sequential( # in_ch=32x2 out_ch=24
+ nn.UpsamplingNearest2d(scale_factor=2),
+ nn.Conv2d(32*2, 24, kernel_size=3, padding=1),
+ nn.InstanceNorm2d(24),
+ nn.LeakyReLU(0.1),
+ nn.Dropout(p=0.2)
+ )
+ self.de_block1 = nn.Sequential( # in_ch=24x2 out_ch=16
+ nn.UpsamplingNearest2d(scale_factor=2),
+ nn.Conv2d(24*2, 16, kernel_size=3, padding=1),
+ nn.InstanceNorm2d(16),
+ nn.LeakyReLU(0.1),
+ nn.Dropout(p=0.2)
+ )
+
+ self.de_block0 = nn.Sequential( # in_ch=16x2 out_ch=7
+ nn.UpsamplingNearest2d(scale_factor=2),
+ nn.Conv2d(16*2, self.NUM_SEG_CLASSES, kernel_size=3, padding=1),
+ nn.Softmax2d()
+ )
+
+ def forward(self, x):
+ e0 = self.en_block0(x)
+ e1 = self.en_block1(e0)
+ e2 = self.en_block2(e1)
+ e3 = self.en_block3(e2)
+ e4 = self.en_block4(e3)
+
+ d4 = self.de_block4(e4)
+ c4 = torch.cat((d4,e3),1)
+ d3 = self.de_block3(c4)
+ c3 = torch.cat((d3,e2),1)
+ d2 = self.de_block2(c3)
+ c2 =torch.cat((d2,e1),1)
+ d1 = self.de_block1(c2)
+ c1 = torch.cat((d1,e0),1)
+ y = self.de_block0(c1)
+
+ return y
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/anime_face_segment/util.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/anime_face_segment/util.py
new file mode 100644
index 0000000000000000000000000000000000000000..f6f3d22543675f9b098b8bc57d244c9e437d0636
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/anime_face_segment/util.py
@@ -0,0 +1,40 @@
+#https://github.com/siyeong0/Anime-Face-Segmentation/blob/main/util.py
+#The color palette is changed according to https://github.com/Mikubill/sd-webui-controlnet/blob/91f67ddcc7bc47537a6285864abfc12590f46c3f/annotator/anime_face_segment/__init__.py
+import cv2 as cv
+import glob
+import numpy as np
+import os
+
+"""
+COLOR_BACKGROUND = (0,255,255)
+COLOR_HAIR = (255,0,0)
+COLOR_EYE = (0,0,255)
+COLOR_MOUTH = (255,255,255)
+COLOR_FACE = (0,255,0)
+COLOR_SKIN = (255,255,0)
+COLOR_CLOTHES = (255,0,255)
+"""
+COLOR_BACKGROUND = (255,255,0)
+COLOR_HAIR = (0,0,255)
+COLOR_EYE = (255,0,0)
+COLOR_MOUTH = (255,255,255)
+COLOR_FACE = (0,255,0)
+COLOR_SKIN = (0,255,255)
+COLOR_CLOTHES = (255,0,255)
+PALETTE = [COLOR_BACKGROUND,COLOR_HAIR,COLOR_EYE,COLOR_MOUTH,COLOR_FACE,COLOR_SKIN,COLOR_CLOTHES]
+
+def img2seg(path):
+ src = cv.imread(path)
+ src = src.reshape(-1, 3)
+ seg_list = []
+ for color in PALETTE:
+ seg_list.append(np.where(np.all(src==color, axis=1), 1.0, 0.0))
+ dst = np.stack(seg_list,axis=1).reshape(512,512,7)
+
+ return dst.astype(np.float32)
+
+def seg2img(src):
+ src = np.moveaxis(src,0,2)
+ dst = [[PALETTE[np.argmax(val)] for val in buf]for buf in src]
+
+ return np.array(dst).astype(np.uint8)
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/binary/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/binary/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e45cdd6c23e23f7785486f7c566ce1924eefc657
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/binary/__init__.py
@@ -0,0 +1,38 @@
+import warnings
+import cv2
+import numpy as np
+from PIL import Image
+from controlnet_aux.util import HWC3, resize_image_with_pad
+
+class BinaryDetector:
+ def __call__(self, input_image=None, bin_threshold=0, detect_resolution=512, output_type=None, upscale_method="INTER_CUBIC", **kwargs):
+ if "img" in kwargs:
+ warnings.warn("img is deprecated, please use `input_image=...` instead.", DeprecationWarning)
+ input_image = kwargs.pop("img")
+
+ if input_image is None:
+ raise ValueError("input_image must be defined.")
+
+ if not isinstance(input_image, np.ndarray):
+ input_image = np.array(input_image, dtype=np.uint8)
+ output_type = output_type or "pil"
+ else:
+ output_type = output_type or "np"
+
+ detected_map, remove_pad = resize_image_with_pad(input_image, detect_resolution, upscale_method)
+
+ img_gray = cv2.cvtColor(detected_map, cv2.COLOR_RGB2GRAY)
+ if bin_threshold == 0 or bin_threshold == 255:
+ # Otsu's threshold
+ otsu_threshold, img_bin = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
+ print("Otsu threshold:", otsu_threshold)
+ else:
+ _, img_bin = cv2.threshold(img_gray, bin_threshold, 255, cv2.THRESH_BINARY_INV)
+
+ detected_map = cv2.cvtColor(img_bin, cv2.COLOR_GRAY2RGB)
+ detected_map = HWC3(remove_pad(255 - detected_map))
+
+ if output_type == "pil":
+ detected_map = Image.fromarray(detected_map)
+
+ return detected_map
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/canny/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/canny/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..647bd1189b50344e4bd6a9cbe2352edd0be2846e
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/canny/__init__.py
@@ -0,0 +1,17 @@
+import warnings
+import cv2
+import numpy as np
+from PIL import Image
+from controlnet_aux.util import resize_image_with_pad, common_input_validate, HWC3
+
+class CannyDetector:
+ def __call__(self, input_image=None, low_threshold=100, high_threshold=200, detect_resolution=512, output_type=None, upscale_method="INTER_CUBIC", **kwargs):
+ input_image, output_type = common_input_validate(input_image, output_type, **kwargs)
+ detected_map, remove_pad = resize_image_with_pad(input_image, detect_resolution, upscale_method)
+ detected_map = cv2.Canny(detected_map, low_threshold, high_threshold)
+ detected_map = HWC3(remove_pad(detected_map))
+
+ if output_type == "pil":
+ detected_map = Image.fromarray(detected_map)
+
+ return detected_map
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/color/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/color/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..52f872339a4e90b902723f7ff12e6530ed135760
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/color/__init__.py
@@ -0,0 +1,37 @@
+import cv2
+import warnings
+import cv2
+import numpy as np
+from PIL import Image
+from controlnet_aux.util import HWC3, safer_memory, common_input_validate
+
+def cv2_resize_shortest_edge(image, size):
+ h, w = image.shape[:2]
+ if h < w:
+ new_h = size
+ new_w = int(round(w / h * size))
+ else:
+ new_w = size
+ new_h = int(round(h / w * size))
+ resized_image = cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_AREA)
+ return resized_image
+
+def apply_color(img, res=512):
+ img = cv2_resize_shortest_edge(img, res)
+ h, w = img.shape[:2]
+
+ input_img_color = cv2.resize(img, (w//64, h//64), interpolation=cv2.INTER_CUBIC)
+ input_img_color = cv2.resize(input_img_color, (w, h), interpolation=cv2.INTER_NEAREST)
+ return input_img_color
+
+#Color T2I like multiples-of-64, upscale methods are fixed.
+class ColorDetector:
+ def __call__(self, input_image=None, detect_resolution=512, output_type=None, **kwargs):
+ input_image, output_type = common_input_validate(input_image, output_type, **kwargs)
+ input_image = HWC3(input_image)
+ detected_map = HWC3(apply_color(input_image, detect_resolution))
+
+ if output_type == "pil":
+ detected_map = Image.fromarray(detected_map)
+
+ return detected_map
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/densepose/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/densepose/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..7a723dd7a59af0549d5a9688f6b1bb70847470d9
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/densepose/__init__.py
@@ -0,0 +1,44 @@
+import functools
+import os
+import warnings
+
+import cv2
+import numpy as np
+import torch
+import torch.nn as nn
+from einops import rearrange
+from huggingface_hub import hf_hub_download
+from PIL import Image
+
+from controlnet_aux.util import HWC3, resize_image_with_pad, common_input_validate, annotator_ckpts_path, custom_hf_download
+
+class DenseposeDetector:
+ def __init__(self, model):
+ self.dense_pose_estimation = model
+ self.device = "cpu"
+
+ @classmethod
+ def from_pretrained(cls, pretrained_model_or_path, filename=None, cache_dir=annotator_ckpts_path):
+ torchscript_model_path = custom_hf_download(pretrained_model_or_path, filename, cache_dir=cache_dir)
+ densepose = torch.jit.load(torchscript_model_path, map_location="cpu")
+ return cls(densepose)
+
+ def to(self, device):
+ self.dense_pose_estimation.to(device)
+ self.device = device
+ return self
+
+ def __call__(self, input_image, detect_resolution=512, output_type="pil", upscale_method="INTER_CUBIC", cmap="viridis", **kwargs):
+ input_image, output_type = common_input_validate(input_image, output_type, **kwargs)
+ input_image, remove_pad = resize_image_with_pad(input_image, detect_resolution, upscale_method)
+ H, W = input_image.shape[:2]
+ input_image = rearrange(torch.from_numpy(input_image).to(self.device), 'h w c -> c h w')
+ detected_map = self.dense_pose_estimation(input_image)[-1 if cmap=="viridis" else -2]
+ if detected_map.all() == -1:
+ detected_map = np.zeros([H, W, 3], dtype=np.uint8)
+ else:
+ detected_map = detected_map.cpu().detach().numpy()
+ if output_type == "pil":
+ detected_map = Image.fromarray(detected_map)
+ detected_map = remove_pad(HWC3(detected_map))
+ return detected_map
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/LICENSE b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..6f60b76d35fa1012809985780964a5068adce4fd
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/LICENSE
@@ -0,0 +1,108 @@
+OPENPOSE: MULTIPERSON KEYPOINT DETECTION
+SOFTWARE LICENSE AGREEMENT
+ACADEMIC OR NON-PROFIT ORGANIZATION NONCOMMERCIAL RESEARCH USE ONLY
+
+BY USING OR DOWNLOADING THE SOFTWARE, YOU ARE AGREEING TO THE TERMS OF THIS LICENSE AGREEMENT. IF YOU DO NOT AGREE WITH THESE TERMS, YOU MAY NOT USE OR DOWNLOAD THE SOFTWARE.
+
+This is a license agreement ("Agreement") between your academic institution or non-profit organization or self (called "Licensee" or "You" in this Agreement) and Carnegie Mellon University (called "Licensor" in this Agreement). All rights not specifically granted to you in this Agreement are reserved for Licensor.
+
+RESERVATION OF OWNERSHIP AND GRANT OF LICENSE:
+Licensor retains exclusive ownership of any copy of the Software (as defined below) licensed under this Agreement and hereby grants to Licensee a personal, non-exclusive,
+non-transferable license to use the Software for noncommercial research purposes, without the right to sublicense, pursuant to the terms and conditions of this Agreement. As used in this Agreement, the term "Software" means (i) the actual copy of all or any portion of code for program routines made accessible to Licensee by Licensor pursuant to this Agreement, inclusive of backups, updates, and/or merged copies permitted hereunder or subsequently supplied by Licensor, including all or any file structures, programming instructions, user interfaces and screen formats and sequences as well as any and all documentation and instructions related to it, and (ii) all or any derivatives and/or modifications created or made by You to any of the items specified in (i).
+
+CONFIDENTIALITY: Licensee acknowledges that the Software is proprietary to Licensor, and as such, Licensee agrees to receive all such materials in confidence and use the Software only in accordance with the terms of this Agreement. Licensee agrees to use reasonable effort to protect the Software from unauthorized use, reproduction, distribution, or publication.
+
+COPYRIGHT: The Software is owned by Licensor and is protected by United
+States copyright laws and applicable international treaties and/or conventions.
+
+PERMITTED USES: The Software may be used for your own noncommercial internal research purposes. You understand and agree that Licensor is not obligated to implement any suggestions and/or feedback you might provide regarding the Software, but to the extent Licensor does so, you are not entitled to any compensation related thereto.
+
+DERIVATIVES: You may create derivatives of or make modifications to the Software, however, You agree that all and any such derivatives and modifications will be owned by Licensor and become a part of the Software licensed to You under this Agreement. You may only use such derivatives and modifications for your own noncommercial internal research purposes, and you may not otherwise use, distribute or copy such derivatives and modifications in violation of this Agreement.
+
+BACKUPS: If Licensee is an organization, it may make that number of copies of the Software necessary for internal noncommercial use at a single site within its organization provided that all information appearing in or on the original labels, including the copyright and trademark notices are copied onto the labels of the copies.
+
+USES NOT PERMITTED: You may not distribute, copy or use the Software except as explicitly permitted herein. Licensee has not been granted any trademark license as part of this Agreement and may not use the name or mark “OpenPose", "Carnegie Mellon" or any renditions thereof without the prior written permission of Licensor.
+
+You may not sell, rent, lease, sublicense, lend, time-share or transfer, in whole or in part, or provide third parties access to prior or present versions (or any parts thereof) of the Software.
+
+ASSIGNMENT: You may not assign this Agreement or your rights hereunder without the prior written consent of Licensor. Any attempted assignment without such consent shall be null and void.
+
+TERM: The term of the license granted by this Agreement is from Licensee's acceptance of this Agreement by downloading the Software or by using the Software until terminated as provided below.
+
+The Agreement automatically terminates without notice if you fail to comply with any provision of this Agreement. Licensee may terminate this Agreement by ceasing using the Software. Upon any termination of this Agreement, Licensee will delete any and all copies of the Software. You agree that all provisions which operate to protect the proprietary rights of Licensor shall remain in force should breach occur and that the obligation of confidentiality described in this Agreement is binding in perpetuity and, as such, survives the term of the Agreement.
+
+FEE: Provided Licensee abides completely by the terms and conditions of this Agreement, there is no fee due to Licensor for Licensee's use of the Software in accordance with this Agreement.
+
+DISCLAIMER OF WARRANTIES: THE SOFTWARE IS PROVIDED "AS-IS" WITHOUT WARRANTY OF ANY KIND INCLUDING ANY WARRANTIES OF PERFORMANCE OR MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE OR PURPOSE OR OF NON-INFRINGEMENT. LICENSEE BEARS ALL RISK RELATING TO QUALITY AND PERFORMANCE OF THE SOFTWARE AND RELATED MATERIALS.
+
+SUPPORT AND MAINTENANCE: No Software support or training by the Licensor is provided as part of this Agreement.
+
+EXCLUSIVE REMEDY AND LIMITATION OF LIABILITY: To the maximum extent permitted under applicable law, Licensor shall not be liable for direct, indirect, special, incidental, or consequential damages or lost profits related to Licensee's use of and/or inability to use the Software, even if Licensor is advised of the possibility of such damage.
+
+EXPORT REGULATION: Licensee agrees to comply with any and all applicable
+U.S. export control laws, regulations, and/or other laws related to embargoes and sanction programs administered by the Office of Foreign Assets Control.
+
+SEVERABILITY: If any provision(s) of this Agreement shall be held to be invalid, illegal, or unenforceable by a court or other tribunal of competent jurisdiction, the validity, legality and enforceability of the remaining provisions shall not in any way be affected or impaired thereby.
+
+NO IMPLIED WAIVERS: No failure or delay by Licensor in enforcing any right or remedy under this Agreement shall be construed as a waiver of any future or other exercise of such right or remedy by Licensor.
+
+GOVERNING LAW: This Agreement shall be construed and enforced in accordance with the laws of the Commonwealth of Pennsylvania without reference to conflict of laws principles. You consent to the personal jurisdiction of the courts of this County and waive their rights to venue outside of Allegheny County, Pennsylvania.
+
+ENTIRE AGREEMENT AND AMENDMENTS: This Agreement constitutes the sole and entire agreement between Licensee and Licensor as to the matter set forth herein and supersedes any previous agreements, understandings, and arrangements between the parties relating hereto.
+
+
+
+************************************************************************
+
+THIRD-PARTY SOFTWARE NOTICES AND INFORMATION
+
+This project incorporates material from the project(s) listed below (collectively, "Third Party Code"). This Third Party Code is licensed to you under their original license terms set forth below. We reserves all other rights not expressly granted, whether by implication, estoppel or otherwise.
+
+1. Caffe, version 1.0.0, (https://github.com/BVLC/caffe/)
+
+COPYRIGHT
+
+All contributions by the University of California:
+Copyright (c) 2014-2017 The Regents of the University of California (Regents)
+All rights reserved.
+
+All other contributions:
+Copyright (c) 2014-2017, the respective contributors
+All rights reserved.
+
+Caffe uses a shared copyright model: each contributor holds copyright over
+their contributions to Caffe. The project versioning records all such
+contribution and copyright details. If a contributor wants to further mark
+their specific copyright on a particular contribution, they should indicate
+their copyright solely in the commit message of the change when it is
+committed.
+
+LICENSE
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+CONTRIBUTION AGREEMENT
+
+By contributing to the BVLC/caffe repository through pull-request, comment,
+or otherwise, the contributor releases their content to the
+license and copyright terms herein.
+
+************END OF THIRD-PARTY SOFTWARE NOTICES AND INFORMATION**********
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e65a4b1fbf793aa68c983708561720a6ef05195d
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__init__.py
@@ -0,0 +1,261 @@
+# Openpose
+# Original from CMU https://github.com/CMU-Perceptual-Computing-Lab/openpose
+# 2nd Edited by https://github.com/Hzzone/pytorch-openpose
+# 3rd Edited by ControlNet
+# 4th Edited by ControlNet (added face and correct hands)
+# 5th Edited by ControlNet (Improved JSON serialization/deserialization, and lots of bug fixs)
+# This preprocessor is licensed by CMU for non-commercial use only.
+import torch.utils.benchmark as benchmark
+benchmark.timer()
+
+import os
+os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
+
+import json
+import torch
+import numpy as np
+from . import util
+from .body import Body, BodyResult, Keypoint
+from .hand import Hand
+from .face import Face
+from .types import PoseResult, HandResult, FaceResult
+from huggingface_hub import hf_hub_download
+from .wholebody import Wholebody
+import warnings
+from controlnet_aux.util import HWC3, resize_image_with_pad, common_input_validate, annotator_ckpts_path, custom_hf_download
+import cv2
+from PIL import Image
+from .animalpose import AnimalPoseImage
+
+from typing import Tuple, List, Callable, Union, Optional
+
+def draw_poses(poses: List[PoseResult], H, W, draw_body=True, draw_hand=True, draw_face=True):
+ """
+ Draw the detected poses on an empty canvas.
+
+ Args:
+ poses (List[PoseResult]): A list of PoseResult objects containing the detected poses.
+ H (int): The height of the canvas.
+ W (int): The width of the canvas.
+ draw_body (bool, optional): Whether to draw body keypoints. Defaults to True.
+ draw_hand (bool, optional): Whether to draw hand keypoints. Defaults to True.
+ draw_face (bool, optional): Whether to draw face keypoints. Defaults to True.
+
+ Returns:
+ numpy.ndarray: A 3D numpy array representing the canvas with the drawn poses.
+ """
+ canvas = np.zeros(shape=(H, W, 3), dtype=np.uint8)
+
+ for pose in poses:
+ if draw_body:
+ canvas = util.draw_bodypose(canvas, pose.body.keypoints)
+
+ if draw_hand:
+ canvas = util.draw_handpose(canvas, pose.left_hand)
+ canvas = util.draw_handpose(canvas, pose.right_hand)
+
+ if draw_face:
+ canvas = util.draw_facepose(canvas, pose.face)
+
+ return canvas
+
+
+def decode_json_as_poses(json_string: str, normalize_coords: bool = False) -> Tuple[List[PoseResult], int, int]:
+ """ Decode the json_string complying with the openpose JSON output format
+ to poses that controlnet recognizes.
+ https://github.com/CMU-Perceptual-Computing-Lab/openpose/blob/master/doc/02_output.md
+
+ Args:
+ json_string: The json string to decode.
+ normalize_coords: Whether to normalize coordinates of each keypoint by canvas height/width.
+ `draw_pose` only accepts normalized keypoints. Set this param to True if
+ the input coords are not normalized.
+
+ Returns:
+ poses
+ canvas_height
+ canvas_width
+ """
+ pose_json = json.loads(json_string)
+ height = pose_json['canvas_height']
+ width = pose_json['canvas_width']
+
+ def chunks(lst, n):
+ """Yield successive n-sized chunks from lst."""
+ for i in range(0, len(lst), n):
+ yield lst[i:i + n]
+
+ def decompress_keypoints(numbers: Optional[List[float]]) -> Optional[List[Optional[Keypoint]]]:
+ if not numbers:
+ return None
+
+ assert len(numbers) % 3 == 0
+
+ def create_keypoint(x, y, c):
+ if c < 1.0:
+ return None
+ keypoint = Keypoint(x, y)
+ return keypoint
+
+ return [
+ create_keypoint(x, y, c)
+ for x, y, c in chunks(numbers, n=3)
+ ]
+
+ return (
+ [
+ PoseResult(
+ body=BodyResult(keypoints=decompress_keypoints(pose.get('pose_keypoints_2d'))),
+ left_hand=decompress_keypoints(pose.get('hand_left_keypoints_2d')),
+ right_hand=decompress_keypoints(pose.get('hand_right_keypoints_2d')),
+ face=decompress_keypoints(pose.get('face_keypoints_2d'))
+ )
+ for pose in pose_json['people']
+ ],
+ height,
+ width,
+ )
+
+
+def encode_poses_as_dict(poses: List[PoseResult], canvas_height: int, canvas_width: int) -> str:
+ """ Encode the pose as a dict following openpose JSON output format:
+ https://github.com/CMU-Perceptual-Computing-Lab/openpose/blob/master/doc/02_output.md
+ """
+ def compress_keypoints(keypoints: Union[List[Keypoint], None]) -> Union[List[float], None]:
+ if not keypoints:
+ return None
+
+ return [
+ value
+ for keypoint in keypoints
+ for value in (
+ [float(keypoint.x), float(keypoint.y), 1.0]
+ if keypoint is not None
+ else [0.0, 0.0, 0.0]
+ )
+ ]
+
+ return {
+ 'people': [
+ {
+ 'pose_keypoints_2d': compress_keypoints(pose.body.keypoints),
+ "face_keypoints_2d": compress_keypoints(pose.face),
+ "hand_left_keypoints_2d": compress_keypoints(pose.left_hand),
+ "hand_right_keypoints_2d":compress_keypoints(pose.right_hand),
+ }
+ for pose in poses
+ ],
+ 'canvas_height': canvas_height,
+ 'canvas_width': canvas_width,
+ }
+
+global_cached_dwpose = Wholebody()
+
+class DwposeDetector:
+ """
+ A class for detecting human poses in images using the Dwpose model.
+
+ Attributes:
+ model_dir (str): Path to the directory where the pose models are stored.
+ """
+ def __init__(self, dw_pose_estimation):
+ self.dw_pose_estimation = dw_pose_estimation
+
+ @classmethod
+ def from_pretrained(cls, pretrained_model_or_path, pretrained_det_model_or_path=None, det_filename=None, pose_filename=None, cache_dir=annotator_ckpts_path, torchscript_device="cuda"):
+ global global_cached_dwpose
+ pretrained_det_model_or_path = pretrained_det_model_or_path or pretrained_model_or_path
+ det_filename = det_filename or "yolox_l.onnx"
+ pose_filename = pose_filename or "dw-ll_ucoco_384.onnx"
+ det_model_path = custom_hf_download(pretrained_det_model_or_path, det_filename, cache_dir=cache_dir)
+ pose_model_path = custom_hf_download(pretrained_model_or_path, pose_filename, cache_dir=cache_dir)
+
+ print(f"\nDWPose: Using {det_filename} for bbox detection and {pose_filename} for pose estimation")
+ if global_cached_dwpose.det is None or global_cached_dwpose.det_filename != det_filename:
+ t = Wholebody(det_model_path, None, torchscript_device=torchscript_device)
+ t.pose = global_cached_dwpose.pose
+ t.pose_filename = global_cached_dwpose.pose
+ global_cached_dwpose = t
+
+ if global_cached_dwpose.pose is None or global_cached_dwpose.pose_filename != pose_filename:
+ t = Wholebody(None, pose_model_path, torchscript_device=torchscript_device)
+ t.det = global_cached_dwpose.det
+ t.det_filename = global_cached_dwpose.det_filename
+ global_cached_dwpose = t
+ return cls(global_cached_dwpose)
+
+ def detect_poses(self, oriImg) -> List[PoseResult]:
+ with torch.no_grad():
+ keypoints_info = self.dw_pose_estimation(oriImg.copy())
+ return Wholebody.format_result(keypoints_info)
+
+ def __call__(self, input_image, detect_resolution=512, include_body=True, include_hand=False, include_face=False, hand_and_face=None, output_type="pil", image_and_json=False, upscale_method="INTER_CUBIC", **kwargs):
+ if hand_and_face is not None:
+ warnings.warn("hand_and_face is deprecated. Use include_hand and include_face instead.", DeprecationWarning)
+ include_hand = hand_and_face
+ include_face = hand_and_face
+
+ input_image, output_type = common_input_validate(input_image, output_type, **kwargs)
+ input_image, remove_pad = resize_image_with_pad(input_image, detect_resolution, upscale_method)
+
+ poses = self.detect_poses(input_image)
+ detected_map = remove_pad(input_image)
+ canvas = draw_poses(poses, detected_map.shape[0], detected_map.shape[1], draw_body=include_body, draw_hand=include_hand, draw_face=include_face)
+
+ detected_map = HWC3(canvas)
+
+ if output_type == "pil":
+ detected_map = Image.fromarray(detected_map)
+
+ if image_and_json:
+ return (detected_map, encode_poses_as_dict(poses, detected_map.shape[0], detected_map.shape[1]))
+
+ return detected_map
+
+global_cached_animalpose = AnimalPoseImage()
+class AnimalposeDetector:
+ """
+ A class for detecting animal poses in images using the RTMPose AP10k model.
+
+ Attributes:
+ model_dir (str): Path to the directory where the pose models are stored.
+ """
+ def __init__(self, animal_pose_estimation):
+ self.animal_pose_estimation = animal_pose_estimation
+
+ @classmethod
+ def from_pretrained(cls, pretrained_model_or_path, pretrained_det_model_or_path=None, det_filename=None, pose_filename=None, cache_dir=annotator_ckpts_path, torchscript_device="cuda"):
+ global global_cached_animalpose
+ pretrained_det_model_or_path = pretrained_det_model_or_path or pretrained_model_or_path
+ det_filename = det_filename or "yolox_l.onnx"
+ pose_filename = pose_filename or "dw-ll_ucoco_384.onnx"
+ det_model_path = custom_hf_download(pretrained_det_model_or_path, det_filename, cache_dir=cache_dir)
+ pose_model_path = custom_hf_download(pretrained_model_or_path, pose_filename, cache_dir=cache_dir)
+
+ print(f"\nAnimalPose: Using {det_filename} for bbox detection and {pose_filename} for pose estimation")
+ if global_cached_animalpose.det is None or global_cached_animalpose.det_filename != det_filename:
+ t = AnimalPoseImage(det_model_path, None, torchscript_device=torchscript_device)
+ t.pose = global_cached_animalpose.pose
+ t.pose_filename = global_cached_animalpose.pose
+ global_cached_animalpose = t
+
+ if global_cached_animalpose.pose is None or global_cached_animalpose.pose_filename != pose_filename:
+ t = AnimalPoseImage(None, pose_model_path, torchscript_device=torchscript_device)
+ t.det = global_cached_animalpose.det
+ t.det_filename = global_cached_animalpose.det_filename
+ global_cached_animalpose = t
+ return cls(global_cached_animalpose)
+
+ def __call__(self, input_image, detect_resolution=512, output_type="pil", image_and_json=False, upscale_method="INTER_CUBIC", **kwargs):
+ input_image, output_type = common_input_validate(input_image, output_type, **kwargs)
+ input_image, remove_pad = resize_image_with_pad(input_image, detect_resolution, upscale_method)
+ detected_map, openpose_dict = self.animal_pose_estimation(input_image)
+ detected_map = remove_pad(detected_map)
+
+ if output_type == "pil":
+ detected_map = Image.fromarray(detected_map)
+
+ if image_and_json:
+ return (detected_map, openpose_dict)
+
+ return detected_map
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/__init__.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7db1357c010925f540568e76f8e889ace4585e09
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/__init__.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/__init__.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e46572eb171019b910eee2ba7b945a1638794509
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/__init__.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/animalpose.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/animalpose.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..cbd9120b48c3066878d7ef53e69585ae02673565
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/animalpose.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/body.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/body.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5167f929ad7b500b49577d31006859aaa068d728
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/body.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/body.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/body.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..26c100de75fb2e355b695b1ddef66646f56ffa9d
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/body.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/cv_ox_det.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/cv_ox_det.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8828c9011663c9690573865eb18cfbf843e15f49
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/cv_ox_det.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/cv_ox_det.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/cv_ox_det.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ce7a60a31076fa6cf3a030f13aee403552cf8de6
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/cv_ox_det.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/cv_ox_pose.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/cv_ox_pose.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..3c0312ee26a46e1078b878a45f3636c628e1f72e
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/cv_ox_pose.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/cv_ox_pose.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/cv_ox_pose.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7cc0ab910115ecadbca3f4113adbdd71742a583e
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/cv_ox_pose.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/face.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/face.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..6d54916655108e7f5069afbc117ccf19a897f1be
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/face.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/face.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/face.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..005285d8d6df71321065a80882acd25b2e63c6c8
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/face.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/hand.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/hand.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7ae217e9aa0116286a64f67256b977c42dac417a
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/hand.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/hand.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/hand.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..41e26c57bb1f6668464eb44f724304322f6bfe20
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/hand.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/model.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/model.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..29d67975885ee38a8d3001c09b14176d137b3a01
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/model.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/model.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/model.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5c2e9648e15f9a7aa51818f2586f6db749a5a462
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/model.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/types.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/types.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1f3460ef890b49f644d61ae6febb0e03730de640
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/types.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/types.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/types.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..40df47ab72dc909e5ce87b3e0ac75c9a8835970d
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/types.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/util.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/util.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..bf756fb2b26888b8d90a88bdfb3f5295dc25212d
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/util.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/util.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/util.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..cb2a8ffabc6d078b9e1e8b8399d949eea435fd5a
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/util.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/wholebody.cpython-310.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/wholebody.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..73535efdc8b9a8178d090eb3604a3bc2aa754f73
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/wholebody.cpython-310.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/wholebody.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/wholebody.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..85c67e8a5df39af5bd8cfbc843060cef9c85ec26
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/wholebody.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/yolo_nas.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/yolo_nas.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..56f329e7f8eef450779bd2d4df4caa6d18bc9459
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/__pycache__/yolo_nas.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/animalpose.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/animalpose.py
new file mode 100644
index 0000000000000000000000000000000000000000..50bd63e882ac66ae88e3bfbd4defd206570cd7c4
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/animalpose.py
@@ -0,0 +1,273 @@
+import numpy as np
+import cv2
+import os
+import cv2
+from .dw_onnx.cv_ox_det import inference_detector as inference_onnx_yolox
+from .dw_onnx.cv_ox_yolo_nas import inference_detector as inference_onnx_yolo_nas
+from .dw_onnx.cv_ox_pose import inference_pose as inference_onnx_pose
+
+from .dw_torchscript.jit_det import inference_detector as inference_jit_yolox
+from .dw_torchscript.jit_pose import inference_pose as inference_jit_pose
+from typing import List, Optional
+from .types import PoseResult, BodyResult, Keypoint
+from timeit import default_timer
+from controlnet_aux.dwpose.util import guess_onnx_input_shape_dtype, get_ort_providers, get_model_type, is_model_torchscript
+import json
+import torch
+import torch.utils.benchmark.utils.timer as torch_timer
+
+def drawBetweenKeypoints(pose_img, keypoints, indexes, color, scaleFactor):
+ ind0 = indexes[0] - 1
+ ind1 = indexes[1] - 1
+
+ point1 = (keypoints[ind0][0], keypoints[ind0][1])
+ point2 = (keypoints[ind1][0], keypoints[ind1][1])
+
+ thickness = int(5 // scaleFactor)
+
+
+ cv2.line(pose_img, (int(point1[0]), int(point1[1])), (int(point2[0]), int(point2[1])), color, thickness)
+
+
+def drawBetweenKeypointsList(pose_img, keypoints, keypointPairsList, colorsList, scaleFactor):
+ for ind, keypointPair in enumerate(keypointPairsList):
+ drawBetweenKeypoints(pose_img, keypoints, keypointPair, colorsList[ind], scaleFactor)
+
+def drawBetweenSetofKeypointLists(pose_img, keypoints_set, keypointPairsList, colorsList, scaleFactor):
+ for keypoints in keypoints_set:
+ drawBetweenKeypointsList(pose_img, keypoints, keypointPairsList, colorsList, scaleFactor)
+
+
+def padImg(img, size, blackBorder=True):
+ left, right, top, bottom = 0, 0, 0, 0
+
+ # pad x
+ if img.shape[1] < size[1]:
+ sidePadding = int((size[1] - img.shape[1]) // 2)
+ left = sidePadding
+ right = sidePadding
+
+ # pad extra on right if padding needed is an odd number
+ if img.shape[1] % 2 == 1:
+ right += 1
+
+ # pad y
+ if img.shape[0] < size[0]:
+ topBottomPadding = int((size[0] - img.shape[0]) // 2)
+ top = topBottomPadding
+ bottom = topBottomPadding
+
+ # pad extra on bottom if padding needed is an odd number
+ if img.shape[0] % 2 == 1:
+ bottom += 1
+
+ if blackBorder:
+ paddedImg = cv2.copyMakeBorder(src=img, top=top, bottom=bottom, left=left, right=right, borderType=cv2.BORDER_CONSTANT, value=(0,0,0))
+ else:
+ paddedImg = cv2.copyMakeBorder(src=img, top=top, bottom=bottom, left=left, right=right, borderType=cv2.BORDER_REPLICATE)
+
+ return paddedImg
+
+def smartCrop(img, size, center):
+
+ width = img.shape[1]
+ height = img.shape[0]
+ xSize = size[1]
+ ySize = size[0]
+ xCenter = center[0]
+ yCenter = center[1]
+
+ if img.shape[0] > size[0] or img.shape[1] > size[1]:
+
+
+ leftMargin = xCenter - xSize//2
+ rightMargin = xCenter + xSize//2
+ upMargin = yCenter - ySize//2
+ downMargin = yCenter + ySize//2
+
+
+ if(leftMargin < 0):
+ xCenter += (-leftMargin)
+ if(rightMargin > width):
+ xCenter -= (rightMargin - width)
+
+ if(upMargin < 0):
+ yCenter -= -upMargin
+ if(downMargin > height):
+ yCenter -= (downMargin - height)
+
+
+ img = cv2.getRectSubPix(img, size, (xCenter, yCenter))
+
+
+
+ return img
+
+
+
+def calculateScaleFactor(img, size, poseSpanX, poseSpanY):
+
+ poseSpanX = max(poseSpanX, size[0])
+
+ scaleFactorX = 1
+
+
+ if poseSpanX > size[0]:
+ scaleFactorX = size[0] / poseSpanX
+
+ scaleFactorY = 1
+ if poseSpanY > size[1]:
+ scaleFactorY = size[1] / poseSpanY
+
+ scaleFactor = min(scaleFactorX, scaleFactorY)
+
+
+ return scaleFactor
+
+
+
+def scaleImg(img, size, poseSpanX, poseSpanY, scaleFactor):
+ scaledImg = img
+
+ scaledImg = cv2.resize(img, (0, 0), fx=scaleFactor, fy=scaleFactor)
+
+ return scaledImg, scaleFactor
+
+class AnimalPoseImage:
+ def __init__(self, det_model_path: Optional[str] = None, pose_model_path: Optional[str] = None, torchscript_device="cuda"):
+ self.det_filename = det_model_path and os.path.basename(det_model_path)
+ self.pose_filename = pose_model_path and os.path.basename(pose_model_path)
+ self.det, self.pose = None, None
+ # return type: None ort cv2 torchscript
+ self.det_model_type = get_model_type("AnimalPose",self.det_filename)
+ self.pose_model_type = get_model_type("AnimalPose",self.pose_filename)
+ # Always loads to CPU to avoid building OpenCV.
+ cv2_device = 'cpu'
+ cv2_backend = cv2.dnn.DNN_BACKEND_OPENCV if cv2_device == 'cpu' else cv2.dnn.DNN_BACKEND_CUDA
+ # You need to manually build OpenCV through cmake to work with your GPU.
+ cv2_providers = cv2.dnn.DNN_TARGET_CPU if cv2_device == 'cpu' else cv2.dnn.DNN_TARGET_CUDA
+ ort_providers = get_ort_providers()
+
+ if self.det_model_type is None:
+ pass
+ elif self.det_model_type == "ort":
+ try:
+ import onnxruntime as ort
+ self.det = ort.InferenceSession(det_model_path, providers=ort_providers)
+ except:
+ print(f"Failed to load onnxruntime with {self.det.get_providers()}.\nPlease change EP_list in the config.yaml and restart ComfyUI")
+ self.det = ort.InferenceSession(det_model_path, providers=["CPUExecutionProvider"])
+ elif self.det_model_type == "cv2":
+ try:
+ self.det = cv2.dnn.readNetFromONNX(det_model_path)
+ self.det.setPreferableBackend(cv2_backend)
+ self.det.setPreferableTarget(cv2_providers)
+ except:
+ print("TopK operators may not work on your OpenCV, try use onnxruntime with CPUExecutionProvider")
+ try:
+ import onnxruntime as ort
+ self.det = ort.InferenceSession(det_model_path, providers=["CPUExecutionProvider"])
+ except:
+ print(f"Failed to load {det_model_path}, you can use other models instead")
+ else:
+ self.det = torch.jit.load(det_model_path)
+ self.det.to(torchscript_device)
+
+ if self.pose_model_type is None:
+ pass
+ elif self.pose_model_type == "ort":
+ try:
+ import onnxruntime as ort
+ self.pose = ort.InferenceSession(pose_model_path, providers=ort_providers)
+ except:
+ print(f"Failed to load onnxruntime with {self.pose.get_providers()}.\nPlease change EP_list in the config.yaml and restart ComfyUI")
+ self.pose = ort.InferenceSession(pose_model_path, providers=["CPUExecutionProvider"])
+ elif self.pose_model_type == "cv2":
+ self.pose = cv2.dnn.readNetFromONNX(pose_model_path)
+ self.pose.setPreferableBackend(cv2_backend)
+ self.pose.setPreferableTarget(cv2_providers)
+ else:
+ self.pose = torch.jit.load(pose_model_path)
+ self.pose.to(torchscript_device)
+
+ if self.pose_filename is not None:
+ self.pose_input_size, _ = guess_onnx_input_shape_dtype(self.pose_filename)
+
+ def __call__(self, oriImg) -> Optional[np.ndarray]:
+ detect_classes = list(range(14, 23 + 1)) #https://github.com/ultralytics/ultralytics/blob/main/ultralytics/cfg/datasets/coco.yaml
+
+ if is_model_torchscript(self.det):
+ det_start = torch_timer.timer()
+ det_result = inference_jit_yolox(self.det, oriImg, detect_classes=detect_classes)
+ print(f"AnimalPose: Bbox {((torch_timer.timer() - det_start) * 1000):.2f}ms")
+ else:
+ det_start = default_timer()
+ det_onnx_dtype = np.float32 if "yolox" in self.det_filename else np.uint8
+ if "yolox" in self.det_filename:
+ det_result = inference_onnx_yolox(self.det, oriImg, detect_classes=detect_classes, dtype=det_onnx_dtype)
+ else:
+ #FP16 and INT8 YOLO NAS accept uint8 input
+ det_result = inference_onnx_yolo_nas(self.det, oriImg, detect_classes=detect_classes, dtype=det_onnx_dtype)
+ print(f"AnimalPose: Bbox {((default_timer() - det_start) * 1000):.2f}ms")
+ if (det_result is None) or (det_result.shape[0] == 0):
+ openpose_dict = {
+ 'version': 'ap10k',
+ 'animals': [],
+ 'canvas_height': oriImg.shape[0],
+ 'canvas_width': oriImg.shape[1]
+ }
+ return np.zeros_like(oriImg), openpose_dict
+
+ if is_model_torchscript(self.pose):
+ pose_start = torch_timer.timer()
+ keypoint_sets, scores = inference_jit_pose(self.pose, det_result, oriImg, self.pose_input_size)
+ print(f"AnimalPose: Pose {((torch_timer.timer() - pose_start) * 1000):.2f}ms on {det_result.shape[0]} animals\n")
+ else:
+ pose_start = default_timer()
+ _, pose_onnx_dtype = guess_onnx_input_shape_dtype(self.pose_filename)
+ keypoint_sets, scores = inference_onnx_pose(self.pose, det_result, oriImg, self.pose_input_size, dtype=pose_onnx_dtype)
+ print(f"AnimalPose: Pose {((default_timer() - pose_start) * 1000):.2f}ms on {det_result.shape[0]} animals\n")
+
+ animal_kps_scores = []
+ pose_img = np.zeros((oriImg.shape[0], oriImg.shape[1], 3), dtype = np.uint8)
+ for (idx, keypoints) in enumerate(keypoint_sets):
+ # don't use keypoints that go outside the frame in calculations for the center
+ interorKeypoints = keypoints[((keypoints[:,0] > 0) & (keypoints[:,0] < oriImg.shape[1])) & ((keypoints[:,1] > 0) & (keypoints[:,1] < oriImg.shape[0]))]
+
+ xVals = interorKeypoints[:,0]
+ yVals = interorKeypoints[:,1]
+
+ minX = np.amin(xVals)
+ minY = np.amin(yVals)
+ maxX = np.amax(xVals)
+ maxY = np.amax(yVals)
+
+ poseSpanX = maxX - minX
+ poseSpanY = maxY - minY
+
+ # find mean center
+
+ xSum = np.sum(xVals)
+ ySum = np.sum(yVals)
+
+ xCenter = xSum // xVals.shape[0]
+ yCenter = ySum // yVals.shape[0]
+ center_of_keypoints = (xCenter,yCenter)
+
+ # order of the keypoints for AP10k and a standardized list of colors for limbs
+ keypointPairsList = [(1,2), (2,3), (1,3), (3,4), (4,9), (9,10), (10,11), (4,6), (6,7), (7,8), (4,5), (5,15), (15,16), (16,17), (5,12), (12,13), (13,14)]
+ colorsList = [(255,255,255), (100,255,100), (150,255,255), (100,50,255), (50,150,200), (0,255,255), (0,150,0), (0,0,255), (0,0,150), (255,50,255), (255,0,255), (255,0,0), (150,0,0), (255,255,100), (0,150,0), (255,255,0), (150,150,150)] # 16 colors needed
+
+ drawBetweenKeypointsList(pose_img, keypoints, keypointPairsList, colorsList, scaleFactor=1.0)
+ score = scores[idx, ..., None]
+ score[score > 1.0] = 1.0
+ score[score < 0.0] = 0.0
+ animal_kps_scores.append(np.concatenate((keypoints, score), axis=-1))
+
+ openpose_dict = {
+ 'version': 'ap10k',
+ 'animals': [keypoints.tolist() for keypoints in animal_kps_scores],
+ 'canvas_height': oriImg.shape[0],
+ 'canvas_width': oriImg.shape[1]
+ }
+ return pose_img, openpose_dict
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/body.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/body.py
new file mode 100644
index 0000000000000000000000000000000000000000..32934f19eba4b7e762678fd1fcd6b2bd193811d6
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/body.py
@@ -0,0 +1,261 @@
+import cv2
+import numpy as np
+import math
+import time
+from scipy.ndimage.filters import gaussian_filter
+import matplotlib.pyplot as plt
+import matplotlib
+import torch
+from torchvision import transforms
+from typing import NamedTuple, List, Union
+
+from . import util
+from .model import bodypose_model
+from .types import Keypoint, BodyResult
+
+class Body(object):
+ def __init__(self, model_path):
+ self.model = bodypose_model()
+ # if torch.cuda.is_available():
+ # self.model = self.model.cuda()
+ # print('cuda')
+ model_dict = util.transfer(self.model, torch.load(model_path))
+ self.model.load_state_dict(model_dict)
+ self.model.eval()
+
+ def __call__(self, oriImg):
+ # scale_search = [0.5, 1.0, 1.5, 2.0]
+ scale_search = [0.5]
+ boxsize = 368
+ stride = 8
+ padValue = 128
+ thre1 = 0.1
+ thre2 = 0.05
+ multiplier = [x * boxsize / oriImg.shape[0] for x in scale_search]
+ heatmap_avg = np.zeros((oriImg.shape[0], oriImg.shape[1], 19))
+ paf_avg = np.zeros((oriImg.shape[0], oriImg.shape[1], 38))
+
+ for m in range(len(multiplier)):
+ scale = multiplier[m]
+ imageToTest = util.smart_resize_k(oriImg, fx=scale, fy=scale)
+ imageToTest_padded, pad = util.padRightDownCorner(imageToTest, stride, padValue)
+ im = np.transpose(np.float32(imageToTest_padded[:, :, :, np.newaxis]), (3, 2, 0, 1)) / 256 - 0.5
+ im = np.ascontiguousarray(im)
+
+ data = torch.from_numpy(im).float()
+ if torch.cuda.is_available():
+ data = data.cuda()
+ # data = data.permute([2, 0, 1]).unsqueeze(0).float()
+ with torch.no_grad():
+ data = data.to(self.cn_device)
+ Mconv7_stage6_L1, Mconv7_stage6_L2 = self.model(data)
+ Mconv7_stage6_L1 = Mconv7_stage6_L1.cpu().numpy()
+ Mconv7_stage6_L2 = Mconv7_stage6_L2.cpu().numpy()
+
+ # extract outputs, resize, and remove padding
+ # heatmap = np.transpose(np.squeeze(net.blobs[output_blobs.keys()[1]].data), (1, 2, 0)) # output 1 is heatmaps
+ heatmap = np.transpose(np.squeeze(Mconv7_stage6_L2), (1, 2, 0)) # output 1 is heatmaps
+ heatmap = util.smart_resize_k(heatmap, fx=stride, fy=stride)
+ heatmap = heatmap[:imageToTest_padded.shape[0] - pad[2], :imageToTest_padded.shape[1] - pad[3], :]
+ heatmap = util.smart_resize(heatmap, (oriImg.shape[0], oriImg.shape[1]))
+
+ # paf = np.transpose(np.squeeze(net.blobs[output_blobs.keys()[0]].data), (1, 2, 0)) # output 0 is PAFs
+ paf = np.transpose(np.squeeze(Mconv7_stage6_L1), (1, 2, 0)) # output 0 is PAFs
+ paf = util.smart_resize_k(paf, fx=stride, fy=stride)
+ paf = paf[:imageToTest_padded.shape[0] - pad[2], :imageToTest_padded.shape[1] - pad[3], :]
+ paf = util.smart_resize(paf, (oriImg.shape[0], oriImg.shape[1]))
+
+ heatmap_avg += heatmap_avg + heatmap / len(multiplier)
+ paf_avg += + paf / len(multiplier)
+
+ all_peaks = []
+ peak_counter = 0
+
+ for part in range(18):
+ map_ori = heatmap_avg[:, :, part]
+ one_heatmap = gaussian_filter(map_ori, sigma=3)
+
+ map_left = np.zeros(one_heatmap.shape)
+ map_left[1:, :] = one_heatmap[:-1, :]
+ map_right = np.zeros(one_heatmap.shape)
+ map_right[:-1, :] = one_heatmap[1:, :]
+ map_up = np.zeros(one_heatmap.shape)
+ map_up[:, 1:] = one_heatmap[:, :-1]
+ map_down = np.zeros(one_heatmap.shape)
+ map_down[:, :-1] = one_heatmap[:, 1:]
+
+ peaks_binary = np.logical_and.reduce(
+ (one_heatmap >= map_left, one_heatmap >= map_right, one_heatmap >= map_up, one_heatmap >= map_down, one_heatmap > thre1))
+ peaks = list(zip(np.nonzero(peaks_binary)[1], np.nonzero(peaks_binary)[0])) # note reverse
+ peaks_with_score = [x + (map_ori[x[1], x[0]],) for x in peaks]
+ peak_id = range(peak_counter, peak_counter + len(peaks))
+ peaks_with_score_and_id = [peaks_with_score[i] + (peak_id[i],) for i in range(len(peak_id))]
+
+ all_peaks.append(peaks_with_score_and_id)
+ peak_counter += len(peaks)
+
+ # find connection in the specified sequence, center 29 is in the position 15
+ limbSeq = [[2, 3], [2, 6], [3, 4], [4, 5], [6, 7], [7, 8], [2, 9], [9, 10], \
+ [10, 11], [2, 12], [12, 13], [13, 14], [2, 1], [1, 15], [15, 17], \
+ [1, 16], [16, 18], [3, 17], [6, 18]]
+ # the middle joints heatmap correpondence
+ mapIdx = [[31, 32], [39, 40], [33, 34], [35, 36], [41, 42], [43, 44], [19, 20], [21, 22], \
+ [23, 24], [25, 26], [27, 28], [29, 30], [47, 48], [49, 50], [53, 54], [51, 52], \
+ [55, 56], [37, 38], [45, 46]]
+
+ connection_all = []
+ special_k = []
+ mid_num = 10
+
+ for k in range(len(mapIdx)):
+ score_mid = paf_avg[:, :, [x - 19 for x in mapIdx[k]]]
+ candA = all_peaks[limbSeq[k][0] - 1]
+ candB = all_peaks[limbSeq[k][1] - 1]
+ nA = len(candA)
+ nB = len(candB)
+ indexA, indexB = limbSeq[k]
+ if (nA != 0 and nB != 0):
+ connection_candidate = []
+ for i in range(nA):
+ for j in range(nB):
+ vec = np.subtract(candB[j][:2], candA[i][:2])
+ norm = math.sqrt(vec[0] * vec[0] + vec[1] * vec[1])
+ norm = max(0.001, norm)
+ vec = np.divide(vec, norm)
+
+ startend = list(zip(np.linspace(candA[i][0], candB[j][0], num=mid_num), \
+ np.linspace(candA[i][1], candB[j][1], num=mid_num)))
+
+ vec_x = np.array([score_mid[int(round(startend[I][1])), int(round(startend[I][0])), 0] \
+ for I in range(len(startend))])
+ vec_y = np.array([score_mid[int(round(startend[I][1])), int(round(startend[I][0])), 1] \
+ for I in range(len(startend))])
+
+ score_midpts = np.multiply(vec_x, vec[0]) + np.multiply(vec_y, vec[1])
+ score_with_dist_prior = sum(score_midpts) / len(score_midpts) + min(
+ 0.5 * oriImg.shape[0] / norm - 1, 0)
+ criterion1 = len(np.nonzero(score_midpts > thre2)[0]) > 0.8 * len(score_midpts)
+ criterion2 = score_with_dist_prior > 0
+ if criterion1 and criterion2:
+ connection_candidate.append(
+ [i, j, score_with_dist_prior, score_with_dist_prior + candA[i][2] + candB[j][2]])
+
+ connection_candidate = sorted(connection_candidate, key=lambda x: x[2], reverse=True)
+ connection = np.zeros((0, 5))
+ for c in range(len(connection_candidate)):
+ i, j, s = connection_candidate[c][0:3]
+ if (i not in connection[:, 3] and j not in connection[:, 4]):
+ connection = np.vstack([connection, [candA[i][3], candB[j][3], s, i, j]])
+ if (len(connection) >= min(nA, nB)):
+ break
+
+ connection_all.append(connection)
+ else:
+ special_k.append(k)
+ connection_all.append([])
+
+ # last number in each row is the total parts number of that person
+ # the second last number in each row is the score of the overall configuration
+ subset = -1 * np.ones((0, 20))
+ candidate = np.array([item for sublist in all_peaks for item in sublist])
+
+ for k in range(len(mapIdx)):
+ if k not in special_k:
+ partAs = connection_all[k][:, 0]
+ partBs = connection_all[k][:, 1]
+ indexA, indexB = np.array(limbSeq[k]) - 1
+
+ for i in range(len(connection_all[k])): # = 1:size(temp,1)
+ found = 0
+ subset_idx = [-1, -1]
+ for j in range(len(subset)): # 1:size(subset,1):
+ if subset[j][indexA] == partAs[i] or subset[j][indexB] == partBs[i]:
+ subset_idx[found] = j
+ found += 1
+
+ if found == 1:
+ j = subset_idx[0]
+ if subset[j][indexB] != partBs[i]:
+ subset[j][indexB] = partBs[i]
+ subset[j][-1] += 1
+ subset[j][-2] += candidate[partBs[i].astype(int), 2] + connection_all[k][i][2]
+ elif found == 2: # if found 2 and disjoint, merge them
+ j1, j2 = subset_idx
+ membership = ((subset[j1] >= 0).astype(int) + (subset[j2] >= 0).astype(int))[:-2]
+ if len(np.nonzero(membership == 2)[0]) == 0: # merge
+ subset[j1][:-2] += (subset[j2][:-2] + 1)
+ subset[j1][-2:] += subset[j2][-2:]
+ subset[j1][-2] += connection_all[k][i][2]
+ subset = np.delete(subset, j2, 0)
+ else: # as like found == 1
+ subset[j1][indexB] = partBs[i]
+ subset[j1][-1] += 1
+ subset[j1][-2] += candidate[partBs[i].astype(int), 2] + connection_all[k][i][2]
+
+ # if find no partA in the subset, create a new subset
+ elif not found and k < 17:
+ row = -1 * np.ones(20)
+ row[indexA] = partAs[i]
+ row[indexB] = partBs[i]
+ row[-1] = 2
+ row[-2] = sum(candidate[connection_all[k][i, :2].astype(int), 2]) + connection_all[k][i][2]
+ subset = np.vstack([subset, row])
+ # delete some rows of subset which has few parts occur
+ deleteIdx = []
+ for i in range(len(subset)):
+ if subset[i][-1] < 4 or subset[i][-2] / subset[i][-1] < 0.4:
+ deleteIdx.append(i)
+ subset = np.delete(subset, deleteIdx, axis=0)
+
+ # subset: n*20 array, 0-17 is the index in candidate, 18 is the total score, 19 is the total parts
+ # candidate: x, y, score, id
+ return candidate, subset
+
+ @staticmethod
+ def format_body_result(candidate: np.ndarray, subset: np.ndarray) -> List[BodyResult]:
+ """
+ Format the body results from the candidate and subset arrays into a list of BodyResult objects.
+
+ Args:
+ candidate (np.ndarray): An array of candidates containing the x, y coordinates, score, and id
+ for each body part.
+ subset (np.ndarray): An array of subsets containing indices to the candidate array for each
+ person detected. The last two columns of each row hold the total score and total parts
+ of the person.
+
+ Returns:
+ List[BodyResult]: A list of BodyResult objects, where each object represents a person with
+ detected keypoints, total score, and total parts.
+ """
+ return [
+ BodyResult(
+ keypoints=[
+ Keypoint(
+ x=candidate[candidate_index][0],
+ y=candidate[candidate_index][1],
+ score=candidate[candidate_index][2],
+ id=candidate[candidate_index][3]
+ ) if candidate_index != -1 else None
+ for candidate_index in person[:18].astype(int)
+ ],
+ total_score=person[18],
+ total_parts=person[19]
+ )
+ for person in subset
+ ]
+
+
+if __name__ == "__main__":
+ body_estimation = Body('../model/body_pose_model.pth')
+
+ test_image = '../images/ski.jpg'
+ oriImg = cv2.imread(test_image) # B,G,R order
+ candidate, subset = body_estimation(oriImg)
+ bodies = body_estimation.format_body_result(candidate, subset)
+
+ canvas = oriImg
+ for body in bodies:
+ canvas = util.draw_bodypose(canvas, body)
+
+ plt.imshow(canvas[:, :, [2, 1, 0]])
+ plt.show()
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_onnx/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_onnx/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..33e7a7f594ef441479257c788e4c0d6e08657fc8
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_onnx/__init__.py
@@ -0,0 +1 @@
+#Dummy file ensuring this package will be recognized
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_onnx/__pycache__/__init__.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_onnx/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..83dfca207cc854b0b0ae681e978343e7665b2091
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_onnx/__pycache__/__init__.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_onnx/__pycache__/cv_ox_det.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_onnx/__pycache__/cv_ox_det.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c87047d944d49ced7cf2996340f92a4322808208
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_onnx/__pycache__/cv_ox_det.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_onnx/__pycache__/cv_ox_pose.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_onnx/__pycache__/cv_ox_pose.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..252e8821b9ab476514d4c12bd7c623cca095f66a
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_onnx/__pycache__/cv_ox_pose.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_onnx/__pycache__/cv_ox_yolo_nas.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_onnx/__pycache__/cv_ox_yolo_nas.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4255b2c494f85762b5c8394a52da134fda3a8913
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_onnx/__pycache__/cv_ox_yolo_nas.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_onnx/cv_ox_det.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_onnx/cv_ox_det.py
new file mode 100644
index 0000000000000000000000000000000000000000..0365234c2caef3b98fc01304ba5365da2115ba65
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_onnx/cv_ox_det.py
@@ -0,0 +1,129 @@
+import cv2
+import numpy as np
+
+def nms(boxes, scores, nms_thr):
+ """Single class NMS implemented in Numpy."""
+ x1 = boxes[:, 0]
+ y1 = boxes[:, 1]
+ x2 = boxes[:, 2]
+ y2 = boxes[:, 3]
+
+ areas = (x2 - x1 + 1) * (y2 - y1 + 1)
+ order = scores.argsort()[::-1]
+
+ keep = []
+ while order.size > 0:
+ i = order[0]
+ keep.append(i)
+ xx1 = np.maximum(x1[i], x1[order[1:]])
+ yy1 = np.maximum(y1[i], y1[order[1:]])
+ xx2 = np.minimum(x2[i], x2[order[1:]])
+ yy2 = np.minimum(y2[i], y2[order[1:]])
+
+ w = np.maximum(0.0, xx2 - xx1 + 1)
+ h = np.maximum(0.0, yy2 - yy1 + 1)
+ inter = w * h
+ ovr = inter / (areas[i] + areas[order[1:]] - inter)
+
+ inds = np.where(ovr <= nms_thr)[0]
+ order = order[inds + 1]
+
+ return keep
+
+def multiclass_nms(boxes, scores, nms_thr, score_thr):
+ """Multiclass NMS implemented in Numpy. Class-aware version."""
+ final_dets = []
+ num_classes = scores.shape[1]
+ for cls_ind in range(num_classes):
+ cls_scores = scores[:, cls_ind]
+ valid_score_mask = cls_scores > score_thr
+ if valid_score_mask.sum() == 0:
+ continue
+ else:
+ valid_scores = cls_scores[valid_score_mask]
+ valid_boxes = boxes[valid_score_mask]
+ keep = nms(valid_boxes, valid_scores, nms_thr)
+ if len(keep) > 0:
+ cls_inds = np.ones((len(keep), 1)) * cls_ind
+ dets = np.concatenate(
+ [valid_boxes[keep], valid_scores[keep, None], cls_inds], 1
+ )
+ final_dets.append(dets)
+ if len(final_dets) == 0:
+ return None
+ return np.concatenate(final_dets, 0)
+
+def demo_postprocess(outputs, img_size, p6=False):
+ grids = []
+ expanded_strides = []
+ strides = [8, 16, 32] if not p6 else [8, 16, 32, 64]
+
+ hsizes = [img_size[0] // stride for stride in strides]
+ wsizes = [img_size[1] // stride for stride in strides]
+
+ for hsize, wsize, stride in zip(hsizes, wsizes, strides):
+ xv, yv = np.meshgrid(np.arange(wsize), np.arange(hsize))
+ grid = np.stack((xv, yv), 2).reshape(1, -1, 2)
+ grids.append(grid)
+ shape = grid.shape[:2]
+ expanded_strides.append(np.full((*shape, 1), stride))
+
+ grids = np.concatenate(grids, 1)
+ expanded_strides = np.concatenate(expanded_strides, 1)
+ outputs[..., :2] = (outputs[..., :2] + grids) * expanded_strides
+ outputs[..., 2:4] = np.exp(outputs[..., 2:4]) * expanded_strides
+
+ return outputs
+
+def preprocess(img, input_size, swap=(2, 0, 1)):
+ if len(img.shape) == 3:
+ padded_img = np.ones((input_size[0], input_size[1], 3), dtype=np.uint8) * 114
+ else:
+ padded_img = np.ones(input_size, dtype=np.uint8) * 114
+
+ r = min(input_size[0] / img.shape[0], input_size[1] / img.shape[1])
+ resized_img = cv2.resize(
+ img,
+ (int(img.shape[1] * r), int(img.shape[0] * r)),
+ interpolation=cv2.INTER_LINEAR,
+ ).astype(np.uint8)
+ padded_img[: int(img.shape[0] * r), : int(img.shape[1] * r)] = resized_img
+
+ padded_img = padded_img.transpose(swap)
+ padded_img = np.ascontiguousarray(padded_img, dtype=np.float32)
+ return padded_img, r
+
+def inference_detector(session, oriImg, detect_classes=[0], dtype=np.float32):
+ input_shape = (640,640)
+ img, ratio = preprocess(oriImg, input_shape)
+
+ input = img[None, :, :, :]
+ input = input.astype(dtype)
+ if "InferenceSession" in type(session).__name__:
+ input_name = session.get_inputs()[0].name
+ output = session.run(None, {input_name: input})
+ else:
+ outNames = session.getUnconnectedOutLayersNames()
+ session.setInput(input)
+ output = session.forward(outNames)
+
+ predictions = demo_postprocess(output[0], input_shape)[0]
+
+ boxes = predictions[:, :4]
+ scores = predictions[:, 4:5] * predictions[:, 5:]
+
+ boxes_xyxy = np.ones_like(boxes)
+ boxes_xyxy[:, 0] = boxes[:, 0] - boxes[:, 2]/2.
+ boxes_xyxy[:, 1] = boxes[:, 1] - boxes[:, 3]/2.
+ boxes_xyxy[:, 2] = boxes[:, 0] + boxes[:, 2]/2.
+ boxes_xyxy[:, 3] = boxes[:, 1] + boxes[:, 3]/2.
+ boxes_xyxy /= ratio
+ dets = multiclass_nms(boxes_xyxy, scores, nms_thr=0.45, score_thr=0.1)
+ if dets is None:
+ return None
+ final_boxes, final_scores, final_cls_inds = dets[:, :4], dets[:, 4], dets[:, 5]
+ isscore = final_scores>0.3
+ iscat = np.isin(final_cls_inds, detect_classes)
+ isbbox = [ i and j for (i, j) in zip(isscore, iscat)]
+ final_boxes = final_boxes[isbbox]
+ return final_boxes
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_onnx/cv_ox_pose.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_onnx/cv_ox_pose.py
new file mode 100644
index 0000000000000000000000000000000000000000..956c4bc715214bcc2e6228166032418294df46bc
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_onnx/cv_ox_pose.py
@@ -0,0 +1,363 @@
+from typing import List, Tuple
+
+import cv2
+import numpy as np
+
+def preprocess(
+ img: np.ndarray, out_bbox, input_size: Tuple[int, int] = (192, 256)
+) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
+ """Do preprocessing for DWPose model inference.
+
+ Args:
+ img (np.ndarray): Input image in shape.
+ input_size (tuple): Input image size in shape (w, h).
+
+ Returns:
+ tuple:
+ - resized_img (np.ndarray): Preprocessed image.
+ - center (np.ndarray): Center of image.
+ - scale (np.ndarray): Scale of image.
+ """
+ # get shape of image
+ img_shape = img.shape[:2]
+ out_img, out_center, out_scale = [], [], []
+ if len(out_bbox) == 0:
+ out_bbox = [[0, 0, img_shape[1], img_shape[0]]]
+ for i in range(len(out_bbox)):
+ x0 = out_bbox[i][0]
+ y0 = out_bbox[i][1]
+ x1 = out_bbox[i][2]
+ y1 = out_bbox[i][3]
+ bbox = np.array([x0, y0, x1, y1])
+
+ # get center and scale
+ center, scale = bbox_xyxy2cs(bbox, padding=1.25)
+
+ # do affine transformation
+ resized_img, scale = top_down_affine(input_size, scale, center, img)
+
+ # normalize image
+ mean = np.array([123.675, 116.28, 103.53])
+ std = np.array([58.395, 57.12, 57.375])
+ resized_img = (resized_img - mean) / std
+
+ out_img.append(resized_img)
+ out_center.append(center)
+ out_scale.append(scale)
+
+ return out_img, out_center, out_scale
+
+
+def inference(sess, img, dtype=np.float32):
+ """Inference DWPose model. Processing all image segments at once to take advantage of GPU's parallelism ability if onnxruntime is installed
+
+ Args:
+ sess : ONNXRuntime session.
+ img : Input image in shape.
+
+ Returns:
+ outputs : Output of DWPose model.
+ """
+ all_out = []
+ # build input
+ input = np.stack(img, axis=0).transpose(0, 3, 1, 2)
+ input = input.astype(dtype)
+ if "InferenceSession" in type(sess).__name__:
+ input_name = sess.get_inputs()[0].name
+ all_outputs = sess.run(None, {input_name: input})
+ for batch_idx in range(len(all_outputs[0])):
+ outputs = [all_outputs[i][batch_idx:batch_idx+1,...] for i in range(len(all_outputs))]
+ all_out.append(outputs)
+ return all_out
+
+ #OpenCV doesn't support batch processing sadly
+ for i in range(len(img)):
+ input = img[i].transpose(2, 0, 1)
+ input = input[None, :, :, :]
+
+ outNames = sess.getUnconnectedOutLayersNames()
+ sess.setInput(input)
+ outputs = sess.forward(outNames)
+ all_out.append(outputs)
+
+ return all_out
+
+def postprocess(outputs: List[np.ndarray],
+ model_input_size: Tuple[int, int],
+ center: Tuple[int, int],
+ scale: Tuple[int, int],
+ simcc_split_ratio: float = 2.0
+ ) -> Tuple[np.ndarray, np.ndarray]:
+ """Postprocess for DWPose model output.
+
+ Args:
+ outputs (np.ndarray): Output of RTMPose model.
+ model_input_size (tuple): RTMPose model Input image size.
+ center (tuple): Center of bbox in shape (x, y).
+ scale (tuple): Scale of bbox in shape (w, h).
+ simcc_split_ratio (float): Split ratio of simcc.
+
+ Returns:
+ tuple:
+ - keypoints (np.ndarray): Rescaled keypoints.
+ - scores (np.ndarray): Model predict scores.
+ """
+ all_key = []
+ all_score = []
+ for i in range(len(outputs)):
+ # use simcc to decode
+ simcc_x, simcc_y = outputs[i]
+ keypoints, scores = decode(simcc_x, simcc_y, simcc_split_ratio)
+
+ # rescale keypoints
+ keypoints = keypoints / model_input_size * scale[i] + center[i] - scale[i] / 2
+ all_key.append(keypoints[0])
+ all_score.append(scores[0])
+
+ return np.array(all_key), np.array(all_score)
+
+
+def bbox_xyxy2cs(bbox: np.ndarray,
+ padding: float = 1.) -> Tuple[np.ndarray, np.ndarray]:
+ """Transform the bbox format from (x,y,w,h) into (center, scale)
+
+ Args:
+ bbox (ndarray): Bounding box(es) in shape (4,) or (n, 4), formatted
+ as (left, top, right, bottom)
+ padding (float): BBox padding factor that will be multilied to scale.
+ Default: 1.0
+
+ Returns:
+ tuple: A tuple containing center and scale.
+ - np.ndarray[float32]: Center (x, y) of the bbox in shape (2,) or
+ (n, 2)
+ - np.ndarray[float32]: Scale (w, h) of the bbox in shape (2,) or
+ (n, 2)
+ """
+ # convert single bbox from (4, ) to (1, 4)
+ dim = bbox.ndim
+ if dim == 1:
+ bbox = bbox[None, :]
+
+ # get bbox center and scale
+ x1, y1, x2, y2 = np.hsplit(bbox, [1, 2, 3])
+ center = np.hstack([x1 + x2, y1 + y2]) * 0.5
+ scale = np.hstack([x2 - x1, y2 - y1]) * padding
+
+ if dim == 1:
+ center = center[0]
+ scale = scale[0]
+
+ return center, scale
+
+
+def _fix_aspect_ratio(bbox_scale: np.ndarray,
+ aspect_ratio: float) -> np.ndarray:
+ """Extend the scale to match the given aspect ratio.
+
+ Args:
+ scale (np.ndarray): The image scale (w, h) in shape (2, )
+ aspect_ratio (float): The ratio of ``w/h``
+
+ Returns:
+ np.ndarray: The reshaped image scale in (2, )
+ """
+ w, h = np.hsplit(bbox_scale, [1])
+ bbox_scale = np.where(w > h * aspect_ratio,
+ np.hstack([w, w / aspect_ratio]),
+ np.hstack([h * aspect_ratio, h]))
+ return bbox_scale
+
+
+def _rotate_point(pt: np.ndarray, angle_rad: float) -> np.ndarray:
+ """Rotate a point by an angle.
+
+ Args:
+ pt (np.ndarray): 2D point coordinates (x, y) in shape (2, )
+ angle_rad (float): rotation angle in radian
+
+ Returns:
+ np.ndarray: Rotated point in shape (2, )
+ """
+ sn, cs = np.sin(angle_rad), np.cos(angle_rad)
+ rot_mat = np.array([[cs, -sn], [sn, cs]])
+ return rot_mat @ pt
+
+
+def _get_3rd_point(a: np.ndarray, b: np.ndarray) -> np.ndarray:
+ """To calculate the affine matrix, three pairs of points are required. This
+ function is used to get the 3rd point, given 2D points a & b.
+
+ The 3rd point is defined by rotating vector `a - b` by 90 degrees
+ anticlockwise, using b as the rotation center.
+
+ Args:
+ a (np.ndarray): The 1st point (x,y) in shape (2, )
+ b (np.ndarray): The 2nd point (x,y) in shape (2, )
+
+ Returns:
+ np.ndarray: The 3rd point.
+ """
+ direction = a - b
+ c = b + np.r_[-direction[1], direction[0]]
+ return c
+
+
+def get_warp_matrix(center: np.ndarray,
+ scale: np.ndarray,
+ rot: float,
+ output_size: Tuple[int, int],
+ shift: Tuple[float, float] = (0., 0.),
+ inv: bool = False) -> np.ndarray:
+ """Calculate the affine transformation matrix that can warp the bbox area
+ in the input image to the output size.
+
+ Args:
+ center (np.ndarray[2, ]): Center of the bounding box (x, y).
+ scale (np.ndarray[2, ]): Scale of the bounding box
+ wrt [width, height].
+ rot (float): Rotation angle (degree).
+ output_size (np.ndarray[2, ] | list(2,)): Size of the
+ destination heatmaps.
+ shift (0-100%): Shift translation ratio wrt the width/height.
+ Default (0., 0.).
+ inv (bool): Option to inverse the affine transform direction.
+ (inv=False: src->dst or inv=True: dst->src)
+
+ Returns:
+ np.ndarray: A 2x3 transformation matrix
+ """
+ shift = np.array(shift)
+ src_w = scale[0]
+ dst_w = output_size[0]
+ dst_h = output_size[1]
+
+ # compute transformation matrix
+ rot_rad = np.deg2rad(rot)
+ src_dir = _rotate_point(np.array([0., src_w * -0.5]), rot_rad)
+ dst_dir = np.array([0., dst_w * -0.5])
+
+ # get four corners of the src rectangle in the original image
+ src = np.zeros((3, 2), dtype=np.float32)
+ src[0, :] = center + scale * shift
+ src[1, :] = center + src_dir + scale * shift
+ src[2, :] = _get_3rd_point(src[0, :], src[1, :])
+
+ # get four corners of the dst rectangle in the input image
+ dst = np.zeros((3, 2), dtype=np.float32)
+ dst[0, :] = [dst_w * 0.5, dst_h * 0.5]
+ dst[1, :] = np.array([dst_w * 0.5, dst_h * 0.5]) + dst_dir
+ dst[2, :] = _get_3rd_point(dst[0, :], dst[1, :])
+
+ if inv:
+ warp_mat = cv2.getAffineTransform(np.float32(dst), np.float32(src))
+ else:
+ warp_mat = cv2.getAffineTransform(np.float32(src), np.float32(dst))
+
+ return warp_mat
+
+
+def top_down_affine(input_size: dict, bbox_scale: dict, bbox_center: dict,
+ img: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
+ """Get the bbox image as the model input by affine transform.
+
+ Args:
+ input_size (dict): The input size of the model.
+ bbox_scale (dict): The bbox scale of the img.
+ bbox_center (dict): The bbox center of the img.
+ img (np.ndarray): The original image.
+
+ Returns:
+ tuple: A tuple containing center and scale.
+ - np.ndarray[float32]: img after affine transform.
+ - np.ndarray[float32]: bbox scale after affine transform.
+ """
+ w, h = input_size
+ warp_size = (int(w), int(h))
+
+ # reshape bbox to fixed aspect ratio
+ bbox_scale = _fix_aspect_ratio(bbox_scale, aspect_ratio=w / h)
+
+ # get the affine matrix
+ center = bbox_center
+ scale = bbox_scale
+ rot = 0
+ warp_mat = get_warp_matrix(center, scale, rot, output_size=(w, h))
+
+ # do affine transform
+ img = cv2.warpAffine(img, warp_mat, warp_size, flags=cv2.INTER_LINEAR)
+
+ return img, bbox_scale
+
+
+def get_simcc_maximum(simcc_x: np.ndarray,
+ simcc_y: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
+ """Get maximum response location and value from simcc representations.
+
+ Note:
+ instance number: N
+ num_keypoints: K
+ heatmap height: H
+ heatmap width: W
+
+ Args:
+ simcc_x (np.ndarray): x-axis SimCC in shape (K, Wx) or (N, K, Wx)
+ simcc_y (np.ndarray): y-axis SimCC in shape (K, Wy) or (N, K, Wy)
+
+ Returns:
+ tuple:
+ - locs (np.ndarray): locations of maximum heatmap responses in shape
+ (K, 2) or (N, K, 2)
+ - vals (np.ndarray): values of maximum heatmap responses in shape
+ (K,) or (N, K)
+ """
+ N, K, Wx = simcc_x.shape
+ simcc_x = simcc_x.reshape(N * K, -1)
+ simcc_y = simcc_y.reshape(N * K, -1)
+
+ # get maximum value locations
+ x_locs = np.argmax(simcc_x, axis=1)
+ y_locs = np.argmax(simcc_y, axis=1)
+ locs = np.stack((x_locs, y_locs), axis=-1).astype(np.float32)
+ max_val_x = np.amax(simcc_x, axis=1)
+ max_val_y = np.amax(simcc_y, axis=1)
+
+ # get maximum value across x and y axis
+ mask = max_val_x > max_val_y
+ max_val_x[mask] = max_val_y[mask]
+ vals = max_val_x
+ locs[vals <= 0.] = -1
+
+ # reshape
+ locs = locs.reshape(N, K, 2)
+ vals = vals.reshape(N, K)
+
+ return locs, vals
+
+
+def decode(simcc_x: np.ndarray, simcc_y: np.ndarray,
+ simcc_split_ratio) -> Tuple[np.ndarray, np.ndarray]:
+ """Modulate simcc distribution with Gaussian.
+
+ Args:
+ simcc_x (np.ndarray[K, Wx]): model predicted simcc in x.
+ simcc_y (np.ndarray[K, Wy]): model predicted simcc in y.
+ simcc_split_ratio (int): The split ratio of simcc.
+
+ Returns:
+ tuple: A tuple containing center and scale.
+ - np.ndarray[float32]: keypoints in shape (K, 2) or (n, K, 2)
+ - np.ndarray[float32]: scores in shape (K,) or (n, K)
+ """
+ keypoints, scores = get_simcc_maximum(simcc_x, simcc_y)
+ keypoints /= simcc_split_ratio
+
+ return keypoints, scores
+
+
+def inference_pose(session, out_bbox, oriImg, model_input_size=(288, 384), dtype=np.float32):
+ resized_img, center, scale = preprocess(oriImg, out_bbox, model_input_size)
+ outputs = inference(session, resized_img, dtype)
+ keypoints, scores = postprocess(outputs, model_input_size, center, scale)
+
+ return keypoints, scores
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_onnx/cv_ox_yolo_nas.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_onnx/cv_ox_yolo_nas.py
new file mode 100644
index 0000000000000000000000000000000000000000..67ff249be283b11e0eb7d95ef7c0adc024c48285
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_onnx/cv_ox_yolo_nas.py
@@ -0,0 +1,60 @@
+# Source: https://github.com/Hyuto/yolo-nas-onnx/tree/master/yolo-nas-py
+# Inspired from: https://github.com/Deci-AI/super-gradients/blob/3.1.1/src/super_gradients/training/processing/processing.py
+
+import numpy as np
+import cv2
+
+def preprocess(img, input_size, swap=(2, 0, 1)):
+ if len(img.shape) == 3:
+ padded_img = np.ones((input_size[0], input_size[1], 3), dtype=np.uint8) * 114
+ else:
+ padded_img = np.ones(input_size, dtype=np.uint8) * 114
+
+ r = min(input_size[0] / img.shape[0], input_size[1] / img.shape[1])
+ resized_img = cv2.resize(
+ img,
+ (int(img.shape[1] * r), int(img.shape[0] * r)),
+ interpolation=cv2.INTER_LINEAR,
+ ).astype(np.uint8)
+ padded_img[: int(img.shape[0] * r), : int(img.shape[1] * r)] = resized_img
+
+ padded_img = padded_img.transpose(swap)
+ padded_img = np.ascontiguousarray(padded_img, dtype=np.float32)
+ return padded_img, r
+
+def inference_detector(session, oriImg, detect_classes=[0], dtype=np.uint8):
+ """
+ This function is only compatible with onnx models exported from the new API with built-in NMS
+ ```py
+ from super_gradients.conversion.conversion_enums import ExportQuantizationMode
+ from super_gradients.common.object_names import Models
+ from super_gradients.training import models
+
+ model = models.get(Models.YOLO_NAS_L, pretrained_weights="coco")
+
+ export_result = model.export(
+ "yolo_nas/yolo_nas_l_fp16.onnx",
+ quantization_mode=ExportQuantizationMode.FP16,
+ device="cuda"
+ )
+ ```
+ """
+ input_shape = (640,640)
+ img, ratio = preprocess(oriImg, input_shape)
+ input = img[None, :, :, :]
+ input = input.astype(dtype)
+ if "InferenceSession" in type(session).__name__:
+ input_name = session.get_inputs()[0].name
+ output = session.run(None, {input_name: input})
+ else:
+ outNames = session.getUnconnectedOutLayersNames()
+ session.setInput(input)
+ output = session.forward(outNames)
+ num_preds, pred_boxes, pred_scores, pred_classes = output
+ num_preds = num_preds[0,0]
+ if num_preds == 0:
+ return None
+ idxs = np.where((np.isin(pred_classes[0, :num_preds], detect_classes)) & (pred_scores[0, :num_preds] > 0.3))
+ if (len(idxs) == 0) or (idxs[0].size == 0):
+ return None
+ return pred_boxes[0, idxs].squeeze(axis=0) / ratio
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_torchscript/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_torchscript/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..33e7a7f594ef441479257c788e4c0d6e08657fc8
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_torchscript/__init__.py
@@ -0,0 +1 @@
+#Dummy file ensuring this package will be recognized
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_torchscript/__pycache__/__init__.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_torchscript/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d5b3a5714567df1650cfde3d080d53accb0e34e4
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_torchscript/__pycache__/__init__.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_torchscript/__pycache__/jit_det.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_torchscript/__pycache__/jit_det.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..440518671756b7ea15c2601f11f8840c888e4cc9
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_torchscript/__pycache__/jit_det.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_torchscript/__pycache__/jit_pose.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_torchscript/__pycache__/jit_pose.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d2e2e10e48e79634d95fc7f80faa7df86c52cd5f
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_torchscript/__pycache__/jit_pose.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_torchscript/jit_det.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_torchscript/jit_det.py
new file mode 100644
index 0000000000000000000000000000000000000000..b220350ad241be59fdf42a71ff76f01d7bec26ed
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_torchscript/jit_det.py
@@ -0,0 +1,125 @@
+import cv2
+import numpy as np
+import torch
+
+def nms(boxes, scores, nms_thr):
+ """Single class NMS implemented in Numpy."""
+ x1 = boxes[:, 0]
+ y1 = boxes[:, 1]
+ x2 = boxes[:, 2]
+ y2 = boxes[:, 3]
+
+ areas = (x2 - x1 + 1) * (y2 - y1 + 1)
+ order = scores.argsort()[::-1]
+
+ keep = []
+ while order.size > 0:
+ i = order[0]
+ keep.append(i)
+ xx1 = np.maximum(x1[i], x1[order[1:]])
+ yy1 = np.maximum(y1[i], y1[order[1:]])
+ xx2 = np.minimum(x2[i], x2[order[1:]])
+ yy2 = np.minimum(y2[i], y2[order[1:]])
+
+ w = np.maximum(0.0, xx2 - xx1 + 1)
+ h = np.maximum(0.0, yy2 - yy1 + 1)
+ inter = w * h
+ ovr = inter / (areas[i] + areas[order[1:]] - inter)
+
+ inds = np.where(ovr <= nms_thr)[0]
+ order = order[inds + 1]
+
+ return keep
+
+def multiclass_nms(boxes, scores, nms_thr, score_thr):
+ """Multiclass NMS implemented in Numpy. Class-aware version."""
+ final_dets = []
+ num_classes = scores.shape[1]
+ for cls_ind in range(num_classes):
+ cls_scores = scores[:, cls_ind]
+ valid_score_mask = cls_scores > score_thr
+ if valid_score_mask.sum() == 0:
+ continue
+ else:
+ valid_scores = cls_scores[valid_score_mask]
+ valid_boxes = boxes[valid_score_mask]
+ keep = nms(valid_boxes, valid_scores, nms_thr)
+ if len(keep) > 0:
+ cls_inds = np.ones((len(keep), 1)) * cls_ind
+ dets = np.concatenate(
+ [valid_boxes[keep], valid_scores[keep, None], cls_inds], 1
+ )
+ final_dets.append(dets)
+ if len(final_dets) == 0:
+ return None
+ return np.concatenate(final_dets, 0)
+
+def demo_postprocess(outputs, img_size, p6=False):
+ grids = []
+ expanded_strides = []
+ strides = [8, 16, 32] if not p6 else [8, 16, 32, 64]
+
+ hsizes = [img_size[0] // stride for stride in strides]
+ wsizes = [img_size[1] // stride for stride in strides]
+
+ for hsize, wsize, stride in zip(hsizes, wsizes, strides):
+ xv, yv = np.meshgrid(np.arange(wsize), np.arange(hsize))
+ grid = np.stack((xv, yv), 2).reshape(1, -1, 2)
+ grids.append(grid)
+ shape = grid.shape[:2]
+ expanded_strides.append(np.full((*shape, 1), stride))
+
+ grids = np.concatenate(grids, 1)
+ expanded_strides = np.concatenate(expanded_strides, 1)
+ outputs[..., :2] = (outputs[..., :2] + grids) * expanded_strides
+ outputs[..., 2:4] = np.exp(outputs[..., 2:4]) * expanded_strides
+
+ return outputs
+
+def preprocess(img, input_size, swap=(2, 0, 1)):
+ if len(img.shape) == 3:
+ padded_img = np.ones((input_size[0], input_size[1], 3), dtype=np.uint8) * 114
+ else:
+ padded_img = np.ones(input_size, dtype=np.uint8) * 114
+
+ r = min(input_size[0] / img.shape[0], input_size[1] / img.shape[1])
+ resized_img = cv2.resize(
+ img,
+ (int(img.shape[1] * r), int(img.shape[0] * r)),
+ interpolation=cv2.INTER_LINEAR,
+ ).astype(np.uint8)
+ padded_img[: int(img.shape[0] * r), : int(img.shape[1] * r)] = resized_img
+
+ padded_img = padded_img.transpose(swap)
+ padded_img = np.ascontiguousarray(padded_img, dtype=np.float32)
+ return padded_img, r
+
+def inference_detector(model, oriImg, detect_classes=[0]):
+ input_shape = (640,640)
+ img, ratio = preprocess(oriImg, input_shape)
+
+ device, dtype = next(model.parameters()).device, next(model.parameters()).dtype
+ input = img[None, :, :, :]
+ input = torch.from_numpy(input).to(device).to(dtype)
+
+ output = model(input).float().cpu().detach().numpy()
+ predictions = demo_postprocess(output[0], input_shape)
+
+ boxes = predictions[:, :4]
+ scores = predictions[:, 4:5] * predictions[:, 5:]
+
+ boxes_xyxy = np.ones_like(boxes)
+ boxes_xyxy[:, 0] = boxes[:, 0] - boxes[:, 2]/2.
+ boxes_xyxy[:, 1] = boxes[:, 1] - boxes[:, 3]/2.
+ boxes_xyxy[:, 2] = boxes[:, 0] + boxes[:, 2]/2.
+ boxes_xyxy[:, 3] = boxes[:, 1] + boxes[:, 3]/2.
+ boxes_xyxy /= ratio
+ dets = multiclass_nms(boxes_xyxy, scores, nms_thr=0.45, score_thr=0.1)
+ if dets is None:
+ return None
+ final_boxes, final_scores, final_cls_inds = dets[:, :4], dets[:, 4], dets[:, 5]
+ isscore = final_scores>0.3
+ iscat = np.isin(final_cls_inds, detect_classes)
+ isbbox = [ i and j for (i, j) in zip(isscore, iscat)]
+ final_boxes = final_boxes[isbbox]
+ return final_boxes
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_torchscript/jit_pose.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_torchscript/jit_pose.py
new file mode 100644
index 0000000000000000000000000000000000000000..a6b015086cec0a81088cb72e24feed400610dc8f
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/dw_torchscript/jit_pose.py
@@ -0,0 +1,363 @@
+from typing import List, Tuple
+
+import cv2
+import numpy as np
+import torch
+
+def preprocess(
+ img: np.ndarray, out_bbox, input_size: Tuple[int, int] = (192, 256)
+) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
+ """Do preprocessing for DWPose model inference.
+
+ Args:
+ img (np.ndarray): Input image in shape.
+ input_size (tuple): Input image size in shape (w, h).
+
+ Returns:
+ tuple:
+ - resized_img (np.ndarray): Preprocessed image.
+ - center (np.ndarray): Center of image.
+ - scale (np.ndarray): Scale of image.
+ """
+ # get shape of image
+ img_shape = img.shape[:2]
+ out_img, out_center, out_scale = [], [], []
+ if len(out_bbox) == 0:
+ out_bbox = [[0, 0, img_shape[1], img_shape[0]]]
+ for i in range(len(out_bbox)):
+ x0 = out_bbox[i][0]
+ y0 = out_bbox[i][1]
+ x1 = out_bbox[i][2]
+ y1 = out_bbox[i][3]
+ bbox = np.array([x0, y0, x1, y1])
+
+ # get center and scale
+ center, scale = bbox_xyxy2cs(bbox, padding=1.25)
+
+ # do affine transformation
+ resized_img, scale = top_down_affine(input_size, scale, center, img)
+
+ # normalize image
+ mean = np.array([123.675, 116.28, 103.53])
+ std = np.array([58.395, 57.12, 57.375])
+ resized_img = (resized_img - mean) / std
+
+ out_img.append(resized_img)
+ out_center.append(center)
+ out_scale.append(scale)
+
+ return out_img, out_center, out_scale
+
+def inference(model, img, bs=5):
+ """Inference DWPose model implemented in TorchScript.
+
+ Args:
+ model : TorchScript Model.
+ img : Input image in shape.
+
+ Returns:
+ outputs : Output of DWPose model.
+ """
+ all_out = []
+ # build input
+ orig_img_count = len(img)
+ #Pad zeros to fit batch size
+ for _ in range(bs - (orig_img_count % bs)):
+ img.append(np.zeros_like(img[0]))
+ input = np.stack(img, axis=0).transpose(0, 3, 1, 2)
+ device, dtype = next(model.parameters()).device, next(model.parameters()).dtype
+ input = torch.from_numpy(input).to(device).to(dtype)
+
+ out1, out2 = [], []
+ for i in range(input.shape[0] // bs):
+ curr_batch_output = model(input[i*bs:(i+1)*bs])
+ out1.append(curr_batch_output[0].float())
+ out2.append(curr_batch_output[1].float())
+ out1, out2 = torch.cat(out1, dim=0)[:orig_img_count], torch.cat(out2, dim=0)[:orig_img_count]
+ out1, out2 = out1.float().cpu().detach().numpy(), out2.float().cpu().detach().numpy()
+ all_outputs = out1, out2
+
+ for batch_idx in range(len(all_outputs[0])):
+ outputs = [all_outputs[i][batch_idx:batch_idx+1,...] for i in range(len(all_outputs))]
+ all_out.append(outputs)
+ return all_out
+def postprocess(outputs: List[np.ndarray],
+ model_input_size: Tuple[int, int],
+ center: Tuple[int, int],
+ scale: Tuple[int, int],
+ simcc_split_ratio: float = 2.0
+ ) -> Tuple[np.ndarray, np.ndarray]:
+ """Postprocess for DWPose model output.
+
+ Args:
+ outputs (np.ndarray): Output of RTMPose model.
+ model_input_size (tuple): RTMPose model Input image size.
+ center (tuple): Center of bbox in shape (x, y).
+ scale (tuple): Scale of bbox in shape (w, h).
+ simcc_split_ratio (float): Split ratio of simcc.
+
+ Returns:
+ tuple:
+ - keypoints (np.ndarray): Rescaled keypoints.
+ - scores (np.ndarray): Model predict scores.
+ """
+ all_key = []
+ all_score = []
+ for i in range(len(outputs)):
+ # use simcc to decode
+ simcc_x, simcc_y = outputs[i]
+ keypoints, scores = decode(simcc_x, simcc_y, simcc_split_ratio)
+
+ # rescale keypoints
+ keypoints = keypoints / model_input_size * scale[i] + center[i] - scale[i] / 2
+ all_key.append(keypoints[0])
+ all_score.append(scores[0])
+
+ return np.array(all_key), np.array(all_score)
+
+
+def bbox_xyxy2cs(bbox: np.ndarray,
+ padding: float = 1.) -> Tuple[np.ndarray, np.ndarray]:
+ """Transform the bbox format from (x,y,w,h) into (center, scale)
+
+ Args:
+ bbox (ndarray): Bounding box(es) in shape (4,) or (n, 4), formatted
+ as (left, top, right, bottom)
+ padding (float): BBox padding factor that will be multilied to scale.
+ Default: 1.0
+
+ Returns:
+ tuple: A tuple containing center and scale.
+ - np.ndarray[float32]: Center (x, y) of the bbox in shape (2,) or
+ (n, 2)
+ - np.ndarray[float32]: Scale (w, h) of the bbox in shape (2,) or
+ (n, 2)
+ """
+ # convert single bbox from (4, ) to (1, 4)
+ dim = bbox.ndim
+ if dim == 1:
+ bbox = bbox[None, :]
+
+ # get bbox center and scale
+ x1, y1, x2, y2 = np.hsplit(bbox, [1, 2, 3])
+ center = np.hstack([x1 + x2, y1 + y2]) * 0.5
+ scale = np.hstack([x2 - x1, y2 - y1]) * padding
+
+ if dim == 1:
+ center = center[0]
+ scale = scale[0]
+
+ return center, scale
+
+
+def _fix_aspect_ratio(bbox_scale: np.ndarray,
+ aspect_ratio: float) -> np.ndarray:
+ """Extend the scale to match the given aspect ratio.
+
+ Args:
+ scale (np.ndarray): The image scale (w, h) in shape (2, )
+ aspect_ratio (float): The ratio of ``w/h``
+
+ Returns:
+ np.ndarray: The reshaped image scale in (2, )
+ """
+ w, h = np.hsplit(bbox_scale, [1])
+ bbox_scale = np.where(w > h * aspect_ratio,
+ np.hstack([w, w / aspect_ratio]),
+ np.hstack([h * aspect_ratio, h]))
+ return bbox_scale
+
+
+def _rotate_point(pt: np.ndarray, angle_rad: float) -> np.ndarray:
+ """Rotate a point by an angle.
+
+ Args:
+ pt (np.ndarray): 2D point coordinates (x, y) in shape (2, )
+ angle_rad (float): rotation angle in radian
+
+ Returns:
+ np.ndarray: Rotated point in shape (2, )
+ """
+ sn, cs = np.sin(angle_rad), np.cos(angle_rad)
+ rot_mat = np.array([[cs, -sn], [sn, cs]])
+ return rot_mat @ pt
+
+
+def _get_3rd_point(a: np.ndarray, b: np.ndarray) -> np.ndarray:
+ """To calculate the affine matrix, three pairs of points are required. This
+ function is used to get the 3rd point, given 2D points a & b.
+
+ The 3rd point is defined by rotating vector `a - b` by 90 degrees
+ anticlockwise, using b as the rotation center.
+
+ Args:
+ a (np.ndarray): The 1st point (x,y) in shape (2, )
+ b (np.ndarray): The 2nd point (x,y) in shape (2, )
+
+ Returns:
+ np.ndarray: The 3rd point.
+ """
+ direction = a - b
+ c = b + np.r_[-direction[1], direction[0]]
+ return c
+
+
+def get_warp_matrix(center: np.ndarray,
+ scale: np.ndarray,
+ rot: float,
+ output_size: Tuple[int, int],
+ shift: Tuple[float, float] = (0., 0.),
+ inv: bool = False) -> np.ndarray:
+ """Calculate the affine transformation matrix that can warp the bbox area
+ in the input image to the output size.
+
+ Args:
+ center (np.ndarray[2, ]): Center of the bounding box (x, y).
+ scale (np.ndarray[2, ]): Scale of the bounding box
+ wrt [width, height].
+ rot (float): Rotation angle (degree).
+ output_size (np.ndarray[2, ] | list(2,)): Size of the
+ destination heatmaps.
+ shift (0-100%): Shift translation ratio wrt the width/height.
+ Default (0., 0.).
+ inv (bool): Option to inverse the affine transform direction.
+ (inv=False: src->dst or inv=True: dst->src)
+
+ Returns:
+ np.ndarray: A 2x3 transformation matrix
+ """
+ shift = np.array(shift)
+ src_w = scale[0]
+ dst_w = output_size[0]
+ dst_h = output_size[1]
+
+ # compute transformation matrix
+ rot_rad = np.deg2rad(rot)
+ src_dir = _rotate_point(np.array([0., src_w * -0.5]), rot_rad)
+ dst_dir = np.array([0., dst_w * -0.5])
+
+ # get four corners of the src rectangle in the original image
+ src = np.zeros((3, 2), dtype=np.float32)
+ src[0, :] = center + scale * shift
+ src[1, :] = center + src_dir + scale * shift
+ src[2, :] = _get_3rd_point(src[0, :], src[1, :])
+
+ # get four corners of the dst rectangle in the input image
+ dst = np.zeros((3, 2), dtype=np.float32)
+ dst[0, :] = [dst_w * 0.5, dst_h * 0.5]
+ dst[1, :] = np.array([dst_w * 0.5, dst_h * 0.5]) + dst_dir
+ dst[2, :] = _get_3rd_point(dst[0, :], dst[1, :])
+
+ if inv:
+ warp_mat = cv2.getAffineTransform(np.float32(dst), np.float32(src))
+ else:
+ warp_mat = cv2.getAffineTransform(np.float32(src), np.float32(dst))
+
+ return warp_mat
+
+
+def top_down_affine(input_size: dict, bbox_scale: dict, bbox_center: dict,
+ img: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
+ """Get the bbox image as the model input by affine transform.
+
+ Args:
+ input_size (dict): The input size of the model.
+ bbox_scale (dict): The bbox scale of the img.
+ bbox_center (dict): The bbox center of the img.
+ img (np.ndarray): The original image.
+
+ Returns:
+ tuple: A tuple containing center and scale.
+ - np.ndarray[float32]: img after affine transform.
+ - np.ndarray[float32]: bbox scale after affine transform.
+ """
+ w, h = input_size
+ warp_size = (int(w), int(h))
+
+ # reshape bbox to fixed aspect ratio
+ bbox_scale = _fix_aspect_ratio(bbox_scale, aspect_ratio=w / h)
+
+ # get the affine matrix
+ center = bbox_center
+ scale = bbox_scale
+ rot = 0
+ warp_mat = get_warp_matrix(center, scale, rot, output_size=(w, h))
+
+ # do affine transform
+ img = cv2.warpAffine(img, warp_mat, warp_size, flags=cv2.INTER_LINEAR)
+
+ return img, bbox_scale
+
+
+def get_simcc_maximum(simcc_x: np.ndarray,
+ simcc_y: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
+ """Get maximum response location and value from simcc representations.
+
+ Note:
+ instance number: N
+ num_keypoints: K
+ heatmap height: H
+ heatmap width: W
+
+ Args:
+ simcc_x (np.ndarray): x-axis SimCC in shape (K, Wx) or (N, K, Wx)
+ simcc_y (np.ndarray): y-axis SimCC in shape (K, Wy) or (N, K, Wy)
+
+ Returns:
+ tuple:
+ - locs (np.ndarray): locations of maximum heatmap responses in shape
+ (K, 2) or (N, K, 2)
+ - vals (np.ndarray): values of maximum heatmap responses in shape
+ (K,) or (N, K)
+ """
+ N, K, Wx = simcc_x.shape
+ simcc_x = simcc_x.reshape(N * K, -1)
+ simcc_y = simcc_y.reshape(N * K, -1)
+
+ # get maximum value locations
+ x_locs = np.argmax(simcc_x, axis=1)
+ y_locs = np.argmax(simcc_y, axis=1)
+ locs = np.stack((x_locs, y_locs), axis=-1).astype(np.float32)
+ max_val_x = np.amax(simcc_x, axis=1)
+ max_val_y = np.amax(simcc_y, axis=1)
+
+ # get maximum value across x and y axis
+ mask = max_val_x > max_val_y
+ max_val_x[mask] = max_val_y[mask]
+ vals = max_val_x
+ locs[vals <= 0.] = -1
+
+ # reshape
+ locs = locs.reshape(N, K, 2)
+ vals = vals.reshape(N, K)
+
+ return locs, vals
+
+
+def decode(simcc_x: np.ndarray, simcc_y: np.ndarray,
+ simcc_split_ratio) -> Tuple[np.ndarray, np.ndarray]:
+ """Modulate simcc distribution with Gaussian.
+
+ Args:
+ simcc_x (np.ndarray[K, Wx]): model predicted simcc in x.
+ simcc_y (np.ndarray[K, Wy]): model predicted simcc in y.
+ simcc_split_ratio (int): The split ratio of simcc.
+
+ Returns:
+ tuple: A tuple containing center and scale.
+ - np.ndarray[float32]: keypoints in shape (K, 2) or (n, K, 2)
+ - np.ndarray[float32]: scores in shape (K,) or (n, K)
+ """
+ keypoints, scores = get_simcc_maximum(simcc_x, simcc_y)
+ keypoints /= simcc_split_ratio
+
+ return keypoints, scores
+
+def inference_pose(model, out_bbox, oriImg, model_input_size=(288, 384)):
+ resized_img, center, scale = preprocess(oriImg, out_bbox, model_input_size)
+ #outputs = inference(session, resized_img, dtype)
+ outputs = inference(model, resized_img)
+
+ keypoints, scores = postprocess(outputs, model_input_size, center, scale)
+
+ return keypoints, scores
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/face.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/face.py
new file mode 100644
index 0000000000000000000000000000000000000000..f3c46d77664aa9fa91c63785a1485a396f05cacc
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/face.py
@@ -0,0 +1,362 @@
+import logging
+import numpy as np
+from torchvision.transforms import ToTensor, ToPILImage
+import torch
+import torch.nn.functional as F
+import cv2
+
+from . import util
+from torch.nn import Conv2d, Module, ReLU, MaxPool2d, init
+
+
+class FaceNet(Module):
+ """Model the cascading heatmaps. """
+ def __init__(self):
+ super(FaceNet, self).__init__()
+ # cnn to make feature map
+ self.relu = ReLU()
+ self.max_pooling_2d = MaxPool2d(kernel_size=2, stride=2)
+ self.conv1_1 = Conv2d(in_channels=3, out_channels=64,
+ kernel_size=3, stride=1, padding=1)
+ self.conv1_2 = Conv2d(
+ in_channels=64, out_channels=64, kernel_size=3, stride=1,
+ padding=1)
+ self.conv2_1 = Conv2d(
+ in_channels=64, out_channels=128, kernel_size=3, stride=1,
+ padding=1)
+ self.conv2_2 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=3, stride=1,
+ padding=1)
+ self.conv3_1 = Conv2d(
+ in_channels=128, out_channels=256, kernel_size=3, stride=1,
+ padding=1)
+ self.conv3_2 = Conv2d(
+ in_channels=256, out_channels=256, kernel_size=3, stride=1,
+ padding=1)
+ self.conv3_3 = Conv2d(
+ in_channels=256, out_channels=256, kernel_size=3, stride=1,
+ padding=1)
+ self.conv3_4 = Conv2d(
+ in_channels=256, out_channels=256, kernel_size=3, stride=1,
+ padding=1)
+ self.conv4_1 = Conv2d(
+ in_channels=256, out_channels=512, kernel_size=3, stride=1,
+ padding=1)
+ self.conv4_2 = Conv2d(
+ in_channels=512, out_channels=512, kernel_size=3, stride=1,
+ padding=1)
+ self.conv4_3 = Conv2d(
+ in_channels=512, out_channels=512, kernel_size=3, stride=1,
+ padding=1)
+ self.conv4_4 = Conv2d(
+ in_channels=512, out_channels=512, kernel_size=3, stride=1,
+ padding=1)
+ self.conv5_1 = Conv2d(
+ in_channels=512, out_channels=512, kernel_size=3, stride=1,
+ padding=1)
+ self.conv5_2 = Conv2d(
+ in_channels=512, out_channels=512, kernel_size=3, stride=1,
+ padding=1)
+ self.conv5_3_CPM = Conv2d(
+ in_channels=512, out_channels=128, kernel_size=3, stride=1,
+ padding=1)
+
+ # stage1
+ self.conv6_1_CPM = Conv2d(
+ in_channels=128, out_channels=512, kernel_size=1, stride=1,
+ padding=0)
+ self.conv6_2_CPM = Conv2d(
+ in_channels=512, out_channels=71, kernel_size=1, stride=1,
+ padding=0)
+
+ # stage2
+ self.Mconv1_stage2 = Conv2d(
+ in_channels=199, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv2_stage2 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv3_stage2 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv4_stage2 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv5_stage2 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv6_stage2 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=1, stride=1,
+ padding=0)
+ self.Mconv7_stage2 = Conv2d(
+ in_channels=128, out_channels=71, kernel_size=1, stride=1,
+ padding=0)
+
+ # stage3
+ self.Mconv1_stage3 = Conv2d(
+ in_channels=199, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv2_stage3 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv3_stage3 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv4_stage3 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv5_stage3 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv6_stage3 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=1, stride=1,
+ padding=0)
+ self.Mconv7_stage3 = Conv2d(
+ in_channels=128, out_channels=71, kernel_size=1, stride=1,
+ padding=0)
+
+ # stage4
+ self.Mconv1_stage4 = Conv2d(
+ in_channels=199, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv2_stage4 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv3_stage4 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv4_stage4 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv5_stage4 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv6_stage4 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=1, stride=1,
+ padding=0)
+ self.Mconv7_stage4 = Conv2d(
+ in_channels=128, out_channels=71, kernel_size=1, stride=1,
+ padding=0)
+
+ # stage5
+ self.Mconv1_stage5 = Conv2d(
+ in_channels=199, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv2_stage5 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv3_stage5 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv4_stage5 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv5_stage5 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv6_stage5 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=1, stride=1,
+ padding=0)
+ self.Mconv7_stage5 = Conv2d(
+ in_channels=128, out_channels=71, kernel_size=1, stride=1,
+ padding=0)
+
+ # stage6
+ self.Mconv1_stage6 = Conv2d(
+ in_channels=199, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv2_stage6 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv3_stage6 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv4_stage6 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv5_stage6 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv6_stage6 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=1, stride=1,
+ padding=0)
+ self.Mconv7_stage6 = Conv2d(
+ in_channels=128, out_channels=71, kernel_size=1, stride=1,
+ padding=0)
+
+ for m in self.modules():
+ if isinstance(m, Conv2d):
+ init.constant_(m.bias, 0)
+
+ def forward(self, x):
+ """Return a list of heatmaps."""
+ heatmaps = []
+
+ h = self.relu(self.conv1_1(x))
+ h = self.relu(self.conv1_2(h))
+ h = self.max_pooling_2d(h)
+ h = self.relu(self.conv2_1(h))
+ h = self.relu(self.conv2_2(h))
+ h = self.max_pooling_2d(h)
+ h = self.relu(self.conv3_1(h))
+ h = self.relu(self.conv3_2(h))
+ h = self.relu(self.conv3_3(h))
+ h = self.relu(self.conv3_4(h))
+ h = self.max_pooling_2d(h)
+ h = self.relu(self.conv4_1(h))
+ h = self.relu(self.conv4_2(h))
+ h = self.relu(self.conv4_3(h))
+ h = self.relu(self.conv4_4(h))
+ h = self.relu(self.conv5_1(h))
+ h = self.relu(self.conv5_2(h))
+ h = self.relu(self.conv5_3_CPM(h))
+ feature_map = h
+
+ # stage1
+ h = self.relu(self.conv6_1_CPM(h))
+ h = self.conv6_2_CPM(h)
+ heatmaps.append(h)
+
+ # stage2
+ h = torch.cat([h, feature_map], dim=1) # channel concat
+ h = self.relu(self.Mconv1_stage2(h))
+ h = self.relu(self.Mconv2_stage2(h))
+ h = self.relu(self.Mconv3_stage2(h))
+ h = self.relu(self.Mconv4_stage2(h))
+ h = self.relu(self.Mconv5_stage2(h))
+ h = self.relu(self.Mconv6_stage2(h))
+ h = self.Mconv7_stage2(h)
+ heatmaps.append(h)
+
+ # stage3
+ h = torch.cat([h, feature_map], dim=1) # channel concat
+ h = self.relu(self.Mconv1_stage3(h))
+ h = self.relu(self.Mconv2_stage3(h))
+ h = self.relu(self.Mconv3_stage3(h))
+ h = self.relu(self.Mconv4_stage3(h))
+ h = self.relu(self.Mconv5_stage3(h))
+ h = self.relu(self.Mconv6_stage3(h))
+ h = self.Mconv7_stage3(h)
+ heatmaps.append(h)
+
+ # stage4
+ h = torch.cat([h, feature_map], dim=1) # channel concat
+ h = self.relu(self.Mconv1_stage4(h))
+ h = self.relu(self.Mconv2_stage4(h))
+ h = self.relu(self.Mconv3_stage4(h))
+ h = self.relu(self.Mconv4_stage4(h))
+ h = self.relu(self.Mconv5_stage4(h))
+ h = self.relu(self.Mconv6_stage4(h))
+ h = self.Mconv7_stage4(h)
+ heatmaps.append(h)
+
+ # stage5
+ h = torch.cat([h, feature_map], dim=1) # channel concat
+ h = self.relu(self.Mconv1_stage5(h))
+ h = self.relu(self.Mconv2_stage5(h))
+ h = self.relu(self.Mconv3_stage5(h))
+ h = self.relu(self.Mconv4_stage5(h))
+ h = self.relu(self.Mconv5_stage5(h))
+ h = self.relu(self.Mconv6_stage5(h))
+ h = self.Mconv7_stage5(h)
+ heatmaps.append(h)
+
+ # stage6
+ h = torch.cat([h, feature_map], dim=1) # channel concat
+ h = self.relu(self.Mconv1_stage6(h))
+ h = self.relu(self.Mconv2_stage6(h))
+ h = self.relu(self.Mconv3_stage6(h))
+ h = self.relu(self.Mconv4_stage6(h))
+ h = self.relu(self.Mconv5_stage6(h))
+ h = self.relu(self.Mconv6_stage6(h))
+ h = self.Mconv7_stage6(h)
+ heatmaps.append(h)
+
+ return heatmaps
+
+
+LOG = logging.getLogger(__name__)
+TOTEN = ToTensor()
+TOPIL = ToPILImage()
+
+
+params = {
+ 'gaussian_sigma': 2.5,
+ 'inference_img_size': 736, # 368, 736, 1312
+ 'heatmap_peak_thresh': 0.1,
+ 'crop_scale': 1.5,
+ 'line_indices': [
+ [0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6],
+ [6, 7], [7, 8], [8, 9], [9, 10], [10, 11], [11, 12], [12, 13],
+ [13, 14], [14, 15], [15, 16],
+ [17, 18], [18, 19], [19, 20], [20, 21],
+ [22, 23], [23, 24], [24, 25], [25, 26],
+ [27, 28], [28, 29], [29, 30],
+ [31, 32], [32, 33], [33, 34], [34, 35],
+ [36, 37], [37, 38], [38, 39], [39, 40], [40, 41], [41, 36],
+ [42, 43], [43, 44], [44, 45], [45, 46], [46, 47], [47, 42],
+ [48, 49], [49, 50], [50, 51], [51, 52], [52, 53], [53, 54],
+ [54, 55], [55, 56], [56, 57], [57, 58], [58, 59], [59, 48],
+ [60, 61], [61, 62], [62, 63], [63, 64], [64, 65], [65, 66],
+ [66, 67], [67, 60]
+ ],
+}
+
+
+class Face(object):
+ """
+ The OpenPose face landmark detector model.
+
+ Args:
+ inference_size: set the size of the inference image size, suggested:
+ 368, 736, 1312, default 736
+ gaussian_sigma: blur the heatmaps, default 2.5
+ heatmap_peak_thresh: return landmark if over threshold, default 0.1
+
+ """
+ def __init__(self, face_model_path,
+ inference_size=None,
+ gaussian_sigma=None,
+ heatmap_peak_thresh=None):
+ self.inference_size = inference_size or params["inference_img_size"]
+ self.sigma = gaussian_sigma or params['gaussian_sigma']
+ self.threshold = heatmap_peak_thresh or params["heatmap_peak_thresh"]
+ self.model = FaceNet()
+ self.model.load_state_dict(torch.load(face_model_path))
+ # if torch.cuda.is_available():
+ # self.model = self.model.cuda()
+ # print('cuda')
+ self.model.eval()
+
+ def __call__(self, face_img):
+ H, W, C = face_img.shape
+
+ w_size = 384
+ x_data = torch.from_numpy(util.smart_resize(face_img, (w_size, w_size))).permute([2, 0, 1]) / 256.0 - 0.5
+
+ x_data = x_data.to(self.cn_device)
+
+ with torch.no_grad():
+ hs = self.model(x_data[None, ...])
+ heatmaps = F.interpolate(
+ hs[-1],
+ (H, W),
+ mode='bilinear', align_corners=True).cpu().numpy()[0]
+ return heatmaps
+
+ def compute_peaks_from_heatmaps(self, heatmaps):
+ all_peaks = []
+ for part in range(heatmaps.shape[0]):
+ map_ori = heatmaps[part].copy()
+ binary = np.ascontiguousarray(map_ori > 0.05, dtype=np.uint8)
+
+ if np.sum(binary) == 0:
+ continue
+
+ positions = np.where(binary > 0.5)
+ intensities = map_ori[positions]
+ mi = np.argmax(intensities)
+ y, x = positions[0][mi], positions[1][mi]
+ all_peaks.append([x, y])
+
+ return np.array(all_peaks)
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/hand.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/hand.py
new file mode 100644
index 0000000000000000000000000000000000000000..74767def506c72612954fe3b79056d17a83b1e16
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/hand.py
@@ -0,0 +1,94 @@
+import cv2
+import json
+import numpy as np
+import math
+import time
+from scipy.ndimage.filters import gaussian_filter
+import matplotlib.pyplot as plt
+import matplotlib
+import torch
+from skimage.measure import label
+
+from .model import handpose_model
+from . import util
+
+class Hand(object):
+ def __init__(self, model_path):
+ self.model = handpose_model()
+ # if torch.cuda.is_available():
+ # self.model = self.model.cuda()
+ # print('cuda')
+ model_dict = util.transfer(self.model, torch.load(model_path))
+ self.model.load_state_dict(model_dict)
+ self.model.eval()
+
+ def __call__(self, oriImgRaw):
+ scale_search = [0.5, 1.0, 1.5, 2.0]
+ # scale_search = [0.5]
+ boxsize = 368
+ stride = 8
+ padValue = 128
+ thre = 0.05
+ multiplier = [x * boxsize for x in scale_search]
+
+ wsize = 128
+ heatmap_avg = np.zeros((wsize, wsize, 22))
+
+ Hr, Wr, Cr = oriImgRaw.shape
+
+ oriImg = cv2.GaussianBlur(oriImgRaw, (0, 0), 0.8)
+
+ for m in range(len(multiplier)):
+ scale = multiplier[m]
+ imageToTest = util.smart_resize(oriImg, (scale, scale))
+
+ imageToTest_padded, pad = util.padRightDownCorner(imageToTest, stride, padValue)
+ im = np.transpose(np.float32(imageToTest_padded[:, :, :, np.newaxis]), (3, 2, 0, 1)) / 256 - 0.5
+ im = np.ascontiguousarray(im)
+
+ data = torch.from_numpy(im).float()
+ if torch.cuda.is_available():
+ data = data.cuda()
+
+ with torch.no_grad():
+ data = data.to(self.cn_device)
+ output = self.model(data).cpu().numpy()
+
+ # extract outputs, resize, and remove padding
+ heatmap = np.transpose(np.squeeze(output), (1, 2, 0)) # output 1 is heatmaps
+ heatmap = util.smart_resize_k(heatmap, fx=stride, fy=stride)
+ heatmap = heatmap[:imageToTest_padded.shape[0] - pad[2], :imageToTest_padded.shape[1] - pad[3], :]
+ heatmap = util.smart_resize(heatmap, (wsize, wsize))
+
+ heatmap_avg += heatmap / len(multiplier)
+
+ all_peaks = []
+ for part in range(21):
+ map_ori = heatmap_avg[:, :, part]
+ one_heatmap = gaussian_filter(map_ori, sigma=3)
+ binary = np.ascontiguousarray(one_heatmap > thre, dtype=np.uint8)
+
+ if np.sum(binary) == 0:
+ all_peaks.append([0, 0])
+ continue
+ label_img, label_numbers = label(binary, return_num=True, connectivity=binary.ndim)
+ max_index = np.argmax([np.sum(map_ori[label_img == i]) for i in range(1, label_numbers + 1)]) + 1
+ label_img[label_img != max_index] = 0
+ map_ori[label_img == 0] = 0
+
+ y, x = util.npmax(map_ori)
+ y = int(float(y) * float(Hr) / float(wsize))
+ x = int(float(x) * float(Wr) / float(wsize))
+ all_peaks.append([x, y])
+ return np.array(all_peaks)
+
+if __name__ == "__main__":
+ hand_estimation = Hand('../model/hand_pose_model.pth')
+
+ # test_image = '../images/hand.jpg'
+ test_image = '../images/hand.jpg'
+ oriImg = cv2.imread(test_image) # B,G,R order
+ peaks = hand_estimation(oriImg)
+ canvas = util.draw_handpose(oriImg, peaks, True)
+ cv2.imshow('', canvas)
+ cv2.waitKey(0)
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/model.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/model.py
new file mode 100644
index 0000000000000000000000000000000000000000..72dc79ad857933a7c108d21494d6395572b816e6
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/model.py
@@ -0,0 +1,218 @@
+import torch
+from collections import OrderedDict
+
+import torch
+import torch.nn as nn
+
+def make_layers(block, no_relu_layers):
+ layers = []
+ for layer_name, v in block.items():
+ if 'pool' in layer_name:
+ layer = nn.MaxPool2d(kernel_size=v[0], stride=v[1],
+ padding=v[2])
+ layers.append((layer_name, layer))
+ else:
+ conv2d = nn.Conv2d(in_channels=v[0], out_channels=v[1],
+ kernel_size=v[2], stride=v[3],
+ padding=v[4])
+ layers.append((layer_name, conv2d))
+ if layer_name not in no_relu_layers:
+ layers.append(('relu_'+layer_name, nn.ReLU(inplace=True)))
+
+ return nn.Sequential(OrderedDict(layers))
+
+class bodypose_model(nn.Module):
+ def __init__(self):
+ super(bodypose_model, self).__init__()
+
+ # these layers have no relu layer
+ no_relu_layers = ['conv5_5_CPM_L1', 'conv5_5_CPM_L2', 'Mconv7_stage2_L1',\
+ 'Mconv7_stage2_L2', 'Mconv7_stage3_L1', 'Mconv7_stage3_L2',\
+ 'Mconv7_stage4_L1', 'Mconv7_stage4_L2', 'Mconv7_stage5_L1',\
+ 'Mconv7_stage5_L2', 'Mconv7_stage6_L1', 'Mconv7_stage6_L1']
+ blocks = {}
+ block0 = OrderedDict([
+ ('conv1_1', [3, 64, 3, 1, 1]),
+ ('conv1_2', [64, 64, 3, 1, 1]),
+ ('pool1_stage1', [2, 2, 0]),
+ ('conv2_1', [64, 128, 3, 1, 1]),
+ ('conv2_2', [128, 128, 3, 1, 1]),
+ ('pool2_stage1', [2, 2, 0]),
+ ('conv3_1', [128, 256, 3, 1, 1]),
+ ('conv3_2', [256, 256, 3, 1, 1]),
+ ('conv3_3', [256, 256, 3, 1, 1]),
+ ('conv3_4', [256, 256, 3, 1, 1]),
+ ('pool3_stage1', [2, 2, 0]),
+ ('conv4_1', [256, 512, 3, 1, 1]),
+ ('conv4_2', [512, 512, 3, 1, 1]),
+ ('conv4_3_CPM', [512, 256, 3, 1, 1]),
+ ('conv4_4_CPM', [256, 128, 3, 1, 1])
+ ])
+
+
+ # Stage 1
+ block1_1 = OrderedDict([
+ ('conv5_1_CPM_L1', [128, 128, 3, 1, 1]),
+ ('conv5_2_CPM_L1', [128, 128, 3, 1, 1]),
+ ('conv5_3_CPM_L1', [128, 128, 3, 1, 1]),
+ ('conv5_4_CPM_L1', [128, 512, 1, 1, 0]),
+ ('conv5_5_CPM_L1', [512, 38, 1, 1, 0])
+ ])
+
+ block1_2 = OrderedDict([
+ ('conv5_1_CPM_L2', [128, 128, 3, 1, 1]),
+ ('conv5_2_CPM_L2', [128, 128, 3, 1, 1]),
+ ('conv5_3_CPM_L2', [128, 128, 3, 1, 1]),
+ ('conv5_4_CPM_L2', [128, 512, 1, 1, 0]),
+ ('conv5_5_CPM_L2', [512, 19, 1, 1, 0])
+ ])
+ blocks['block1_1'] = block1_1
+ blocks['block1_2'] = block1_2
+
+ self.model0 = make_layers(block0, no_relu_layers)
+
+ # Stages 2 - 6
+ for i in range(2, 7):
+ blocks['block%d_1' % i] = OrderedDict([
+ ('Mconv1_stage%d_L1' % i, [185, 128, 7, 1, 3]),
+ ('Mconv2_stage%d_L1' % i, [128, 128, 7, 1, 3]),
+ ('Mconv3_stage%d_L1' % i, [128, 128, 7, 1, 3]),
+ ('Mconv4_stage%d_L1' % i, [128, 128, 7, 1, 3]),
+ ('Mconv5_stage%d_L1' % i, [128, 128, 7, 1, 3]),
+ ('Mconv6_stage%d_L1' % i, [128, 128, 1, 1, 0]),
+ ('Mconv7_stage%d_L1' % i, [128, 38, 1, 1, 0])
+ ])
+
+ blocks['block%d_2' % i] = OrderedDict([
+ ('Mconv1_stage%d_L2' % i, [185, 128, 7, 1, 3]),
+ ('Mconv2_stage%d_L2' % i, [128, 128, 7, 1, 3]),
+ ('Mconv3_stage%d_L2' % i, [128, 128, 7, 1, 3]),
+ ('Mconv4_stage%d_L2' % i, [128, 128, 7, 1, 3]),
+ ('Mconv5_stage%d_L2' % i, [128, 128, 7, 1, 3]),
+ ('Mconv6_stage%d_L2' % i, [128, 128, 1, 1, 0]),
+ ('Mconv7_stage%d_L2' % i, [128, 19, 1, 1, 0])
+ ])
+
+ for k in blocks.keys():
+ blocks[k] = make_layers(blocks[k], no_relu_layers)
+
+ self.model1_1 = blocks['block1_1']
+ self.model2_1 = blocks['block2_1']
+ self.model3_1 = blocks['block3_1']
+ self.model4_1 = blocks['block4_1']
+ self.model5_1 = blocks['block5_1']
+ self.model6_1 = blocks['block6_1']
+
+ self.model1_2 = blocks['block1_2']
+ self.model2_2 = blocks['block2_2']
+ self.model3_2 = blocks['block3_2']
+ self.model4_2 = blocks['block4_2']
+ self.model5_2 = blocks['block5_2']
+ self.model6_2 = blocks['block6_2']
+
+
+ def forward(self, x):
+
+ out1 = self.model0(x)
+
+ out1_1 = self.model1_1(out1)
+ out1_2 = self.model1_2(out1)
+ out2 = torch.cat([out1_1, out1_2, out1], 1)
+
+ out2_1 = self.model2_1(out2)
+ out2_2 = self.model2_2(out2)
+ out3 = torch.cat([out2_1, out2_2, out1], 1)
+
+ out3_1 = self.model3_1(out3)
+ out3_2 = self.model3_2(out3)
+ out4 = torch.cat([out3_1, out3_2, out1], 1)
+
+ out4_1 = self.model4_1(out4)
+ out4_2 = self.model4_2(out4)
+ out5 = torch.cat([out4_1, out4_2, out1], 1)
+
+ out5_1 = self.model5_1(out5)
+ out5_2 = self.model5_2(out5)
+ out6 = torch.cat([out5_1, out5_2, out1], 1)
+
+ out6_1 = self.model6_1(out6)
+ out6_2 = self.model6_2(out6)
+
+ return out6_1, out6_2
+
+class handpose_model(nn.Module):
+ def __init__(self):
+ super(handpose_model, self).__init__()
+
+ # these layers have no relu layer
+ no_relu_layers = ['conv6_2_CPM', 'Mconv7_stage2', 'Mconv7_stage3',\
+ 'Mconv7_stage4', 'Mconv7_stage5', 'Mconv7_stage6']
+ # stage 1
+ block1_0 = OrderedDict([
+ ('conv1_1', [3, 64, 3, 1, 1]),
+ ('conv1_2', [64, 64, 3, 1, 1]),
+ ('pool1_stage1', [2, 2, 0]),
+ ('conv2_1', [64, 128, 3, 1, 1]),
+ ('conv2_2', [128, 128, 3, 1, 1]),
+ ('pool2_stage1', [2, 2, 0]),
+ ('conv3_1', [128, 256, 3, 1, 1]),
+ ('conv3_2', [256, 256, 3, 1, 1]),
+ ('conv3_3', [256, 256, 3, 1, 1]),
+ ('conv3_4', [256, 256, 3, 1, 1]),
+ ('pool3_stage1', [2, 2, 0]),
+ ('conv4_1', [256, 512, 3, 1, 1]),
+ ('conv4_2', [512, 512, 3, 1, 1]),
+ ('conv4_3', [512, 512, 3, 1, 1]),
+ ('conv4_4', [512, 512, 3, 1, 1]),
+ ('conv5_1', [512, 512, 3, 1, 1]),
+ ('conv5_2', [512, 512, 3, 1, 1]),
+ ('conv5_3_CPM', [512, 128, 3, 1, 1])
+ ])
+
+ block1_1 = OrderedDict([
+ ('conv6_1_CPM', [128, 512, 1, 1, 0]),
+ ('conv6_2_CPM', [512, 22, 1, 1, 0])
+ ])
+
+ blocks = {}
+ blocks['block1_0'] = block1_0
+ blocks['block1_1'] = block1_1
+
+ # stage 2-6
+ for i in range(2, 7):
+ blocks['block%d' % i] = OrderedDict([
+ ('Mconv1_stage%d' % i, [150, 128, 7, 1, 3]),
+ ('Mconv2_stage%d' % i, [128, 128, 7, 1, 3]),
+ ('Mconv3_stage%d' % i, [128, 128, 7, 1, 3]),
+ ('Mconv4_stage%d' % i, [128, 128, 7, 1, 3]),
+ ('Mconv5_stage%d' % i, [128, 128, 7, 1, 3]),
+ ('Mconv6_stage%d' % i, [128, 128, 1, 1, 0]),
+ ('Mconv7_stage%d' % i, [128, 22, 1, 1, 0])
+ ])
+
+ for k in blocks.keys():
+ blocks[k] = make_layers(blocks[k], no_relu_layers)
+
+ self.model1_0 = blocks['block1_0']
+ self.model1_1 = blocks['block1_1']
+ self.model2 = blocks['block2']
+ self.model3 = blocks['block3']
+ self.model4 = blocks['block4']
+ self.model5 = blocks['block5']
+ self.model6 = blocks['block6']
+
+ def forward(self, x):
+ out1_0 = self.model1_0(x)
+ out1_1 = self.model1_1(out1_0)
+ concat_stage2 = torch.cat([out1_1, out1_0], 1)
+ out_stage2 = self.model2(concat_stage2)
+ concat_stage3 = torch.cat([out_stage2, out1_0], 1)
+ out_stage3 = self.model3(concat_stage3)
+ concat_stage4 = torch.cat([out_stage3, out1_0], 1)
+ out_stage4 = self.model4(concat_stage4)
+ concat_stage5 = torch.cat([out_stage4, out1_0], 1)
+ out_stage5 = self.model5(concat_stage5)
+ concat_stage6 = torch.cat([out_stage5, out1_0], 1)
+ out_stage6 = self.model6(concat_stage6)
+ return out_stage6
+
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/types.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/types.py
new file mode 100644
index 0000000000000000000000000000000000000000..e521e65dcbe155dc8fe863c0a016184d829ec751
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/types.py
@@ -0,0 +1,29 @@
+from typing import NamedTuple, List, Optional
+
+class Keypoint(NamedTuple):
+ x: float
+ y: float
+ score: float = 1.0
+ id: int = -1
+
+
+class BodyResult(NamedTuple):
+ # Note: Using `Optional` instead of `|` operator as the ladder is a Python
+ # 3.10 feature.
+ # Annotator code should be Python 3.8 Compatible, as controlnet repo uses
+ # Python 3.8 environment.
+ # https://github.com/lllyasviel/ControlNet/blob/d3284fcd0972c510635a4f5abe2eeb71dc0de524/environment.yaml#L6
+ keypoints: List[Optional[Keypoint]]
+ total_score: float = 0.0
+ total_parts: int = 0
+
+
+HandResult = List[Keypoint]
+FaceResult = List[Keypoint]
+
+
+class PoseResult(NamedTuple):
+ body: BodyResult
+ left_hand: Optional[HandResult]
+ right_hand: Optional[HandResult]
+ face: Optional[FaceResult]
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/util.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/util.py
new file mode 100644
index 0000000000000000000000000000000000000000..cce0dc28a0989af7ae1f04ca6f782d694e27a4bd
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/util.py
@@ -0,0 +1,457 @@
+import math
+import numpy as np
+import matplotlib
+import cv2
+import os
+from typing import List, Tuple, Union, Optional
+
+from .body import BodyResult, Keypoint
+
+eps = 0.01
+
+
+def smart_resize(x, s):
+ Ht, Wt = s
+ if x.ndim == 2:
+ Ho, Wo = x.shape
+ Co = 1
+ else:
+ Ho, Wo, Co = x.shape
+ if Co == 3 or Co == 1:
+ k = float(Ht + Wt) / float(Ho + Wo)
+ return cv2.resize(x, (int(Wt), int(Ht)), interpolation=cv2.INTER_AREA if k < 1 else cv2.INTER_LANCZOS4)
+ else:
+ return np.stack([smart_resize(x[:, :, i], s) for i in range(Co)], axis=2)
+
+
+def smart_resize_k(x, fx, fy):
+ if x.ndim == 2:
+ Ho, Wo = x.shape
+ Co = 1
+ else:
+ Ho, Wo, Co = x.shape
+ Ht, Wt = Ho * fy, Wo * fx
+ if Co == 3 or Co == 1:
+ k = float(Ht + Wt) / float(Ho + Wo)
+ return cv2.resize(x, (int(Wt), int(Ht)), interpolation=cv2.INTER_AREA if k < 1 else cv2.INTER_LANCZOS4)
+ else:
+ return np.stack([smart_resize_k(x[:, :, i], fx, fy) for i in range(Co)], axis=2)
+
+
+def padRightDownCorner(img, stride, padValue):
+ h = img.shape[0]
+ w = img.shape[1]
+
+ pad = 4 * [None]
+ pad[0] = 0 # up
+ pad[1] = 0 # left
+ pad[2] = 0 if (h % stride == 0) else stride - (h % stride) # down
+ pad[3] = 0 if (w % stride == 0) else stride - (w % stride) # right
+
+ img_padded = img
+ pad_up = np.tile(img_padded[0:1, :, :]*0 + padValue, (pad[0], 1, 1))
+ img_padded = np.concatenate((pad_up, img_padded), axis=0)
+ pad_left = np.tile(img_padded[:, 0:1, :]*0 + padValue, (1, pad[1], 1))
+ img_padded = np.concatenate((pad_left, img_padded), axis=1)
+ pad_down = np.tile(img_padded[-2:-1, :, :]*0 + padValue, (pad[2], 1, 1))
+ img_padded = np.concatenate((img_padded, pad_down), axis=0)
+ pad_right = np.tile(img_padded[:, -2:-1, :]*0 + padValue, (1, pad[3], 1))
+ img_padded = np.concatenate((img_padded, pad_right), axis=1)
+
+ return img_padded, pad
+
+
+def transfer(model, model_weights):
+ transfered_model_weights = {}
+ for weights_name in model.state_dict().keys():
+ transfered_model_weights[weights_name] = model_weights['.'.join(weights_name.split('.')[1:])]
+ return transfered_model_weights
+
+
+def is_normalized(keypoints: List[Optional[Keypoint]]) -> bool:
+ point_normalized = [
+ 0 <= abs(k.x) <= 1 and 0 <= abs(k.y) <= 1
+ for k in keypoints
+ if k is not None
+ ]
+ if not point_normalized:
+ return False
+ return all(point_normalized)
+
+
+def draw_bodypose(canvas: np.ndarray, keypoints: List[Keypoint]) -> np.ndarray:
+ """
+ Draw keypoints and limbs representing body pose on a given canvas.
+
+ Args:
+ canvas (np.ndarray): A 3D numpy array representing the canvas (image) on which to draw the body pose.
+ keypoints (List[Keypoint]): A list of Keypoint objects representing the body keypoints to be drawn.
+
+ Returns:
+ np.ndarray: A 3D numpy array representing the modified canvas with the drawn body pose.
+
+ Note:
+ The function expects the x and y coordinates of the keypoints to be normalized between 0 and 1.
+ """
+ if not is_normalized(keypoints):
+ H, W = 1.0, 1.0
+ else:
+ H, W, _ = canvas.shape
+
+ stickwidth = 4
+
+ limbSeq = [
+ [2, 3], [2, 6], [3, 4], [4, 5],
+ [6, 7], [7, 8], [2, 9], [9, 10],
+ [10, 11], [2, 12], [12, 13], [13, 14],
+ [2, 1], [1, 15], [15, 17], [1, 16],
+ [16, 18],
+ ]
+
+ colors = [[255, 0, 0], [255, 85, 0], [255, 170, 0], [255, 255, 0], [170, 255, 0], [85, 255, 0], [0, 255, 0], \
+ [0, 255, 85], [0, 255, 170], [0, 255, 255], [0, 170, 255], [0, 85, 255], [0, 0, 255], [85, 0, 255], \
+ [170, 0, 255], [255, 0, 255], [255, 0, 170], [255, 0, 85]]
+
+ for (k1_index, k2_index), color in zip(limbSeq, colors):
+ keypoint1 = keypoints[k1_index - 1]
+ keypoint2 = keypoints[k2_index - 1]
+
+ if keypoint1 is None or keypoint2 is None:
+ continue
+
+ Y = np.array([keypoint1.x, keypoint2.x]) * float(W)
+ X = np.array([keypoint1.y, keypoint2.y]) * float(H)
+ mX = np.mean(X)
+ mY = np.mean(Y)
+ length = ((X[0] - X[1]) ** 2 + (Y[0] - Y[1]) ** 2) ** 0.5
+ angle = math.degrees(math.atan2(X[0] - X[1], Y[0] - Y[1]))
+ polygon = cv2.ellipse2Poly((int(mY), int(mX)), (int(length / 2), stickwidth), int(angle), 0, 360, 1)
+ cv2.fillConvexPoly(canvas, polygon, [int(float(c) * 0.6) for c in color])
+
+ for keypoint, color in zip(keypoints, colors):
+ if keypoint is None:
+ continue
+
+ x, y = keypoint.x, keypoint.y
+ x = int(x * W)
+ y = int(y * H)
+ cv2.circle(canvas, (int(x), int(y)), 4, color, thickness=-1)
+
+ return canvas
+
+
+def draw_handpose(canvas: np.ndarray, keypoints: Union[List[Keypoint], None]) -> np.ndarray:
+ """
+ Draw keypoints and connections representing hand pose on a given canvas.
+
+ Args:
+ canvas (np.ndarray): A 3D numpy array representing the canvas (image) on which to draw the hand pose.
+ keypoints (List[Keypoint]| None): A list of Keypoint objects representing the hand keypoints to be drawn
+ or None if no keypoints are present.
+
+ Returns:
+ np.ndarray: A 3D numpy array representing the modified canvas with the drawn hand pose.
+
+ Note:
+ The function expects the x and y coordinates of the keypoints to be normalized between 0 and 1.
+ """
+ if not keypoints:
+ return canvas
+
+ if not is_normalized(keypoints):
+ H, W = 1.0, 1.0
+ else:
+ H, W, _ = canvas.shape
+
+ edges = [[0, 1], [1, 2], [2, 3], [3, 4], [0, 5], [5, 6], [6, 7], [7, 8], [0, 9], [9, 10], \
+ [10, 11], [11, 12], [0, 13], [13, 14], [14, 15], [15, 16], [0, 17], [17, 18], [18, 19], [19, 20]]
+
+ for ie, (e1, e2) in enumerate(edges):
+ k1 = keypoints[e1]
+ k2 = keypoints[e2]
+ if k1 is None or k2 is None:
+ continue
+
+ x1 = int(k1.x * W)
+ y1 = int(k1.y * H)
+ x2 = int(k2.x * W)
+ y2 = int(k2.y * H)
+ if x1 > eps and y1 > eps and x2 > eps and y2 > eps:
+ cv2.line(canvas, (x1, y1), (x2, y2), matplotlib.colors.hsv_to_rgb([ie / float(len(edges)), 1.0, 1.0]) * 255, thickness=2)
+
+ for keypoint in keypoints:
+ if keypoint is None:
+ continue
+
+ x, y = keypoint.x, keypoint.y
+ x = int(x * W)
+ y = int(y * H)
+ if x > eps and y > eps:
+ cv2.circle(canvas, (x, y), 4, (0, 0, 255), thickness=-1)
+ return canvas
+
+
+def draw_facepose(canvas: np.ndarray, keypoints: Union[List[Keypoint], None]) -> np.ndarray:
+ """
+ Draw keypoints representing face pose on a given canvas.
+
+ Args:
+ canvas (np.ndarray): A 3D numpy array representing the canvas (image) on which to draw the face pose.
+ keypoints (List[Keypoint]| None): A list of Keypoint objects representing the face keypoints to be drawn
+ or None if no keypoints are present.
+
+ Returns:
+ np.ndarray: A 3D numpy array representing the modified canvas with the drawn face pose.
+
+ Note:
+ The function expects the x and y coordinates of the keypoints to be normalized between 0 and 1.
+ """
+ if not keypoints:
+ return canvas
+
+ if not is_normalized(keypoints):
+ H, W = 1.0, 1.0
+ else:
+ H, W, _ = canvas.shape
+
+ for keypoint in keypoints:
+ if keypoint is None:
+ continue
+
+ x, y = keypoint.x, keypoint.y
+ x = int(x * W)
+ y = int(y * H)
+ if x > eps and y > eps:
+ cv2.circle(canvas, (x, y), 3, (255, 255, 255), thickness=-1)
+ return canvas
+
+
+# detect hand according to body pose keypoints
+# please refer to https://github.com/CMU-Perceptual-Computing-Lab/openpose/blob/master/src/openpose/hand/handDetector.cpp
+def handDetect(body: BodyResult, oriImg) -> List[Tuple[int, int, int, bool]]:
+ """
+ Detect hands in the input body pose keypoints and calculate the bounding box for each hand.
+
+ Args:
+ body (BodyResult): A BodyResult object containing the detected body pose keypoints.
+ oriImg (numpy.ndarray): A 3D numpy array representing the original input image.
+
+ Returns:
+ List[Tuple[int, int, int, bool]]: A list of tuples, each containing the coordinates (x, y) of the top-left
+ corner of the bounding box, the width (height) of the bounding box, and
+ a boolean flag indicating whether the hand is a left hand (True) or a
+ right hand (False).
+
+ Notes:
+ - The width and height of the bounding boxes are equal since the network requires squared input.
+ - The minimum bounding box size is 20 pixels.
+ """
+ ratioWristElbow = 0.33
+ detect_result = []
+ image_height, image_width = oriImg.shape[0:2]
+
+ keypoints = body.keypoints
+ # right hand: wrist 4, elbow 3, shoulder 2
+ # left hand: wrist 7, elbow 6, shoulder 5
+ left_shoulder = keypoints[5]
+ left_elbow = keypoints[6]
+ left_wrist = keypoints[7]
+ right_shoulder = keypoints[2]
+ right_elbow = keypoints[3]
+ right_wrist = keypoints[4]
+
+ # if any of three not detected
+ has_left = all(keypoint is not None for keypoint in (left_shoulder, left_elbow, left_wrist))
+ has_right = all(keypoint is not None for keypoint in (right_shoulder, right_elbow, right_wrist))
+ if not (has_left or has_right):
+ return []
+
+ hands = []
+ #left hand
+ if has_left:
+ hands.append([
+ left_shoulder.x, left_shoulder.y,
+ left_elbow.x, left_elbow.y,
+ left_wrist.x, left_wrist.y,
+ True
+ ])
+ # right hand
+ if has_right:
+ hands.append([
+ right_shoulder.x, right_shoulder.y,
+ right_elbow.x, right_elbow.y,
+ right_wrist.x, right_wrist.y,
+ False
+ ])
+
+ for x1, y1, x2, y2, x3, y3, is_left in hands:
+ # pos_hand = pos_wrist + ratio * (pos_wrist - pos_elbox) = (1 + ratio) * pos_wrist - ratio * pos_elbox
+ # handRectangle.x = posePtr[wrist*3] + ratioWristElbow * (posePtr[wrist*3] - posePtr[elbow*3]);
+ # handRectangle.y = posePtr[wrist*3+1] + ratioWristElbow * (posePtr[wrist*3+1] - posePtr[elbow*3+1]);
+ # const auto distanceWristElbow = getDistance(poseKeypoints, person, wrist, elbow);
+ # const auto distanceElbowShoulder = getDistance(poseKeypoints, person, elbow, shoulder);
+ # handRectangle.width = 1.5f * fastMax(distanceWristElbow, 0.9f * distanceElbowShoulder);
+ x = x3 + ratioWristElbow * (x3 - x2)
+ y = y3 + ratioWristElbow * (y3 - y2)
+ distanceWristElbow = math.sqrt((x3 - x2) ** 2 + (y3 - y2) ** 2)
+ distanceElbowShoulder = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
+ width = 1.5 * max(distanceWristElbow, 0.9 * distanceElbowShoulder)
+ # x-y refers to the center --> offset to topLeft point
+ # handRectangle.x -= handRectangle.width / 2.f;
+ # handRectangle.y -= handRectangle.height / 2.f;
+ x -= width / 2
+ y -= width / 2 # width = height
+ # overflow the image
+ if x < 0: x = 0
+ if y < 0: y = 0
+ width1 = width
+ width2 = width
+ if x + width > image_width: width1 = image_width - x
+ if y + width > image_height: width2 = image_height - y
+ width = min(width1, width2)
+ # the max hand box value is 20 pixels
+ if width >= 20:
+ detect_result.append((int(x), int(y), int(width), is_left))
+
+ '''
+ return value: [[x, y, w, True if left hand else False]].
+ width=height since the network require squared input.
+ x, y is the coordinate of top left
+ '''
+ return detect_result
+
+
+# Written by Lvmin
+def faceDetect(body: BodyResult, oriImg) -> Union[Tuple[int, int, int], None]:
+ """
+ Detect the face in the input body pose keypoints and calculate the bounding box for the face.
+
+ Args:
+ body (BodyResult): A BodyResult object containing the detected body pose keypoints.
+ oriImg (numpy.ndarray): A 3D numpy array representing the original input image.
+
+ Returns:
+ Tuple[int, int, int] | None: A tuple containing the coordinates (x, y) of the top-left corner of the
+ bounding box and the width (height) of the bounding box, or None if the
+ face is not detected or the bounding box width is less than 20 pixels.
+
+ Notes:
+ - The width and height of the bounding box are equal.
+ - The minimum bounding box size is 20 pixels.
+ """
+ # left right eye ear 14 15 16 17
+ image_height, image_width = oriImg.shape[0:2]
+
+ keypoints = body.keypoints
+ head = keypoints[0]
+ left_eye = keypoints[14]
+ right_eye = keypoints[15]
+ left_ear = keypoints[16]
+ right_ear = keypoints[17]
+
+ if head is None or all(keypoint is None for keypoint in (left_eye, right_eye, left_ear, right_ear)):
+ return None
+
+ width = 0.0
+ x0, y0 = head.x, head.y
+
+ if left_eye is not None:
+ x1, y1 = left_eye.x, left_eye.y
+ d = max(abs(x0 - x1), abs(y0 - y1))
+ width = max(width, d * 3.0)
+
+ if right_eye is not None:
+ x1, y1 = right_eye.x, right_eye.y
+ d = max(abs(x0 - x1), abs(y0 - y1))
+ width = max(width, d * 3.0)
+
+ if left_ear is not None:
+ x1, y1 = left_ear.x, left_ear.y
+ d = max(abs(x0 - x1), abs(y0 - y1))
+ width = max(width, d * 1.5)
+
+ if right_ear is not None:
+ x1, y1 = right_ear.x, right_ear.y
+ d = max(abs(x0 - x1), abs(y0 - y1))
+ width = max(width, d * 1.5)
+
+ x, y = x0, y0
+
+ x -= width
+ y -= width
+
+ if x < 0:
+ x = 0
+
+ if y < 0:
+ y = 0
+
+ width1 = width * 2
+ width2 = width * 2
+
+ if x + width > image_width:
+ width1 = image_width - x
+
+ if y + width > image_height:
+ width2 = image_height - y
+
+ width = min(width1, width2)
+
+ if width >= 20:
+ return int(x), int(y), int(width)
+ else:
+ return None
+
+
+# get max index of 2d array
+def npmax(array):
+ arrayindex = array.argmax(1)
+ arrayvalue = array.max(1)
+ i = arrayvalue.argmax()
+ j = arrayindex[i]
+ return i, j
+
+def guess_onnx_input_shape_dtype(filename):
+ dtype = np.float32
+ if "fp16" in filename:
+ dtype = np.float16
+ elif "int8" in filename:
+ dtype = np.uint8
+ input_size = (640, 640) if "yolo" in filename else (192, 256)
+ if "384" in filename:
+ input_size = (288, 384)
+ elif "256" in filename:
+ input_size = (256, 256)
+ return input_size, dtype
+
+if os.getenv('AUX_ORT_PROVIDERS'):
+ ONNX_PROVIDERS = os.getenv('AUX_ORT_PROVIDERS').split(',')
+else:
+ ONNX_PROVIDERS = ["CUDAExecutionProvider", "DirectMLExecutionProvider", "OpenVINOExecutionProvider", "ROCMExecutionProvider", "CPUExecutionProvider"]
+def get_ort_providers() -> List[str]:
+ providers = []
+ try:
+ import onnxruntime as ort
+ for provider in ONNX_PROVIDERS:
+ if provider in ort.get_available_providers():
+ providers.append(provider)
+ return providers
+ except:
+ return []
+
+def is_model_torchscript(model) -> bool:
+ return bool(type(model).__name__ == "RecursiveScriptModule")
+
+def get_model_type(Nodesname, filename) -> str:
+ ort_providers = list(filter(lambda x : x != "CPUExecutionProvider", get_ort_providers()))
+ if filename is None:
+ return None
+ elif ("onnx" in filename) and ort_providers:
+ print(f"{Nodesname}: Caching ONNXRuntime session {filename}...")
+ return "ort"
+ elif ("onnx" in filename):
+ print(f"{Nodesname}: Caching OpenCV DNN module {filename} on cv2.DNN...")
+ return "cv2"
+ else:
+ print(f"{Nodesname}: Caching TorchScript module {filename} on ...")
+ return "torchscript"
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/wholebody.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/wholebody.py
new file mode 100644
index 0000000000000000000000000000000000000000..83fbf720b2cf08a1b672f5c75793fa62009dd93e
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/dwpose/wholebody.py
@@ -0,0 +1,172 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import cv2
+import numpy as np
+
+from .dw_onnx.cv_ox_det import inference_detector as inference_onnx_yolox
+from .dw_onnx.cv_ox_yolo_nas import inference_detector as inference_onnx_yolo_nas
+from .dw_onnx.cv_ox_pose import inference_pose as inference_onnx_pose
+
+from .dw_torchscript.jit_det import inference_detector as inference_jit_yolox
+from .dw_torchscript.jit_pose import inference_pose as inference_jit_pose
+
+from typing import List, Optional
+from .types import PoseResult, BodyResult, Keypoint
+from timeit import default_timer
+import os
+from controlnet_aux.dwpose.util import guess_onnx_input_shape_dtype, get_model_type, get_ort_providers, is_model_torchscript
+import torch
+import torch.utils.benchmark.utils.timer as torch_timer
+
+class Wholebody:
+ def __init__(self, det_model_path: Optional[str] = None, pose_model_path: Optional[str] = None, torchscript_device="cuda"):
+ self.det_filename = det_model_path and os.path.basename(det_model_path)
+ self.pose_filename = pose_model_path and os.path.basename(pose_model_path)
+ self.det, self.pose = None, None
+ # return type: None ort cv2 torchscript
+ self.det_model_type = get_model_type("DWPose",self.det_filename)
+ self.pose_model_type = get_model_type("DWPose",self.pose_filename)
+ # Always loads to CPU to avoid building OpenCV.
+ cv2_device = 'cpu'
+ cv2_backend = cv2.dnn.DNN_BACKEND_OPENCV if cv2_device == 'cpu' else cv2.dnn.DNN_BACKEND_CUDA
+ # You need to manually build OpenCV through cmake to work with your GPU.
+ cv2_providers = cv2.dnn.DNN_TARGET_CPU if cv2_device == 'cpu' else cv2.dnn.DNN_TARGET_CUDA
+ ort_providers = get_ort_providers()
+
+ if self.det_model_type is None:
+ pass
+ elif self.det_model_type == "ort":
+ try:
+ import onnxruntime as ort
+ self.det = ort.InferenceSession(det_model_path, providers=ort_providers)
+ except:
+ print(f"Failed to load onnxruntime with {self.det.get_providers()}.\nPlease change EP_list in the config.yaml and restart ComfyUI")
+ self.det = ort.InferenceSession(det_model_path, providers=["CPUExecutionProvider"])
+ elif self.det_model_type == "cv2":
+ try:
+ self.det = cv2.dnn.readNetFromONNX(det_model_path)
+ self.det.setPreferableBackend(cv2_backend)
+ self.det.setPreferableTarget(cv2_providers)
+ except:
+ print("TopK operators may not work on your OpenCV, try use onnxruntime with CPUExecutionProvider")
+ try:
+ import onnxruntime as ort
+ self.det = ort.InferenceSession(det_model_path, providers=["CPUExecutionProvider"])
+ except:
+ print(f"Failed to load {det_model_path}, you can use other models instead")
+ else:
+ self.det = torch.jit.load(det_model_path)
+ self.det.to(torchscript_device)
+
+ if self.pose_model_type is None:
+ pass
+ elif self.pose_model_type == "ort":
+ try:
+ import onnxruntime as ort
+ self.pose = ort.InferenceSession(pose_model_path, providers=ort_providers)
+ except:
+ print(f"Failed to load onnxruntime with {self.pose.get_providers()}.\nPlease change EP_list in the config.yaml and restart ComfyUI")
+ self.pose = ort.InferenceSession(pose_model_path, providers=["CPUExecutionProvider"])
+ elif self.pose_model_type == "cv2":
+ self.pose = cv2.dnn.readNetFromONNX(pose_model_path)
+ self.pose.setPreferableBackend(cv2_backend)
+ self.pose.setPreferableTarget(cv2_providers)
+ else:
+ self.pose = torch.jit.load(pose_model_path)
+ self.pose.to(torchscript_device)
+
+ if self.pose_filename is not None:
+ self.pose_input_size, _ = guess_onnx_input_shape_dtype(self.pose_filename)
+
+ def __call__(self, oriImg) -> Optional[np.ndarray]:
+
+ if is_model_torchscript(self.det):
+ det_start = torch_timer.timer()
+ det_result = inference_jit_yolox(self.det, oriImg, detect_classes=[0])
+ print(f"DWPose: Bbox {((torch_timer.timer() - det_start) * 1000):.2f}ms")
+ else:
+ det_start = default_timer()
+ if "yolox" in self.det_filename:
+ det_result = inference_onnx_yolox(self.det, oriImg, detect_classes=[0], dtype=np.float32)
+ else:
+ #FP16 and INT8 YOLO NAS accept uint8 input
+ det_result = inference_onnx_yolo_nas(self.det, oriImg, detect_classes=[0], dtype=np.uint8)
+ print(f"DWPose: Bbox {((default_timer() - det_start) * 1000):.2f}ms")
+ if (det_result is None) or (det_result.shape[0] == 0):
+ return None
+
+ if is_model_torchscript(self.pose):
+ pose_start = torch_timer.timer()
+ keypoints, scores = inference_jit_pose(self.pose, det_result, oriImg, self.pose_input_size)
+ print(f"DWPose: Pose {((torch_timer.timer() - pose_start) * 1000):.2f}ms on {det_result.shape[0]} people\n")
+ else:
+ pose_start = default_timer()
+ _, pose_onnx_dtype = guess_onnx_input_shape_dtype(self.pose_filename)
+ keypoints, scores = inference_onnx_pose(self.pose, det_result, oriImg, self.pose_input_size, dtype=pose_onnx_dtype)
+ print(f"DWPose: Pose {((default_timer() - pose_start) * 1000):.2f}ms on {det_result.shape[0]} people\n")
+
+ keypoints_info = np.concatenate(
+ (keypoints, scores[..., None]), axis=-1)
+ # compute neck joint
+ neck = np.mean(keypoints_info[:, [5, 6]], axis=1)
+ # neck score when visualizing pred
+ neck[:, 2:4] = np.logical_and(
+ keypoints_info[:, 5, 2:4] > 0.3,
+ keypoints_info[:, 6, 2:4] > 0.3).astype(int)
+ new_keypoints_info = np.insert(
+ keypoints_info, 17, neck, axis=1)
+ mmpose_idx = [
+ 17, 6, 8, 10, 7, 9, 12, 14, 16, 13, 15, 2, 1, 4, 3
+ ]
+ openpose_idx = [
+ 1, 2, 3, 4, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, 17
+ ]
+ new_keypoints_info[:, openpose_idx] = \
+ new_keypoints_info[:, mmpose_idx]
+ keypoints_info = new_keypoints_info
+
+ return keypoints_info
+
+ @staticmethod
+ def format_result(keypoints_info: Optional[np.ndarray]) -> List[PoseResult]:
+ def format_keypoint_part(
+ part: np.ndarray,
+ ) -> Optional[List[Optional[Keypoint]]]:
+ keypoints = [
+ Keypoint(x, y, score, i) if score >= 0.3 else None
+ for i, (x, y, score) in enumerate(part)
+ ]
+ return (
+ None if all(keypoint is None for keypoint in keypoints) else keypoints
+ )
+
+ def total_score(keypoints: Optional[List[Optional[Keypoint]]]) -> float:
+ return (
+ sum(keypoint.score for keypoint in keypoints if keypoint is not None)
+ if keypoints is not None
+ else 0.0
+ )
+
+ pose_results = []
+ if keypoints_info is None:
+ return pose_results
+
+ for instance in keypoints_info:
+ body_keypoints = format_keypoint_part(instance[:18]) or ([None] * 18)
+ left_hand = format_keypoint_part(instance[92:113])
+ right_hand = format_keypoint_part(instance[113:134])
+ face = format_keypoint_part(instance[24:92])
+
+ # Openpose face consists of 70 points in total, while DWPose only
+ # provides 68 points. Padding the last 2 points.
+ if face is not None:
+ # left eye
+ face.append(body_keypoints[14])
+ # right eye
+ face.append(body_keypoints[15])
+
+ body = BodyResult(
+ body_keypoints, total_score(body_keypoints), len(body_keypoints)
+ )
+ pose_results.append(PoseResult(body, left_hand, right_hand, face))
+
+ return pose_results
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/hed/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/hed/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d238ea572183f20030fe8c464d69889f6804c0dc
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/hed/__init__.py
@@ -0,0 +1,110 @@
+# This is an improved version and model of HED edge detection with Apache License, Version 2.0.
+# Please use this implementation in your products
+# This implementation may produce slightly different results from Saining Xie's official implementations,
+# but it generates smoother edges and is more suitable for ControlNet as well as other image-to-image translations.
+# Different from official models and other implementations, this is an RGB-input model (rather than BGR)
+# and in this way it works better for gradio's RGB protocol
+
+import os
+import warnings
+
+import cv2
+import numpy as np
+import torch
+from einops import rearrange
+from PIL import Image
+
+from controlnet_aux.util import HWC3, nms, resize_image_with_pad, safe_step, common_input_validate, annotator_ckpts_path, custom_hf_download
+
+
+class DoubleConvBlock(torch.nn.Module):
+ def __init__(self, input_channel, output_channel, layer_number):
+ super().__init__()
+ self.convs = torch.nn.Sequential()
+ self.convs.append(torch.nn.Conv2d(in_channels=input_channel, out_channels=output_channel, kernel_size=(3, 3), stride=(1, 1), padding=1))
+ for i in range(1, layer_number):
+ self.convs.append(torch.nn.Conv2d(in_channels=output_channel, out_channels=output_channel, kernel_size=(3, 3), stride=(1, 1), padding=1))
+ self.projection = torch.nn.Conv2d(in_channels=output_channel, out_channels=1, kernel_size=(1, 1), stride=(1, 1), padding=0)
+
+ def __call__(self, x, down_sampling=False):
+ h = x
+ if down_sampling:
+ h = torch.nn.functional.max_pool2d(h, kernel_size=(2, 2), stride=(2, 2))
+ for conv in self.convs:
+ h = conv(h)
+ h = torch.nn.functional.relu(h)
+ return h, self.projection(h)
+
+
+class ControlNetHED_Apache2(torch.nn.Module):
+ def __init__(self):
+ super().__init__()
+ self.norm = torch.nn.Parameter(torch.zeros(size=(1, 3, 1, 1)))
+ self.block1 = DoubleConvBlock(input_channel=3, output_channel=64, layer_number=2)
+ self.block2 = DoubleConvBlock(input_channel=64, output_channel=128, layer_number=2)
+ self.block3 = DoubleConvBlock(input_channel=128, output_channel=256, layer_number=3)
+ self.block4 = DoubleConvBlock(input_channel=256, output_channel=512, layer_number=3)
+ self.block5 = DoubleConvBlock(input_channel=512, output_channel=512, layer_number=3)
+
+ def __call__(self, x):
+ h = x - self.norm
+ h, projection1 = self.block1(h)
+ h, projection2 = self.block2(h, down_sampling=True)
+ h, projection3 = self.block3(h, down_sampling=True)
+ h, projection4 = self.block4(h, down_sampling=True)
+ h, projection5 = self.block5(h, down_sampling=True)
+ return projection1, projection2, projection3, projection4, projection5
+
+class HEDdetector:
+ def __init__(self, netNetwork):
+ self.netNetwork = netNetwork
+
+ @classmethod
+ def from_pretrained(cls, pretrained_model_or_path, filename=None, cache_dir=annotator_ckpts_path):
+ filename = filename or "ControlNetHED.pth"
+ model_path = custom_hf_download(pretrained_model_or_path, filename, cache_dir=cache_dir)
+
+ netNetwork = ControlNetHED_Apache2()
+ netNetwork.load_state_dict(torch.load(model_path, map_location='cpu'))
+ netNetwork.float().eval()
+
+ return cls(netNetwork)
+
+ def to(self, device):
+ self.netNetwork.to(device)
+ return self
+
+
+ def __call__(self, input_image, detect_resolution=512, safe=False, output_type="pil", scribble=False, upscale_method="INTER_CUBIC", **kwargs):
+ input_image, output_type = common_input_validate(input_image, output_type, **kwargs)
+ input_image, remove_pad = resize_image_with_pad(input_image, detect_resolution, upscale_method)
+
+ assert input_image.ndim == 3
+ H, W, C = input_image.shape
+ with torch.no_grad():
+ device = next(iter(self.netNetwork.parameters())).device
+ image_hed = torch.from_numpy(input_image).float().to(device)
+ image_hed = rearrange(image_hed, 'h w c -> 1 c h w')
+ edges = self.netNetwork(image_hed)
+ edges = [e.detach().cpu().numpy().astype(np.float32)[0, 0] for e in edges]
+ edges = [cv2.resize(e, (W, H), interpolation=cv2.INTER_LINEAR) for e in edges]
+ edges = np.stack(edges, axis=2)
+ edge = 1 / (1 + np.exp(-np.mean(edges, axis=2).astype(np.float64)))
+ if safe:
+ edge = safe_step(edge)
+ edge = (edge * 255.0).clip(0, 255).astype(np.uint8)
+
+ detected_map = edge
+
+ if scribble:
+ detected_map = nms(detected_map, 127, 3.0)
+ detected_map = cv2.GaussianBlur(detected_map, (0, 0), 3.0)
+ detected_map[detected_map > 4] = 255
+ detected_map[detected_map < 255] = 0
+
+ detected_map = HWC3(remove_pad(detected_map))
+
+ if output_type == "pil":
+ detected_map = Image.fromarray(detected_map)
+
+ return detected_map
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/hed/__pycache__/__init__.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/hed/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0d53332a0b789e4819d8cc467fe56f1117843c37
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/hed/__pycache__/__init__.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..9571b19968a7207929311eadb73e4a2be8760c40
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/__init__.py
@@ -0,0 +1,95 @@
+import os
+
+import cv2
+import numpy as np
+import torch
+from PIL import Image
+
+from controlnet_aux.util import HWC3, common_input_validate, resize_image_with_pad, annotator_ckpts_path, custom_hf_download
+from .leres.depthmap import estimateboost, estimateleres
+from .leres.multi_depth_model_woauxi import RelDepthModel
+from .leres.net_tools import strip_prefix_if_present
+from .pix2pix.models.pix2pix4depth_model import Pix2Pix4DepthModel
+from .pix2pix.options.test_options import TestOptions
+
+
+class LeresDetector:
+ def __init__(self, model, pix2pixmodel):
+ self.model = model
+ self.pix2pixmodel = pix2pixmodel
+
+ @classmethod
+ def from_pretrained(cls, pretrained_model_or_path, filename=None, pix2pix_filename=None, cache_dir=annotator_ckpts_path):
+ filename = filename or "res101.pth"
+ pix2pix_filename = pix2pix_filename or "latest_net_G.pth"
+ model_path = custom_hf_download(pretrained_model_or_path, filename, cache_dir=cache_dir)
+ checkpoint = torch.load(model_path, map_location=torch.device('cpu'))
+
+ model = RelDepthModel(backbone='resnext101')
+ model.load_state_dict(strip_prefix_if_present(checkpoint['depth_model'], "module."), strict=True)
+ del checkpoint
+
+ pix2pix_model_path = custom_hf_download(pretrained_model_or_path, pix2pix_filename, cache_dir=cache_dir)
+
+ opt = TestOptions().parse()
+ if not torch.cuda.is_available():
+ opt.gpu_ids = [] # cpu mode
+ pix2pixmodel = Pix2Pix4DepthModel(opt)
+ pix2pixmodel.save_dir = os.path.dirname(pix2pix_model_path)
+ pix2pixmodel.load_networks('latest')
+ pix2pixmodel.eval()
+
+ return cls(model, pix2pixmodel)
+
+ def to(self, device):
+ self.model.to(device)
+ # TODO - refactor pix2pix implementation to support device migration
+ # self.pix2pixmodel.to(device)
+ return self
+
+ def __call__(self, input_image, thr_a=0, thr_b=0, boost=False, detect_resolution=512, output_type="pil", upscale_method="INTER_CUBIC", **kwargs):
+ input_image, output_type = common_input_validate(input_image, output_type, **kwargs)
+ detected_map, remove_pad = resize_image_with_pad(input_image, detect_resolution, upscale_method)
+
+ with torch.no_grad():
+ if boost:
+ depth = estimateboost(detected_map, self.model, 0, self.pix2pixmodel, max(detected_map.shape[1], detected_map.shape[0]))
+ else:
+ depth = estimateleres(detected_map, self.model, detected_map.shape[1], detected_map.shape[0])
+
+ numbytes=2
+ depth_min = depth.min()
+ depth_max = depth.max()
+ max_val = (2**(8*numbytes))-1
+
+ # check output before normalizing and mapping to 16 bit
+ if depth_max - depth_min > np.finfo("float").eps:
+ out = max_val * (depth - depth_min) / (depth_max - depth_min)
+ else:
+ out = np.zeros(depth.shape)
+
+ # single channel, 16 bit image
+ depth_image = out.astype("uint16")
+
+ # convert to uint8
+ depth_image = cv2.convertScaleAbs(depth_image, alpha=(255.0/65535.0))
+
+ # remove near
+ if thr_a != 0:
+ thr_a = ((thr_a/100)*255)
+ depth_image = cv2.threshold(depth_image, thr_a, 255, cv2.THRESH_TOZERO)[1]
+
+ # invert image
+ depth_image = cv2.bitwise_not(depth_image)
+
+ # remove bg
+ if thr_b != 0:
+ thr_b = ((thr_b/100)*255)
+ depth_image = cv2.threshold(depth_image, thr_b, 255, cv2.THRESH_TOZERO)[1]
+
+ detected_map = HWC3(remove_pad(depth_image))
+
+ if output_type == "pil":
+ detected_map = Image.fromarray(detected_map)
+
+ return detected_map
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/leres/LICENSE b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/leres/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..e0f1d07d98d4e85e684734d058dfe2515d215405
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/leres/LICENSE
@@ -0,0 +1,23 @@
+https://github.com/thygate/stable-diffusion-webui-depthmap-script
+
+MIT License
+
+Copyright (c) 2023 Bob Thiry
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/leres/Resnet.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/leres/Resnet.py
new file mode 100644
index 0000000000000000000000000000000000000000..f12c9975c1aa05401269be3ca3dbaa56bde55581
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/leres/Resnet.py
@@ -0,0 +1,199 @@
+import torch.nn as nn
+import torch.nn as NN
+
+__all__ = ['ResNet', 'resnet18', 'resnet34', 'resnet50', 'resnet101',
+ 'resnet152']
+
+
+model_urls = {
+ 'resnet18': 'https://download.pytorch.org/models/resnet18-5c106cde.pth',
+ 'resnet34': 'https://download.pytorch.org/models/resnet34-333f7ec4.pth',
+ 'resnet50': 'https://download.pytorch.org/models/resnet50-19c8e357.pth',
+ 'resnet101': 'https://download.pytorch.org/models/resnet101-5d3b4d8f.pth',
+ 'resnet152': 'https://download.pytorch.org/models/resnet152-b121ed2d.pth',
+}
+
+
+def conv3x3(in_planes, out_planes, stride=1):
+ """3x3 convolution with padding"""
+ return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
+ padding=1, bias=False)
+
+
+class BasicBlock(nn.Module):
+ expansion = 1
+
+ def __init__(self, inplanes, planes, stride=1, downsample=None):
+ super(BasicBlock, self).__init__()
+ self.conv1 = conv3x3(inplanes, planes, stride)
+ self.bn1 = NN.BatchNorm2d(planes) #NN.BatchNorm2d
+ self.relu = nn.ReLU(inplace=True)
+ self.conv2 = conv3x3(planes, planes)
+ self.bn2 = NN.BatchNorm2d(planes) #NN.BatchNorm2d
+ self.downsample = downsample
+ self.stride = stride
+
+ def forward(self, x):
+ residual = x
+
+ out = self.conv1(x)
+ out = self.bn1(out)
+ out = self.relu(out)
+
+ out = self.conv2(out)
+ out = self.bn2(out)
+
+ if self.downsample is not None:
+ residual = self.downsample(x)
+
+ out += residual
+ out = self.relu(out)
+
+ return out
+
+
+class Bottleneck(nn.Module):
+ expansion = 4
+
+ def __init__(self, inplanes, planes, stride=1, downsample=None):
+ super(Bottleneck, self).__init__()
+ self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)
+ self.bn1 = NN.BatchNorm2d(planes) #NN.BatchNorm2d
+ self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,
+ padding=1, bias=False)
+ self.bn2 = NN.BatchNorm2d(planes) #NN.BatchNorm2d
+ self.conv3 = nn.Conv2d(planes, planes * self.expansion, kernel_size=1, bias=False)
+ self.bn3 = NN.BatchNorm2d(planes * self.expansion) #NN.BatchNorm2d
+ self.relu = nn.ReLU(inplace=True)
+ self.downsample = downsample
+ self.stride = stride
+
+ def forward(self, x):
+ residual = x
+
+ out = self.conv1(x)
+ out = self.bn1(out)
+ out = self.relu(out)
+
+ out = self.conv2(out)
+ out = self.bn2(out)
+ out = self.relu(out)
+
+ out = self.conv3(out)
+ out = self.bn3(out)
+
+ if self.downsample is not None:
+ residual = self.downsample(x)
+
+ out += residual
+ out = self.relu(out)
+
+ return out
+
+
+class ResNet(nn.Module):
+
+ def __init__(self, block, layers, num_classes=1000):
+ self.inplanes = 64
+ super(ResNet, self).__init__()
+ self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3,
+ bias=False)
+ self.bn1 = NN.BatchNorm2d(64) #NN.BatchNorm2d
+ self.relu = nn.ReLU(inplace=True)
+ self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
+ self.layer1 = self._make_layer(block, 64, layers[0])
+ self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
+ self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
+ self.layer4 = self._make_layer(block, 512, layers[3], stride=2)
+ #self.avgpool = nn.AvgPool2d(7, stride=1)
+ #self.fc = nn.Linear(512 * block.expansion, num_classes)
+
+ for m in self.modules():
+ if isinstance(m, nn.Conv2d):
+ nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
+ elif isinstance(m, nn.BatchNorm2d):
+ nn.init.constant_(m.weight, 1)
+ nn.init.constant_(m.bias, 0)
+
+ def _make_layer(self, block, planes, blocks, stride=1):
+ downsample = None
+ if stride != 1 or self.inplanes != planes * block.expansion:
+ downsample = nn.Sequential(
+ nn.Conv2d(self.inplanes, planes * block.expansion,
+ kernel_size=1, stride=stride, bias=False),
+ NN.BatchNorm2d(planes * block.expansion), #NN.BatchNorm2d
+ )
+
+ layers = []
+ layers.append(block(self.inplanes, planes, stride, downsample))
+ self.inplanes = planes * block.expansion
+ for i in range(1, blocks):
+ layers.append(block(self.inplanes, planes))
+
+ return nn.Sequential(*layers)
+
+ def forward(self, x):
+ features = []
+
+ x = self.conv1(x)
+ x = self.bn1(x)
+ x = self.relu(x)
+ x = self.maxpool(x)
+
+ x = self.layer1(x)
+ features.append(x)
+ x = self.layer2(x)
+ features.append(x)
+ x = self.layer3(x)
+ features.append(x)
+ x = self.layer4(x)
+ features.append(x)
+
+ return features
+
+
+def resnet18(pretrained=True, **kwargs):
+ """Constructs a ResNet-18 model.
+ Args:
+ pretrained (bool): If True, returns a model pre-trained on ImageNet
+ """
+ model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs)
+ return model
+
+
+def resnet34(pretrained=True, **kwargs):
+ """Constructs a ResNet-34 model.
+ Args:
+ pretrained (bool): If True, returns a model pre-trained on ImageNet
+ """
+ model = ResNet(BasicBlock, [3, 4, 6, 3], **kwargs)
+ return model
+
+
+def resnet50(pretrained=True, **kwargs):
+ """Constructs a ResNet-50 model.
+ Args:
+ pretrained (bool): If True, returns a model pre-trained on ImageNet
+ """
+ model = ResNet(Bottleneck, [3, 4, 6, 3], **kwargs)
+
+ return model
+
+
+def resnet101(pretrained=True, **kwargs):
+ """Constructs a ResNet-101 model.
+ Args:
+ pretrained (bool): If True, returns a model pre-trained on ImageNet
+ """
+ model = ResNet(Bottleneck, [3, 4, 23, 3], **kwargs)
+
+ return model
+
+
+def resnet152(pretrained=True, **kwargs):
+ """Constructs a ResNet-152 model.
+ Args:
+ pretrained (bool): If True, returns a model pre-trained on ImageNet
+ """
+ model = ResNet(Bottleneck, [3, 8, 36, 3], **kwargs)
+ return model
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/leres/Resnext_torch.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/leres/Resnext_torch.py
new file mode 100644
index 0000000000000000000000000000000000000000..9af54fcc3e5b363935ef60c8aaf269110c0d6611
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/leres/Resnext_torch.py
@@ -0,0 +1,237 @@
+#!/usr/bin/env python
+# coding: utf-8
+import torch.nn as nn
+
+try:
+ from urllib import urlretrieve
+except ImportError:
+ from urllib.request import urlretrieve
+
+__all__ = ['resnext101_32x8d']
+
+
+model_urls = {
+ 'resnext50_32x4d': 'https://download.pytorch.org/models/resnext50_32x4d-7cdf4587.pth',
+ 'resnext101_32x8d': 'https://download.pytorch.org/models/resnext101_32x8d-8ba56ff5.pth',
+}
+
+
+def conv3x3(in_planes, out_planes, stride=1, groups=1, dilation=1):
+ """3x3 convolution with padding"""
+ return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
+ padding=dilation, groups=groups, bias=False, dilation=dilation)
+
+
+def conv1x1(in_planes, out_planes, stride=1):
+ """1x1 convolution"""
+ return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False)
+
+
+class BasicBlock(nn.Module):
+ expansion = 1
+
+ def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
+ base_width=64, dilation=1, norm_layer=None):
+ super(BasicBlock, self).__init__()
+ if norm_layer is None:
+ norm_layer = nn.BatchNorm2d
+ if groups != 1 or base_width != 64:
+ raise ValueError('BasicBlock only supports groups=1 and base_width=64')
+ if dilation > 1:
+ raise NotImplementedError("Dilation > 1 not supported in BasicBlock")
+ # Both self.conv1 and self.downsample layers downsample the input when stride != 1
+ self.conv1 = conv3x3(inplanes, planes, stride)
+ self.bn1 = norm_layer(planes)
+ self.relu = nn.ReLU(inplace=True)
+ self.conv2 = conv3x3(planes, planes)
+ self.bn2 = norm_layer(planes)
+ self.downsample = downsample
+ self.stride = stride
+
+ def forward(self, x):
+ identity = x
+
+ out = self.conv1(x)
+ out = self.bn1(out)
+ out = self.relu(out)
+
+ out = self.conv2(out)
+ out = self.bn2(out)
+
+ if self.downsample is not None:
+ identity = self.downsample(x)
+
+ out += identity
+ out = self.relu(out)
+
+ return out
+
+
+class Bottleneck(nn.Module):
+ # Bottleneck in torchvision places the stride for downsampling at 3x3 convolution(self.conv2)
+ # while original implementation places the stride at the first 1x1 convolution(self.conv1)
+ # according to "Deep residual learning for image recognition"https://arxiv.org/abs/1512.03385.
+ # This variant is also known as ResNet V1.5 and improves accuracy according to
+ # https://ngc.nvidia.com/catalog/model-scripts/nvidia:resnet_50_v1_5_for_pytorch.
+
+ expansion = 4
+
+ def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
+ base_width=64, dilation=1, norm_layer=None):
+ super(Bottleneck, self).__init__()
+ if norm_layer is None:
+ norm_layer = nn.BatchNorm2d
+ width = int(planes * (base_width / 64.)) * groups
+ # Both self.conv2 and self.downsample layers downsample the input when stride != 1
+ self.conv1 = conv1x1(inplanes, width)
+ self.bn1 = norm_layer(width)
+ self.conv2 = conv3x3(width, width, stride, groups, dilation)
+ self.bn2 = norm_layer(width)
+ self.conv3 = conv1x1(width, planes * self.expansion)
+ self.bn3 = norm_layer(planes * self.expansion)
+ self.relu = nn.ReLU(inplace=True)
+ self.downsample = downsample
+ self.stride = stride
+
+ def forward(self, x):
+ identity = x
+
+ out = self.conv1(x)
+ out = self.bn1(out)
+ out = self.relu(out)
+
+ out = self.conv2(out)
+ out = self.bn2(out)
+ out = self.relu(out)
+
+ out = self.conv3(out)
+ out = self.bn3(out)
+
+ if self.downsample is not None:
+ identity = self.downsample(x)
+
+ out += identity
+ out = self.relu(out)
+
+ return out
+
+
+class ResNet(nn.Module):
+
+ def __init__(self, block, layers, num_classes=1000, zero_init_residual=False,
+ groups=1, width_per_group=64, replace_stride_with_dilation=None,
+ norm_layer=None):
+ super(ResNet, self).__init__()
+ if norm_layer is None:
+ norm_layer = nn.BatchNorm2d
+ self._norm_layer = norm_layer
+
+ self.inplanes = 64
+ self.dilation = 1
+ if replace_stride_with_dilation is None:
+ # each element in the tuple indicates if we should replace
+ # the 2x2 stride with a dilated convolution instead
+ replace_stride_with_dilation = [False, False, False]
+ if len(replace_stride_with_dilation) != 3:
+ raise ValueError("replace_stride_with_dilation should be None "
+ "or a 3-element tuple, got {}".format(replace_stride_with_dilation))
+ self.groups = groups
+ self.base_width = width_per_group
+ self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=7, stride=2, padding=3,
+ bias=False)
+ self.bn1 = norm_layer(self.inplanes)
+ self.relu = nn.ReLU(inplace=True)
+ self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
+ self.layer1 = self._make_layer(block, 64, layers[0])
+ self.layer2 = self._make_layer(block, 128, layers[1], stride=2,
+ dilate=replace_stride_with_dilation[0])
+ self.layer3 = self._make_layer(block, 256, layers[2], stride=2,
+ dilate=replace_stride_with_dilation[1])
+ self.layer4 = self._make_layer(block, 512, layers[3], stride=2,
+ dilate=replace_stride_with_dilation[2])
+ #self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
+ #self.fc = nn.Linear(512 * block.expansion, num_classes)
+
+ for m in self.modules():
+ if isinstance(m, nn.Conv2d):
+ nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
+ elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):
+ nn.init.constant_(m.weight, 1)
+ nn.init.constant_(m.bias, 0)
+
+ # Zero-initialize the last BN in each residual branch,
+ # so that the residual branch starts with zeros, and each residual block behaves like an identity.
+ # This improves the model by 0.2~0.3% according to https://arxiv.org/abs/1706.02677
+ if zero_init_residual:
+ for m in self.modules():
+ if isinstance(m, Bottleneck):
+ nn.init.constant_(m.bn3.weight, 0)
+ elif isinstance(m, BasicBlock):
+ nn.init.constant_(m.bn2.weight, 0)
+
+ def _make_layer(self, block, planes, blocks, stride=1, dilate=False):
+ norm_layer = self._norm_layer
+ downsample = None
+ previous_dilation = self.dilation
+ if dilate:
+ self.dilation *= stride
+ stride = 1
+ if stride != 1 or self.inplanes != planes * block.expansion:
+ downsample = nn.Sequential(
+ conv1x1(self.inplanes, planes * block.expansion, stride),
+ norm_layer(planes * block.expansion),
+ )
+
+ layers = []
+ layers.append(block(self.inplanes, planes, stride, downsample, self.groups,
+ self.base_width, previous_dilation, norm_layer))
+ self.inplanes = planes * block.expansion
+ for _ in range(1, blocks):
+ layers.append(block(self.inplanes, planes, groups=self.groups,
+ base_width=self.base_width, dilation=self.dilation,
+ norm_layer=norm_layer))
+
+ return nn.Sequential(*layers)
+
+ def _forward_impl(self, x):
+ # See note [TorchScript super()]
+ features = []
+ x = self.conv1(x)
+ x = self.bn1(x)
+ x = self.relu(x)
+ x = self.maxpool(x)
+
+ x = self.layer1(x)
+ features.append(x)
+
+ x = self.layer2(x)
+ features.append(x)
+
+ x = self.layer3(x)
+ features.append(x)
+
+ x = self.layer4(x)
+ features.append(x)
+
+ #x = self.avgpool(x)
+ #x = torch.flatten(x, 1)
+ #x = self.fc(x)
+
+ return features
+
+ def forward(self, x):
+ return self._forward_impl(x)
+
+
+
+def resnext101_32x8d(pretrained=True, **kwargs):
+ """Constructs a ResNet-152 model.
+ Args:
+ pretrained (bool): If True, returns a model pre-trained on ImageNet
+ """
+ kwargs['groups'] = 32
+ kwargs['width_per_group'] = 8
+
+ model = ResNet(Bottleneck, [3, 4, 23, 3], **kwargs)
+ return model
+
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/leres/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/leres/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/leres/depthmap.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/leres/depthmap.py
new file mode 100644
index 0000000000000000000000000000000000000000..fc743bf4946b514a53f8d286a395e33c7b612582
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/leres/depthmap.py
@@ -0,0 +1,548 @@
+# Author: thygate
+# https://github.com/thygate/stable-diffusion-webui-depthmap-script
+
+import gc
+from operator import getitem
+
+import cv2
+import numpy as np
+import skimage.measure
+import torch
+from torchvision.transforms import transforms
+
+from ...util import torch_gc
+
+whole_size_threshold = 1600 # R_max from the paper
+pix2pixsize = 1024
+
+def scale_torch(img):
+ """
+ Scale the image and output it in torch.tensor.
+ :param img: input rgb is in shape [H, W, C], input depth/disp is in shape [H, W]
+ :param scale: the scale factor. float
+ :return: img. [C, H, W]
+ """
+ if len(img.shape) == 2:
+ img = img[np.newaxis, :, :]
+ if img.shape[2] == 3:
+ transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.485, 0.456, 0.406) , (0.229, 0.224, 0.225) )])
+ img = transform(img.astype(np.float32))
+ else:
+ img = img.astype(np.float32)
+ img = torch.from_numpy(img)
+ return img
+
+def estimateleres(img, model, w, h):
+ device = next(iter(model.parameters())).device
+ # leres transform input
+ rgb_c = img[:, :, ::-1].copy()
+ A_resize = cv2.resize(rgb_c, (w, h))
+ img_torch = scale_torch(A_resize)[None, :, :, :]
+
+ # compute
+ with torch.no_grad():
+ img_torch = img_torch.to(device)
+ prediction = model.depth_model(img_torch)
+
+ prediction = prediction.squeeze().cpu().numpy()
+ prediction = cv2.resize(prediction, (img.shape[1], img.shape[0]), interpolation=cv2.INTER_CUBIC)
+
+ return prediction
+
+def generatemask(size):
+ # Generates a Guassian mask
+ mask = np.zeros(size, dtype=np.float32)
+ sigma = int(size[0]/16)
+ k_size = int(2 * np.ceil(2 * int(size[0]/16)) + 1)
+ mask[int(0.15*size[0]):size[0] - int(0.15*size[0]), int(0.15*size[1]): size[1] - int(0.15*size[1])] = 1
+ mask = cv2.GaussianBlur(mask, (int(k_size), int(k_size)), sigma)
+ mask = (mask - mask.min()) / (mask.max() - mask.min())
+ mask = mask.astype(np.float32)
+ return mask
+
+def resizewithpool(img, size):
+ i_size = img.shape[0]
+ n = int(np.floor(i_size/size))
+
+ out = skimage.measure.block_reduce(img, (n, n), np.max)
+ return out
+
+def rgb2gray(rgb):
+ # Converts rgb to gray
+ return np.dot(rgb[..., :3], [0.2989, 0.5870, 0.1140])
+
+def calculateprocessingres(img, basesize, confidence=0.1, scale_threshold=3, whole_size_threshold=3000):
+ # Returns the R_x resolution described in section 5 of the main paper.
+
+ # Parameters:
+ # img :input rgb image
+ # basesize : size the dilation kernel which is equal to receptive field of the network.
+ # confidence: value of x in R_x; allowed percentage of pixels that are not getting any contextual cue.
+ # scale_threshold: maximum allowed upscaling on the input image ; it has been set to 3.
+ # whole_size_threshold: maximum allowed resolution. (R_max from section 6 of the main paper)
+
+ # Returns:
+ # outputsize_scale*speed_scale :The computed R_x resolution
+ # patch_scale: K parameter from section 6 of the paper
+
+ # speed scale parameter is to process every image in a smaller size to accelerate the R_x resolution search
+ speed_scale = 32
+ image_dim = int(min(img.shape[0:2]))
+
+ gray = rgb2gray(img)
+ grad = np.abs(cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)) + np.abs(cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3))
+ grad = cv2.resize(grad, (image_dim, image_dim), cv2.INTER_AREA)
+
+ # thresholding the gradient map to generate the edge-map as a proxy of the contextual cues
+ m = grad.min()
+ M = grad.max()
+ middle = m + (0.4 * (M - m))
+ grad[grad < middle] = 0
+ grad[grad >= middle] = 1
+
+ # dilation kernel with size of the receptive field
+ kernel = np.ones((int(basesize/speed_scale), int(basesize/speed_scale)), float)
+ # dilation kernel with size of the a quarter of receptive field used to compute k
+ # as described in section 6 of main paper
+ kernel2 = np.ones((int(basesize / (4*speed_scale)), int(basesize / (4*speed_scale))), float)
+
+ # Output resolution limit set by the whole_size_threshold and scale_threshold.
+ threshold = min(whole_size_threshold, scale_threshold * max(img.shape[:2]))
+
+ outputsize_scale = basesize / speed_scale
+ for p_size in range(int(basesize/speed_scale), int(threshold/speed_scale), int(basesize / (2*speed_scale))):
+ grad_resized = resizewithpool(grad, p_size)
+ grad_resized = cv2.resize(grad_resized, (p_size, p_size), cv2.INTER_NEAREST)
+ grad_resized[grad_resized >= 0.5] = 1
+ grad_resized[grad_resized < 0.5] = 0
+
+ dilated = cv2.dilate(grad_resized, kernel, iterations=1)
+ meanvalue = (1-dilated).mean()
+ if meanvalue > confidence:
+ break
+ else:
+ outputsize_scale = p_size
+
+ grad_region = cv2.dilate(grad_resized, kernel2, iterations=1)
+ patch_scale = grad_region.mean()
+
+ return int(outputsize_scale*speed_scale), patch_scale
+
+# Generate a double-input depth estimation
+def doubleestimate(img, size1, size2, pix2pixsize, model, net_type, pix2pixmodel):
+ # Generate the low resolution estimation
+ estimate1 = singleestimate(img, size1, model, net_type)
+ # Resize to the inference size of merge network.
+ estimate1 = cv2.resize(estimate1, (pix2pixsize, pix2pixsize), interpolation=cv2.INTER_CUBIC)
+
+ # Generate the high resolution estimation
+ estimate2 = singleestimate(img, size2, model, net_type)
+ # Resize to the inference size of merge network.
+ estimate2 = cv2.resize(estimate2, (pix2pixsize, pix2pixsize), interpolation=cv2.INTER_CUBIC)
+
+ # Inference on the merge model
+ pix2pixmodel.set_input(estimate1, estimate2)
+ pix2pixmodel.test()
+ visuals = pix2pixmodel.get_current_visuals()
+ prediction_mapped = visuals['fake_B']
+ prediction_mapped = (prediction_mapped+1)/2
+ prediction_mapped = (prediction_mapped - torch.min(prediction_mapped)) / (
+ torch.max(prediction_mapped) - torch.min(prediction_mapped))
+ prediction_mapped = prediction_mapped.squeeze().cpu().numpy()
+
+ return prediction_mapped
+
+# Generate a single-input depth estimation
+def singleestimate(img, msize, model, net_type):
+ # if net_type == 0:
+ return estimateleres(img, model, msize, msize)
+ # else:
+ # return estimatemidasBoost(img, model, msize, msize)
+
+def applyGridpatch(blsize, stride, img, box):
+ # Extract a simple grid patch.
+ counter1 = 0
+ patch_bound_list = {}
+ for k in range(blsize, img.shape[1] - blsize, stride):
+ for j in range(blsize, img.shape[0] - blsize, stride):
+ patch_bound_list[str(counter1)] = {}
+ patchbounds = [j - blsize, k - blsize, j - blsize + 2 * blsize, k - blsize + 2 * blsize]
+ patch_bound = [box[0] + patchbounds[1], box[1] + patchbounds[0], patchbounds[3] - patchbounds[1],
+ patchbounds[2] - patchbounds[0]]
+ patch_bound_list[str(counter1)]['rect'] = patch_bound
+ patch_bound_list[str(counter1)]['size'] = patch_bound[2]
+ counter1 = counter1 + 1
+ return patch_bound_list
+
+# Generating local patches to perform the local refinement described in section 6 of the main paper.
+def generatepatchs(img, base_size):
+
+ # Compute the gradients as a proxy of the contextual cues.
+ img_gray = rgb2gray(img)
+ whole_grad = np.abs(cv2.Sobel(img_gray, cv2.CV_64F, 0, 1, ksize=3)) +\
+ np.abs(cv2.Sobel(img_gray, cv2.CV_64F, 1, 0, ksize=3))
+
+ threshold = whole_grad[whole_grad > 0].mean()
+ whole_grad[whole_grad < threshold] = 0
+
+ # We use the integral image to speed-up the evaluation of the amount of gradients for each patch.
+ gf = whole_grad.sum()/len(whole_grad.reshape(-1))
+ grad_integral_image = cv2.integral(whole_grad)
+
+ # Variables are selected such that the initial patch size would be the receptive field size
+ # and the stride is set to 1/3 of the receptive field size.
+ blsize = int(round(base_size/2))
+ stride = int(round(blsize*0.75))
+
+ # Get initial Grid
+ patch_bound_list = applyGridpatch(blsize, stride, img, [0, 0, 0, 0])
+
+ # Refine initial Grid of patches by discarding the flat (in terms of gradients of the rgb image) ones. Refine
+ # each patch size to ensure that there will be enough depth cues for the network to generate a consistent depth map.
+ print("Selecting patches ...")
+ patch_bound_list = adaptiveselection(grad_integral_image, patch_bound_list, gf)
+
+ # Sort the patch list to make sure the merging operation will be done with the correct order: starting from biggest
+ # patch
+ patchset = sorted(patch_bound_list.items(), key=lambda x: getitem(x[1], 'size'), reverse=True)
+ return patchset
+
+def getGF_fromintegral(integralimage, rect):
+ # Computes the gradient density of a given patch from the gradient integral image.
+ x1 = rect[1]
+ x2 = rect[1]+rect[3]
+ y1 = rect[0]
+ y2 = rect[0]+rect[2]
+ value = integralimage[x2, y2]-integralimage[x1, y2]-integralimage[x2, y1]+integralimage[x1, y1]
+ return value
+
+# Adaptively select patches
+def adaptiveselection(integral_grad, patch_bound_list, gf):
+ patchlist = {}
+ count = 0
+ height, width = integral_grad.shape
+
+ search_step = int(32/factor)
+
+ # Go through all patches
+ for c in range(len(patch_bound_list)):
+ # Get patch
+ bbox = patch_bound_list[str(c)]['rect']
+
+ # Compute the amount of gradients present in the patch from the integral image.
+ cgf = getGF_fromintegral(integral_grad, bbox)/(bbox[2]*bbox[3])
+
+ # Check if patching is beneficial by comparing the gradient density of the patch to
+ # the gradient density of the whole image
+ if cgf >= gf:
+ bbox_test = bbox.copy()
+ patchlist[str(count)] = {}
+
+ # Enlarge each patch until the gradient density of the patch is equal
+ # to the whole image gradient density
+ while True:
+
+ bbox_test[0] = bbox_test[0] - int(search_step/2)
+ bbox_test[1] = bbox_test[1] - int(search_step/2)
+
+ bbox_test[2] = bbox_test[2] + search_step
+ bbox_test[3] = bbox_test[3] + search_step
+
+ # Check if we are still within the image
+ if bbox_test[0] < 0 or bbox_test[1] < 0 or bbox_test[1] + bbox_test[3] >= height \
+ or bbox_test[0] + bbox_test[2] >= width:
+ break
+
+ # Compare gradient density
+ cgf = getGF_fromintegral(integral_grad, bbox_test)/(bbox_test[2]*bbox_test[3])
+ if cgf < gf:
+ break
+ bbox = bbox_test.copy()
+
+ # Add patch to selected patches
+ patchlist[str(count)]['rect'] = bbox
+ patchlist[str(count)]['size'] = bbox[2]
+ count = count + 1
+
+ # Return selected patches
+ return patchlist
+
+def impatch(image, rect):
+ # Extract the given patch pixels from a given image.
+ w1 = rect[0]
+ h1 = rect[1]
+ w2 = w1 + rect[2]
+ h2 = h1 + rect[3]
+ image_patch = image[h1:h2, w1:w2]
+ return image_patch
+
+class ImageandPatchs:
+ def __init__(self, root_dir, name, patchsinfo, rgb_image, scale=1):
+ self.root_dir = root_dir
+ self.patchsinfo = patchsinfo
+ self.name = name
+ self.patchs = patchsinfo
+ self.scale = scale
+
+ self.rgb_image = cv2.resize(rgb_image, (round(rgb_image.shape[1]*scale), round(rgb_image.shape[0]*scale)),
+ interpolation=cv2.INTER_CUBIC)
+
+ self.do_have_estimate = False
+ self.estimation_updated_image = None
+ self.estimation_base_image = None
+
+ def __len__(self):
+ return len(self.patchs)
+
+ def set_base_estimate(self, est):
+ self.estimation_base_image = est
+ if self.estimation_updated_image is not None:
+ self.do_have_estimate = True
+
+ def set_updated_estimate(self, est):
+ self.estimation_updated_image = est
+ if self.estimation_base_image is not None:
+ self.do_have_estimate = True
+
+ def __getitem__(self, index):
+ patch_id = int(self.patchs[index][0])
+ rect = np.array(self.patchs[index][1]['rect'])
+ msize = self.patchs[index][1]['size']
+
+ ## applying scale to rect:
+ rect = np.round(rect * self.scale)
+ rect = rect.astype('int')
+ msize = round(msize * self.scale)
+
+ patch_rgb = impatch(self.rgb_image, rect)
+ if self.do_have_estimate:
+ patch_whole_estimate_base = impatch(self.estimation_base_image, rect)
+ patch_whole_estimate_updated = impatch(self.estimation_updated_image, rect)
+ return {'patch_rgb': patch_rgb, 'patch_whole_estimate_base': patch_whole_estimate_base,
+ 'patch_whole_estimate_updated': patch_whole_estimate_updated, 'rect': rect,
+ 'size': msize, 'id': patch_id}
+ else:
+ return {'patch_rgb': patch_rgb, 'rect': rect, 'size': msize, 'id': patch_id}
+
+ def print_options(self, opt):
+ """Print and save options
+
+ It will print both current options and default values(if different).
+ It will save options into a text file / [checkpoints_dir] / opt.txt
+ """
+ message = ''
+ message += '----------------- Options ---------------\n'
+ for k, v in sorted(vars(opt).items()):
+ comment = ''
+ default = self.parser.get_default(k)
+ if v != default:
+ comment = '\t[default: %s]' % str(default)
+ message += '{:>25}: {:<30}{}\n'.format(str(k), str(v), comment)
+ message += '----------------- End -------------------'
+ print(message)
+
+ # save to the disk
+ """
+ expr_dir = os.path.join(opt.checkpoints_dir, opt.name)
+ util.mkdirs(expr_dir)
+ file_name = os.path.join(expr_dir, '{}_opt.txt'.format(opt.phase))
+ with open(file_name, 'wt') as opt_file:
+ opt_file.write(message)
+ opt_file.write('\n')
+ """
+
+ def parse(self):
+ """Parse our options, create checkpoints directory suffix, and set up gpu device."""
+ opt = self.gather_options()
+ opt.isTrain = self.isTrain # train or test
+
+ # process opt.suffix
+ if opt.suffix:
+ suffix = ('_' + opt.suffix.format(**vars(opt))) if opt.suffix != '' else ''
+ opt.name = opt.name + suffix
+
+ #self.print_options(opt)
+
+ # set gpu ids
+ str_ids = opt.gpu_ids.split(',')
+ opt.gpu_ids = []
+ for str_id in str_ids:
+ id = int(str_id)
+ if id >= 0:
+ opt.gpu_ids.append(id)
+ #if len(opt.gpu_ids) > 0:
+ # torch.cuda.set_device(opt.gpu_ids[0])
+
+ self.opt = opt
+ return self.opt
+
+
+def estimateboost(img, model, model_type, pix2pixmodel, max_res=512, depthmap_script_boost_rmax=None):
+ global whole_size_threshold
+
+ # get settings
+ if depthmap_script_boost_rmax:
+ whole_size_threshold = depthmap_script_boost_rmax
+
+ if model_type == 0: #leres
+ net_receptive_field_size = 448
+ patch_netsize = 2 * net_receptive_field_size
+ elif model_type == 1: #dpt_beit_large_512
+ net_receptive_field_size = 512
+ patch_netsize = 2 * net_receptive_field_size
+ else: #other midas
+ net_receptive_field_size = 384
+ patch_netsize = 2 * net_receptive_field_size
+
+ gc.collect()
+ torch_gc()
+
+ # Generate mask used to smoothly blend the local pathc estimations to the base estimate.
+ # It is arbitrarily large to avoid artifacts during rescaling for each crop.
+ mask_org = generatemask((3000, 3000))
+ mask = mask_org.copy()
+
+ # Value x of R_x defined in the section 5 of the main paper.
+ r_threshold_value = 0.2
+ #if R0:
+ # r_threshold_value = 0
+
+ input_resolution = img.shape
+ scale_threshold = 3 # Allows up-scaling with a scale up to 3
+
+ # Find the best input resolution R-x. The resolution search described in section 5-double estimation of the main paper and section B of the
+ # supplementary material.
+ whole_image_optimal_size, patch_scale = calculateprocessingres(img, net_receptive_field_size, r_threshold_value, scale_threshold, whole_size_threshold)
+
+ # print('wholeImage being processed in :', whole_image_optimal_size)
+
+ # Generate the base estimate using the double estimation.
+ whole_estimate = doubleestimate(img, net_receptive_field_size, whole_image_optimal_size, pix2pixsize, model, model_type, pix2pixmodel)
+
+ # Compute the multiplier described in section 6 of the main paper to make sure our initial patch can select
+ # small high-density regions of the image.
+ global factor
+ factor = max(min(1, 4 * patch_scale * whole_image_optimal_size / whole_size_threshold), 0.2)
+ # print('Adjust factor is:', 1/factor)
+
+ # Check if Local boosting is beneficial.
+ if max_res < whole_image_optimal_size:
+ # print("No Local boosting. Specified Max Res is smaller than R20, Returning doubleestimate result")
+ return cv2.resize(whole_estimate, (input_resolution[1], input_resolution[0]), interpolation=cv2.INTER_CUBIC)
+
+ # Compute the default target resolution.
+ if img.shape[0] > img.shape[1]:
+ a = 2 * whole_image_optimal_size
+ b = round(2 * whole_image_optimal_size * img.shape[1] / img.shape[0])
+ else:
+ a = round(2 * whole_image_optimal_size * img.shape[0] / img.shape[1])
+ b = 2 * whole_image_optimal_size
+ b = int(round(b / factor))
+ a = int(round(a / factor))
+
+ """
+ # recompute a, b and saturate to max res.
+ if max(a,b) > max_res:
+ print('Default Res is higher than max-res: Reducing final resolution')
+ if img.shape[0] > img.shape[1]:
+ a = max_res
+ b = round(max_res * img.shape[1] / img.shape[0])
+ else:
+ a = round(max_res * img.shape[0] / img.shape[1])
+ b = max_res
+ b = int(b)
+ a = int(a)
+ """
+
+ img = cv2.resize(img, (b, a), interpolation=cv2.INTER_CUBIC)
+
+ # Extract selected patches for local refinement
+ base_size = net_receptive_field_size * 2
+ patchset = generatepatchs(img, base_size)
+
+ # print('Target resolution: ', img.shape)
+
+ # Computing a scale in case user prompted to generate the results as the same resolution of the input.
+ # Notice that our method output resolution is independent of the input resolution and this parameter will only
+ # enable a scaling operation during the local patch merge implementation to generate results with the same resolution
+ # as the input.
+ """
+ if output_resolution == 1:
+ mergein_scale = input_resolution[0] / img.shape[0]
+ print('Dynamicly change merged-in resolution; scale:', mergein_scale)
+ else:
+ mergein_scale = 1
+ """
+ # always rescale to input res for now
+ mergein_scale = input_resolution[0] / img.shape[0]
+
+ imageandpatchs = ImageandPatchs('', '', patchset, img, mergein_scale)
+ whole_estimate_resized = cv2.resize(whole_estimate, (round(img.shape[1]*mergein_scale),
+ round(img.shape[0]*mergein_scale)), interpolation=cv2.INTER_CUBIC)
+ imageandpatchs.set_base_estimate(whole_estimate_resized.copy())
+ imageandpatchs.set_updated_estimate(whole_estimate_resized.copy())
+
+ print('Resulting depthmap resolution will be :', whole_estimate_resized.shape[:2])
+ print('Patches to process: '+str(len(imageandpatchs)))
+
+ # Enumerate through all patches, generate their estimations and refining the base estimate.
+ for patch_ind in range(len(imageandpatchs)):
+
+ # Get patch information
+ patch = imageandpatchs[patch_ind] # patch object
+ patch_rgb = patch['patch_rgb'] # rgb patch
+ patch_whole_estimate_base = patch['patch_whole_estimate_base'] # corresponding patch from base
+ rect = patch['rect'] # patch size and location
+ patch_id = patch['id'] # patch ID
+ org_size = patch_whole_estimate_base.shape # the original size from the unscaled input
+ print('\t Processing patch', patch_ind, '/', len(imageandpatchs)-1, '|', rect)
+
+ # We apply double estimation for patches. The high resolution value is fixed to twice the receptive
+ # field size of the network for patches to accelerate the process.
+ patch_estimation = doubleestimate(patch_rgb, net_receptive_field_size, patch_netsize, pix2pixsize, model, model_type, pix2pixmodel)
+ patch_estimation = cv2.resize(patch_estimation, (pix2pixsize, pix2pixsize), interpolation=cv2.INTER_CUBIC)
+ patch_whole_estimate_base = cv2.resize(patch_whole_estimate_base, (pix2pixsize, pix2pixsize), interpolation=cv2.INTER_CUBIC)
+
+ # Merging the patch estimation into the base estimate using our merge network:
+ # We feed the patch estimation and the same region from the updated base estimate to the merge network
+ # to generate the target estimate for the corresponding region.
+ pix2pixmodel.set_input(patch_whole_estimate_base, patch_estimation)
+
+ # Run merging network
+ pix2pixmodel.test()
+ visuals = pix2pixmodel.get_current_visuals()
+
+ prediction_mapped = visuals['fake_B']
+ prediction_mapped = (prediction_mapped+1)/2
+ prediction_mapped = prediction_mapped.squeeze().cpu().numpy()
+
+ mapped = prediction_mapped
+
+ # We use a simple linear polynomial to make sure the result of the merge network would match the values of
+ # base estimate
+ p_coef = np.polyfit(mapped.reshape(-1), patch_whole_estimate_base.reshape(-1), deg=1)
+ merged = np.polyval(p_coef, mapped.reshape(-1)).reshape(mapped.shape)
+
+ merged = cv2.resize(merged, (org_size[1],org_size[0]), interpolation=cv2.INTER_CUBIC)
+
+ # Get patch size and location
+ w1 = rect[0]
+ h1 = rect[1]
+ w2 = w1 + rect[2]
+ h2 = h1 + rect[3]
+
+ # To speed up the implementation, we only generate the Gaussian mask once with a sufficiently large size
+ # and resize it to our needed size while merging the patches.
+ if mask.shape != org_size:
+ mask = cv2.resize(mask_org, (org_size[1],org_size[0]), interpolation=cv2.INTER_LINEAR)
+
+ tobemergedto = imageandpatchs.estimation_updated_image
+
+ # Update the whole estimation:
+ # We use a simple Gaussian mask to blend the merged patch region with the base estimate to ensure seamless
+ # blending at the boundaries of the patch region.
+ tobemergedto[h1:h2, w1:w2] = np.multiply(tobemergedto[h1:h2, w1:w2], 1 - mask) + np.multiply(merged, mask)
+ imageandpatchs.set_updated_estimate(tobemergedto)
+
+ # output
+ return cv2.resize(imageandpatchs.estimation_updated_image, (input_resolution[1], input_resolution[0]), interpolation=cv2.INTER_CUBIC)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/leres/multi_depth_model_woauxi.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/leres/multi_depth_model_woauxi.py
new file mode 100644
index 0000000000000000000000000000000000000000..fdf35d7843e00be5d3c831d72b9ab5d64d130f93
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/leres/multi_depth_model_woauxi.py
@@ -0,0 +1,35 @@
+import torch
+import torch.nn as nn
+
+from . import network_auxi as network
+from .net_tools import get_func
+
+
+class RelDepthModel(nn.Module):
+ def __init__(self, backbone='resnet50'):
+ super(RelDepthModel, self).__init__()
+ if backbone == 'resnet50':
+ encoder = 'resnet50_stride32'
+ elif backbone == 'resnext101':
+ encoder = 'resnext101_stride32x8d'
+ self.depth_model = DepthModel(encoder)
+
+ def inference(self, rgb):
+ with torch.no_grad():
+ input = rgb.to(self.depth_model.device)
+ depth = self.depth_model(input)
+ #pred_depth_out = depth - depth.min() + 0.01
+ return depth #pred_depth_out
+
+
+class DepthModel(nn.Module):
+ def __init__(self, encoder):
+ super(DepthModel, self).__init__()
+ backbone = network.__name__.split('.')[-1] + '.' + encoder
+ self.encoder_modules = get_func(backbone)()
+ self.decoder_modules = network.Decoder()
+
+ def forward(self, x):
+ lateral_out = self.encoder_modules(x)
+ out_logit = self.decoder_modules(lateral_out)
+ return out_logit
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/leres/net_tools.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/leres/net_tools.py
new file mode 100644
index 0000000000000000000000000000000000000000..2f213315046e078bb861d65d3ef4a6fc446e945d
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/leres/net_tools.py
@@ -0,0 +1,54 @@
+import importlib
+import torch
+import os
+from collections import OrderedDict
+
+
+def get_func(func_name):
+ """Helper to return a function object by name. func_name must identify a
+ function in this module or the path to a function relative to the base
+ 'modeling' module.
+ """
+ if func_name == '':
+ return None
+ try:
+ parts = func_name.split('.')
+ # Refers to a function in this module
+ if len(parts) == 1:
+ return globals()[parts[0]]
+ # Otherwise, assume we're referencing a module under modeling
+ module_name = 'controlnet_aux.leres.leres.' + '.'.join(parts[:-1])
+ module = importlib.import_module(module_name)
+ return getattr(module, parts[-1])
+ except Exception:
+ print('Failed to f1ind function: %s', func_name)
+ raise
+
+def load_ckpt(args, depth_model, shift_model, focal_model):
+ """
+ Load checkpoint.
+ """
+ if os.path.isfile(args.load_ckpt):
+ print("loading checkpoint %s" % args.load_ckpt)
+ checkpoint = torch.load(args.load_ckpt)
+ if shift_model is not None:
+ shift_model.load_state_dict(strip_prefix_if_present(checkpoint['shift_model'], 'module.'),
+ strict=True)
+ if focal_model is not None:
+ focal_model.load_state_dict(strip_prefix_if_present(checkpoint['focal_model'], 'module.'),
+ strict=True)
+ depth_model.load_state_dict(strip_prefix_if_present(checkpoint['depth_model'], "module."),
+ strict=True)
+ del checkpoint
+ if torch.cuda.is_available():
+ torch.cuda.empty_cache()
+
+
+def strip_prefix_if_present(state_dict, prefix):
+ keys = sorted(state_dict.keys())
+ if not all(key.startswith(prefix) for key in keys):
+ return state_dict
+ stripped_state_dict = OrderedDict()
+ for key, value in state_dict.items():
+ stripped_state_dict[key.replace(prefix, "")] = value
+ return stripped_state_dict
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/leres/network_auxi.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/leres/network_auxi.py
new file mode 100644
index 0000000000000000000000000000000000000000..1bd87011a5339aca632d1a10b217c8737bdc794f
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/leres/network_auxi.py
@@ -0,0 +1,417 @@
+import torch
+import torch.nn as nn
+import torch.nn.init as init
+
+from . import Resnet, Resnext_torch
+
+
+def resnet50_stride32():
+ return DepthNet(backbone='resnet', depth=50, upfactors=[2, 2, 2, 2])
+
+def resnext101_stride32x8d():
+ return DepthNet(backbone='resnext101_32x8d', depth=101, upfactors=[2, 2, 2, 2])
+
+
+class Decoder(nn.Module):
+ def __init__(self):
+ super(Decoder, self).__init__()
+ self.inchannels = [256, 512, 1024, 2048]
+ self.midchannels = [256, 256, 256, 512]
+ self.upfactors = [2,2,2,2]
+ self.outchannels = 1
+
+ self.conv = FTB(inchannels=self.inchannels[3], midchannels=self.midchannels[3])
+ self.conv1 = nn.Conv2d(in_channels=self.midchannels[3], out_channels=self.midchannels[2], kernel_size=3, padding=1, stride=1, bias=True)
+ self.upsample = nn.Upsample(scale_factor=self.upfactors[3], mode='bilinear', align_corners=True)
+
+ self.ffm2 = FFM(inchannels=self.inchannels[2], midchannels=self.midchannels[2], outchannels = self.midchannels[2], upfactor=self.upfactors[2])
+ self.ffm1 = FFM(inchannels=self.inchannels[1], midchannels=self.midchannels[1], outchannels = self.midchannels[1], upfactor=self.upfactors[1])
+ self.ffm0 = FFM(inchannels=self.inchannels[0], midchannels=self.midchannels[0], outchannels = self.midchannels[0], upfactor=self.upfactors[0])
+
+ self.outconv = AO(inchannels=self.midchannels[0], outchannels=self.outchannels, upfactor=2)
+ self._init_params()
+
+ def _init_params(self):
+ for m in self.modules():
+ if isinstance(m, nn.Conv2d):
+ init.normal_(m.weight, std=0.01)
+ if m.bias is not None:
+ init.constant_(m.bias, 0)
+ elif isinstance(m, nn.ConvTranspose2d):
+ init.normal_(m.weight, std=0.01)
+ if m.bias is not None:
+ init.constant_(m.bias, 0)
+ elif isinstance(m, nn.BatchNorm2d): #NN.BatchNorm2d
+ init.constant_(m.weight, 1)
+ init.constant_(m.bias, 0)
+ elif isinstance(m, nn.Linear):
+ init.normal_(m.weight, std=0.01)
+ if m.bias is not None:
+ init.constant_(m.bias, 0)
+
+ def forward(self, features):
+ x_32x = self.conv(features[3]) # 1/32
+ x_32 = self.conv1(x_32x)
+ x_16 = self.upsample(x_32) # 1/16
+
+ x_8 = self.ffm2(features[2], x_16) # 1/8
+ x_4 = self.ffm1(features[1], x_8) # 1/4
+ x_2 = self.ffm0(features[0], x_4) # 1/2
+ #-----------------------------------------
+ x = self.outconv(x_2) # original size
+ return x
+
+class DepthNet(nn.Module):
+ __factory = {
+ 18: Resnet.resnet18,
+ 34: Resnet.resnet34,
+ 50: Resnet.resnet50,
+ 101: Resnet.resnet101,
+ 152: Resnet.resnet152
+ }
+ def __init__(self,
+ backbone='resnet',
+ depth=50,
+ upfactors=[2, 2, 2, 2]):
+ super(DepthNet, self).__init__()
+ self.backbone = backbone
+ self.depth = depth
+ self.pretrained = False
+ self.inchannels = [256, 512, 1024, 2048]
+ self.midchannels = [256, 256, 256, 512]
+ self.upfactors = upfactors
+ self.outchannels = 1
+
+ # Build model
+ if self.backbone == 'resnet':
+ if self.depth not in DepthNet.__factory:
+ raise KeyError("Unsupported depth:", self.depth)
+ self.encoder = DepthNet.__factory[depth](pretrained=self.pretrained)
+ elif self.backbone == 'resnext101_32x8d':
+ self.encoder = Resnext_torch.resnext101_32x8d(pretrained=self.pretrained)
+ else:
+ self.encoder = Resnext_torch.resnext101(pretrained=self.pretrained)
+
+ def forward(self, x):
+ x = self.encoder(x) # 1/32, 1/16, 1/8, 1/4
+ return x
+
+
+class FTB(nn.Module):
+ def __init__(self, inchannels, midchannels=512):
+ super(FTB, self).__init__()
+ self.in1 = inchannels
+ self.mid = midchannels
+ self.conv1 = nn.Conv2d(in_channels=self.in1, out_channels=self.mid, kernel_size=3, padding=1, stride=1,
+ bias=True)
+ # NN.BatchNorm2d
+ self.conv_branch = nn.Sequential(nn.ReLU(inplace=True), \
+ nn.Conv2d(in_channels=self.mid, out_channels=self.mid, kernel_size=3,
+ padding=1, stride=1, bias=True), \
+ nn.BatchNorm2d(num_features=self.mid), \
+ nn.ReLU(inplace=True), \
+ nn.Conv2d(in_channels=self.mid, out_channels=self.mid, kernel_size=3,
+ padding=1, stride=1, bias=True))
+ self.relu = nn.ReLU(inplace=True)
+
+ self.init_params()
+
+ def forward(self, x):
+ x = self.conv1(x)
+ x = x + self.conv_branch(x)
+ x = self.relu(x)
+
+ return x
+
+ def init_params(self):
+ for m in self.modules():
+ if isinstance(m, nn.Conv2d):
+ init.normal_(m.weight, std=0.01)
+ if m.bias is not None:
+ init.constant_(m.bias, 0)
+ elif isinstance(m, nn.ConvTranspose2d):
+ # init.kaiming_normal_(m.weight, mode='fan_out')
+ init.normal_(m.weight, std=0.01)
+ # init.xavier_normal_(m.weight)
+ if m.bias is not None:
+ init.constant_(m.bias, 0)
+ elif isinstance(m, nn.BatchNorm2d): # NN.BatchNorm2d
+ init.constant_(m.weight, 1)
+ init.constant_(m.bias, 0)
+ elif isinstance(m, nn.Linear):
+ init.normal_(m.weight, std=0.01)
+ if m.bias is not None:
+ init.constant_(m.bias, 0)
+
+
+class ATA(nn.Module):
+ def __init__(self, inchannels, reduction=8):
+ super(ATA, self).__init__()
+ self.inchannels = inchannels
+ self.avg_pool = nn.AdaptiveAvgPool2d(1)
+ self.fc = nn.Sequential(nn.Linear(self.inchannels * 2, self.inchannels // reduction),
+ nn.ReLU(inplace=True),
+ nn.Linear(self.inchannels // reduction, self.inchannels),
+ nn.Sigmoid())
+ self.init_params()
+
+ def forward(self, low_x, high_x):
+ n, c, _, _ = low_x.size()
+ x = torch.cat([low_x, high_x], 1)
+ x = self.avg_pool(x)
+ x = x.view(n, -1)
+ x = self.fc(x).view(n, c, 1, 1)
+ x = low_x * x + high_x
+
+ return x
+
+ def init_params(self):
+ for m in self.modules():
+ if isinstance(m, nn.Conv2d):
+ # init.kaiming_normal_(m.weight, mode='fan_out')
+ # init.normal(m.weight, std=0.01)
+ init.xavier_normal_(m.weight)
+ if m.bias is not None:
+ init.constant_(m.bias, 0)
+ elif isinstance(m, nn.ConvTranspose2d):
+ # init.kaiming_normal_(m.weight, mode='fan_out')
+ # init.normal_(m.weight, std=0.01)
+ init.xavier_normal_(m.weight)
+ if m.bias is not None:
+ init.constant_(m.bias, 0)
+ elif isinstance(m, nn.BatchNorm2d): # NN.BatchNorm2d
+ init.constant_(m.weight, 1)
+ init.constant_(m.bias, 0)
+ elif isinstance(m, nn.Linear):
+ init.normal_(m.weight, std=0.01)
+ if m.bias is not None:
+ init.constant_(m.bias, 0)
+
+
+class FFM(nn.Module):
+ def __init__(self, inchannels, midchannels, outchannels, upfactor=2):
+ super(FFM, self).__init__()
+ self.inchannels = inchannels
+ self.midchannels = midchannels
+ self.outchannels = outchannels
+ self.upfactor = upfactor
+
+ self.ftb1 = FTB(inchannels=self.inchannels, midchannels=self.midchannels)
+ # self.ata = ATA(inchannels = self.midchannels)
+ self.ftb2 = FTB(inchannels=self.midchannels, midchannels=self.outchannels)
+
+ self.upsample = nn.Upsample(scale_factor=self.upfactor, mode='bilinear', align_corners=True)
+
+ self.init_params()
+
+ def forward(self, low_x, high_x):
+ x = self.ftb1(low_x)
+ x = x + high_x
+ x = self.ftb2(x)
+ x = self.upsample(x)
+
+ return x
+
+ def init_params(self):
+ for m in self.modules():
+ if isinstance(m, nn.Conv2d):
+ # init.kaiming_normal_(m.weight, mode='fan_out')
+ init.normal_(m.weight, std=0.01)
+ # init.xavier_normal_(m.weight)
+ if m.bias is not None:
+ init.constant_(m.bias, 0)
+ elif isinstance(m, nn.ConvTranspose2d):
+ # init.kaiming_normal_(m.weight, mode='fan_out')
+ init.normal_(m.weight, std=0.01)
+ # init.xavier_normal_(m.weight)
+ if m.bias is not None:
+ init.constant_(m.bias, 0)
+ elif isinstance(m, nn.BatchNorm2d): # NN.Batchnorm2d
+ init.constant_(m.weight, 1)
+ init.constant_(m.bias, 0)
+ elif isinstance(m, nn.Linear):
+ init.normal_(m.weight, std=0.01)
+ if m.bias is not None:
+ init.constant_(m.bias, 0)
+
+
+class AO(nn.Module):
+ # Adaptive output module
+ def __init__(self, inchannels, outchannels, upfactor=2):
+ super(AO, self).__init__()
+ self.inchannels = inchannels
+ self.outchannels = outchannels
+ self.upfactor = upfactor
+
+ self.adapt_conv = nn.Sequential(
+ nn.Conv2d(in_channels=self.inchannels, out_channels=self.inchannels // 2, kernel_size=3, padding=1,
+ stride=1, bias=True), \
+ nn.BatchNorm2d(num_features=self.inchannels // 2), \
+ nn.ReLU(inplace=True), \
+ nn.Conv2d(in_channels=self.inchannels // 2, out_channels=self.outchannels, kernel_size=3, padding=1,
+ stride=1, bias=True), \
+ nn.Upsample(scale_factor=self.upfactor, mode='bilinear', align_corners=True))
+
+ self.init_params()
+
+ def forward(self, x):
+ x = self.adapt_conv(x)
+ return x
+
+ def init_params(self):
+ for m in self.modules():
+ if isinstance(m, nn.Conv2d):
+ # init.kaiming_normal_(m.weight, mode='fan_out')
+ init.normal_(m.weight, std=0.01)
+ # init.xavier_normal_(m.weight)
+ if m.bias is not None:
+ init.constant_(m.bias, 0)
+ elif isinstance(m, nn.ConvTranspose2d):
+ # init.kaiming_normal_(m.weight, mode='fan_out')
+ init.normal_(m.weight, std=0.01)
+ # init.xavier_normal_(m.weight)
+ if m.bias is not None:
+ init.constant_(m.bias, 0)
+ elif isinstance(m, nn.BatchNorm2d): # NN.Batchnorm2d
+ init.constant_(m.weight, 1)
+ init.constant_(m.bias, 0)
+ elif isinstance(m, nn.Linear):
+ init.normal_(m.weight, std=0.01)
+ if m.bias is not None:
+ init.constant_(m.bias, 0)
+
+
+
+# ==============================================================================================================
+
+
+class ResidualConv(nn.Module):
+ def __init__(self, inchannels):
+ super(ResidualConv, self).__init__()
+ # NN.BatchNorm2d
+ self.conv = nn.Sequential(
+ # nn.BatchNorm2d(num_features=inchannels),
+ nn.ReLU(inplace=False),
+ # nn.Conv2d(in_channels=inchannels, out_channels=inchannels, kernel_size=3, padding=1, stride=1, groups=inchannels,bias=True),
+ # nn.Conv2d(in_channels=inchannels, out_channels=inchannels, kernel_size=1, padding=0, stride=1, groups=1,bias=True)
+ nn.Conv2d(in_channels=inchannels, out_channels=inchannels / 2, kernel_size=3, padding=1, stride=1,
+ bias=False),
+ nn.BatchNorm2d(num_features=inchannels / 2),
+ nn.ReLU(inplace=False),
+ nn.Conv2d(in_channels=inchannels / 2, out_channels=inchannels, kernel_size=3, padding=1, stride=1,
+ bias=False)
+ )
+ self.init_params()
+
+ def forward(self, x):
+ x = self.conv(x) + x
+ return x
+
+ def init_params(self):
+ for m in self.modules():
+ if isinstance(m, nn.Conv2d):
+ # init.kaiming_normal_(m.weight, mode='fan_out')
+ init.normal_(m.weight, std=0.01)
+ # init.xavier_normal_(m.weight)
+ if m.bias is not None:
+ init.constant_(m.bias, 0)
+ elif isinstance(m, nn.ConvTranspose2d):
+ # init.kaiming_normal_(m.weight, mode='fan_out')
+ init.normal_(m.weight, std=0.01)
+ # init.xavier_normal_(m.weight)
+ if m.bias is not None:
+ init.constant_(m.bias, 0)
+ elif isinstance(m, nn.BatchNorm2d): # NN.BatchNorm2d
+ init.constant_(m.weight, 1)
+ init.constant_(m.bias, 0)
+ elif isinstance(m, nn.Linear):
+ init.normal_(m.weight, std=0.01)
+ if m.bias is not None:
+ init.constant_(m.bias, 0)
+
+
+class FeatureFusion(nn.Module):
+ def __init__(self, inchannels, outchannels):
+ super(FeatureFusion, self).__init__()
+ self.conv = ResidualConv(inchannels=inchannels)
+ # NN.BatchNorm2d
+ self.up = nn.Sequential(ResidualConv(inchannels=inchannels),
+ nn.ConvTranspose2d(in_channels=inchannels, out_channels=outchannels, kernel_size=3,
+ stride=2, padding=1, output_padding=1),
+ nn.BatchNorm2d(num_features=outchannels),
+ nn.ReLU(inplace=True))
+
+ def forward(self, lowfeat, highfeat):
+ return self.up(highfeat + self.conv(lowfeat))
+
+ def init_params(self):
+ for m in self.modules():
+ if isinstance(m, nn.Conv2d):
+ # init.kaiming_normal_(m.weight, mode='fan_out')
+ init.normal_(m.weight, std=0.01)
+ # init.xavier_normal_(m.weight)
+ if m.bias is not None:
+ init.constant_(m.bias, 0)
+ elif isinstance(m, nn.ConvTranspose2d):
+ # init.kaiming_normal_(m.weight, mode='fan_out')
+ init.normal_(m.weight, std=0.01)
+ # init.xavier_normal_(m.weight)
+ if m.bias is not None:
+ init.constant_(m.bias, 0)
+ elif isinstance(m, nn.BatchNorm2d): # NN.BatchNorm2d
+ init.constant_(m.weight, 1)
+ init.constant_(m.bias, 0)
+ elif isinstance(m, nn.Linear):
+ init.normal_(m.weight, std=0.01)
+ if m.bias is not None:
+ init.constant_(m.bias, 0)
+
+
+class SenceUnderstand(nn.Module):
+ def __init__(self, channels):
+ super(SenceUnderstand, self).__init__()
+ self.channels = channels
+ self.conv1 = nn.Sequential(nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, padding=1),
+ nn.ReLU(inplace=True))
+ self.pool = nn.AdaptiveAvgPool2d(8)
+ self.fc = nn.Sequential(nn.Linear(512 * 8 * 8, self.channels),
+ nn.ReLU(inplace=True))
+ self.conv2 = nn.Sequential(
+ nn.Conv2d(in_channels=self.channels, out_channels=self.channels, kernel_size=1, padding=0),
+ nn.ReLU(inplace=True))
+ self.initial_params()
+
+ def forward(self, x):
+ n, c, h, w = x.size()
+ x = self.conv1(x)
+ x = self.pool(x)
+ x = x.view(n, -1)
+ x = self.fc(x)
+ x = x.view(n, self.channels, 1, 1)
+ x = self.conv2(x)
+ x = x.repeat(1, 1, h, w)
+ return x
+
+ def initial_params(self, dev=0.01):
+ for m in self.modules():
+ if isinstance(m, nn.Conv2d):
+ # print torch.sum(m.weight)
+ m.weight.data.normal_(0, dev)
+ if m.bias is not None:
+ m.bias.data.fill_(0)
+ elif isinstance(m, nn.ConvTranspose2d):
+ # print torch.sum(m.weight)
+ m.weight.data.normal_(0, dev)
+ if m.bias is not None:
+ m.bias.data.fill_(0)
+ elif isinstance(m, nn.Linear):
+ m.weight.data.normal_(0, dev)
+
+
+if __name__ == '__main__':
+ net = DepthNet(depth=50, pretrained=True)
+ print(net)
+ inputs = torch.ones(4,3,128,128)
+ out = net(inputs)
+ print(out.size())
+
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/LICENSE b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..38b1a24fd389a138b930dcf1ee606ef97a0186c8
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/LICENSE
@@ -0,0 +1,19 @@
+https://github.com/compphoto/BoostingMonocularDepth
+
+Copyright 2021, Seyed Mahdi Hosseini Miangoleh, Sebastian Dille, Computational Photography Laboratory. All rights reserved.
+
+This software is for academic use only. A redistribution of this
+software, with or without modifications, has to be for academic
+use only, while giving the appropriate credit to the original
+authors of the software. The methods implemented as a part of
+this software may be covered under patents or patent applications.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/models/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/models/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..301c966fca7a375c359b7ee7d455e23ee82ebb64
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/models/__init__.py
@@ -0,0 +1,67 @@
+"""This package contains modules related to objective functions, optimizations, and network architectures.
+
+To add a custom model class called 'dummy', you need to add a file called 'dummy_model.py' and define a subclass DummyModel inherited from BaseModel.
+You need to implement the following five functions:
+ -- <__init__>: initialize the class; first call BaseModel.__init__(self, opt).
+ -- : unpack data from dataset and apply preprocessing.
+ -- : produce intermediate results.
+ -- : calculate loss, gradients, and update network weights.
+ -- : (optionally) add model-specific options and set default options.
+
+In the function <__init__>, you need to define four lists:
+ -- self.loss_names (str list): specify the training losses that you want to plot and save.
+ -- self.model_names (str list): define networks used in our training.
+ -- self.visual_names (str list): specify the images that you want to display and save.
+ -- self.optimizers (optimizer list): define and initialize optimizers. You can define one optimizer for each network. If two networks are updated at the same time, you can use itertools.chain to group them. See cycle_gan_model.py for an usage.
+
+Now you can use the model class by specifying flag '--model dummy'.
+See our template model class 'template_model.py' for more details.
+"""
+
+import importlib
+from .base_model import BaseModel
+
+
+def find_model_using_name(model_name):
+ """Import the module "models/[model_name]_model.py".
+
+ In the file, the class called DatasetNameModel() will
+ be instantiated. It has to be a subclass of BaseModel,
+ and it is case-insensitive.
+ """
+ model_filename = "controlnet_aux.leres.pix2pix.models." + model_name + "_model"
+ modellib = importlib.import_module(model_filename)
+ model = None
+ target_model_name = model_name.replace('_', '') + 'model'
+ for name, cls in modellib.__dict__.items():
+ if name.lower() == target_model_name.lower() \
+ and issubclass(cls, BaseModel):
+ model = cls
+
+ if model is None:
+ print("In %s.py, there should be a subclass of BaseModel with class name that matches %s in lowercase." % (model_filename, target_model_name))
+ exit(0)
+
+ return model
+
+
+def get_option_setter(model_name):
+ """Return the static method of the model class."""
+ model_class = find_model_using_name(model_name)
+ return model_class.modify_commandline_options
+
+
+def create_model(opt):
+ """Create a model given the option.
+
+ This function warps the class CustomDatasetDataLoader.
+ This is the main interface between this package and 'train.py'/'test.py'
+
+ Example:
+ >>> from models import create_model
+ >>> model = create_model(opt)
+ """
+ model = find_model_using_name(opt.model)
+ instance = model(opt)
+ print("model [%s] was created" % type(instance).__name__)
+ return instance
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/models/base_model.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/models/base_model.py
new file mode 100644
index 0000000000000000000000000000000000000000..66ec298f77cf769e39da38d1107e0b6dc38d519d
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/models/base_model.py
@@ -0,0 +1,244 @@
+import gc
+import os
+from abc import ABC, abstractmethod
+from collections import OrderedDict
+
+import torch
+
+from ....util import torch_gc
+from . import networks
+
+
+class BaseModel(ABC):
+ """This class is an abstract base class (ABC) for models.
+ To create a subclass, you need to implement the following five functions:
+ -- <__init__>: initialize the class; first call BaseModel.__init__(self, opt).
+ -- : unpack data from dataset and apply preprocessing.
+ -- : produce intermediate results.
+ -- : calculate losses, gradients, and update network weights.
+ -- : (optionally) add model-specific options and set default options.
+ """
+
+ def __init__(self, opt):
+ """Initialize the BaseModel class.
+
+ Parameters:
+ opt (Option class)-- stores all the experiment flags; needs to be a subclass of BaseOptions
+
+ When creating your custom class, you need to implement your own initialization.
+ In this function, you should first call
+ Then, you need to define four lists:
+ -- self.loss_names (str list): specify the training losses that you want to plot and save.
+ -- self.model_names (str list): define networks used in our training.
+ -- self.visual_names (str list): specify the images that you want to display and save.
+ -- self.optimizers (optimizer list): define and initialize optimizers. You can define one optimizer for each network. If two networks are updated at the same time, you can use itertools.chain to group them. See cycle_gan_model.py for an example.
+ """
+ self.opt = opt
+ self.gpu_ids = opt.gpu_ids
+ self.isTrain = opt.isTrain
+ self.device = torch.device('cuda:{}'.format(self.gpu_ids[0])) if self.gpu_ids else torch.device('cpu') # get device name: CPU or GPU
+ self.save_dir = os.path.join(opt.checkpoints_dir, opt.name) # save all the checkpoints to save_dir
+ if opt.preprocess != 'scale_width': # with [scale_width], input images might have different sizes, which hurts the performance of cudnn.benchmark.
+ torch.backends.cudnn.benchmark = True
+ self.loss_names = []
+ self.model_names = []
+ self.visual_names = []
+ self.optimizers = []
+ self.image_paths = []
+ self.metric = 0 # used for learning rate policy 'plateau'
+
+ @staticmethod
+ def modify_commandline_options(parser, is_train):
+ """Add new model-specific options, and rewrite default values for existing options.
+
+ Parameters:
+ parser -- original option parser
+ is_train (bool) -- whether training phase or test phase. You can use this flag to add training-specific or test-specific options.
+
+ Returns:
+ the modified parser.
+ """
+ return parser
+
+ @abstractmethod
+ def set_input(self, input):
+ """Unpack input data from the dataloader and perform necessary pre-processing steps.
+
+ Parameters:
+ input (dict): includes the data itself and its metadata information.
+ """
+ pass
+
+ @abstractmethod
+ def forward(self):
+ """Run forward pass; called by both functions and ."""
+ pass
+
+ @abstractmethod
+ def optimize_parameters(self):
+ """Calculate losses, gradients, and update network weights; called in every training iteration"""
+ pass
+
+ def setup(self, opt):
+ """Load and print networks; create schedulers
+
+ Parameters:
+ opt (Option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions
+ """
+ if self.isTrain:
+ self.schedulers = [networks.get_scheduler(optimizer, opt) for optimizer in self.optimizers]
+ if not self.isTrain or opt.continue_train:
+ load_suffix = 'iter_%d' % opt.load_iter if opt.load_iter > 0 else opt.epoch
+ self.load_networks(load_suffix)
+ self.print_networks(opt.verbose)
+
+ def eval(self):
+ """Make models eval mode during test time"""
+ for name in self.model_names:
+ if isinstance(name, str):
+ net = getattr(self, 'net' + name)
+ net.eval()
+
+ def test(self):
+ """Forward function used in test time.
+
+ This function wraps function in no_grad() so we don't save intermediate steps for backprop
+ It also calls to produce additional visualization results
+ """
+ with torch.no_grad():
+ self.forward()
+ self.compute_visuals()
+
+ def compute_visuals(self):
+ """Calculate additional output images for visdom and HTML visualization"""
+ pass
+
+ def get_image_paths(self):
+ """ Return image paths that are used to load current data"""
+ return self.image_paths
+
+ def update_learning_rate(self):
+ """Update learning rates for all the networks; called at the end of every epoch"""
+ old_lr = self.optimizers[0].param_groups[0]['lr']
+ for scheduler in self.schedulers:
+ if self.opt.lr_policy == 'plateau':
+ scheduler.step(self.metric)
+ else:
+ scheduler.step()
+
+ lr = self.optimizers[0].param_groups[0]['lr']
+ print('learning rate %.7f -> %.7f' % (old_lr, lr))
+
+ def get_current_visuals(self):
+ """Return visualization images. train.py will display these images with visdom, and save the images to a HTML"""
+ visual_ret = OrderedDict()
+ for name in self.visual_names:
+ if isinstance(name, str):
+ visual_ret[name] = getattr(self, name)
+ return visual_ret
+
+ def get_current_losses(self):
+ """Return traning losses / errors. train.py will print out these errors on console, and save them to a file"""
+ errors_ret = OrderedDict()
+ for name in self.loss_names:
+ if isinstance(name, str):
+ errors_ret[name] = float(getattr(self, 'loss_' + name)) # float(...) works for both scalar tensor and float number
+ return errors_ret
+
+ def save_networks(self, epoch):
+ """Save all the networks to the disk.
+
+ Parameters:
+ epoch (int) -- current epoch; used in the file name '%s_net_%s.pth' % (epoch, name)
+ """
+ for name in self.model_names:
+ if isinstance(name, str):
+ save_filename = '%s_net_%s.pth' % (epoch, name)
+ save_path = os.path.join(self.save_dir, save_filename)
+ net = getattr(self, 'net' + name)
+
+ if len(self.gpu_ids) > 0 and torch.cuda.is_available():
+ torch.save(net.module.cpu().state_dict(), save_path)
+ net.cuda(self.gpu_ids[0])
+ else:
+ torch.save(net.cpu().state_dict(), save_path)
+
+ def unload_network(self, name):
+ """Unload network and gc.
+ """
+ if isinstance(name, str):
+ net = getattr(self, 'net' + name)
+ del net
+ gc.collect()
+ torch_gc()
+ return None
+
+ def __patch_instance_norm_state_dict(self, state_dict, module, keys, i=0):
+ """Fix InstanceNorm checkpoints incompatibility (prior to 0.4)"""
+ key = keys[i]
+ if i + 1 == len(keys): # at the end, pointing to a parameter/buffer
+ if module.__class__.__name__.startswith('InstanceNorm') and \
+ (key == 'running_mean' or key == 'running_var'):
+ if getattr(module, key) is None:
+ state_dict.pop('.'.join(keys))
+ if module.__class__.__name__.startswith('InstanceNorm') and \
+ (key == 'num_batches_tracked'):
+ state_dict.pop('.'.join(keys))
+ else:
+ self.__patch_instance_norm_state_dict(state_dict, getattr(module, key), keys, i + 1)
+
+ def load_networks(self, epoch):
+ """Load all the networks from the disk.
+
+ Parameters:
+ epoch (int) -- current epoch; used in the file name '%s_net_%s.pth' % (epoch, name)
+ """
+ for name in self.model_names:
+ if isinstance(name, str):
+ load_filename = '%s_net_%s.pth' % (epoch, name)
+ load_path = os.path.join(self.save_dir, load_filename)
+ net = getattr(self, 'net' + name)
+ if isinstance(net, torch.nn.DataParallel):
+ net = net.module
+ # print('Loading depth boost model from %s' % load_path)
+ # if you are using PyTorch newer than 0.4 (e.g., built from
+ # GitHub source), you can remove str() on self.device
+ state_dict = torch.load(load_path, map_location=str(self.device))
+ if hasattr(state_dict, '_metadata'):
+ del state_dict._metadata
+
+ # patch InstanceNorm checkpoints prior to 0.4
+ for key in list(state_dict.keys()): # need to copy keys here because we mutate in loop
+ self.__patch_instance_norm_state_dict(state_dict, net, key.split('.'))
+ net.load_state_dict(state_dict)
+
+ def print_networks(self, verbose):
+ """Print the total number of parameters in the network and (if verbose) network architecture
+
+ Parameters:
+ verbose (bool) -- if verbose: print the network architecture
+ """
+ print('---------- Networks initialized -------------')
+ for name in self.model_names:
+ if isinstance(name, str):
+ net = getattr(self, 'net' + name)
+ num_params = 0
+ for param in net.parameters():
+ num_params += param.numel()
+ if verbose:
+ print(net)
+ print('[Network %s] Total number of parameters : %.3f M' % (name, num_params / 1e6))
+ print('-----------------------------------------------')
+
+ def set_requires_grad(self, nets, requires_grad=False):
+ """Set requies_grad=Fasle for all the networks to avoid unnecessary computations
+ Parameters:
+ nets (network list) -- a list of networks
+ requires_grad (bool) -- whether the networks require gradients or not
+ """
+ if not isinstance(nets, list):
+ nets = [nets]
+ for net in nets:
+ if net is not None:
+ for param in net.parameters():
+ param.requires_grad = requires_grad
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/models/base_model_hg.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/models/base_model_hg.py
new file mode 100644
index 0000000000000000000000000000000000000000..1709accdf0b048b3793dfd1f58d1b06c35f7b907
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/models/base_model_hg.py
@@ -0,0 +1,58 @@
+import os
+import torch
+
+class BaseModelHG():
+ def name(self):
+ return 'BaseModel'
+
+ def initialize(self, opt):
+ self.opt = opt
+ self.gpu_ids = opt.gpu_ids
+ self.isTrain = opt.isTrain
+ self.Tensor = torch.cuda.FloatTensor if self.gpu_ids else torch.Tensor
+ self.save_dir = os.path.join(opt.checkpoints_dir, opt.name)
+
+ def set_input(self, input):
+ self.input = input
+
+ def forward(self):
+ pass
+
+ # used in test time, no backprop
+ def test(self):
+ pass
+
+ def get_image_paths(self):
+ pass
+
+ def optimize_parameters(self):
+ pass
+
+ def get_current_visuals(self):
+ return self.input
+
+ def get_current_errors(self):
+ return {}
+
+ def save(self, label):
+ pass
+
+ # helper saving function that can be used by subclasses
+ def save_network(self, network, network_label, epoch_label, gpu_ids):
+ save_filename = '_%s_net_%s.pth' % (epoch_label, network_label)
+ save_path = os.path.join(self.save_dir, save_filename)
+ torch.save(network.cpu().state_dict(), save_path)
+ if len(gpu_ids) and torch.cuda.is_available():
+ network.cuda(device_id=gpu_ids[0])
+
+ # helper loading function that can be used by subclasses
+ def load_network(self, network, network_label, epoch_label):
+ save_filename = '%s_net_%s.pth' % (epoch_label, network_label)
+ save_path = os.path.join(self.save_dir, save_filename)
+ print(save_path)
+ model = torch.load(save_path)
+ return model
+ # network.load_state_dict(torch.load(save_path))
+
+ def update_learning_rate():
+ pass
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/models/networks.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/models/networks.py
new file mode 100644
index 0000000000000000000000000000000000000000..0cf912b2973721a02deefd042af621e732bad59f
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/models/networks.py
@@ -0,0 +1,623 @@
+import torch
+import torch.nn as nn
+from torch.nn import init
+import functools
+from torch.optim import lr_scheduler
+
+
+###############################################################################
+# Helper Functions
+###############################################################################
+
+
+class Identity(nn.Module):
+ def forward(self, x):
+ return x
+
+
+def get_norm_layer(norm_type='instance'):
+ """Return a normalization layer
+
+ Parameters:
+ norm_type (str) -- the name of the normalization layer: batch | instance | none
+
+ For BatchNorm, we use learnable affine parameters and track running statistics (mean/stddev).
+ For InstanceNorm, we do not use learnable affine parameters. We do not track running statistics.
+ """
+ if norm_type == 'batch':
+ norm_layer = functools.partial(nn.BatchNorm2d, affine=True, track_running_stats=True)
+ elif norm_type == 'instance':
+ norm_layer = functools.partial(nn.InstanceNorm2d, affine=False, track_running_stats=False)
+ elif norm_type == 'none':
+ def norm_layer(x): return Identity()
+ else:
+ raise NotImplementedError('normalization layer [%s] is not found' % norm_type)
+ return norm_layer
+
+
+def get_scheduler(optimizer, opt):
+ """Return a learning rate scheduler
+
+ Parameters:
+ optimizer -- the optimizer of the network
+ opt (option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions.
+ opt.lr_policy is the name of learning rate policy: linear | step | plateau | cosine
+
+ For 'linear', we keep the same learning rate for the first epochs
+ and linearly decay the rate to zero over the next epochs.
+ For other schedulers (step, plateau, and cosine), we use the default PyTorch schedulers.
+ See https://pytorch.org/docs/stable/optim.html for more details.
+ """
+ if opt.lr_policy == 'linear':
+ def lambda_rule(epoch):
+ lr_l = 1.0 - max(0, epoch + opt.epoch_count - opt.n_epochs) / float(opt.n_epochs_decay + 1)
+ return lr_l
+ scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lambda_rule)
+ elif opt.lr_policy == 'step':
+ scheduler = lr_scheduler.StepLR(optimizer, step_size=opt.lr_decay_iters, gamma=0.1)
+ elif opt.lr_policy == 'plateau':
+ scheduler = lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.2, threshold=0.01, patience=5)
+ elif opt.lr_policy == 'cosine':
+ scheduler = lr_scheduler.CosineAnnealingLR(optimizer, T_max=opt.n_epochs, eta_min=0)
+ else:
+ return NotImplementedError('learning rate policy [%s] is not implemented', opt.lr_policy)
+ return scheduler
+
+
+def init_weights(net, init_type='normal', init_gain=0.02):
+ """Initialize network weights.
+
+ Parameters:
+ net (network) -- network to be initialized
+ init_type (str) -- the name of an initialization method: normal | xavier | kaiming | orthogonal
+ init_gain (float) -- scaling factor for normal, xavier and orthogonal.
+
+ We use 'normal' in the original pix2pix and CycleGAN paper. But xavier and kaiming might
+ work better for some applications. Feel free to try yourself.
+ """
+ def init_func(m): # define the initialization function
+ classname = m.__class__.__name__
+ if hasattr(m, 'weight') and (classname.find('Conv') != -1 or classname.find('Linear') != -1):
+ if init_type == 'normal':
+ init.normal_(m.weight.data, 0.0, init_gain)
+ elif init_type == 'xavier':
+ init.xavier_normal_(m.weight.data, gain=init_gain)
+ elif init_type == 'kaiming':
+ init.kaiming_normal_(m.weight.data, a=0, mode='fan_in')
+ elif init_type == 'orthogonal':
+ init.orthogonal_(m.weight.data, gain=init_gain)
+ else:
+ raise NotImplementedError('initialization method [%s] is not implemented' % init_type)
+ if hasattr(m, 'bias') and m.bias is not None:
+ init.constant_(m.bias.data, 0.0)
+ elif classname.find('BatchNorm2d') != -1: # BatchNorm Layer's weight is not a matrix; only normal distribution applies.
+ init.normal_(m.weight.data, 1.0, init_gain)
+ init.constant_(m.bias.data, 0.0)
+
+ # print('initialize network with %s' % init_type)
+ net.apply(init_func) # apply the initialization function
+
+
+def init_net(net, init_type='normal', init_gain=0.02, gpu_ids=[]):
+ """Initialize a network: 1. register CPU/GPU device (with multi-GPU support); 2. initialize the network weights
+ Parameters:
+ net (network) -- the network to be initialized
+ init_type (str) -- the name of an initialization method: normal | xavier | kaiming | orthogonal
+ gain (float) -- scaling factor for normal, xavier and orthogonal.
+ gpu_ids (int list) -- which GPUs the network runs on: e.g., 0,1,2
+
+ Return an initialized network.
+ """
+ if len(gpu_ids) > 0:
+ assert(torch.cuda.is_available())
+ net.to(gpu_ids[0])
+ net = torch.nn.DataParallel(net, gpu_ids) # multi-GPUs
+ init_weights(net, init_type, init_gain=init_gain)
+ return net
+
+
+def define_G(input_nc, output_nc, ngf, netG, norm='batch', use_dropout=False, init_type='normal', init_gain=0.02, gpu_ids=[]):
+ """Create a generator
+
+ Parameters:
+ input_nc (int) -- the number of channels in input images
+ output_nc (int) -- the number of channels in output images
+ ngf (int) -- the number of filters in the last conv layer
+ netG (str) -- the architecture's name: resnet_9blocks | resnet_6blocks | unet_256 | unet_128
+ norm (str) -- the name of normalization layers used in the network: batch | instance | none
+ use_dropout (bool) -- if use dropout layers.
+ init_type (str) -- the name of our initialization method.
+ init_gain (float) -- scaling factor for normal, xavier and orthogonal.
+ gpu_ids (int list) -- which GPUs the network runs on: e.g., 0,1,2
+
+ Returns a generator
+
+ Our current implementation provides two types of generators:
+ U-Net: [unet_128] (for 128x128 input images) and [unet_256] (for 256x256 input images)
+ The original U-Net paper: https://arxiv.org/abs/1505.04597
+
+ Resnet-based generator: [resnet_6blocks] (with 6 Resnet blocks) and [resnet_9blocks] (with 9 Resnet blocks)
+ Resnet-based generator consists of several Resnet blocks between a few downsampling/upsampling operations.
+ We adapt Torch code from Justin Johnson's neural style transfer project (https://github.com/jcjohnson/fast-neural-style).
+
+
+ The generator has been initialized by . It uses RELU for non-linearity.
+ """
+ net = None
+ norm_layer = get_norm_layer(norm_type=norm)
+
+ if netG == 'resnet_9blocks':
+ net = ResnetGenerator(input_nc, output_nc, ngf, norm_layer=norm_layer, use_dropout=use_dropout, n_blocks=9)
+ elif netG == 'resnet_6blocks':
+ net = ResnetGenerator(input_nc, output_nc, ngf, norm_layer=norm_layer, use_dropout=use_dropout, n_blocks=6)
+ elif netG == 'resnet_12blocks':
+ net = ResnetGenerator(input_nc, output_nc, ngf, norm_layer=norm_layer, use_dropout=use_dropout, n_blocks=12)
+ elif netG == 'unet_128':
+ net = UnetGenerator(input_nc, output_nc, 7, ngf, norm_layer=norm_layer, use_dropout=use_dropout)
+ elif netG == 'unet_256':
+ net = UnetGenerator(input_nc, output_nc, 8, ngf, norm_layer=norm_layer, use_dropout=use_dropout)
+ elif netG == 'unet_672':
+ net = UnetGenerator(input_nc, output_nc, 5, ngf, norm_layer=norm_layer, use_dropout=use_dropout)
+ elif netG == 'unet_960':
+ net = UnetGenerator(input_nc, output_nc, 6, ngf, norm_layer=norm_layer, use_dropout=use_dropout)
+ elif netG == 'unet_1024':
+ net = UnetGenerator(input_nc, output_nc, 10, ngf, norm_layer=norm_layer, use_dropout=use_dropout)
+ else:
+ raise NotImplementedError('Generator model name [%s] is not recognized' % netG)
+ return init_net(net, init_type, init_gain, gpu_ids)
+
+
+def define_D(input_nc, ndf, netD, n_layers_D=3, norm='batch', init_type='normal', init_gain=0.02, gpu_ids=[]):
+ """Create a discriminator
+
+ Parameters:
+ input_nc (int) -- the number of channels in input images
+ ndf (int) -- the number of filters in the first conv layer
+ netD (str) -- the architecture's name: basic | n_layers | pixel
+ n_layers_D (int) -- the number of conv layers in the discriminator; effective when netD=='n_layers'
+ norm (str) -- the type of normalization layers used in the network.
+ init_type (str) -- the name of the initialization method.
+ init_gain (float) -- scaling factor for normal, xavier and orthogonal.
+ gpu_ids (int list) -- which GPUs the network runs on: e.g., 0,1,2
+
+ Returns a discriminator
+
+ Our current implementation provides three types of discriminators:
+ [basic]: 'PatchGAN' classifier described in the original pix2pix paper.
+ It can classify whether 70×70 overlapping patches are real or fake.
+ Such a patch-level discriminator architecture has fewer parameters
+ than a full-image discriminator and can work on arbitrarily-sized images
+ in a fully convolutional fashion.
+
+ [n_layers]: With this mode, you can specify the number of conv layers in the discriminator
+ with the parameter (default=3 as used in [basic] (PatchGAN).)
+
+ [pixel]: 1x1 PixelGAN discriminator can classify whether a pixel is real or not.
+ It encourages greater color diversity but has no effect on spatial statistics.
+
+ The discriminator has been initialized by . It uses Leakly RELU for non-linearity.
+ """
+ net = None
+ norm_layer = get_norm_layer(norm_type=norm)
+
+ if netD == 'basic': # default PatchGAN classifier
+ net = NLayerDiscriminator(input_nc, ndf, n_layers=3, norm_layer=norm_layer)
+ elif netD == 'n_layers': # more options
+ net = NLayerDiscriminator(input_nc, ndf, n_layers_D, norm_layer=norm_layer)
+ elif netD == 'pixel': # classify if each pixel is real or fake
+ net = PixelDiscriminator(input_nc, ndf, norm_layer=norm_layer)
+ else:
+ raise NotImplementedError('Discriminator model name [%s] is not recognized' % netD)
+ return init_net(net, init_type, init_gain, gpu_ids)
+
+
+##############################################################################
+# Classes
+##############################################################################
+class GANLoss(nn.Module):
+ """Define different GAN objectives.
+
+ The GANLoss class abstracts away the need to create the target label tensor
+ that has the same size as the input.
+ """
+
+ def __init__(self, gan_mode, target_real_label=1.0, target_fake_label=0.0):
+ """ Initialize the GANLoss class.
+
+ Parameters:
+ gan_mode (str) - - the type of GAN objective. It currently supports vanilla, lsgan, and wgangp.
+ target_real_label (bool) - - label for a real image
+ target_fake_label (bool) - - label of a fake image
+
+ Note: Do not use sigmoid as the last layer of Discriminator.
+ LSGAN needs no sigmoid. vanilla GANs will handle it with BCEWithLogitsLoss.
+ """
+ super(GANLoss, self).__init__()
+ self.register_buffer('real_label', torch.tensor(target_real_label))
+ self.register_buffer('fake_label', torch.tensor(target_fake_label))
+ self.gan_mode = gan_mode
+ if gan_mode == 'lsgan':
+ self.loss = nn.MSELoss()
+ elif gan_mode == 'vanilla':
+ self.loss = nn.BCEWithLogitsLoss()
+ elif gan_mode in ['wgangp']:
+ self.loss = None
+ else:
+ raise NotImplementedError('gan mode %s not implemented' % gan_mode)
+
+ def get_target_tensor(self, prediction, target_is_real):
+ """Create label tensors with the same size as the input.
+
+ Parameters:
+ prediction (tensor) - - tpyically the prediction from a discriminator
+ target_is_real (bool) - - if the ground truth label is for real images or fake images
+
+ Returns:
+ A label tensor filled with ground truth label, and with the size of the input
+ """
+
+ if target_is_real:
+ target_tensor = self.real_label
+ else:
+ target_tensor = self.fake_label
+ return target_tensor.expand_as(prediction)
+
+ def __call__(self, prediction, target_is_real):
+ """Calculate loss given Discriminator's output and grount truth labels.
+
+ Parameters:
+ prediction (tensor) - - tpyically the prediction output from a discriminator
+ target_is_real (bool) - - if the ground truth label is for real images or fake images
+
+ Returns:
+ the calculated loss.
+ """
+ if self.gan_mode in ['lsgan', 'vanilla']:
+ target_tensor = self.get_target_tensor(prediction, target_is_real)
+ loss = self.loss(prediction, target_tensor)
+ elif self.gan_mode == 'wgangp':
+ if target_is_real:
+ loss = -prediction.mean()
+ else:
+ loss = prediction.mean()
+ return loss
+
+
+def cal_gradient_penalty(netD, real_data, fake_data, device, type='mixed', constant=1.0, lambda_gp=10.0):
+ """Calculate the gradient penalty loss, used in WGAN-GP paper https://arxiv.org/abs/1704.00028
+
+ Arguments:
+ netD (network) -- discriminator network
+ real_data (tensor array) -- real images
+ fake_data (tensor array) -- generated images from the generator
+ device (str) -- GPU / CPU: from torch.device('cuda:{}'.format(self.gpu_ids[0])) if self.gpu_ids else torch.device('cpu')
+ type (str) -- if we mix real and fake data or not [real | fake | mixed].
+ constant (float) -- the constant used in formula ( ||gradient||_2 - constant)^2
+ lambda_gp (float) -- weight for this loss
+
+ Returns the gradient penalty loss
+ """
+ if lambda_gp > 0.0:
+ if type == 'real': # either use real images, fake images, or a linear interpolation of two.
+ interpolatesv = real_data
+ elif type == 'fake':
+ interpolatesv = fake_data
+ elif type == 'mixed':
+ alpha = torch.rand(real_data.shape[0], 1, device=device)
+ alpha = alpha.expand(real_data.shape[0], real_data.nelement() // real_data.shape[0]).contiguous().view(*real_data.shape)
+ interpolatesv = alpha * real_data + ((1 - alpha) * fake_data)
+ else:
+ raise NotImplementedError('{} not implemented'.format(type))
+ interpolatesv.requires_grad_(True)
+ disc_interpolates = netD(interpolatesv)
+ gradients = torch.autograd.grad(outputs=disc_interpolates, inputs=interpolatesv,
+ grad_outputs=torch.ones(disc_interpolates.size()).to(device),
+ create_graph=True, retain_graph=True, only_inputs=True)
+ gradients = gradients[0].view(real_data.size(0), -1) # flat the data
+ gradient_penalty = (((gradients + 1e-16).norm(2, dim=1) - constant) ** 2).mean() * lambda_gp # added eps
+ return gradient_penalty, gradients
+ else:
+ return 0.0, None
+
+
+class ResnetGenerator(nn.Module):
+ """Resnet-based generator that consists of Resnet blocks between a few downsampling/upsampling operations.
+
+ We adapt Torch code and idea from Justin Johnson's neural style transfer project(https://github.com/jcjohnson/fast-neural-style)
+ """
+
+ def __init__(self, input_nc, output_nc, ngf=64, norm_layer=nn.BatchNorm2d, use_dropout=False, n_blocks=6, padding_type='reflect'):
+ """Construct a Resnet-based generator
+
+ Parameters:
+ input_nc (int) -- the number of channels in input images
+ output_nc (int) -- the number of channels in output images
+ ngf (int) -- the number of filters in the last conv layer
+ norm_layer -- normalization layer
+ use_dropout (bool) -- if use dropout layers
+ n_blocks (int) -- the number of ResNet blocks
+ padding_type (str) -- the name of padding layer in conv layers: reflect | replicate | zero
+ """
+ assert(n_blocks >= 0)
+ super(ResnetGenerator, self).__init__()
+ if type(norm_layer) == functools.partial:
+ use_bias = norm_layer.func == nn.InstanceNorm2d
+ else:
+ use_bias = norm_layer == nn.InstanceNorm2d
+
+ model = [nn.ReflectionPad2d(3),
+ nn.Conv2d(input_nc, ngf, kernel_size=7, padding=0, bias=use_bias),
+ norm_layer(ngf),
+ nn.ReLU(True)]
+
+ n_downsampling = 2
+ for i in range(n_downsampling): # add downsampling layers
+ mult = 2 ** i
+ model += [nn.Conv2d(ngf * mult, ngf * mult * 2, kernel_size=3, stride=2, padding=1, bias=use_bias),
+ norm_layer(ngf * mult * 2),
+ nn.ReLU(True)]
+
+ mult = 2 ** n_downsampling
+ for i in range(n_blocks): # add ResNet blocks
+
+ model += [ResnetBlock(ngf * mult, padding_type=padding_type, norm_layer=norm_layer, use_dropout=use_dropout, use_bias=use_bias)]
+
+ for i in range(n_downsampling): # add upsampling layers
+ mult = 2 ** (n_downsampling - i)
+ model += [nn.ConvTranspose2d(ngf * mult, int(ngf * mult / 2),
+ kernel_size=3, stride=2,
+ padding=1, output_padding=1,
+ bias=use_bias),
+ norm_layer(int(ngf * mult / 2)),
+ nn.ReLU(True)]
+ model += [nn.ReflectionPad2d(3)]
+ model += [nn.Conv2d(ngf, output_nc, kernel_size=7, padding=0)]
+ model += [nn.Tanh()]
+
+ self.model = nn.Sequential(*model)
+
+ def forward(self, input):
+ """Standard forward"""
+ return self.model(input)
+
+
+class ResnetBlock(nn.Module):
+ """Define a Resnet block"""
+
+ def __init__(self, dim, padding_type, norm_layer, use_dropout, use_bias):
+ """Initialize the Resnet block
+
+ A resnet block is a conv block with skip connections
+ We construct a conv block with build_conv_block function,
+ and implement skip connections in function.
+ Original Resnet paper: https://arxiv.org/pdf/1512.03385.pdf
+ """
+ super(ResnetBlock, self).__init__()
+ self.conv_block = self.build_conv_block(dim, padding_type, norm_layer, use_dropout, use_bias)
+
+ def build_conv_block(self, dim, padding_type, norm_layer, use_dropout, use_bias):
+ """Construct a convolutional block.
+
+ Parameters:
+ dim (int) -- the number of channels in the conv layer.
+ padding_type (str) -- the name of padding layer: reflect | replicate | zero
+ norm_layer -- normalization layer
+ use_dropout (bool) -- if use dropout layers.
+ use_bias (bool) -- if the conv layer uses bias or not
+
+ Returns a conv block (with a conv layer, a normalization layer, and a non-linearity layer (ReLU))
+ """
+ conv_block = []
+ p = 0
+ if padding_type == 'reflect':
+ conv_block += [nn.ReflectionPad2d(1)]
+ elif padding_type == 'replicate':
+ conv_block += [nn.ReplicationPad2d(1)]
+ elif padding_type == 'zero':
+ p = 1
+ else:
+ raise NotImplementedError('padding [%s] is not implemented' % padding_type)
+
+ conv_block += [nn.Conv2d(dim, dim, kernel_size=3, padding=p, bias=use_bias), norm_layer(dim), nn.ReLU(True)]
+ if use_dropout:
+ conv_block += [nn.Dropout(0.5)]
+
+ p = 0
+ if padding_type == 'reflect':
+ conv_block += [nn.ReflectionPad2d(1)]
+ elif padding_type == 'replicate':
+ conv_block += [nn.ReplicationPad2d(1)]
+ elif padding_type == 'zero':
+ p = 1
+ else:
+ raise NotImplementedError('padding [%s] is not implemented' % padding_type)
+ conv_block += [nn.Conv2d(dim, dim, kernel_size=3, padding=p, bias=use_bias), norm_layer(dim)]
+
+ return nn.Sequential(*conv_block)
+
+ def forward(self, x):
+ """Forward function (with skip connections)"""
+ out = x + self.conv_block(x) # add skip connections
+ return out
+
+
+class UnetGenerator(nn.Module):
+ """Create a Unet-based generator"""
+
+ def __init__(self, input_nc, output_nc, num_downs, ngf=64, norm_layer=nn.BatchNorm2d, use_dropout=False):
+ """Construct a Unet generator
+ Parameters:
+ input_nc (int) -- the number of channels in input images
+ output_nc (int) -- the number of channels in output images
+ num_downs (int) -- the number of downsamplings in UNet. For example, # if |num_downs| == 7,
+ image of size 128x128 will become of size 1x1 # at the bottleneck
+ ngf (int) -- the number of filters in the last conv layer
+ norm_layer -- normalization layer
+
+ We construct the U-Net from the innermost layer to the outermost layer.
+ It is a recursive process.
+ """
+ super(UnetGenerator, self).__init__()
+ # construct unet structure
+ unet_block = UnetSkipConnectionBlock(ngf * 8, ngf * 8, input_nc=None, submodule=None, norm_layer=norm_layer, innermost=True) # add the innermost layer
+ for i in range(num_downs - 5): # add intermediate layers with ngf * 8 filters
+ unet_block = UnetSkipConnectionBlock(ngf * 8, ngf * 8, input_nc=None, submodule=unet_block, norm_layer=norm_layer, use_dropout=use_dropout)
+ # gradually reduce the number of filters from ngf * 8 to ngf
+ unet_block = UnetSkipConnectionBlock(ngf * 4, ngf * 8, input_nc=None, submodule=unet_block, norm_layer=norm_layer)
+ unet_block = UnetSkipConnectionBlock(ngf * 2, ngf * 4, input_nc=None, submodule=unet_block, norm_layer=norm_layer)
+ unet_block = UnetSkipConnectionBlock(ngf, ngf * 2, input_nc=None, submodule=unet_block, norm_layer=norm_layer)
+ self.model = UnetSkipConnectionBlock(output_nc, ngf, input_nc=input_nc, submodule=unet_block, outermost=True, norm_layer=norm_layer) # add the outermost layer
+
+ def forward(self, input):
+ """Standard forward"""
+ return self.model(input)
+
+
+class UnetSkipConnectionBlock(nn.Module):
+ """Defines the Unet submodule with skip connection.
+ X -------------------identity----------------------
+ |-- downsampling -- |submodule| -- upsampling --|
+ """
+
+ def __init__(self, outer_nc, inner_nc, input_nc=None,
+ submodule=None, outermost=False, innermost=False, norm_layer=nn.BatchNorm2d, use_dropout=False):
+ """Construct a Unet submodule with skip connections.
+
+ Parameters:
+ outer_nc (int) -- the number of filters in the outer conv layer
+ inner_nc (int) -- the number of filters in the inner conv layer
+ input_nc (int) -- the number of channels in input images/features
+ submodule (UnetSkipConnectionBlock) -- previously defined submodules
+ outermost (bool) -- if this module is the outermost module
+ innermost (bool) -- if this module is the innermost module
+ norm_layer -- normalization layer
+ use_dropout (bool) -- if use dropout layers.
+ """
+ super(UnetSkipConnectionBlock, self).__init__()
+ self.outermost = outermost
+ if type(norm_layer) == functools.partial:
+ use_bias = norm_layer.func == nn.InstanceNorm2d
+ else:
+ use_bias = norm_layer == nn.InstanceNorm2d
+ if input_nc is None:
+ input_nc = outer_nc
+ downconv = nn.Conv2d(input_nc, inner_nc, kernel_size=4,
+ stride=2, padding=1, bias=use_bias)
+ downrelu = nn.LeakyReLU(0.2, True)
+ downnorm = norm_layer(inner_nc)
+ uprelu = nn.ReLU(True)
+ upnorm = norm_layer(outer_nc)
+
+ if outermost:
+ upconv = nn.ConvTranspose2d(inner_nc * 2, outer_nc,
+ kernel_size=4, stride=2,
+ padding=1)
+ down = [downconv]
+ up = [uprelu, upconv, nn.Tanh()]
+ model = down + [submodule] + up
+ elif innermost:
+ upconv = nn.ConvTranspose2d(inner_nc, outer_nc,
+ kernel_size=4, stride=2,
+ padding=1, bias=use_bias)
+ down = [downrelu, downconv]
+ up = [uprelu, upconv, upnorm]
+ model = down + up
+ else:
+ upconv = nn.ConvTranspose2d(inner_nc * 2, outer_nc,
+ kernel_size=4, stride=2,
+ padding=1, bias=use_bias)
+ down = [downrelu, downconv, downnorm]
+ up = [uprelu, upconv, upnorm]
+
+ if use_dropout:
+ model = down + [submodule] + up + [nn.Dropout(0.5)]
+ else:
+ model = down + [submodule] + up
+
+ self.model = nn.Sequential(*model)
+
+ def forward(self, x):
+ if self.outermost:
+ return self.model(x)
+ else: # add skip connections
+ return torch.cat([x, self.model(x)], 1)
+
+
+class NLayerDiscriminator(nn.Module):
+ """Defines a PatchGAN discriminator"""
+
+ def __init__(self, input_nc, ndf=64, n_layers=3, norm_layer=nn.BatchNorm2d):
+ """Construct a PatchGAN discriminator
+
+ Parameters:
+ input_nc (int) -- the number of channels in input images
+ ndf (int) -- the number of filters in the last conv layer
+ n_layers (int) -- the number of conv layers in the discriminator
+ norm_layer -- normalization layer
+ """
+ super(NLayerDiscriminator, self).__init__()
+ if type(norm_layer) == functools.partial: # no need to use bias as BatchNorm2d has affine parameters
+ use_bias = norm_layer.func == nn.InstanceNorm2d
+ else:
+ use_bias = norm_layer == nn.InstanceNorm2d
+
+ kw = 4
+ padw = 1
+ sequence = [nn.Conv2d(input_nc, ndf, kernel_size=kw, stride=2, padding=padw), nn.LeakyReLU(0.2, True)]
+ nf_mult = 1
+ nf_mult_prev = 1
+ for n in range(1, n_layers): # gradually increase the number of filters
+ nf_mult_prev = nf_mult
+ nf_mult = min(2 ** n, 8)
+ sequence += [
+ nn.Conv2d(ndf * nf_mult_prev, ndf * nf_mult, kernel_size=kw, stride=2, padding=padw, bias=use_bias),
+ norm_layer(ndf * nf_mult),
+ nn.LeakyReLU(0.2, True)
+ ]
+
+ nf_mult_prev = nf_mult
+ nf_mult = min(2 ** n_layers, 8)
+ sequence += [
+ nn.Conv2d(ndf * nf_mult_prev, ndf * nf_mult, kernel_size=kw, stride=1, padding=padw, bias=use_bias),
+ norm_layer(ndf * nf_mult),
+ nn.LeakyReLU(0.2, True)
+ ]
+
+ sequence += [nn.Conv2d(ndf * nf_mult, 1, kernel_size=kw, stride=1, padding=padw)] # output 1 channel prediction map
+ self.model = nn.Sequential(*sequence)
+
+ def forward(self, input):
+ """Standard forward."""
+ return self.model(input)
+
+
+class PixelDiscriminator(nn.Module):
+ """Defines a 1x1 PatchGAN discriminator (pixelGAN)"""
+
+ def __init__(self, input_nc, ndf=64, norm_layer=nn.BatchNorm2d):
+ """Construct a 1x1 PatchGAN discriminator
+
+ Parameters:
+ input_nc (int) -- the number of channels in input images
+ ndf (int) -- the number of filters in the last conv layer
+ norm_layer -- normalization layer
+ """
+ super(PixelDiscriminator, self).__init__()
+ if type(norm_layer) == functools.partial: # no need to use bias as BatchNorm2d has affine parameters
+ use_bias = norm_layer.func == nn.InstanceNorm2d
+ else:
+ use_bias = norm_layer == nn.InstanceNorm2d
+
+ self.net = [
+ nn.Conv2d(input_nc, ndf, kernel_size=1, stride=1, padding=0),
+ nn.LeakyReLU(0.2, True),
+ nn.Conv2d(ndf, ndf * 2, kernel_size=1, stride=1, padding=0, bias=use_bias),
+ norm_layer(ndf * 2),
+ nn.LeakyReLU(0.2, True),
+ nn.Conv2d(ndf * 2, 1, kernel_size=1, stride=1, padding=0, bias=use_bias)]
+
+ self.net = nn.Sequential(*self.net)
+
+ def forward(self, input):
+ """Standard forward."""
+ return self.net(input)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/models/pix2pix4depth_model.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/models/pix2pix4depth_model.py
new file mode 100644
index 0000000000000000000000000000000000000000..89e89652feb96314973a050c5a2477b474630abb
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/models/pix2pix4depth_model.py
@@ -0,0 +1,155 @@
+import torch
+from .base_model import BaseModel
+from . import networks
+
+
+class Pix2Pix4DepthModel(BaseModel):
+ """ This class implements the pix2pix model, for learning a mapping from input images to output images given paired data.
+
+ The model training requires '--dataset_mode aligned' dataset.
+ By default, it uses a '--netG unet256' U-Net generator,
+ a '--netD basic' discriminator (PatchGAN),
+ and a '--gan_mode' vanilla GAN loss (the cross-entropy objective used in the orignal GAN paper).
+
+ pix2pix paper: https://arxiv.org/pdf/1611.07004.pdf
+ """
+ @staticmethod
+ def modify_commandline_options(parser, is_train=True):
+ """Add new dataset-specific options, and rewrite default values for existing options.
+
+ Parameters:
+ parser -- original option parser
+ is_train (bool) -- whether training phase or test phase. You can use this flag to add training-specific or test-specific options.
+
+ Returns:
+ the modified parser.
+
+ For pix2pix, we do not use image buffer
+ The training objective is: GAN Loss + lambda_L1 * ||G(A)-B||_1
+ By default, we use vanilla GAN loss, UNet with batchnorm, and aligned datasets.
+ """
+ # changing the default values to match the pix2pix paper (https://phillipi.github.io/pix2pix/)
+ parser.set_defaults(input_nc=2,output_nc=1,norm='none', netG='unet_1024', dataset_mode='depthmerge')
+ if is_train:
+ parser.set_defaults(pool_size=0, gan_mode='vanilla',)
+ parser.add_argument('--lambda_L1', type=float, default=1000, help='weight for L1 loss')
+ return parser
+
+ def __init__(self, opt):
+ """Initialize the pix2pix class.
+
+ Parameters:
+ opt (Option class)-- stores all the experiment flags; needs to be a subclass of BaseOptions
+ """
+ BaseModel.__init__(self, opt)
+ # specify the training losses you want to print out. The training/test scripts will call
+
+ self.loss_names = ['G_GAN', 'G_L1', 'D_real', 'D_fake']
+ # self.loss_names = ['G_L1']
+
+ # specify the images you want to save/display. The training/test scripts will call
+ if self.isTrain:
+ self.visual_names = ['outer','inner', 'fake_B', 'real_B']
+ else:
+ self.visual_names = ['fake_B']
+
+ # specify the models you want to save to the disk. The training/test scripts will call and
+ if self.isTrain:
+ self.model_names = ['G','D']
+ else: # during test time, only load G
+ self.model_names = ['G']
+
+ # define networks (both generator and discriminator)
+ self.netG = networks.define_G(opt.input_nc, opt.output_nc, 64, 'unet_1024', 'none',
+ False, 'normal', 0.02, self.gpu_ids)
+
+ if self.isTrain: # define a discriminator; conditional GANs need to take both input and output images; Therefore, #channels for D is input_nc + output_nc
+ self.netD = networks.define_D(opt.input_nc + opt.output_nc, opt.ndf, opt.netD,
+ opt.n_layers_D, opt.norm, opt.init_type, opt.init_gain, self.gpu_ids)
+
+ if self.isTrain:
+ # define loss functions
+ self.criterionGAN = networks.GANLoss(opt.gan_mode).to(self.device)
+ self.criterionL1 = torch.nn.L1Loss()
+ # initialize optimizers; schedulers will be automatically created by function .
+ self.optimizer_G = torch.optim.Adam(self.netG.parameters(), lr=1e-4, betas=(opt.beta1, 0.999))
+ self.optimizer_D = torch.optim.Adam(self.netD.parameters(), lr=2e-06, betas=(opt.beta1, 0.999))
+ self.optimizers.append(self.optimizer_G)
+ self.optimizers.append(self.optimizer_D)
+
+ def set_input_train(self, input):
+ self.outer = input['data_outer'].to(self.device)
+ self.outer = torch.nn.functional.interpolate(self.outer,(1024,1024),mode='bilinear',align_corners=False)
+
+ self.inner = input['data_inner'].to(self.device)
+ self.inner = torch.nn.functional.interpolate(self.inner,(1024,1024),mode='bilinear',align_corners=False)
+
+ self.image_paths = input['image_path']
+
+ if self.isTrain:
+ self.gtfake = input['data_gtfake'].to(self.device)
+ self.gtfake = torch.nn.functional.interpolate(self.gtfake, (1024, 1024), mode='bilinear', align_corners=False)
+ self.real_B = self.gtfake
+
+ self.real_A = torch.cat((self.outer, self.inner), 1)
+
+ def set_input(self, outer, inner):
+ inner = torch.from_numpy(inner).unsqueeze(0).unsqueeze(0)
+ outer = torch.from_numpy(outer).unsqueeze(0).unsqueeze(0)
+
+ inner = (inner - torch.min(inner))/(torch.max(inner)-torch.min(inner))
+ outer = (outer - torch.min(outer))/(torch.max(outer)-torch.min(outer))
+
+ inner = self.normalize(inner)
+ outer = self.normalize(outer)
+
+ self.real_A = torch.cat((outer, inner), 1).to(self.device)
+
+
+ def normalize(self, input):
+ input = input * 2
+ input = input - 1
+ return input
+
+ def forward(self):
+ """Run forward pass; called by both functions and ."""
+ self.fake_B = self.netG(self.real_A) # G(A)
+
+ def backward_D(self):
+ """Calculate GAN loss for the discriminator"""
+ # Fake; stop backprop to the generator by detaching fake_B
+ fake_AB = torch.cat((self.real_A, self.fake_B), 1) # we use conditional GANs; we need to feed both input and output to the discriminator
+ pred_fake = self.netD(fake_AB.detach())
+ self.loss_D_fake = self.criterionGAN(pred_fake, False)
+ # Real
+ real_AB = torch.cat((self.real_A, self.real_B), 1)
+ pred_real = self.netD(real_AB)
+ self.loss_D_real = self.criterionGAN(pred_real, True)
+ # combine loss and calculate gradients
+ self.loss_D = (self.loss_D_fake + self.loss_D_real) * 0.5
+ self.loss_D.backward()
+
+ def backward_G(self):
+ """Calculate GAN and L1 loss for the generator"""
+ # First, G(A) should fake the discriminator
+ fake_AB = torch.cat((self.real_A, self.fake_B), 1)
+ pred_fake = self.netD(fake_AB)
+ self.loss_G_GAN = self.criterionGAN(pred_fake, True)
+ # Second, G(A) = B
+ self.loss_G_L1 = self.criterionL1(self.fake_B, self.real_B) * self.opt.lambda_L1
+ # combine loss and calculate gradients
+ self.loss_G = self.loss_G_L1 + self.loss_G_GAN
+ self.loss_G.backward()
+
+ def optimize_parameters(self):
+ self.forward() # compute fake images: G(A)
+ # update D
+ self.set_requires_grad(self.netD, True) # enable backprop for D
+ self.optimizer_D.zero_grad() # set D's gradients to zero
+ self.backward_D() # calculate gradients for D
+ self.optimizer_D.step() # update D's weights
+ # update G
+ self.set_requires_grad(self.netD, False) # D requires no gradients when optimizing G
+ self.optimizer_G.zero_grad() # set G's gradients to zero
+ self.backward_G() # calculate graidents for G
+ self.optimizer_G.step() # udpate G's weights
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/options/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/options/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e7eedebe54aa70169fd25951b3034d819e396c90
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/options/__init__.py
@@ -0,0 +1 @@
+"""This package options includes option modules: training options, test options, and basic options (used in both training and test)."""
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/options/base_options.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/options/base_options.py
new file mode 100644
index 0000000000000000000000000000000000000000..533a1e88a7e8494223f6994e6861c93667754f83
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/options/base_options.py
@@ -0,0 +1,156 @@
+import argparse
+import os
+from ...pix2pix.util import util
+# import torch
+from ...pix2pix import models
+# import pix2pix.data
+import numpy as np
+
+class BaseOptions():
+ """This class defines options used during both training and test time.
+
+ It also implements several helper functions such as parsing, printing, and saving the options.
+ It also gathers additional options defined in functions in both dataset class and model class.
+ """
+
+ def __init__(self):
+ """Reset the class; indicates the class hasn't been initailized"""
+ self.initialized = False
+
+ def initialize(self, parser):
+ """Define the common options that are used in both training and test."""
+ # basic parameters
+ parser.add_argument('--dataroot', help='path to images (should have subfolders trainA, trainB, valA, valB, etc)')
+ parser.add_argument('--name', type=str, default='void', help='mahdi_unet_new, scaled_unet')
+ parser.add_argument('--gpu_ids', type=str, default='0', help='gpu ids: e.g. 0 0,1,2, 0,2. use -1 for CPU')
+ parser.add_argument('--checkpoints_dir', type=str, default='./pix2pix/checkpoints', help='models are saved here')
+ # model parameters
+ parser.add_argument('--model', type=str, default='cycle_gan', help='chooses which model to use. [cycle_gan | pix2pix | test | colorization]')
+ parser.add_argument('--input_nc', type=int, default=2, help='# of input image channels: 3 for RGB and 1 for grayscale')
+ parser.add_argument('--output_nc', type=int, default=1, help='# of output image channels: 3 for RGB and 1 for grayscale')
+ parser.add_argument('--ngf', type=int, default=64, help='# of gen filters in the last conv layer')
+ parser.add_argument('--ndf', type=int, default=64, help='# of discrim filters in the first conv layer')
+ parser.add_argument('--netD', type=str, default='basic', help='specify discriminator architecture [basic | n_layers | pixel]. The basic model is a 70x70 PatchGAN. n_layers allows you to specify the layers in the discriminator')
+ parser.add_argument('--netG', type=str, default='resnet_9blocks', help='specify generator architecture [resnet_9blocks | resnet_6blocks | unet_256 | unet_128]')
+ parser.add_argument('--n_layers_D', type=int, default=3, help='only used if netD==n_layers')
+ parser.add_argument('--norm', type=str, default='instance', help='instance normalization or batch normalization [instance | batch | none]')
+ parser.add_argument('--init_type', type=str, default='normal', help='network initialization [normal | xavier | kaiming | orthogonal]')
+ parser.add_argument('--init_gain', type=float, default=0.02, help='scaling factor for normal, xavier and orthogonal.')
+ parser.add_argument('--no_dropout', action='store_true', help='no dropout for the generator')
+ # dataset parameters
+ parser.add_argument('--dataset_mode', type=str, default='unaligned', help='chooses how datasets are loaded. [unaligned | aligned | single | colorization]')
+ parser.add_argument('--direction', type=str, default='AtoB', help='AtoB or BtoA')
+ parser.add_argument('--serial_batches', action='store_true', help='if true, takes images in order to make batches, otherwise takes them randomly')
+ parser.add_argument('--num_threads', default=4, type=int, help='# threads for loading data')
+ parser.add_argument('--batch_size', type=int, default=1, help='input batch size')
+ parser.add_argument('--load_size', type=int, default=672, help='scale images to this size')
+ parser.add_argument('--crop_size', type=int, default=672, help='then crop to this size')
+ parser.add_argument('--max_dataset_size', type=int, default=10000, help='Maximum number of samples allowed per dataset. If the dataset directory contains more than max_dataset_size, only a subset is loaded.')
+ parser.add_argument('--preprocess', type=str, default='resize_and_crop', help='scaling and cropping of images at load time [resize_and_crop | crop | scale_width | scale_width_and_crop | none]')
+ parser.add_argument('--no_flip', action='store_true', help='if specified, do not flip the images for data augmentation')
+ parser.add_argument('--display_winsize', type=int, default=256, help='display window size for both visdom and HTML')
+ # additional parameters
+ parser.add_argument('--epoch', type=str, default='latest', help='which epoch to load? set to latest to use latest cached model')
+ parser.add_argument('--load_iter', type=int, default='0', help='which iteration to load? if load_iter > 0, the code will load models by iter_[load_iter]; otherwise, the code will load models by [epoch]')
+ parser.add_argument('--verbose', action='store_true', help='if specified, print more debugging information')
+ parser.add_argument('--suffix', default='', type=str, help='customized suffix: opt.name = opt.name + suffix: e.g., {model}_{netG}_size{load_size}')
+
+ parser.add_argument('--data_dir', type=str, required=False,
+ help='input files directory images can be .png .jpg .tiff')
+ parser.add_argument('--output_dir', type=str, required=False,
+ help='result dir. result depth will be png. vides are JMPG as avi')
+ parser.add_argument('--savecrops', type=int, required=False)
+ parser.add_argument('--savewholeest', type=int, required=False)
+ parser.add_argument('--output_resolution', type=int, required=False,
+ help='0 for no restriction 1 for resize to input size')
+ parser.add_argument('--net_receptive_field_size', type=int, required=False)
+ parser.add_argument('--pix2pixsize', type=int, required=False)
+ parser.add_argument('--generatevideo', type=int, required=False)
+ parser.add_argument('--depthNet', type=int, required=False, help='0: midas 1:strurturedRL')
+ parser.add_argument('--R0', action='store_true')
+ parser.add_argument('--R20', action='store_true')
+ parser.add_argument('--Final', action='store_true')
+ parser.add_argument('--colorize_results', action='store_true')
+ parser.add_argument('--max_res', type=float, default=np.inf)
+
+ self.initialized = True
+ return parser
+
+ def gather_options(self):
+ """Initialize our parser with basic options(only once).
+ Add additional model-specific and dataset-specific options.
+ These options are defined in the function
+ in model and dataset classes.
+ """
+ if not self.initialized: # check if it has been initialized
+ parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+ parser = self.initialize(parser)
+
+ # get the basic options
+ opt, _ = parser.parse_known_args()
+
+ # modify model-related parser options
+ model_name = opt.model
+ model_option_setter = models.get_option_setter(model_name)
+ parser = model_option_setter(parser, self.isTrain)
+ opt, _ = parser.parse_known_args() # parse again with new defaults
+
+ # modify dataset-related parser options
+ # dataset_name = opt.dataset_mode
+ # dataset_option_setter = pix2pix.data.get_option_setter(dataset_name)
+ # parser = dataset_option_setter(parser, self.isTrain)
+
+ # save and return the parser
+ self.parser = parser
+ #return parser.parse_args() #EVIL
+ return opt
+
+ def print_options(self, opt):
+ """Print and save options
+
+ It will print both current options and default values(if different).
+ It will save options into a text file / [checkpoints_dir] / opt.txt
+ """
+ message = ''
+ message += '----------------- Options ---------------\n'
+ for k, v in sorted(vars(opt).items()):
+ comment = ''
+ default = self.parser.get_default(k)
+ if v != default:
+ comment = '\t[default: %s]' % str(default)
+ message += '{:>25}: {:<30}{}\n'.format(str(k), str(v), comment)
+ message += '----------------- End -------------------'
+ print(message)
+
+ # save to the disk
+ expr_dir = os.path.join(opt.checkpoints_dir, opt.name)
+ util.mkdirs(expr_dir)
+ file_name = os.path.join(expr_dir, '{}_opt.txt'.format(opt.phase))
+ with open(file_name, 'wt') as opt_file:
+ opt_file.write(message)
+ opt_file.write('\n')
+
+ def parse(self):
+ """Parse our options, create checkpoints directory suffix, and set up gpu device."""
+ opt = self.gather_options()
+ opt.isTrain = self.isTrain # train or test
+
+ # process opt.suffix
+ if opt.suffix:
+ suffix = ('_' + opt.suffix.format(**vars(opt))) if opt.suffix != '' else ''
+ opt.name = opt.name + suffix
+
+ #self.print_options(opt)
+
+ # set gpu ids
+ str_ids = opt.gpu_ids.split(',')
+ opt.gpu_ids = []
+ for str_id in str_ids:
+ id = int(str_id)
+ if id >= 0:
+ opt.gpu_ids.append(id)
+ #if len(opt.gpu_ids) > 0:
+ # torch.cuda.set_device(opt.gpu_ids[0])
+
+ self.opt = opt
+ return self.opt
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/options/test_options.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/options/test_options.py
new file mode 100644
index 0000000000000000000000000000000000000000..a3424b5e3b66d6813f74c8cecad691d7488d121c
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/options/test_options.py
@@ -0,0 +1,22 @@
+from .base_options import BaseOptions
+
+
+class TestOptions(BaseOptions):
+ """This class includes test options.
+
+ It also includes shared options defined in BaseOptions.
+ """
+
+ def initialize(self, parser):
+ parser = BaseOptions.initialize(self, parser) # define shared options
+ parser.add_argument('--aspect_ratio', type=float, default=1.0, help='aspect ratio of result images')
+ parser.add_argument('--phase', type=str, default='test', help='train, val, test, etc')
+ # Dropout and Batchnorm has different behavioir during training and test.
+ parser.add_argument('--eval', action='store_true', help='use eval mode during test time.')
+ parser.add_argument('--num_test', type=int, default=50, help='how many test images to run')
+ # rewrite devalue values
+ parser.set_defaults(model='pix2pix4depth')
+ # To avoid cropping, the load_size should be the same as crop_size
+ parser.set_defaults(load_size=parser.get_default('crop_size'))
+ self.isTrain = False
+ return parser
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/util/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/util/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..ae36f63d8859ec0c60dcbfe67c4ac324e751ddf7
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/util/__init__.py
@@ -0,0 +1 @@
+"""This package includes a miscellaneous collection of useful helper functions."""
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/util/util.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/util/util.py
new file mode 100644
index 0000000000000000000000000000000000000000..8a7aceaa00681cb76675df7866bf8db58c8d2caf
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/leres/pix2pix/util/util.py
@@ -0,0 +1,105 @@
+"""This module contains simple helper functions """
+from __future__ import print_function
+import torch
+import numpy as np
+from PIL import Image
+import os
+
+
+def tensor2im(input_image, imtype=np.uint16):
+ """"Converts a Tensor array into a numpy image array.
+
+ Parameters:
+ input_image (tensor) -- the input image tensor array
+ imtype (type) -- the desired type of the converted numpy array
+ """
+ if not isinstance(input_image, np.ndarray):
+ if isinstance(input_image, torch.Tensor): # get the data from a variable
+ image_tensor = input_image.data
+ else:
+ return input_image
+ image_numpy = torch.squeeze(image_tensor).cpu().numpy() # convert it into a numpy array
+ image_numpy = (image_numpy + 1) / 2.0 * (2**16-1) #
+ else: # if it is a numpy array, do nothing
+ image_numpy = input_image
+ return image_numpy.astype(imtype)
+
+
+def diagnose_network(net, name='network'):
+ """Calculate and print the mean of average absolute(gradients)
+
+ Parameters:
+ net (torch network) -- Torch network
+ name (str) -- the name of the network
+ """
+ mean = 0.0
+ count = 0
+ for param in net.parameters():
+ if param.grad is not None:
+ mean += torch.mean(torch.abs(param.grad.data))
+ count += 1
+ if count > 0:
+ mean = mean / count
+ print(name)
+ print(mean)
+
+
+def save_image(image_numpy, image_path, aspect_ratio=1.0):
+ """Save a numpy image to the disk
+
+ Parameters:
+ image_numpy (numpy array) -- input numpy array
+ image_path (str) -- the path of the image
+ """
+ image_pil = Image.fromarray(image_numpy)
+
+ image_pil = image_pil.convert('I;16')
+
+ # image_pil = Image.fromarray(image_numpy)
+ # h, w, _ = image_numpy.shape
+ #
+ # if aspect_ratio > 1.0:
+ # image_pil = image_pil.resize((h, int(w * aspect_ratio)), Image.BICUBIC)
+ # if aspect_ratio < 1.0:
+ # image_pil = image_pil.resize((int(h / aspect_ratio), w), Image.BICUBIC)
+
+ image_pil.save(image_path)
+
+
+def print_numpy(x, val=True, shp=False):
+ """Print the mean, min, max, median, std, and size of a numpy array
+
+ Parameters:
+ val (bool) -- if print the values of the numpy array
+ shp (bool) -- if print the shape of the numpy array
+ """
+ x = x.astype(np.float64)
+ if shp:
+ print('shape,', x.shape)
+ if val:
+ x = x.flatten()
+ print('mean = %3.3f, min = %3.3f, max = %3.3f, median = %3.3f, std=%3.3f' % (
+ np.mean(x), np.min(x), np.max(x), np.median(x), np.std(x)))
+
+
+def mkdirs(paths):
+ """create empty directories if they don't exist
+
+ Parameters:
+ paths (str list) -- a list of directory paths
+ """
+ if isinstance(paths, list) and not isinstance(paths, str):
+ for path in paths:
+ mkdir(path)
+ else:
+ mkdir(paths)
+
+
+def mkdir(path):
+ """create a single empty directory if it didn't exist
+
+ Parameters:
+ path (str) -- a single directory path
+ """
+ if not os.path.exists(path):
+ os.makedirs(path)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/lineart/LICENSE b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/lineart/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..16a9d56a3d4c15e4f34ac5426459c58487b01520
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/lineart/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Caroline Chan
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/lineart/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/lineart/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..863c8372f7a3cccf189fcf468e24a25b2c6024e1
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/lineart/__init__.py
@@ -0,0 +1,142 @@
+import os
+import warnings
+
+import cv2
+import numpy as np
+import torch
+import torch.nn as nn
+from einops import rearrange
+from PIL import Image
+
+from controlnet_aux.util import HWC3, resize_image_with_pad, common_input_validate, annotator_ckpts_path, custom_hf_download
+
+norm_layer = nn.InstanceNorm2d
+
+
+class ResidualBlock(nn.Module):
+ def __init__(self, in_features):
+ super(ResidualBlock, self).__init__()
+
+ conv_block = [ nn.ReflectionPad2d(1),
+ nn.Conv2d(in_features, in_features, 3),
+ norm_layer(in_features),
+ nn.ReLU(inplace=True),
+ nn.ReflectionPad2d(1),
+ nn.Conv2d(in_features, in_features, 3),
+ norm_layer(in_features)
+ ]
+
+ self.conv_block = nn.Sequential(*conv_block)
+
+ def forward(self, x):
+ return x + self.conv_block(x)
+
+
+class Generator(nn.Module):
+ def __init__(self, input_nc, output_nc, n_residual_blocks=9, sigmoid=True):
+ super(Generator, self).__init__()
+
+ # Initial convolution block
+ model0 = [ nn.ReflectionPad2d(3),
+ nn.Conv2d(input_nc, 64, 7),
+ norm_layer(64),
+ nn.ReLU(inplace=True) ]
+ self.model0 = nn.Sequential(*model0)
+
+ # Downsampling
+ model1 = []
+ in_features = 64
+ out_features = in_features*2
+ for _ in range(2):
+ model1 += [ nn.Conv2d(in_features, out_features, 3, stride=2, padding=1),
+ norm_layer(out_features),
+ nn.ReLU(inplace=True) ]
+ in_features = out_features
+ out_features = in_features*2
+ self.model1 = nn.Sequential(*model1)
+
+ model2 = []
+ # Residual blocks
+ for _ in range(n_residual_blocks):
+ model2 += [ResidualBlock(in_features)]
+ self.model2 = nn.Sequential(*model2)
+
+ # Upsampling
+ model3 = []
+ out_features = in_features//2
+ for _ in range(2):
+ model3 += [ nn.ConvTranspose2d(in_features, out_features, 3, stride=2, padding=1, output_padding=1),
+ norm_layer(out_features),
+ nn.ReLU(inplace=True) ]
+ in_features = out_features
+ out_features = in_features//2
+ self.model3 = nn.Sequential(*model3)
+
+ # Output layer
+ model4 = [ nn.ReflectionPad2d(3),
+ nn.Conv2d(64, output_nc, 7)]
+ if sigmoid:
+ model4 += [nn.Sigmoid()]
+
+ self.model4 = nn.Sequential(*model4)
+
+ def forward(self, x, cond=None):
+ out = self.model0(x)
+ out = self.model1(out)
+ out = self.model2(out)
+ out = self.model3(out)
+ out = self.model4(out)
+
+ return out
+
+
+class LineartDetector:
+ def __init__(self, model, coarse_model):
+ self.model = model
+ self.model_coarse = coarse_model
+
+ @classmethod
+ def from_pretrained(cls, pretrained_model_or_path, filename=None, coarse_filename=None, cache_dir=annotator_ckpts_path):
+ filename = filename or "sk_model.pth"
+ coarse_filename = coarse_filename or "sk_model2.pth"
+ model_path = custom_hf_download(pretrained_model_or_path, filename, cache_dir=cache_dir)
+ coarse_model_path = custom_hf_download(pretrained_model_or_path, coarse_filename, cache_dir=cache_dir)
+
+ model = Generator(3, 1, 3)
+ model.load_state_dict(torch.load(model_path, map_location=torch.device('cpu')))
+ model.eval()
+
+ coarse_model = Generator(3, 1, 3)
+ coarse_model.load_state_dict(torch.load(coarse_model_path, map_location=torch.device('cpu')))
+ coarse_model.eval()
+
+ return cls(model, coarse_model)
+
+ def to(self, device):
+ self.model.to(device)
+ self.model_coarse.to(device)
+ return self
+
+ def __call__(self, input_image, coarse=False, detect_resolution=512, output_type="pil", upscale_method="INTER_CUBIC", **kwargs):
+ input_image, output_type = common_input_validate(input_image, output_type, **kwargs)
+ detected_map, remove_pad = resize_image_with_pad(input_image, detect_resolution, upscale_method)
+
+ device = next(iter(self.model.parameters())).device
+ model = self.model_coarse if coarse else self.model
+ assert detected_map.ndim == 3
+ with torch.no_grad():
+ image = torch.from_numpy(detected_map).float().to(device)
+ image = image / 255.0
+ image = rearrange(image, 'h w c -> 1 c h w')
+ line = model(image)[0][0]
+
+ line = line.cpu().numpy()
+ line = (line * 255.0).clip(0, 255).astype(np.uint8)
+
+ detected_map = HWC3(line)
+ detected_map = remove_pad(255 - detected_map)
+
+ if output_type == "pil":
+ detected_map = Image.fromarray(detected_map)
+
+ return detected_map
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/lineart_anime/LICENSE b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/lineart_anime/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..16a9d56a3d4c15e4f34ac5426459c58487b01520
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/lineart_anime/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Caroline Chan
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/lineart_anime/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/lineart_anime/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d6df7bfadb28e49d97445fd6a24d40e66dc501ba
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/lineart_anime/__init__.py
@@ -0,0 +1,167 @@
+import functools
+import os
+import warnings
+
+import cv2
+import numpy as np
+import torch
+import torch.nn as nn
+from einops import rearrange
+from huggingface_hub import hf_hub_download
+from PIL import Image
+
+from controlnet_aux.util import HWC3, resize_image_with_pad, common_input_validate, annotator_ckpts_path, custom_hf_download
+
+
+class UnetGenerator(nn.Module):
+ """Create a Unet-based generator"""
+
+ def __init__(self, input_nc, output_nc, num_downs, ngf=64, norm_layer=nn.BatchNorm2d, use_dropout=False):
+ """Construct a Unet generator
+ Parameters:
+ input_nc (int) -- the number of channels in input images
+ output_nc (int) -- the number of channels in output images
+ num_downs (int) -- the number of downsamplings in UNet. For example, # if |num_downs| == 7,
+ image of size 128x128 will become of size 1x1 # at the bottleneck
+ ngf (int) -- the number of filters in the last conv layer
+ norm_layer -- normalization layer
+ We construct the U-Net from the innermost layer to the outermost layer.
+ It is a recursive process.
+ """
+ super(UnetGenerator, self).__init__()
+ # construct unet structure
+ unet_block = UnetSkipConnectionBlock(ngf * 8, ngf * 8, input_nc=None, submodule=None, norm_layer=norm_layer, innermost=True) # add the innermost layer
+ for _ in range(num_downs - 5): # add intermediate layers with ngf * 8 filters
+ unet_block = UnetSkipConnectionBlock(ngf * 8, ngf * 8, input_nc=None, submodule=unet_block, norm_layer=norm_layer, use_dropout=use_dropout)
+ # gradually reduce the number of filters from ngf * 8 to ngf
+ unet_block = UnetSkipConnectionBlock(ngf * 4, ngf * 8, input_nc=None, submodule=unet_block, norm_layer=norm_layer)
+ unet_block = UnetSkipConnectionBlock(ngf * 2, ngf * 4, input_nc=None, submodule=unet_block, norm_layer=norm_layer)
+ unet_block = UnetSkipConnectionBlock(ngf, ngf * 2, input_nc=None, submodule=unet_block, norm_layer=norm_layer)
+ self.model = UnetSkipConnectionBlock(output_nc, ngf, input_nc=input_nc, submodule=unet_block, outermost=True, norm_layer=norm_layer) # add the outermost layer
+
+ def forward(self, input):
+ """Standard forward"""
+ return self.model(input)
+
+
+class UnetSkipConnectionBlock(nn.Module):
+ """Defines the Unet submodule with skip connection.
+ X -------------------identity----------------------
+ |-- downsampling -- |submodule| -- upsampling --|
+ """
+
+ def __init__(self, outer_nc, inner_nc, input_nc=None,
+ submodule=None, outermost=False, innermost=False, norm_layer=nn.BatchNorm2d, use_dropout=False):
+ """Construct a Unet submodule with skip connections.
+ Parameters:
+ outer_nc (int) -- the number of filters in the outer conv layer
+ inner_nc (int) -- the number of filters in the inner conv layer
+ input_nc (int) -- the number of channels in input images/features
+ submodule (UnetSkipConnectionBlock) -- previously defined submodules
+ outermost (bool) -- if this module is the outermost module
+ innermost (bool) -- if this module is the innermost module
+ norm_layer -- normalization layer
+ use_dropout (bool) -- if use dropout layers.
+ """
+ super(UnetSkipConnectionBlock, self).__init__()
+ self.outermost = outermost
+ if type(norm_layer) == functools.partial:
+ use_bias = norm_layer.func == nn.InstanceNorm2d
+ else:
+ use_bias = norm_layer == nn.InstanceNorm2d
+ if input_nc is None:
+ input_nc = outer_nc
+ downconv = nn.Conv2d(input_nc, inner_nc, kernel_size=4,
+ stride=2, padding=1, bias=use_bias)
+ downrelu = nn.LeakyReLU(0.2, True)
+ downnorm = norm_layer(inner_nc)
+ uprelu = nn.ReLU(True)
+ upnorm = norm_layer(outer_nc)
+
+ if outermost:
+ upconv = nn.ConvTranspose2d(inner_nc * 2, outer_nc,
+ kernel_size=4, stride=2,
+ padding=1)
+ down = [downconv]
+ up = [uprelu, upconv, nn.Tanh()]
+ model = down + [submodule] + up
+ elif innermost:
+ upconv = nn.ConvTranspose2d(inner_nc, outer_nc,
+ kernel_size=4, stride=2,
+ padding=1, bias=use_bias)
+ down = [downrelu, downconv]
+ up = [uprelu, upconv, upnorm]
+ model = down + up
+ else:
+ upconv = nn.ConvTranspose2d(inner_nc * 2, outer_nc,
+ kernel_size=4, stride=2,
+ padding=1, bias=use_bias)
+ down = [downrelu, downconv, downnorm]
+ up = [uprelu, upconv, upnorm]
+
+ if use_dropout:
+ model = down + [submodule] + up + [nn.Dropout(0.5)]
+ else:
+ model = down + [submodule] + up
+
+ self.model = nn.Sequential(*model)
+
+ def forward(self, x):
+ if self.outermost:
+ return self.model(x)
+ else: # add skip connections
+ return torch.cat([x, self.model(x)], 1)
+
+
+class LineartAnimeDetector:
+ def __init__(self, model):
+ self.model = model
+
+ @classmethod
+ def from_pretrained(cls, pretrained_model_or_path, filename=None, cache_dir=annotator_ckpts_path):
+ filename = filename or "netG.pth"
+ model_path = custom_hf_download(pretrained_model_or_path, filename, cache_dir=cache_dir)
+
+ norm_layer = functools.partial(nn.InstanceNorm2d, affine=False, track_running_stats=False)
+ net = UnetGenerator(3, 1, 8, 64, norm_layer=norm_layer, use_dropout=False)
+ ckpt = torch.load(model_path)
+ for key in list(ckpt.keys()):
+ if 'module.' in key:
+ ckpt[key.replace('module.', '')] = ckpt[key]
+ del ckpt[key]
+ net.load_state_dict(ckpt)
+ net.eval()
+
+ return cls(net)
+
+ def to(self, device):
+ self.model.to(device)
+ return self
+
+ def __call__(self, input_image, detect_resolution=512, output_type="pil", upscale_method="INTER_CUBIC", **kwargs):
+ input_image, output_type = common_input_validate(input_image, output_type, **kwargs)
+ input_image, remove_pad = resize_image_with_pad(input_image, detect_resolution, upscale_method)
+
+ H, W, C = input_image.shape
+ Hn = 256 * int(np.ceil(float(H) / 256.0))
+ Wn = 256 * int(np.ceil(float(W) / 256.0))
+ input_image = cv2.resize(input_image, (Wn, Hn), interpolation=cv2.INTER_CUBIC)
+
+ device = next(iter(self.model.parameters())).device
+ with torch.no_grad():
+ image_feed = torch.from_numpy(input_image).float().to(device)
+ image_feed = image_feed / 127.5 - 1.0
+ image_feed = rearrange(image_feed, 'h w c -> 1 c h w')
+
+ line = self.model(image_feed)[0, 0] * 127.5 + 127.5
+ line = line.cpu().numpy()
+ line = line.clip(0, 255).astype(np.uint8)
+
+ #A1111 uses INTER AREA for downscaling so ig that is the best choice
+ detected_map = cv2.resize(HWC3(line), (W, H), interpolation=cv2.INTER_AREA)
+ detected_map = remove_pad(255 - detected_map)
+
+ if output_type == "pil":
+ detected_map = Image.fromarray(detected_map)
+
+ return detected_map
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/manga_line/LICENSE b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/manga_line/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..bdca75a54d05781782e3d939401e93161cdd88f7
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/manga_line/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 Miaomiao Li
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/manga_line/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/manga_line/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..51331e2c32bf8d84322cc53e06e48bd81fa9d08c
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/manga_line/__init__.py
@@ -0,0 +1,63 @@
+# MangaLineExtraction_PyTorch
+# https://github.com/ljsabc/MangaLineExtraction_PyTorch
+
+#NOTE: This preprocessor is designed to work with lineart_anime ControlNet so the result will be white lines on black canvas
+
+import torch
+import numpy as np
+import os
+import cv2
+from einops import rearrange
+from .model_torch import res_skip
+from PIL import Image
+import warnings
+
+from controlnet_aux.util import HWC3, resize_image_with_pad, common_input_validate, annotator_ckpts_path, custom_hf_download
+
+class LineartMangaDetector:
+ def __init__(self, model):
+ self.model = model
+
+ @classmethod
+ def from_pretrained(cls, pretrained_model_or_path=None, filename=None, cache_dir=annotator_ckpts_path):
+ filename = filename or "erika.pth"
+ model_path = custom_hf_download(pretrained_model_or_path, filename, cache_dir=cache_dir)
+
+ net = res_skip()
+ ckpt = torch.load(model_path)
+ for key in list(ckpt.keys()):
+ if 'module.' in key:
+ ckpt[key.replace('module.', '')] = ckpt[key]
+ del ckpt[key]
+ net.load_state_dict(ckpt)
+ net.eval()
+ return cls(net)
+
+ def to(self, device):
+ self.model.to(device)
+ return self
+
+ def __call__(self, input_image, detect_resolution=512, output_type="pil", upscale_method="INTER_CUBIC", **kwargs):
+ input_image, output_type = common_input_validate(input_image, output_type, **kwargs)
+ detected_map, remove_pad = resize_image_with_pad(input_image, 256 * int(np.ceil(float(detect_resolution) / 256.0)), upscale_method)
+ device = next(iter(self.model.parameters())).device
+
+ img = cv2.cvtColor(detected_map, cv2.COLOR_RGB2GRAY)
+ with torch.no_grad():
+ image_feed = torch.from_numpy(img).float().to(device)
+ image_feed = rearrange(image_feed, 'h w -> 1 1 h w')
+
+ line = self.model(image_feed)
+ line = line.cpu().numpy()[0,0,:,:]
+ line[line > 255] = 255
+ line[line < 0] = 0
+
+ line = line.astype(np.uint8)
+
+ detected_map = HWC3(line)
+ detected_map = remove_pad(255 - detected_map)
+
+ if output_type == "pil":
+ detected_map = Image.fromarray(detected_map)
+
+ return detected_map
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/manga_line/model_torch.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/manga_line/model_torch.py
new file mode 100644
index 0000000000000000000000000000000000000000..de5828ccc486d74490b8da710d644651067bd5f3
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/manga_line/model_torch.py
@@ -0,0 +1,196 @@
+import torch.nn as nn
+import numpy as np
+
+#torch.set_printoptions(precision=10)
+
+
+class _bn_relu_conv(nn.Module):
+ def __init__(self, in_filters, nb_filters, fw, fh, subsample=1):
+ super(_bn_relu_conv, self).__init__()
+ self.model = nn.Sequential(
+ nn.BatchNorm2d(in_filters, eps=1e-3),
+ nn.LeakyReLU(0.2),
+ nn.Conv2d(in_filters, nb_filters, (fw, fh), stride=subsample, padding=(fw//2, fh//2), padding_mode='zeros')
+ )
+
+ def forward(self, x):
+ return self.model(x)
+
+ # the following are for debugs
+ print("****", np.max(x.cpu().numpy()), np.min(x.cpu().numpy()), np.mean(x.cpu().numpy()), np.std(x.cpu().numpy()), x.shape)
+ for i,layer in enumerate(self.model):
+ if i != 2:
+ x = layer(x)
+ else:
+ x = layer(x)
+ #x = nn.functional.pad(x, (1, 1, 1, 1), mode='constant', value=0)
+ print("____", np.max(x.cpu().numpy()), np.min(x.cpu().numpy()), np.mean(x.cpu().numpy()), np.std(x.cpu().numpy()), x.shape)
+ print(x[0])
+ return x
+
+
+class _u_bn_relu_conv(nn.Module):
+ def __init__(self, in_filters, nb_filters, fw, fh, subsample=1):
+ super(_u_bn_relu_conv, self).__init__()
+ self.model = nn.Sequential(
+ nn.BatchNorm2d(in_filters, eps=1e-3),
+ nn.LeakyReLU(0.2),
+ nn.Conv2d(in_filters, nb_filters, (fw, fh), stride=subsample, padding=(fw//2, fh//2)),
+ nn.Upsample(scale_factor=2, mode='nearest')
+ )
+
+ def forward(self, x):
+ return self.model(x)
+
+
+
+class _shortcut(nn.Module):
+ def __init__(self, in_filters, nb_filters, subsample=1):
+ super(_shortcut, self).__init__()
+ self.process = False
+ self.model = None
+ if in_filters != nb_filters or subsample != 1:
+ self.process = True
+ self.model = nn.Sequential(
+ nn.Conv2d(in_filters, nb_filters, (1, 1), stride=subsample)
+ )
+
+ def forward(self, x, y):
+ #print(x.size(), y.size(), self.process)
+ if self.process:
+ y0 = self.model(x)
+ #print("merge+", torch.max(y0+y), torch.min(y0+y),torch.mean(y0+y), torch.std(y0+y), y0.shape)
+ return y0 + y
+ else:
+ #print("merge", torch.max(x+y), torch.min(x+y),torch.mean(x+y), torch.std(x+y), y.shape)
+ return x + y
+
+class _u_shortcut(nn.Module):
+ def __init__(self, in_filters, nb_filters, subsample):
+ super(_u_shortcut, self).__init__()
+ self.process = False
+ self.model = None
+ if in_filters != nb_filters:
+ self.process = True
+ self.model = nn.Sequential(
+ nn.Conv2d(in_filters, nb_filters, (1, 1), stride=subsample, padding_mode='zeros'),
+ nn.Upsample(scale_factor=2, mode='nearest')
+ )
+
+ def forward(self, x, y):
+ if self.process:
+ return self.model(x) + y
+ else:
+ return x + y
+
+
+class basic_block(nn.Module):
+ def __init__(self, in_filters, nb_filters, init_subsample=1):
+ super(basic_block, self).__init__()
+ self.conv1 = _bn_relu_conv(in_filters, nb_filters, 3, 3, subsample=init_subsample)
+ self.residual = _bn_relu_conv(nb_filters, nb_filters, 3, 3)
+ self.shortcut = _shortcut(in_filters, nb_filters, subsample=init_subsample)
+
+ def forward(self, x):
+ x1 = self.conv1(x)
+ x2 = self.residual(x1)
+ return self.shortcut(x, x2)
+
+class _u_basic_block(nn.Module):
+ def __init__(self, in_filters, nb_filters, init_subsample=1):
+ super(_u_basic_block, self).__init__()
+ self.conv1 = _u_bn_relu_conv(in_filters, nb_filters, 3, 3, subsample=init_subsample)
+ self.residual = _bn_relu_conv(nb_filters, nb_filters, 3, 3)
+ self.shortcut = _u_shortcut(in_filters, nb_filters, subsample=init_subsample)
+
+ def forward(self, x):
+ y = self.residual(self.conv1(x))
+ return self.shortcut(x, y)
+
+
+class _residual_block(nn.Module):
+ def __init__(self, in_filters, nb_filters, repetitions, is_first_layer=False):
+ super(_residual_block, self).__init__()
+ layers = []
+ for i in range(repetitions):
+ init_subsample = 1
+ if i == repetitions - 1 and not is_first_layer:
+ init_subsample = 2
+ if i == 0:
+ l = basic_block(in_filters=in_filters, nb_filters=nb_filters, init_subsample=init_subsample)
+ else:
+ l = basic_block(in_filters=nb_filters, nb_filters=nb_filters, init_subsample=init_subsample)
+ layers.append(l)
+
+ self.model = nn.Sequential(*layers)
+
+ def forward(self, x):
+ return self.model(x)
+
+
+class _upsampling_residual_block(nn.Module):
+ def __init__(self, in_filters, nb_filters, repetitions):
+ super(_upsampling_residual_block, self).__init__()
+ layers = []
+ for i in range(repetitions):
+ l = None
+ if i == 0:
+ l = _u_basic_block(in_filters=in_filters, nb_filters=nb_filters)#(input)
+ else:
+ l = basic_block(in_filters=nb_filters, nb_filters=nb_filters)#(input)
+ layers.append(l)
+
+ self.model = nn.Sequential(*layers)
+
+ def forward(self, x):
+ return self.model(x)
+
+
+class res_skip(nn.Module):
+
+ def __init__(self):
+ super(res_skip, self).__init__()
+ self.block0 = _residual_block(in_filters=1, nb_filters=24, repetitions=2, is_first_layer=True)#(input)
+ self.block1 = _residual_block(in_filters=24, nb_filters=48, repetitions=3)#(block0)
+ self.block2 = _residual_block(in_filters=48, nb_filters=96, repetitions=5)#(block1)
+ self.block3 = _residual_block(in_filters=96, nb_filters=192, repetitions=7)#(block2)
+ self.block4 = _residual_block(in_filters=192, nb_filters=384, repetitions=12)#(block3)
+
+ self.block5 = _upsampling_residual_block(in_filters=384, nb_filters=192, repetitions=7)#(block4)
+ self.res1 = _shortcut(in_filters=192, nb_filters=192)#(block3, block5, subsample=(1,1))
+
+ self.block6 = _upsampling_residual_block(in_filters=192, nb_filters=96, repetitions=5)#(res1)
+ self.res2 = _shortcut(in_filters=96, nb_filters=96)#(block2, block6, subsample=(1,1))
+
+ self.block7 = _upsampling_residual_block(in_filters=96, nb_filters=48, repetitions=3)#(res2)
+ self.res3 = _shortcut(in_filters=48, nb_filters=48)#(block1, block7, subsample=(1,1))
+
+ self.block8 = _upsampling_residual_block(in_filters=48, nb_filters=24, repetitions=2)#(res3)
+ self.res4 = _shortcut(in_filters=24, nb_filters=24)#(block0,block8, subsample=(1,1))
+
+ self.block9 = _residual_block(in_filters=24, nb_filters=16, repetitions=2, is_first_layer=True)#(res4)
+ self.conv15 = _bn_relu_conv(in_filters=16, nb_filters=1, fh=1, fw=1, subsample=1)#(block7)
+
+ def forward(self, x):
+ x0 = self.block0(x)
+ x1 = self.block1(x0)
+ x2 = self.block2(x1)
+ x3 = self.block3(x2)
+ x4 = self.block4(x3)
+
+ x5 = self.block5(x4)
+ res1 = self.res1(x3, x5)
+
+ x6 = self.block6(res1)
+ res2 = self.res2(x2, x6)
+
+ x7 = self.block7(res2)
+ res3 = self.res3(x1, x7)
+
+ x8 = self.block8(res3)
+ res4 = self.res4(x0, x8)
+
+ x9 = self.block9(res4)
+ y = self.conv15(x9)
+
+ return y
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/mediapipe_face/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/mediapipe_face/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..f911daa9e729fb8541b701a3afc3f7b300a2989e
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/mediapipe_face/__init__.py
@@ -0,0 +1,31 @@
+import warnings
+from typing import Union
+
+import cv2
+import numpy as np
+from PIL import Image
+
+from controlnet_aux.util import HWC3, common_input_validate, resize_image_with_pad
+from .mediapipe_face_common import generate_annotation
+
+
+class MediapipeFaceDetector:
+ def __call__(self,
+ input_image: Union[np.ndarray, Image.Image] = None,
+ max_faces: int = 1,
+ min_confidence: float = 0.5,
+ output_type: str = "pil",
+ detect_resolution: int = 512,
+ image_resolution: int = 512,
+ upscale_method="INTER_CUBIC",
+ **kwargs):
+
+ input_image, output_type = common_input_validate(input_image, output_type, **kwargs)
+ detected_map, remove_pad = resize_image_with_pad(input_image, detect_resolution, upscale_method)
+ detected_map = generate_annotation(detected_map, max_faces, min_confidence)
+ detected_map = remove_pad(HWC3(detected_map))
+
+ if output_type == "pil":
+ detected_map = Image.fromarray(detected_map)
+
+ return detected_map
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/mediapipe_face/mediapipe_face_common.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/mediapipe_face/mediapipe_face_common.py
new file mode 100644
index 0000000000000000000000000000000000000000..32eeaf7455df2dd9efa5976def5e617b08757598
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/mediapipe_face/mediapipe_face_common.py
@@ -0,0 +1,156 @@
+from typing import Mapping
+import warnings
+
+import mediapipe as mp
+import numpy
+
+if mp:
+ mp_drawing = mp.solutions.drawing_utils
+ mp_drawing_styles = mp.solutions.drawing_styles
+ mp_face_detection = mp.solutions.face_detection # Only for counting faces.
+ mp_face_mesh = mp.solutions.face_mesh
+ mp_face_connections = mp.solutions.face_mesh_connections.FACEMESH_TESSELATION
+ mp_hand_connections = mp.solutions.hands_connections.HAND_CONNECTIONS
+ mp_body_connections = mp.solutions.pose_connections.POSE_CONNECTIONS
+
+ DrawingSpec = mp.solutions.drawing_styles.DrawingSpec
+ PoseLandmark = mp.solutions.drawing_styles.PoseLandmark
+
+ min_face_size_pixels: int = 64
+ f_thick = 2
+ f_rad = 1
+ right_iris_draw = DrawingSpec(color=(10, 200, 250), thickness=f_thick, circle_radius=f_rad)
+ right_eye_draw = DrawingSpec(color=(10, 200, 180), thickness=f_thick, circle_radius=f_rad)
+ right_eyebrow_draw = DrawingSpec(color=(10, 220, 180), thickness=f_thick, circle_radius=f_rad)
+ left_iris_draw = DrawingSpec(color=(250, 200, 10), thickness=f_thick, circle_radius=f_rad)
+ left_eye_draw = DrawingSpec(color=(180, 200, 10), thickness=f_thick, circle_radius=f_rad)
+ left_eyebrow_draw = DrawingSpec(color=(180, 220, 10), thickness=f_thick, circle_radius=f_rad)
+ mouth_draw = DrawingSpec(color=(10, 180, 10), thickness=f_thick, circle_radius=f_rad)
+ head_draw = DrawingSpec(color=(10, 200, 10), thickness=f_thick, circle_radius=f_rad)
+
+ # mp_face_mesh.FACEMESH_CONTOURS has all the items we care about.
+ face_connection_spec = {}
+ for edge in mp_face_mesh.FACEMESH_FACE_OVAL:
+ face_connection_spec[edge] = head_draw
+ for edge in mp_face_mesh.FACEMESH_LEFT_EYE:
+ face_connection_spec[edge] = left_eye_draw
+ for edge in mp_face_mesh.FACEMESH_LEFT_EYEBROW:
+ face_connection_spec[edge] = left_eyebrow_draw
+ # for edge in mp_face_mesh.FACEMESH_LEFT_IRIS:
+ # face_connection_spec[edge] = left_iris_draw
+ for edge in mp_face_mesh.FACEMESH_RIGHT_EYE:
+ face_connection_spec[edge] = right_eye_draw
+ for edge in mp_face_mesh.FACEMESH_RIGHT_EYEBROW:
+ face_connection_spec[edge] = right_eyebrow_draw
+ # for edge in mp_face_mesh.FACEMESH_RIGHT_IRIS:
+ # face_connection_spec[edge] = right_iris_draw
+ for edge in mp_face_mesh.FACEMESH_LIPS:
+ face_connection_spec[edge] = mouth_draw
+ iris_landmark_spec = {468: right_iris_draw, 473: left_iris_draw}
+
+
+def draw_pupils(image, landmark_list, drawing_spec, halfwidth: int = 2):
+ """We have a custom function to draw the pupils because the mp.draw_landmarks method requires a parameter for all
+ landmarks. Until our PR is merged into mediapipe, we need this separate method."""
+ if len(image.shape) != 3:
+ raise ValueError("Input image must be H,W,C.")
+ image_rows, image_cols, image_channels = image.shape
+ if image_channels != 3: # BGR channels
+ raise ValueError('Input image must contain three channel bgr data.')
+ for idx, landmark in enumerate(landmark_list.landmark):
+ if (
+ (landmark.HasField('visibility') and landmark.visibility < 0.9) or
+ (landmark.HasField('presence') and landmark.presence < 0.5)
+ ):
+ continue
+ if landmark.x >= 1.0 or landmark.x < 0 or landmark.y >= 1.0 or landmark.y < 0:
+ continue
+ image_x = int(image_cols*landmark.x)
+ image_y = int(image_rows*landmark.y)
+ draw_color = None
+ if isinstance(drawing_spec, Mapping):
+ if drawing_spec.get(idx) is None:
+ continue
+ else:
+ draw_color = drawing_spec[idx].color
+ elif isinstance(drawing_spec, DrawingSpec):
+ draw_color = drawing_spec.color
+ image[image_y-halfwidth:image_y+halfwidth, image_x-halfwidth:image_x+halfwidth, :] = draw_color
+
+
+def reverse_channels(image):
+ """Given a numpy array in RGB form, convert to BGR. Will also convert from BGR to RGB."""
+ # im[:,:,::-1] is a neat hack to convert BGR to RGB by reversing the indexing order.
+ # im[:,:,::[2,1,0]] would also work but makes a copy of the data.
+ return image[:, :, ::-1]
+
+
+def generate_annotation(
+ img_rgb,
+ max_faces: int,
+ min_confidence: float
+):
+ """
+ Find up to 'max_faces' inside the provided input image.
+ If min_face_size_pixels is provided and nonzero it will be used to filter faces that occupy less than this many
+ pixels in the image.
+ """
+ with mp_face_mesh.FaceMesh(
+ static_image_mode=True,
+ max_num_faces=max_faces,
+ refine_landmarks=True,
+ min_detection_confidence=min_confidence,
+ ) as facemesh:
+ img_height, img_width, img_channels = img_rgb.shape
+ assert(img_channels == 3)
+
+ results = facemesh.process(img_rgb).multi_face_landmarks
+
+ if results is None:
+ print("No faces detected in controlnet image for Mediapipe face annotator.")
+ return numpy.zeros_like(img_rgb)
+
+ # Filter faces that are too small
+ filtered_landmarks = []
+ for lm in results:
+ landmarks = lm.landmark
+ face_rect = [
+ landmarks[0].x,
+ landmarks[0].y,
+ landmarks[0].x,
+ landmarks[0].y,
+ ] # Left, up, right, down.
+ for i in range(len(landmarks)):
+ face_rect[0] = min(face_rect[0], landmarks[i].x)
+ face_rect[1] = min(face_rect[1], landmarks[i].y)
+ face_rect[2] = max(face_rect[2], landmarks[i].x)
+ face_rect[3] = max(face_rect[3], landmarks[i].y)
+ if min_face_size_pixels > 0:
+ face_width = abs(face_rect[2] - face_rect[0])
+ face_height = abs(face_rect[3] - face_rect[1])
+ face_width_pixels = face_width * img_width
+ face_height_pixels = face_height * img_height
+ face_size = min(face_width_pixels, face_height_pixels)
+ if face_size >= min_face_size_pixels:
+ filtered_landmarks.append(lm)
+ else:
+ filtered_landmarks.append(lm)
+
+ # Annotations are drawn in BGR for some reason, but we don't need to flip a zero-filled image at the start.
+ empty = numpy.zeros_like(img_rgb)
+
+ # Draw detected faces:
+ for face_landmarks in filtered_landmarks:
+ mp_drawing.draw_landmarks(
+ empty,
+ face_landmarks,
+ connections=face_connection_spec.keys(),
+ landmark_drawing_spec=None,
+ connection_drawing_spec=face_connection_spec
+ )
+ draw_pupils(empty, face_landmarks, iris_landmark_spec, 2)
+
+ # Flip BGR back to RGB.
+ empty = reverse_channels(empty).copy()
+
+ return empty
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/midas/LICENSE b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/midas/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..277b5c11be103f028a8d10985139f1da10c2f08e
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/midas/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Intel ISL (Intel Intelligent Systems Lab)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/midas/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/midas/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..51600f19253080787487f40e19e8b7d5379a781f
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/midas/__init__.py
@@ -0,0 +1,76 @@
+import os
+
+import cv2
+import numpy as np
+import torch
+from einops import rearrange
+from PIL import Image
+
+from controlnet_aux.util import HWC3, common_input_validate, resize_image_with_pad, annotator_ckpts_path, custom_hf_download
+from .api import MiDaSInference
+
+
+class MidasDetector:
+ def __init__(self, model):
+ self.model = model
+
+ @classmethod
+ def from_pretrained(cls, pretrained_model_or_path, model_type="dpt_hybrid", filename=None, cache_dir=annotator_ckpts_path):
+ filename = filename or "dpt_hybrid-midas-501f0c75.pt"
+ subfolder = "annotator/ckpts" if pretrained_model_or_path == "lllyasviel/ControlNet" else ''
+ model_path = custom_hf_download(pretrained_model_or_path, filename, cache_dir=cache_dir, subfolder=subfolder)
+ model = MiDaSInference(model_type=model_type, model_path=model_path)
+ return cls(model)
+
+
+ def to(self, device):
+ self.model.to(device)
+ return self
+
+ def __call__(self, input_image, a=np.pi * 2.0, bg_th=0.1, depth_and_normal=False, detect_resolution=512, output_type=None, upscale_method="INTER_CUBIC", **kwargs):
+ device = next(iter(self.model.parameters())).device
+ input_image, output_type = common_input_validate(input_image, output_type, **kwargs)
+ detected_map, remove_pad = resize_image_with_pad(input_image, detect_resolution, upscale_method)
+ image_depth = detected_map
+ with torch.no_grad():
+ image_depth = torch.from_numpy(image_depth).float()
+ image_depth = image_depth.to(device)
+ image_depth = image_depth / 127.5 - 1.0
+ image_depth = rearrange(image_depth, 'h w c -> 1 c h w')
+ depth = self.model(image_depth)[0]
+
+ depth_pt = depth.clone()
+ depth_pt -= torch.min(depth_pt)
+ depth_pt /= torch.max(depth_pt)
+ depth_pt = depth_pt.cpu().numpy()
+ depth_image = (depth_pt * 255.0).clip(0, 255).astype(np.uint8)
+
+ if depth_and_normal:
+ depth_np = depth.cpu().numpy()
+ x = cv2.Sobel(depth_np, cv2.CV_32F, 1, 0, ksize=3)
+ y = cv2.Sobel(depth_np, cv2.CV_32F, 0, 1, ksize=3)
+ z = np.ones_like(x) * a
+ x[depth_pt < bg_th] = 0
+ y[depth_pt < bg_th] = 0
+ normal = np.stack([x, y, z], axis=2)
+ normal /= np.sum(normal ** 2.0, axis=2, keepdims=True) ** 0.5
+ normal_image = (normal * 127.5 + 127.5).clip(0, 255).astype(np.uint8)[:, :, ::-1]
+
+ depth_image = HWC3(depth_image)
+ if depth_and_normal:
+ normal_image = HWC3(normal_image)
+
+
+ depth_image = remove_pad(depth_image)
+ if depth_and_normal:
+ normal_image = remove_pad(normal_image)
+
+ if output_type == "pil":
+ depth_image = Image.fromarray(depth_image)
+ if depth_and_normal:
+ normal_image = Image.fromarray(normal_image)
+
+ if depth_and_normal:
+ return depth_image, normal_image
+ else:
+ return depth_image
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/midas/api.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/midas/api.py
new file mode 100644
index 0000000000000000000000000000000000000000..6d7475008e9b9643ed762dbb6c122bcd655794d1
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/midas/api.py
@@ -0,0 +1,169 @@
+# based on https://github.com/isl-org/MiDaS
+
+import cv2
+import os
+import torch
+import torch.nn as nn
+from torchvision.transforms import Compose
+
+from custom_midas_repo.midas.dpt_depth import DPTDepthModel
+from custom_midas_repo.midas.midas_net import MidasNet
+from custom_midas_repo.midas.midas_net_custom import MidasNet_small
+from custom_midas_repo.midas.transforms import Resize, NormalizeImage, PrepareForNet
+from controlnet_aux.util import annotator_ckpts_path
+
+
+ISL_PATHS = {
+ "dpt_large": os.path.join(annotator_ckpts_path, "dpt_large-midas-2f21e586.pt"),
+ "dpt_hybrid": os.path.join(annotator_ckpts_path, "dpt_hybrid-midas-501f0c75.pt"),
+ "midas_v21": "",
+ "midas_v21_small": "",
+}
+
+remote_model_path = "https://huggingface.co/lllyasviel/ControlNet/resolve/main/annotator/ckpts/dpt_hybrid-midas-501f0c75.pt"
+
+
+def disabled_train(self, mode=True):
+ """Overwrite model.train with this function to make sure train/eval mode
+ does not change anymore."""
+ return self
+
+
+def load_midas_transform(model_type):
+ # https://github.com/isl-org/MiDaS/blob/master/run.py
+ # load transform only
+ if model_type == "dpt_large": # DPT-Large
+ net_w, net_h = 384, 384
+ resize_mode = "minimal"
+ normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
+
+ elif model_type == "dpt_hybrid": # DPT-Hybrid
+ net_w, net_h = 384, 384
+ resize_mode = "minimal"
+ normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
+
+ elif model_type == "midas_v21":
+ net_w, net_h = 384, 384
+ resize_mode = "upper_bound"
+ normalization = NormalizeImage(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
+
+ elif model_type == "midas_v21_small":
+ net_w, net_h = 256, 256
+ resize_mode = "upper_bound"
+ normalization = NormalizeImage(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
+
+ else:
+ assert False, f"model_type '{model_type}' not implemented, use: --model_type large"
+
+ transform = Compose(
+ [
+ Resize(
+ net_w,
+ net_h,
+ resize_target=None,
+ keep_aspect_ratio=True,
+ ensure_multiple_of=32,
+ resize_method=resize_mode,
+ image_interpolation_method=cv2.INTER_CUBIC,
+ ),
+ normalization,
+ PrepareForNet(),
+ ]
+ )
+
+ return transform
+
+
+def load_model(model_type, model_path=None):
+ # https://github.com/isl-org/MiDaS/blob/master/run.py
+ # load network
+ model_path = model_path or ISL_PATHS[model_type]
+ if model_type == "dpt_large": # DPT-Large
+ model = DPTDepthModel(
+ path=model_path,
+ backbone="vitl16_384",
+ non_negative=True,
+ )
+ net_w, net_h = 384, 384
+ resize_mode = "minimal"
+ normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
+
+ elif model_type == "dpt_hybrid": # DPT-Hybrid
+ if not os.path.exists(model_path):
+ from basicsr.utils.download_util import load_file_from_url
+ load_file_from_url(remote_model_path, model_dir=annotator_ckpts_path)
+
+ model = DPTDepthModel(
+ path=model_path,
+ backbone="vitb_rn50_384",
+ non_negative=True,
+ )
+ net_w, net_h = 384, 384
+ resize_mode = "minimal"
+ normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
+
+ elif model_type == "midas_v21":
+ model = MidasNet(model_path, non_negative=True)
+ net_w, net_h = 384, 384
+ resize_mode = "upper_bound"
+ normalization = NormalizeImage(
+ mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]
+ )
+
+ elif model_type == "midas_v21_small":
+ model = MidasNet_small(model_path, features=64, backbone="efficientnet_lite3", exportable=True,
+ non_negative=True, blocks={'expand': True})
+ net_w, net_h = 256, 256
+ resize_mode = "upper_bound"
+ normalization = NormalizeImage(
+ mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]
+ )
+
+ else:
+ print(f"model_type '{model_type}' not implemented, use: --model_type large")
+ assert False
+
+ transform = Compose(
+ [
+ Resize(
+ net_w,
+ net_h,
+ resize_target=None,
+ keep_aspect_ratio=True,
+ ensure_multiple_of=32,
+ resize_method=resize_mode,
+ image_interpolation_method=cv2.INTER_CUBIC,
+ ),
+ normalization,
+ PrepareForNet(),
+ ]
+ )
+
+ return model.eval(), transform
+
+
+class MiDaSInference(nn.Module):
+ MODEL_TYPES_TORCH_HUB = [
+ "DPT_Large",
+ "DPT_Hybrid",
+ "MiDaS_small"
+ ]
+ MODEL_TYPES_ISL = [
+ "dpt_large",
+ "dpt_hybrid",
+ "midas_v21",
+ "midas_v21_small",
+ ]
+
+ def __init__(self, model_type, model_path):
+ super().__init__()
+ assert (model_type in self.MODEL_TYPES_ISL)
+ model, _ = load_model(model_type, model_path)
+ self.model = model
+ self.model.train = disabled_train
+
+ def forward(self, x):
+ with torch.no_grad():
+ prediction = self.model(x)
+ return prediction
+
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/midas/utils.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/midas/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..9a9d3b5b66370fa98da9e067ba53ead848ea9a59
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/midas/utils.py
@@ -0,0 +1,189 @@
+"""Utils for monoDepth."""
+import sys
+import re
+import numpy as np
+import cv2
+import torch
+
+
+def read_pfm(path):
+ """Read pfm file.
+
+ Args:
+ path (str): path to file
+
+ Returns:
+ tuple: (data, scale)
+ """
+ with open(path, "rb") as file:
+
+ color = None
+ width = None
+ height = None
+ scale = None
+ endian = None
+
+ header = file.readline().rstrip()
+ if header.decode("ascii") == "PF":
+ color = True
+ elif header.decode("ascii") == "Pf":
+ color = False
+ else:
+ raise Exception("Not a PFM file: " + path)
+
+ dim_match = re.match(r"^(\d+)\s(\d+)\s$", file.readline().decode("ascii"))
+ if dim_match:
+ width, height = list(map(int, dim_match.groups()))
+ else:
+ raise Exception("Malformed PFM header.")
+
+ scale = float(file.readline().decode("ascii").rstrip())
+ if scale < 0:
+ # little-endian
+ endian = "<"
+ scale = -scale
+ else:
+ # big-endian
+ endian = ">"
+
+ data = np.fromfile(file, endian + "f")
+ shape = (height, width, 3) if color else (height, width)
+
+ data = np.reshape(data, shape)
+ data = np.flipud(data)
+
+ return data, scale
+
+
+def write_pfm(path, image, scale=1):
+ """Write pfm file.
+
+ Args:
+ path (str): pathto file
+ image (array): data
+ scale (int, optional): Scale. Defaults to 1.
+ """
+
+ with open(path, "wb") as file:
+ color = None
+
+ if image.dtype.name != "float32":
+ raise Exception("Image dtype must be float32.")
+
+ image = np.flipud(image)
+
+ if len(image.shape) == 3 and image.shape[2] == 3: # color image
+ color = True
+ elif (
+ len(image.shape) == 2 or len(image.shape) == 3 and image.shape[2] == 1
+ ): # greyscale
+ color = False
+ else:
+ raise Exception("Image must have H x W x 3, H x W x 1 or H x W dimensions.")
+
+ file.write("PF\n" if color else "Pf\n".encode())
+ file.write("%d %d\n".encode() % (image.shape[1], image.shape[0]))
+
+ endian = image.dtype.byteorder
+
+ if endian == "<" or endian == "=" and sys.byteorder == "little":
+ scale = -scale
+
+ file.write("%f\n".encode() % scale)
+
+ image.tofile(file)
+
+
+def read_image(path):
+ """Read image and output RGB image (0-1).
+
+ Args:
+ path (str): path to file
+
+ Returns:
+ array: RGB image (0-1)
+ """
+ img = cv2.imread(path)
+
+ if img.ndim == 2:
+ img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
+
+ img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) / 255.0
+
+ return img
+
+
+def resize_image(img):
+ """Resize image and make it fit for network.
+
+ Args:
+ img (array): image
+
+ Returns:
+ tensor: data ready for network
+ """
+ height_orig = img.shape[0]
+ width_orig = img.shape[1]
+
+ if width_orig > height_orig:
+ scale = width_orig / 384
+ else:
+ scale = height_orig / 384
+
+ height = (np.ceil(height_orig / scale / 32) * 32).astype(int)
+ width = (np.ceil(width_orig / scale / 32) * 32).astype(int)
+
+ img_resized = cv2.resize(img, (width, height), interpolation=cv2.INTER_AREA)
+
+ img_resized = (
+ torch.from_numpy(np.transpose(img_resized, (2, 0, 1))).contiguous().float()
+ )
+ img_resized = img_resized.unsqueeze(0)
+
+ return img_resized
+
+
+def resize_depth(depth, width, height):
+ """Resize depth map and bring to CPU (numpy).
+
+ Args:
+ depth (tensor): depth
+ width (int): image width
+ height (int): image height
+
+ Returns:
+ array: processed depth
+ """
+ depth = torch.squeeze(depth[0, :, :, :]).to("cpu")
+
+ depth_resized = cv2.resize(
+ depth.numpy(), (width, height), interpolation=cv2.INTER_CUBIC
+ )
+
+ return depth_resized
+
+def write_depth(path, depth, bits=1):
+ """Write depth map to pfm and png file.
+
+ Args:
+ path (str): filepath without extension
+ depth (array): depth
+ """
+ write_pfm(path + ".pfm", depth.astype(np.float32))
+
+ depth_min = depth.min()
+ depth_max = depth.max()
+
+ max_val = (2**(8*bits))-1
+
+ if depth_max - depth_min > np.finfo("float").eps:
+ out = max_val * (depth - depth_min) / (depth_max - depth_min)
+ else:
+ out = np.zeros(depth.shape, dtype=depth.type)
+
+ if bits == 1:
+ cv2.imwrite(path + ".png", out.astype("uint8"))
+ elif bits == 2:
+ cv2.imwrite(path + ".png", out.astype("uint16"))
+
+ return
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/mlsd/LICENSE b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/mlsd/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..d855c6db44b4e873eedd750d34fa2eaf22e22363
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/mlsd/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2021-present NAVER Corp.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/mlsd/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/mlsd/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..7a2b63ca78931940dcc67fc9ac0297edfcf2ca52
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/mlsd/__init__.py
@@ -0,0 +1,52 @@
+import os
+import warnings
+
+import cv2
+import numpy as np
+import torch
+from PIL import Image
+
+from controlnet_aux.util import HWC3, common_input_validate, resize_image_with_pad, annotator_ckpts_path, custom_hf_download
+from .models.mbv2_mlsd_large import MobileV2_MLSD_Large
+from .utils import pred_lines
+
+
+class MLSDdetector:
+ def __init__(self, model):
+ self.model = model
+
+ @classmethod
+ def from_pretrained(cls, pretrained_model_or_path, filename=None, cache_dir=annotator_ckpts_path):
+ filename = filename or "mlsd_large_512_fp32.pth"
+ subfolder = "annotator/ckpts" if pretrained_model_or_path == "lllyasviel/ControlNet" else ''
+ model_path = custom_hf_download(pretrained_model_or_path, filename, cache_dir=cache_dir, subfolder=subfolder)
+ model = MobileV2_MLSD_Large()
+ model.load_state_dict(torch.load(model_path), strict=True)
+ model.eval()
+
+ return cls(model)
+
+ def to(self, device):
+ self.model.to(device)
+ return self
+
+ def __call__(self, input_image, thr_v=0.1, thr_d=0.1, detect_resolution=512, output_type="pil", upscale_method="INTER_AREA", **kwargs):
+ input_image, output_type = common_input_validate(input_image, output_type, **kwargs)
+ detected_map, remove_pad = resize_image_with_pad(input_image, detect_resolution, upscale_method)
+ img = detected_map
+ img_output = np.zeros_like(img)
+ try:
+ with torch.no_grad():
+ lines = pred_lines(img, self.model, [img.shape[0], img.shape[1]], thr_v, thr_d)
+ for line in lines:
+ x_start, y_start, x_end, y_end = [int(val) for val in line]
+ cv2.line(img_output, (x_start, y_start), (x_end, y_end), [255, 255, 255], 1)
+ except Exception as e:
+ pass
+
+ detected_map = remove_pad(HWC3(img_output[:, :, 0]))
+
+ if output_type == "pil":
+ detected_map = Image.fromarray(detected_map)
+
+ return detected_map
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/mlsd/models/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/mlsd/models/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/mlsd/models/mbv2_mlsd_large.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/mlsd/models/mbv2_mlsd_large.py
new file mode 100644
index 0000000000000000000000000000000000000000..5b9799e7573ca41549b3c3b13ac47b906b369603
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/mlsd/models/mbv2_mlsd_large.py
@@ -0,0 +1,292 @@
+import os
+import sys
+import torch
+import torch.nn as nn
+import torch.utils.model_zoo as model_zoo
+from torch.nn import functional as F
+
+
+class BlockTypeA(nn.Module):
+ def __init__(self, in_c1, in_c2, out_c1, out_c2, upscale = True):
+ super(BlockTypeA, self).__init__()
+ self.conv1 = nn.Sequential(
+ nn.Conv2d(in_c2, out_c2, kernel_size=1),
+ nn.BatchNorm2d(out_c2),
+ nn.ReLU(inplace=True)
+ )
+ self.conv2 = nn.Sequential(
+ nn.Conv2d(in_c1, out_c1, kernel_size=1),
+ nn.BatchNorm2d(out_c1),
+ nn.ReLU(inplace=True)
+ )
+ self.upscale = upscale
+
+ def forward(self, a, b):
+ b = self.conv1(b)
+ a = self.conv2(a)
+ if self.upscale:
+ b = F.interpolate(b, scale_factor=2.0, mode='bilinear', align_corners=True)
+ return torch.cat((a, b), dim=1)
+
+
+class BlockTypeB(nn.Module):
+ def __init__(self, in_c, out_c):
+ super(BlockTypeB, self).__init__()
+ self.conv1 = nn.Sequential(
+ nn.Conv2d(in_c, in_c, kernel_size=3, padding=1),
+ nn.BatchNorm2d(in_c),
+ nn.ReLU()
+ )
+ self.conv2 = nn.Sequential(
+ nn.Conv2d(in_c, out_c, kernel_size=3, padding=1),
+ nn.BatchNorm2d(out_c),
+ nn.ReLU()
+ )
+
+ def forward(self, x):
+ x = self.conv1(x) + x
+ x = self.conv2(x)
+ return x
+
+class BlockTypeC(nn.Module):
+ def __init__(self, in_c, out_c):
+ super(BlockTypeC, self).__init__()
+ self.conv1 = nn.Sequential(
+ nn.Conv2d(in_c, in_c, kernel_size=3, padding=5, dilation=5),
+ nn.BatchNorm2d(in_c),
+ nn.ReLU()
+ )
+ self.conv2 = nn.Sequential(
+ nn.Conv2d(in_c, in_c, kernel_size=3, padding=1),
+ nn.BatchNorm2d(in_c),
+ nn.ReLU()
+ )
+ self.conv3 = nn.Conv2d(in_c, out_c, kernel_size=1)
+
+ def forward(self, x):
+ x = self.conv1(x)
+ x = self.conv2(x)
+ x = self.conv3(x)
+ return x
+
+def _make_divisible(v, divisor, min_value=None):
+ """
+ This function is taken from the original tf repo.
+ It ensures that all layers have a channel number that is divisible by 8
+ It can be seen here:
+ https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py
+ :param v:
+ :param divisor:
+ :param min_value:
+ :return:
+ """
+ if min_value is None:
+ min_value = divisor
+ new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)
+ # Make sure that round down does not go down by more than 10%.
+ if new_v < 0.9 * v:
+ new_v += divisor
+ return new_v
+
+
+class ConvBNReLU(nn.Sequential):
+ def __init__(self, in_planes, out_planes, kernel_size=3, stride=1, groups=1):
+ self.channel_pad = out_planes - in_planes
+ self.stride = stride
+ #padding = (kernel_size - 1) // 2
+
+ # TFLite uses slightly different padding than PyTorch
+ if stride == 2:
+ padding = 0
+ else:
+ padding = (kernel_size - 1) // 2
+
+ super(ConvBNReLU, self).__init__(
+ nn.Conv2d(in_planes, out_planes, kernel_size, stride, padding, groups=groups, bias=False),
+ nn.BatchNorm2d(out_planes),
+ nn.ReLU6(inplace=True)
+ )
+ self.max_pool = nn.MaxPool2d(kernel_size=stride, stride=stride)
+
+
+ def forward(self, x):
+ # TFLite uses different padding
+ if self.stride == 2:
+ x = F.pad(x, (0, 1, 0, 1), "constant", 0)
+ #print(x.shape)
+
+ for module in self:
+ if not isinstance(module, nn.MaxPool2d):
+ x = module(x)
+ return x
+
+
+class InvertedResidual(nn.Module):
+ def __init__(self, inp, oup, stride, expand_ratio):
+ super(InvertedResidual, self).__init__()
+ self.stride = stride
+ assert stride in [1, 2]
+
+ hidden_dim = int(round(inp * expand_ratio))
+ self.use_res_connect = self.stride == 1 and inp == oup
+
+ layers = []
+ if expand_ratio != 1:
+ # pw
+ layers.append(ConvBNReLU(inp, hidden_dim, kernel_size=1))
+ layers.extend([
+ # dw
+ ConvBNReLU(hidden_dim, hidden_dim, stride=stride, groups=hidden_dim),
+ # pw-linear
+ nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
+ nn.BatchNorm2d(oup),
+ ])
+ self.conv = nn.Sequential(*layers)
+
+ def forward(self, x):
+ if self.use_res_connect:
+ return x + self.conv(x)
+ else:
+ return self.conv(x)
+
+
+class MobileNetV2(nn.Module):
+ def __init__(self, pretrained=True):
+ """
+ MobileNet V2 main class
+ Args:
+ num_classes (int): Number of classes
+ width_mult (float): Width multiplier - adjusts number of channels in each layer by this amount
+ inverted_residual_setting: Network structure
+ round_nearest (int): Round the number of channels in each layer to be a multiple of this number
+ Set to 1 to turn off rounding
+ block: Module specifying inverted residual building block for mobilenet
+ """
+ super(MobileNetV2, self).__init__()
+
+ block = InvertedResidual
+ input_channel = 32
+ last_channel = 1280
+ width_mult = 1.0
+ round_nearest = 8
+
+ inverted_residual_setting = [
+ # t, c, n, s
+ [1, 16, 1, 1],
+ [6, 24, 2, 2],
+ [6, 32, 3, 2],
+ [6, 64, 4, 2],
+ [6, 96, 3, 1],
+ #[6, 160, 3, 2],
+ #[6, 320, 1, 1],
+ ]
+
+ # only check the first element, assuming user knows t,c,n,s are required
+ if len(inverted_residual_setting) == 0 or len(inverted_residual_setting[0]) != 4:
+ raise ValueError("inverted_residual_setting should be non-empty "
+ "or a 4-element list, got {}".format(inverted_residual_setting))
+
+ # building first layer
+ input_channel = _make_divisible(input_channel * width_mult, round_nearest)
+ self.last_channel = _make_divisible(last_channel * max(1.0, width_mult), round_nearest)
+ features = [ConvBNReLU(4, input_channel, stride=2)]
+ # building inverted residual blocks
+ for t, c, n, s in inverted_residual_setting:
+ output_channel = _make_divisible(c * width_mult, round_nearest)
+ for i in range(n):
+ stride = s if i == 0 else 1
+ features.append(block(input_channel, output_channel, stride, expand_ratio=t))
+ input_channel = output_channel
+
+ self.features = nn.Sequential(*features)
+ self.fpn_selected = [1, 3, 6, 10, 13]
+ # weight initialization
+ for m in self.modules():
+ if isinstance(m, nn.Conv2d):
+ nn.init.kaiming_normal_(m.weight, mode='fan_out')
+ if m.bias is not None:
+ nn.init.zeros_(m.bias)
+ elif isinstance(m, nn.BatchNorm2d):
+ nn.init.ones_(m.weight)
+ nn.init.zeros_(m.bias)
+ elif isinstance(m, nn.Linear):
+ nn.init.normal_(m.weight, 0, 0.01)
+ nn.init.zeros_(m.bias)
+ if pretrained:
+ self._load_pretrained_model()
+
+ def _forward_impl(self, x):
+ # This exists since TorchScript doesn't support inheritance, so the superclass method
+ # (this one) needs to have a name other than `forward` that can be accessed in a subclass
+ fpn_features = []
+ for i, f in enumerate(self.features):
+ if i > self.fpn_selected[-1]:
+ break
+ x = f(x)
+ if i in self.fpn_selected:
+ fpn_features.append(x)
+
+ c1, c2, c3, c4, c5 = fpn_features
+ return c1, c2, c3, c4, c5
+
+
+ def forward(self, x):
+ return self._forward_impl(x)
+
+ def _load_pretrained_model(self):
+ pretrain_dict = model_zoo.load_url('https://download.pytorch.org/models/mobilenet_v2-b0353104.pth')
+ model_dict = {}
+ state_dict = self.state_dict()
+ for k, v in pretrain_dict.items():
+ if k in state_dict:
+ model_dict[k] = v
+ state_dict.update(model_dict)
+ self.load_state_dict(state_dict)
+
+
+class MobileV2_MLSD_Large(nn.Module):
+ def __init__(self):
+ super(MobileV2_MLSD_Large, self).__init__()
+
+ self.backbone = MobileNetV2(pretrained=False)
+ ## A, B
+ self.block15 = BlockTypeA(in_c1= 64, in_c2= 96,
+ out_c1= 64, out_c2=64,
+ upscale=False)
+ self.block16 = BlockTypeB(128, 64)
+
+ ## A, B
+ self.block17 = BlockTypeA(in_c1 = 32, in_c2 = 64,
+ out_c1= 64, out_c2= 64)
+ self.block18 = BlockTypeB(128, 64)
+
+ ## A, B
+ self.block19 = BlockTypeA(in_c1=24, in_c2=64,
+ out_c1=64, out_c2=64)
+ self.block20 = BlockTypeB(128, 64)
+
+ ## A, B, C
+ self.block21 = BlockTypeA(in_c1=16, in_c2=64,
+ out_c1=64, out_c2=64)
+ self.block22 = BlockTypeB(128, 64)
+
+ self.block23 = BlockTypeC(64, 16)
+
+ def forward(self, x):
+ c1, c2, c3, c4, c5 = self.backbone(x)
+
+ x = self.block15(c4, c5)
+ x = self.block16(x)
+
+ x = self.block17(c3, x)
+ x = self.block18(x)
+
+ x = self.block19(c2, x)
+ x = self.block20(x)
+
+ x = self.block21(c1, x)
+ x = self.block22(x)
+ x = self.block23(x)
+ x = x[:, 7:, :, :]
+
+ return x
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/mlsd/models/mbv2_mlsd_tiny.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/mlsd/models/mbv2_mlsd_tiny.py
new file mode 100644
index 0000000000000000000000000000000000000000..e3ed633f2cc23ea1829a627fdb879ab39f641f83
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/mlsd/models/mbv2_mlsd_tiny.py
@@ -0,0 +1,275 @@
+import os
+import sys
+import torch
+import torch.nn as nn
+import torch.utils.model_zoo as model_zoo
+from torch.nn import functional as F
+
+
+class BlockTypeA(nn.Module):
+ def __init__(self, in_c1, in_c2, out_c1, out_c2, upscale = True):
+ super(BlockTypeA, self).__init__()
+ self.conv1 = nn.Sequential(
+ nn.Conv2d(in_c2, out_c2, kernel_size=1),
+ nn.BatchNorm2d(out_c2),
+ nn.ReLU(inplace=True)
+ )
+ self.conv2 = nn.Sequential(
+ nn.Conv2d(in_c1, out_c1, kernel_size=1),
+ nn.BatchNorm2d(out_c1),
+ nn.ReLU(inplace=True)
+ )
+ self.upscale = upscale
+
+ def forward(self, a, b):
+ b = self.conv1(b)
+ a = self.conv2(a)
+ b = F.interpolate(b, scale_factor=2.0, mode='bilinear', align_corners=True)
+ return torch.cat((a, b), dim=1)
+
+
+class BlockTypeB(nn.Module):
+ def __init__(self, in_c, out_c):
+ super(BlockTypeB, self).__init__()
+ self.conv1 = nn.Sequential(
+ nn.Conv2d(in_c, in_c, kernel_size=3, padding=1),
+ nn.BatchNorm2d(in_c),
+ nn.ReLU()
+ )
+ self.conv2 = nn.Sequential(
+ nn.Conv2d(in_c, out_c, kernel_size=3, padding=1),
+ nn.BatchNorm2d(out_c),
+ nn.ReLU()
+ )
+
+ def forward(self, x):
+ x = self.conv1(x) + x
+ x = self.conv2(x)
+ return x
+
+class BlockTypeC(nn.Module):
+ def __init__(self, in_c, out_c):
+ super(BlockTypeC, self).__init__()
+ self.conv1 = nn.Sequential(
+ nn.Conv2d(in_c, in_c, kernel_size=3, padding=5, dilation=5),
+ nn.BatchNorm2d(in_c),
+ nn.ReLU()
+ )
+ self.conv2 = nn.Sequential(
+ nn.Conv2d(in_c, in_c, kernel_size=3, padding=1),
+ nn.BatchNorm2d(in_c),
+ nn.ReLU()
+ )
+ self.conv3 = nn.Conv2d(in_c, out_c, kernel_size=1)
+
+ def forward(self, x):
+ x = self.conv1(x)
+ x = self.conv2(x)
+ x = self.conv3(x)
+ return x
+
+def _make_divisible(v, divisor, min_value=None):
+ """
+ This function is taken from the original tf repo.
+ It ensures that all layers have a channel number that is divisible by 8
+ It can be seen here:
+ https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py
+ :param v:
+ :param divisor:
+ :param min_value:
+ :return:
+ """
+ if min_value is None:
+ min_value = divisor
+ new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)
+ # Make sure that round down does not go down by more than 10%.
+ if new_v < 0.9 * v:
+ new_v += divisor
+ return new_v
+
+
+class ConvBNReLU(nn.Sequential):
+ def __init__(self, in_planes, out_planes, kernel_size=3, stride=1, groups=1):
+ self.channel_pad = out_planes - in_planes
+ self.stride = stride
+ #padding = (kernel_size - 1) // 2
+
+ # TFLite uses slightly different padding than PyTorch
+ if stride == 2:
+ padding = 0
+ else:
+ padding = (kernel_size - 1) // 2
+
+ super(ConvBNReLU, self).__init__(
+ nn.Conv2d(in_planes, out_planes, kernel_size, stride, padding, groups=groups, bias=False),
+ nn.BatchNorm2d(out_planes),
+ nn.ReLU6(inplace=True)
+ )
+ self.max_pool = nn.MaxPool2d(kernel_size=stride, stride=stride)
+
+
+ def forward(self, x):
+ # TFLite uses different padding
+ if self.stride == 2:
+ x = F.pad(x, (0, 1, 0, 1), "constant", 0)
+ #print(x.shape)
+
+ for module in self:
+ if not isinstance(module, nn.MaxPool2d):
+ x = module(x)
+ return x
+
+
+class InvertedResidual(nn.Module):
+ def __init__(self, inp, oup, stride, expand_ratio):
+ super(InvertedResidual, self).__init__()
+ self.stride = stride
+ assert stride in [1, 2]
+
+ hidden_dim = int(round(inp * expand_ratio))
+ self.use_res_connect = self.stride == 1 and inp == oup
+
+ layers = []
+ if expand_ratio != 1:
+ # pw
+ layers.append(ConvBNReLU(inp, hidden_dim, kernel_size=1))
+ layers.extend([
+ # dw
+ ConvBNReLU(hidden_dim, hidden_dim, stride=stride, groups=hidden_dim),
+ # pw-linear
+ nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
+ nn.BatchNorm2d(oup),
+ ])
+ self.conv = nn.Sequential(*layers)
+
+ def forward(self, x):
+ if self.use_res_connect:
+ return x + self.conv(x)
+ else:
+ return self.conv(x)
+
+
+class MobileNetV2(nn.Module):
+ def __init__(self, pretrained=True):
+ """
+ MobileNet V2 main class
+ Args:
+ num_classes (int): Number of classes
+ width_mult (float): Width multiplier - adjusts number of channels in each layer by this amount
+ inverted_residual_setting: Network structure
+ round_nearest (int): Round the number of channels in each layer to be a multiple of this number
+ Set to 1 to turn off rounding
+ block: Module specifying inverted residual building block for mobilenet
+ """
+ super(MobileNetV2, self).__init__()
+
+ block = InvertedResidual
+ input_channel = 32
+ last_channel = 1280
+ width_mult = 1.0
+ round_nearest = 8
+
+ inverted_residual_setting = [
+ # t, c, n, s
+ [1, 16, 1, 1],
+ [6, 24, 2, 2],
+ [6, 32, 3, 2],
+ [6, 64, 4, 2],
+ #[6, 96, 3, 1],
+ #[6, 160, 3, 2],
+ #[6, 320, 1, 1],
+ ]
+
+ # only check the first element, assuming user knows t,c,n,s are required
+ if len(inverted_residual_setting) == 0 or len(inverted_residual_setting[0]) != 4:
+ raise ValueError("inverted_residual_setting should be non-empty "
+ "or a 4-element list, got {}".format(inverted_residual_setting))
+
+ # building first layer
+ input_channel = _make_divisible(input_channel * width_mult, round_nearest)
+ self.last_channel = _make_divisible(last_channel * max(1.0, width_mult), round_nearest)
+ features = [ConvBNReLU(4, input_channel, stride=2)]
+ # building inverted residual blocks
+ for t, c, n, s in inverted_residual_setting:
+ output_channel = _make_divisible(c * width_mult, round_nearest)
+ for i in range(n):
+ stride = s if i == 0 else 1
+ features.append(block(input_channel, output_channel, stride, expand_ratio=t))
+ input_channel = output_channel
+ self.features = nn.Sequential(*features)
+
+ self.fpn_selected = [3, 6, 10]
+ # weight initialization
+ for m in self.modules():
+ if isinstance(m, nn.Conv2d):
+ nn.init.kaiming_normal_(m.weight, mode='fan_out')
+ if m.bias is not None:
+ nn.init.zeros_(m.bias)
+ elif isinstance(m, nn.BatchNorm2d):
+ nn.init.ones_(m.weight)
+ nn.init.zeros_(m.bias)
+ elif isinstance(m, nn.Linear):
+ nn.init.normal_(m.weight, 0, 0.01)
+ nn.init.zeros_(m.bias)
+
+ #if pretrained:
+ # self._load_pretrained_model()
+
+ def _forward_impl(self, x):
+ # This exists since TorchScript doesn't support inheritance, so the superclass method
+ # (this one) needs to have a name other than `forward` that can be accessed in a subclass
+ fpn_features = []
+ for i, f in enumerate(self.features):
+ if i > self.fpn_selected[-1]:
+ break
+ x = f(x)
+ if i in self.fpn_selected:
+ fpn_features.append(x)
+
+ c2, c3, c4 = fpn_features
+ return c2, c3, c4
+
+
+ def forward(self, x):
+ return self._forward_impl(x)
+
+ def _load_pretrained_model(self):
+ pretrain_dict = model_zoo.load_url('https://download.pytorch.org/models/mobilenet_v2-b0353104.pth')
+ model_dict = {}
+ state_dict = self.state_dict()
+ for k, v in pretrain_dict.items():
+ if k in state_dict:
+ model_dict[k] = v
+ state_dict.update(model_dict)
+ self.load_state_dict(state_dict)
+
+
+class MobileV2_MLSD_Tiny(nn.Module):
+ def __init__(self):
+ super(MobileV2_MLSD_Tiny, self).__init__()
+
+ self.backbone = MobileNetV2(pretrained=True)
+
+ self.block12 = BlockTypeA(in_c1= 32, in_c2= 64,
+ out_c1= 64, out_c2=64)
+ self.block13 = BlockTypeB(128, 64)
+
+ self.block14 = BlockTypeA(in_c1 = 24, in_c2 = 64,
+ out_c1= 32, out_c2= 32)
+ self.block15 = BlockTypeB(64, 64)
+
+ self.block16 = BlockTypeC(64, 16)
+
+ def forward(self, x):
+ c2, c3, c4 = self.backbone(x)
+
+ x = self.block12(c3, c4)
+ x = self.block13(x)
+ x = self.block14(c2, x)
+ x = self.block15(x)
+ x = self.block16(x)
+ x = x[:, 7:, :, :]
+ #print(x.shape)
+ x = F.interpolate(x, scale_factor=2.0, mode='bilinear', align_corners=True)
+
+ return x
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/mlsd/utils.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/mlsd/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..28071cbf129a2bedb21a44f95d565aef7974e583
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/mlsd/utils.py
@@ -0,0 +1,584 @@
+'''
+modified by lihaoweicv
+pytorch version
+'''
+
+'''
+M-LSD
+Copyright 2021-present NAVER Corp.
+Apache License v2.0
+'''
+
+import os
+import numpy as np
+import cv2
+import torch
+from torch.nn import functional as F
+
+
+def deccode_output_score_and_ptss(tpMap, topk_n = 200, ksize = 5):
+ '''
+ tpMap:
+ center: tpMap[1, 0, :, :]
+ displacement: tpMap[1, 1:5, :, :]
+ '''
+ b, c, h, w = tpMap.shape
+ assert b==1, 'only support bsize==1'
+ displacement = tpMap[:, 1:5, :, :][0]
+ center = tpMap[:, 0, :, :]
+ heat = torch.sigmoid(center)
+ hmax = F.max_pool2d( heat, (ksize, ksize), stride=1, padding=(ksize-1)//2)
+ keep = (hmax == heat).float()
+ heat = heat * keep
+ heat = heat.reshape(-1, )
+
+ scores, indices = torch.topk(heat, topk_n, dim=-1, largest=True)
+ yy = torch.floor_divide(indices, w).unsqueeze(-1)
+ xx = torch.fmod(indices, w).unsqueeze(-1)
+ ptss = torch.cat((yy, xx),dim=-1)
+
+ ptss = ptss.detach().cpu().numpy()
+ scores = scores.detach().cpu().numpy()
+ displacement = displacement.detach().cpu().numpy()
+ displacement = displacement.transpose((1,2,0))
+ return ptss, scores, displacement
+
+
+def pred_lines(image, model,
+ input_shape=[512, 512],
+ score_thr=0.10,
+ dist_thr=20.0):
+ h, w, _ = image.shape
+
+ device = next(iter(model.parameters())).device
+ h_ratio, w_ratio = [h / input_shape[0], w / input_shape[1]]
+
+ resized_image = np.concatenate([cv2.resize(image, (input_shape[1], input_shape[0]), interpolation=cv2.INTER_AREA),
+ np.ones([input_shape[0], input_shape[1], 1])], axis=-1)
+
+ resized_image = resized_image.transpose((2,0,1))
+ batch_image = np.expand_dims(resized_image, axis=0).astype('float32')
+ batch_image = (batch_image / 127.5) - 1.0
+
+ batch_image = torch.from_numpy(batch_image).float()
+ batch_image = batch_image.to(device)
+ outputs = model(batch_image)
+ pts, pts_score, vmap = deccode_output_score_and_ptss(outputs, 200, 3)
+ start = vmap[:, :, :2]
+ end = vmap[:, :, 2:]
+ dist_map = np.sqrt(np.sum((start - end) ** 2, axis=-1))
+
+ segments_list = []
+ for center, score in zip(pts, pts_score):
+ y, x = center
+ distance = dist_map[y, x]
+ if score > score_thr and distance > dist_thr:
+ disp_x_start, disp_y_start, disp_x_end, disp_y_end = vmap[y, x, :]
+ x_start = x + disp_x_start
+ y_start = y + disp_y_start
+ x_end = x + disp_x_end
+ y_end = y + disp_y_end
+ segments_list.append([x_start, y_start, x_end, y_end])
+
+ lines = 2 * np.array(segments_list) # 256 > 512
+ lines[:, 0] = lines[:, 0] * w_ratio
+ lines[:, 1] = lines[:, 1] * h_ratio
+ lines[:, 2] = lines[:, 2] * w_ratio
+ lines[:, 3] = lines[:, 3] * h_ratio
+
+ return lines
+
+
+def pred_squares(image,
+ model,
+ input_shape=[512, 512],
+ params={'score': 0.06,
+ 'outside_ratio': 0.28,
+ 'inside_ratio': 0.45,
+ 'w_overlap': 0.0,
+ 'w_degree': 1.95,
+ 'w_length': 0.0,
+ 'w_area': 1.86,
+ 'w_center': 0.14}):
+ '''
+ shape = [height, width]
+ '''
+ h, w, _ = image.shape
+ original_shape = [h, w]
+ device = next(iter(model.parameters())).device
+
+ resized_image = np.concatenate([cv2.resize(image, (input_shape[0], input_shape[1]), interpolation=cv2.INTER_AREA),
+ np.ones([input_shape[0], input_shape[1], 1])], axis=-1)
+ resized_image = resized_image.transpose((2, 0, 1))
+ batch_image = np.expand_dims(resized_image, axis=0).astype('float32')
+ batch_image = (batch_image / 127.5) - 1.0
+
+ batch_image = torch.from_numpy(batch_image).float().to(device)
+ outputs = model(batch_image)
+
+ pts, pts_score, vmap = deccode_output_score_and_ptss(outputs, 200, 3)
+ start = vmap[:, :, :2] # (x, y)
+ end = vmap[:, :, 2:] # (x, y)
+ dist_map = np.sqrt(np.sum((start - end) ** 2, axis=-1))
+
+ junc_list = []
+ segments_list = []
+ for junc, score in zip(pts, pts_score):
+ y, x = junc
+ distance = dist_map[y, x]
+ if score > params['score'] and distance > 20.0:
+ junc_list.append([x, y])
+ disp_x_start, disp_y_start, disp_x_end, disp_y_end = vmap[y, x, :]
+ d_arrow = 1.0
+ x_start = x + d_arrow * disp_x_start
+ y_start = y + d_arrow * disp_y_start
+ x_end = x + d_arrow * disp_x_end
+ y_end = y + d_arrow * disp_y_end
+ segments_list.append([x_start, y_start, x_end, y_end])
+
+ segments = np.array(segments_list)
+
+ ####### post processing for squares
+ # 1. get unique lines
+ point = np.array([[0, 0]])
+ point = point[0]
+ start = segments[:, :2]
+ end = segments[:, 2:]
+ diff = start - end
+ a = diff[:, 1]
+ b = -diff[:, 0]
+ c = a * start[:, 0] + b * start[:, 1]
+
+ d = np.abs(a * point[0] + b * point[1] - c) / np.sqrt(a ** 2 + b ** 2 + 1e-10)
+ theta = np.arctan2(diff[:, 0], diff[:, 1]) * 180 / np.pi
+ theta[theta < 0.0] += 180
+ hough = np.concatenate([d[:, None], theta[:, None]], axis=-1)
+
+ d_quant = 1
+ theta_quant = 2
+ hough[:, 0] //= d_quant
+ hough[:, 1] //= theta_quant
+ _, indices, counts = np.unique(hough, axis=0, return_index=True, return_counts=True)
+
+ acc_map = np.zeros([512 // d_quant + 1, 360 // theta_quant + 1], dtype='float32')
+ idx_map = np.zeros([512 // d_quant + 1, 360 // theta_quant + 1], dtype='int32') - 1
+ yx_indices = hough[indices, :].astype('int32')
+ acc_map[yx_indices[:, 0], yx_indices[:, 1]] = counts
+ idx_map[yx_indices[:, 0], yx_indices[:, 1]] = indices
+
+ acc_map_np = acc_map
+ # acc_map = acc_map[None, :, :, None]
+ #
+ # ### fast suppression using tensorflow op
+ # acc_map = tf.constant(acc_map, dtype=tf.float32)
+ # max_acc_map = tf.keras.layers.MaxPool2D(pool_size=(5, 5), strides=1, padding='same')(acc_map)
+ # acc_map = acc_map * tf.cast(tf.math.equal(acc_map, max_acc_map), tf.float32)
+ # flatten_acc_map = tf.reshape(acc_map, [1, -1])
+ # topk_values, topk_indices = tf.math.top_k(flatten_acc_map, k=len(pts))
+ # _, h, w, _ = acc_map.shape
+ # y = tf.expand_dims(topk_indices // w, axis=-1)
+ # x = tf.expand_dims(topk_indices % w, axis=-1)
+ # yx = tf.concat([y, x], axis=-1)
+
+ ### fast suppression using pytorch op
+ acc_map = torch.from_numpy(acc_map_np).unsqueeze(0).unsqueeze(0)
+ _,_, h, w = acc_map.shape
+ max_acc_map = F.max_pool2d(acc_map,kernel_size=5, stride=1, padding=2)
+ acc_map = acc_map * ( (acc_map == max_acc_map).float() )
+ flatten_acc_map = acc_map.reshape([-1, ])
+
+ scores, indices = torch.topk(flatten_acc_map, len(pts), dim=-1, largest=True)
+ yy = torch.div(indices, w, rounding_mode='floor').unsqueeze(-1)
+ xx = torch.fmod(indices, w).unsqueeze(-1)
+ yx = torch.cat((yy, xx), dim=-1)
+
+ yx = yx.detach().cpu().numpy()
+
+ topk_values = scores.detach().cpu().numpy()
+ indices = idx_map[yx[:, 0], yx[:, 1]]
+ basis = 5 // 2
+
+ merged_segments = []
+ for yx_pt, max_indice, value in zip(yx, indices, topk_values):
+ y, x = yx_pt
+ if max_indice == -1 or value == 0:
+ continue
+ segment_list = []
+ for y_offset in range(-basis, basis + 1):
+ for x_offset in range(-basis, basis + 1):
+ indice = idx_map[y + y_offset, x + x_offset]
+ cnt = int(acc_map_np[y + y_offset, x + x_offset])
+ if indice != -1:
+ segment_list.append(segments[indice])
+ if cnt > 1:
+ check_cnt = 1
+ current_hough = hough[indice]
+ for new_indice, new_hough in enumerate(hough):
+ if (current_hough == new_hough).all() and indice != new_indice:
+ segment_list.append(segments[new_indice])
+ check_cnt += 1
+ if check_cnt == cnt:
+ break
+ group_segments = np.array(segment_list).reshape([-1, 2])
+ sorted_group_segments = np.sort(group_segments, axis=0)
+ x_min, y_min = sorted_group_segments[0, :]
+ x_max, y_max = sorted_group_segments[-1, :]
+
+ deg = theta[max_indice]
+ if deg >= 90:
+ merged_segments.append([x_min, y_max, x_max, y_min])
+ else:
+ merged_segments.append([x_min, y_min, x_max, y_max])
+
+ # 2. get intersections
+ new_segments = np.array(merged_segments) # (x1, y1, x2, y2)
+ start = new_segments[:, :2] # (x1, y1)
+ end = new_segments[:, 2:] # (x2, y2)
+ new_centers = (start + end) / 2.0
+ diff = start - end
+ dist_segments = np.sqrt(np.sum(diff ** 2, axis=-1))
+
+ # ax + by = c
+ a = diff[:, 1]
+ b = -diff[:, 0]
+ c = a * start[:, 0] + b * start[:, 1]
+ pre_det = a[:, None] * b[None, :]
+ det = pre_det - np.transpose(pre_det)
+
+ pre_inter_y = a[:, None] * c[None, :]
+ inter_y = (pre_inter_y - np.transpose(pre_inter_y)) / (det + 1e-10)
+ pre_inter_x = c[:, None] * b[None, :]
+ inter_x = (pre_inter_x - np.transpose(pre_inter_x)) / (det + 1e-10)
+ inter_pts = np.concatenate([inter_x[:, :, None], inter_y[:, :, None]], axis=-1).astype('int32')
+
+ # 3. get corner information
+ # 3.1 get distance
+ '''
+ dist_segments:
+ | dist(0), dist(1), dist(2), ...|
+ dist_inter_to_segment1:
+ | dist(inter,0), dist(inter,0), dist(inter,0), ... |
+ | dist(inter,1), dist(inter,1), dist(inter,1), ... |
+ ...
+ dist_inter_to_semgnet2:
+ | dist(inter,0), dist(inter,1), dist(inter,2), ... |
+ | dist(inter,0), dist(inter,1), dist(inter,2), ... |
+ ...
+ '''
+
+ dist_inter_to_segment1_start = np.sqrt(
+ np.sum(((inter_pts - start[:, None, :]) ** 2), axis=-1, keepdims=True)) # [n_batch, n_batch, 1]
+ dist_inter_to_segment1_end = np.sqrt(
+ np.sum(((inter_pts - end[:, None, :]) ** 2), axis=-1, keepdims=True)) # [n_batch, n_batch, 1]
+ dist_inter_to_segment2_start = np.sqrt(
+ np.sum(((inter_pts - start[None, :, :]) ** 2), axis=-1, keepdims=True)) # [n_batch, n_batch, 1]
+ dist_inter_to_segment2_end = np.sqrt(
+ np.sum(((inter_pts - end[None, :, :]) ** 2), axis=-1, keepdims=True)) # [n_batch, n_batch, 1]
+
+ # sort ascending
+ dist_inter_to_segment1 = np.sort(
+ np.concatenate([dist_inter_to_segment1_start, dist_inter_to_segment1_end], axis=-1),
+ axis=-1) # [n_batch, n_batch, 2]
+ dist_inter_to_segment2 = np.sort(
+ np.concatenate([dist_inter_to_segment2_start, dist_inter_to_segment2_end], axis=-1),
+ axis=-1) # [n_batch, n_batch, 2]
+
+ # 3.2 get degree
+ inter_to_start = new_centers[:, None, :] - inter_pts
+ deg_inter_to_start = np.arctan2(inter_to_start[:, :, 1], inter_to_start[:, :, 0]) * 180 / np.pi
+ deg_inter_to_start[deg_inter_to_start < 0.0] += 360
+ inter_to_end = new_centers[None, :, :] - inter_pts
+ deg_inter_to_end = np.arctan2(inter_to_end[:, :, 1], inter_to_end[:, :, 0]) * 180 / np.pi
+ deg_inter_to_end[deg_inter_to_end < 0.0] += 360
+
+ '''
+ B -- G
+ | |
+ C -- R
+ B : blue / G: green / C: cyan / R: red
+
+ 0 -- 1
+ | |
+ 3 -- 2
+ '''
+ # rename variables
+ deg1_map, deg2_map = deg_inter_to_start, deg_inter_to_end
+ # sort deg ascending
+ deg_sort = np.sort(np.concatenate([deg1_map[:, :, None], deg2_map[:, :, None]], axis=-1), axis=-1)
+
+ deg_diff_map = np.abs(deg1_map - deg2_map)
+ # we only consider the smallest degree of intersect
+ deg_diff_map[deg_diff_map > 180] = 360 - deg_diff_map[deg_diff_map > 180]
+
+ # define available degree range
+ deg_range = [60, 120]
+
+ corner_dict = {corner_info: [] for corner_info in range(4)}
+ inter_points = []
+ for i in range(inter_pts.shape[0]):
+ for j in range(i + 1, inter_pts.shape[1]):
+ # i, j > line index, always i < j
+ x, y = inter_pts[i, j, :]
+ deg1, deg2 = deg_sort[i, j, :]
+ deg_diff = deg_diff_map[i, j]
+
+ check_degree = deg_diff > deg_range[0] and deg_diff < deg_range[1]
+
+ outside_ratio = params['outside_ratio'] # over ratio >>> drop it!
+ inside_ratio = params['inside_ratio'] # over ratio >>> drop it!
+ check_distance = ((dist_inter_to_segment1[i, j, 1] >= dist_segments[i] and \
+ dist_inter_to_segment1[i, j, 0] <= dist_segments[i] * outside_ratio) or \
+ (dist_inter_to_segment1[i, j, 1] <= dist_segments[i] and \
+ dist_inter_to_segment1[i, j, 0] <= dist_segments[i] * inside_ratio)) and \
+ ((dist_inter_to_segment2[i, j, 1] >= dist_segments[j] and \
+ dist_inter_to_segment2[i, j, 0] <= dist_segments[j] * outside_ratio) or \
+ (dist_inter_to_segment2[i, j, 1] <= dist_segments[j] and \
+ dist_inter_to_segment2[i, j, 0] <= dist_segments[j] * inside_ratio))
+
+ if check_degree and check_distance:
+ corner_info = None
+
+ if (deg1 >= 0 and deg1 <= 45 and deg2 >= 45 and deg2 <= 120) or \
+ (deg2 >= 315 and deg1 >= 45 and deg1 <= 120):
+ corner_info, color_info = 0, 'blue'
+ elif (deg1 >= 45 and deg1 <= 125 and deg2 >= 125 and deg2 <= 225):
+ corner_info, color_info = 1, 'green'
+ elif (deg1 >= 125 and deg1 <= 225 and deg2 >= 225 and deg2 <= 315):
+ corner_info, color_info = 2, 'black'
+ elif (deg1 >= 0 and deg1 <= 45 and deg2 >= 225 and deg2 <= 315) or \
+ (deg2 >= 315 and deg1 >= 225 and deg1 <= 315):
+ corner_info, color_info = 3, 'cyan'
+ else:
+ corner_info, color_info = 4, 'red' # we don't use it
+ continue
+
+ corner_dict[corner_info].append([x, y, i, j])
+ inter_points.append([x, y])
+
+ square_list = []
+ connect_list = []
+ segments_list = []
+ for corner0 in corner_dict[0]:
+ for corner1 in corner_dict[1]:
+ connect01 = False
+ for corner0_line in corner0[2:]:
+ if corner0_line in corner1[2:]:
+ connect01 = True
+ break
+ if connect01:
+ for corner2 in corner_dict[2]:
+ connect12 = False
+ for corner1_line in corner1[2:]:
+ if corner1_line in corner2[2:]:
+ connect12 = True
+ break
+ if connect12:
+ for corner3 in corner_dict[3]:
+ connect23 = False
+ for corner2_line in corner2[2:]:
+ if corner2_line in corner3[2:]:
+ connect23 = True
+ break
+ if connect23:
+ for corner3_line in corner3[2:]:
+ if corner3_line in corner0[2:]:
+ # SQUARE!!!
+ '''
+ 0 -- 1
+ | |
+ 3 -- 2
+ square_list:
+ order: 0 > 1 > 2 > 3
+ | x0, y0, x1, y1, x2, y2, x3, y3 |
+ | x0, y0, x1, y1, x2, y2, x3, y3 |
+ ...
+ connect_list:
+ order: 01 > 12 > 23 > 30
+ | line_idx01, line_idx12, line_idx23, line_idx30 |
+ | line_idx01, line_idx12, line_idx23, line_idx30 |
+ ...
+ segments_list:
+ order: 0 > 1 > 2 > 3
+ | line_idx0_i, line_idx0_j, line_idx1_i, line_idx1_j, line_idx2_i, line_idx2_j, line_idx3_i, line_idx3_j |
+ | line_idx0_i, line_idx0_j, line_idx1_i, line_idx1_j, line_idx2_i, line_idx2_j, line_idx3_i, line_idx3_j |
+ ...
+ '''
+ square_list.append(corner0[:2] + corner1[:2] + corner2[:2] + corner3[:2])
+ connect_list.append([corner0_line, corner1_line, corner2_line, corner3_line])
+ segments_list.append(corner0[2:] + corner1[2:] + corner2[2:] + corner3[2:])
+
+ def check_outside_inside(segments_info, connect_idx):
+ # return 'outside or inside', min distance, cover_param, peri_param
+ if connect_idx == segments_info[0]:
+ check_dist_mat = dist_inter_to_segment1
+ else:
+ check_dist_mat = dist_inter_to_segment2
+
+ i, j = segments_info
+ min_dist, max_dist = check_dist_mat[i, j, :]
+ connect_dist = dist_segments[connect_idx]
+ if max_dist > connect_dist:
+ return 'outside', min_dist, 0, 1
+ else:
+ return 'inside', min_dist, -1, -1
+
+ top_square = None
+
+ try:
+ map_size = input_shape[0] / 2
+ squares = np.array(square_list).reshape([-1, 4, 2])
+ score_array = []
+ connect_array = np.array(connect_list)
+ segments_array = np.array(segments_list).reshape([-1, 4, 2])
+
+ # get degree of corners:
+ squares_rollup = np.roll(squares, 1, axis=1)
+ squares_rolldown = np.roll(squares, -1, axis=1)
+ vec1 = squares_rollup - squares
+ normalized_vec1 = vec1 / (np.linalg.norm(vec1, axis=-1, keepdims=True) + 1e-10)
+ vec2 = squares_rolldown - squares
+ normalized_vec2 = vec2 / (np.linalg.norm(vec2, axis=-1, keepdims=True) + 1e-10)
+ inner_products = np.sum(normalized_vec1 * normalized_vec2, axis=-1) # [n_squares, 4]
+ squares_degree = np.arccos(inner_products) * 180 / np.pi # [n_squares, 4]
+
+ # get square score
+ overlap_scores = []
+ degree_scores = []
+ length_scores = []
+
+ for connects, segments, square, degree in zip(connect_array, segments_array, squares, squares_degree):
+ '''
+ 0 -- 1
+ | |
+ 3 -- 2
+
+ # segments: [4, 2]
+ # connects: [4]
+ '''
+
+ ###################################### OVERLAP SCORES
+ cover = 0
+ perimeter = 0
+ # check 0 > 1 > 2 > 3
+ square_length = []
+
+ for start_idx in range(4):
+ end_idx = (start_idx + 1) % 4
+
+ connect_idx = connects[start_idx] # segment idx of segment01
+ start_segments = segments[start_idx]
+ end_segments = segments[end_idx]
+
+ start_point = square[start_idx]
+ end_point = square[end_idx]
+
+ # check whether outside or inside
+ start_position, start_min, start_cover_param, start_peri_param = check_outside_inside(start_segments,
+ connect_idx)
+ end_position, end_min, end_cover_param, end_peri_param = check_outside_inside(end_segments, connect_idx)
+
+ cover += dist_segments[connect_idx] + start_cover_param * start_min + end_cover_param * end_min
+ perimeter += dist_segments[connect_idx] + start_peri_param * start_min + end_peri_param * end_min
+
+ square_length.append(
+ dist_segments[connect_idx] + start_peri_param * start_min + end_peri_param * end_min)
+
+ overlap_scores.append(cover / perimeter)
+ ######################################
+ ###################################### DEGREE SCORES
+ '''
+ deg0 vs deg2
+ deg1 vs deg3
+ '''
+ deg0, deg1, deg2, deg3 = degree
+ deg_ratio1 = deg0 / deg2
+ if deg_ratio1 > 1.0:
+ deg_ratio1 = 1 / deg_ratio1
+ deg_ratio2 = deg1 / deg3
+ if deg_ratio2 > 1.0:
+ deg_ratio2 = 1 / deg_ratio2
+ degree_scores.append((deg_ratio1 + deg_ratio2) / 2)
+ ######################################
+ ###################################### LENGTH SCORES
+ '''
+ len0 vs len2
+ len1 vs len3
+ '''
+ len0, len1, len2, len3 = square_length
+ len_ratio1 = len0 / len2 if len2 > len0 else len2 / len0
+ len_ratio2 = len1 / len3 if len3 > len1 else len3 / len1
+ length_scores.append((len_ratio1 + len_ratio2) / 2)
+
+ ######################################
+
+ overlap_scores = np.array(overlap_scores)
+ overlap_scores /= np.max(overlap_scores)
+
+ degree_scores = np.array(degree_scores)
+ # degree_scores /= np.max(degree_scores)
+
+ length_scores = np.array(length_scores)
+
+ ###################################### AREA SCORES
+ area_scores = np.reshape(squares, [-1, 4, 2])
+ area_x = area_scores[:, :, 0]
+ area_y = area_scores[:, :, 1]
+ correction = area_x[:, -1] * area_y[:, 0] - area_y[:, -1] * area_x[:, 0]
+ area_scores = np.sum(area_x[:, :-1] * area_y[:, 1:], axis=-1) - np.sum(area_y[:, :-1] * area_x[:, 1:], axis=-1)
+ area_scores = 0.5 * np.abs(area_scores + correction)
+ area_scores /= (map_size * map_size) # np.max(area_scores)
+ ######################################
+
+ ###################################### CENTER SCORES
+ centers = np.array([[256 // 2, 256 // 2]], dtype='float32') # [1, 2]
+ # squares: [n, 4, 2]
+ square_centers = np.mean(squares, axis=1) # [n, 2]
+ center2center = np.sqrt(np.sum((centers - square_centers) ** 2))
+ center_scores = center2center / (map_size / np.sqrt(2.0))
+
+ '''
+ score_w = [overlap, degree, area, center, length]
+ '''
+ score_w = [0.0, 1.0, 10.0, 0.5, 1.0]
+ score_array = params['w_overlap'] * overlap_scores \
+ + params['w_degree'] * degree_scores \
+ + params['w_area'] * area_scores \
+ - params['w_center'] * center_scores \
+ + params['w_length'] * length_scores
+
+ best_square = []
+
+ sorted_idx = np.argsort(score_array)[::-1]
+ score_array = score_array[sorted_idx]
+ squares = squares[sorted_idx]
+
+ except Exception as e:
+ pass
+
+ '''return list
+ merged_lines, squares, scores
+ '''
+
+ try:
+ new_segments[:, 0] = new_segments[:, 0] * 2 / input_shape[1] * original_shape[1]
+ new_segments[:, 1] = new_segments[:, 1] * 2 / input_shape[0] * original_shape[0]
+ new_segments[:, 2] = new_segments[:, 2] * 2 / input_shape[1] * original_shape[1]
+ new_segments[:, 3] = new_segments[:, 3] * 2 / input_shape[0] * original_shape[0]
+ except:
+ new_segments = []
+
+ try:
+ squares[:, :, 0] = squares[:, :, 0] * 2 / input_shape[1] * original_shape[1]
+ squares[:, :, 1] = squares[:, :, 1] * 2 / input_shape[0] * original_shape[0]
+ except:
+ squares = []
+ score_array = []
+
+ try:
+ inter_points = np.array(inter_points)
+ inter_points[:, 0] = inter_points[:, 0] * 2 / input_shape[1] * original_shape[1]
+ inter_points[:, 1] = inter_points[:, 1] * 2 / input_shape[0] * original_shape[0]
+ except:
+ inter_points = []
+
+ return new_segments, squares, score_array, inter_points
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/LICENSE b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..16a9d56a3d4c15e4f34ac5426459c58487b01520
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Caroline Chan
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..c03f81f6ff9ae42fb9e5a8495bf45338acb51b1b
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/__init__.py
@@ -0,0 +1,85 @@
+import os
+import types
+import warnings
+
+import cv2
+import numpy as np
+import torch
+import torchvision.transforms as transforms
+from einops import rearrange
+from PIL import Image
+
+from controlnet_aux.util import HWC3, common_input_validate, resize_image_with_pad, annotator_ckpts_path, custom_hf_download
+from .nets.NNET import NNET
+
+
+# load model
+def load_checkpoint(fpath, model):
+ ckpt = torch.load(fpath, map_location='cpu')['model']
+
+ load_dict = {}
+ for k, v in ckpt.items():
+ if k.startswith('module.'):
+ k_ = k.replace('module.', '')
+ load_dict[k_] = v
+ else:
+ load_dict[k] = v
+
+ model.load_state_dict(load_dict)
+ return model
+
+class NormalBaeDetector:
+ def __init__(self, model):
+ self.model = model
+ self.norm = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
+
+ @classmethod
+ def from_pretrained(cls, pretrained_model_or_path, filename=None, cache_dir=annotator_ckpts_path):
+ filename = filename or "scannet.pt"
+ model_path = custom_hf_download(pretrained_model_or_path, filename, cache_dir=cache_dir)
+
+ args = types.SimpleNamespace()
+ args.mode = 'client'
+ args.architecture = 'BN'
+ args.pretrained = 'scannet'
+ args.sampling_ratio = 0.4
+ args.importance_ratio = 0.7
+ model = NNET(args)
+ model = load_checkpoint(model_path, model)
+ model.eval()
+
+ return cls(model)
+
+ def to(self, device):
+ self.model.to(device)
+ return self
+
+
+ def __call__(self, input_image, detect_resolution=512, output_type="pil", upscale_method="INTER_CUBIC", **kwargs):
+ input_image, output_type = common_input_validate(input_image, output_type, **kwargs)
+ detected_map, remove_pad = resize_image_with_pad(input_image, detect_resolution, upscale_method)
+ device = next(iter(self.model.parameters())).device
+ image_normal = detected_map
+ with torch.no_grad():
+ image_normal = torch.from_numpy(image_normal).float().to(device)
+ image_normal = image_normal / 255.0
+ image_normal = rearrange(image_normal, 'h w c -> 1 c h w')
+ image_normal = self.norm(image_normal)
+
+ normal = self.model(image_normal)
+ normal = normal[0][-1][:, :3]
+ # d = torch.sum(normal ** 2.0, dim=1, keepdim=True) ** 0.5
+ # d = torch.maximum(d, torch.ones_like(d) * 1e-5)
+ # normal /= d
+ normal = ((normal + 1) * 0.5).clip(0, 1)
+
+ normal = rearrange(normal[0], 'c h w -> h w c').cpu().numpy()
+ normal_image = (normal * 255.0).clip(0, 255).astype(np.uint8)
+
+ detected_map = remove_pad(HWC3(normal_image))
+
+ if output_type == "pil":
+ detected_map = Image.fromarray(detected_map)
+
+ return detected_map
+
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/NNET.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/NNET.py
new file mode 100644
index 0000000000000000000000000000000000000000..3ddbc50c3ac18aa4b7f16779fe3c0133981ecc7a
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/NNET.py
@@ -0,0 +1,22 @@
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+
+from .submodules.encoder import Encoder
+from .submodules.decoder import Decoder
+
+
+class NNET(nn.Module):
+ def __init__(self, args):
+ super(NNET, self).__init__()
+ self.encoder = Encoder()
+ self.decoder = Decoder(args)
+
+ def get_1x_lr_params(self): # lr/10 learning rate
+ return self.encoder.parameters()
+
+ def get_10x_lr_params(self): # lr learning rate
+ return self.decoder.parameters()
+
+ def forward(self, img, **kwargs):
+ return self.decoder(self.encoder(img), **kwargs)
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/baseline.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/baseline.py
new file mode 100644
index 0000000000000000000000000000000000000000..602d0fbdac1acc9ede9bc1f2e10a5df78831ce9d
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/baseline.py
@@ -0,0 +1,85 @@
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+
+from .submodules.submodules import UpSampleBN, norm_normalize
+
+
+# This is the baseline encoder-decoder we used in the ablation study
+class NNET(nn.Module):
+ def __init__(self, args=None):
+ super(NNET, self).__init__()
+ self.encoder = Encoder()
+ self.decoder = Decoder(num_classes=4)
+
+ def forward(self, x, **kwargs):
+ out = self.decoder(self.encoder(x), **kwargs)
+
+ # Bilinearly upsample the output to match the input resolution
+ up_out = F.interpolate(out, size=[x.size(2), x.size(3)], mode='bilinear', align_corners=False)
+
+ # L2-normalize the first three channels / ensure positive value for concentration parameters (kappa)
+ up_out = norm_normalize(up_out)
+ return up_out
+
+ def get_1x_lr_params(self): # lr/10 learning rate
+ return self.encoder.parameters()
+
+ def get_10x_lr_params(self): # lr learning rate
+ modules = [self.decoder]
+ for m in modules:
+ yield from m.parameters()
+
+
+# Encoder
+class Encoder(nn.Module):
+ def __init__(self):
+ super(Encoder, self).__init__()
+
+ basemodel_name = 'tf_efficientnet_b5_ap'
+ basemodel = torch.hub.load('rwightman/gen-efficientnet-pytorch', basemodel_name, pretrained=True)
+
+ # Remove last layer
+ basemodel.global_pool = nn.Identity()
+ basemodel.classifier = nn.Identity()
+
+ self.original_model = basemodel
+
+ def forward(self, x):
+ features = [x]
+ for k, v in self.original_model._modules.items():
+ if (k == 'blocks'):
+ for ki, vi in v._modules.items():
+ features.append(vi(features[-1]))
+ else:
+ features.append(v(features[-1]))
+ return features
+
+
+# Decoder (no pixel-wise MLP, no uncertainty-guided sampling)
+class Decoder(nn.Module):
+ def __init__(self, num_classes=4):
+ super(Decoder, self).__init__()
+ self.conv2 = nn.Conv2d(2048, 2048, kernel_size=1, stride=1, padding=0)
+ self.up1 = UpSampleBN(skip_input=2048 + 176, output_features=1024)
+ self.up2 = UpSampleBN(skip_input=1024 + 64, output_features=512)
+ self.up3 = UpSampleBN(skip_input=512 + 40, output_features=256)
+ self.up4 = UpSampleBN(skip_input=256 + 24, output_features=128)
+ self.conv3 = nn.Conv2d(128, num_classes, kernel_size=3, stride=1, padding=1)
+
+ def forward(self, features):
+ x_block0, x_block1, x_block2, x_block3, x_block4 = features[4], features[5], features[6], features[8], features[11]
+ x_d0 = self.conv2(x_block4)
+ x_d1 = self.up1(x_d0, x_block3)
+ x_d2 = self.up2(x_d1, x_block2)
+ x_d3 = self.up3(x_d2, x_block1)
+ x_d4 = self.up4(x_d3, x_block0)
+ out = self.conv3(x_d4)
+ return out
+
+
+if __name__ == '__main__':
+ model = Baseline()
+ x = torch.rand(2, 3, 480, 640)
+ out = model(x)
+ print(out.shape)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/decoder.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/decoder.py
new file mode 100644
index 0000000000000000000000000000000000000000..993203d1792311f1c492091eaea3c1ac9088187f
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/decoder.py
@@ -0,0 +1,202 @@
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+from .submodules import UpSampleBN, UpSampleGN, norm_normalize, sample_points
+
+
+class Decoder(nn.Module):
+ def __init__(self, args):
+ super(Decoder, self).__init__()
+
+ # hyper-parameter for sampling
+ self.sampling_ratio = args.sampling_ratio
+ self.importance_ratio = args.importance_ratio
+
+ # feature-map
+ self.conv2 = nn.Conv2d(2048, 2048, kernel_size=1, stride=1, padding=0)
+ if args.architecture == 'BN':
+ self.up1 = UpSampleBN(skip_input=2048 + 176, output_features=1024)
+ self.up2 = UpSampleBN(skip_input=1024 + 64, output_features=512)
+ self.up3 = UpSampleBN(skip_input=512 + 40, output_features=256)
+ self.up4 = UpSampleBN(skip_input=256 + 24, output_features=128)
+
+ elif args.architecture == 'GN':
+ self.up1 = UpSampleGN(skip_input=2048 + 176, output_features=1024)
+ self.up2 = UpSampleGN(skip_input=1024 + 64, output_features=512)
+ self.up3 = UpSampleGN(skip_input=512 + 40, output_features=256)
+ self.up4 = UpSampleGN(skip_input=256 + 24, output_features=128)
+
+ else:
+ raise Exception('invalid architecture')
+
+ # produces 1/8 res output
+ self.out_conv_res8 = nn.Conv2d(512, 4, kernel_size=3, stride=1, padding=1)
+
+ # produces 1/4 res output
+ self.out_conv_res4 = nn.Sequential(
+ nn.Conv1d(512 + 4, 128, kernel_size=1), nn.ReLU(),
+ nn.Conv1d(128, 128, kernel_size=1), nn.ReLU(),
+ nn.Conv1d(128, 128, kernel_size=1), nn.ReLU(),
+ nn.Conv1d(128, 4, kernel_size=1),
+ )
+
+ # produces 1/2 res output
+ self.out_conv_res2 = nn.Sequential(
+ nn.Conv1d(256 + 4, 128, kernel_size=1), nn.ReLU(),
+ nn.Conv1d(128, 128, kernel_size=1), nn.ReLU(),
+ nn.Conv1d(128, 128, kernel_size=1), nn.ReLU(),
+ nn.Conv1d(128, 4, kernel_size=1),
+ )
+
+ # produces 1/1 res output
+ self.out_conv_res1 = nn.Sequential(
+ nn.Conv1d(128 + 4, 128, kernel_size=1), nn.ReLU(),
+ nn.Conv1d(128, 128, kernel_size=1), nn.ReLU(),
+ nn.Conv1d(128, 128, kernel_size=1), nn.ReLU(),
+ nn.Conv1d(128, 4, kernel_size=1),
+ )
+
+ def forward(self, features, gt_norm_mask=None, mode='test'):
+ x_block0, x_block1, x_block2, x_block3, x_block4 = features[4], features[5], features[6], features[8], features[11]
+
+ # generate feature-map
+
+ x_d0 = self.conv2(x_block4) # x_d0 : [2, 2048, 15, 20] 1/32 res
+ x_d1 = self.up1(x_d0, x_block3) # x_d1 : [2, 1024, 30, 40] 1/16 res
+ x_d2 = self.up2(x_d1, x_block2) # x_d2 : [2, 512, 60, 80] 1/8 res
+ x_d3 = self.up3(x_d2, x_block1) # x_d3: [2, 256, 120, 160] 1/4 res
+ x_d4 = self.up4(x_d3, x_block0) # x_d4: [2, 128, 240, 320] 1/2 res
+
+ # 1/8 res output
+ out_res8 = self.out_conv_res8(x_d2) # out_res8: [2, 4, 60, 80] 1/8 res output
+ out_res8 = norm_normalize(out_res8) # out_res8: [2, 4, 60, 80] 1/8 res output
+
+ ################################################################################################################
+ # out_res4
+ ################################################################################################################
+
+ if mode == 'train':
+ # upsampling ... out_res8: [2, 4, 60, 80] -> out_res8_res4: [2, 4, 120, 160]
+ out_res8_res4 = F.interpolate(out_res8, scale_factor=2, mode='bilinear', align_corners=True)
+ B, _, H, W = out_res8_res4.shape
+
+ # samples: [B, 1, N, 2]
+ point_coords_res4, rows_int, cols_int = sample_points(out_res8_res4.detach(), gt_norm_mask,
+ sampling_ratio=self.sampling_ratio,
+ beta=self.importance_ratio)
+
+ # output (needed for evaluation / visualization)
+ out_res4 = out_res8_res4
+
+ # grid_sample feature-map
+ feat_res4 = F.grid_sample(x_d2, point_coords_res4, mode='bilinear', align_corners=True) # (B, 512, 1, N)
+ init_pred = F.grid_sample(out_res8, point_coords_res4, mode='bilinear', align_corners=True) # (B, 4, 1, N)
+ feat_res4 = torch.cat([feat_res4, init_pred], dim=1) # (B, 512+4, 1, N)
+
+ # prediction (needed to compute loss)
+ samples_pred_res4 = self.out_conv_res4(feat_res4[:, :, 0, :]) # (B, 4, N)
+ samples_pred_res4 = norm_normalize(samples_pred_res4) # (B, 4, N) - normalized
+
+ for i in range(B):
+ out_res4[i, :, rows_int[i, :], cols_int[i, :]] = samples_pred_res4[i, :, :]
+
+ else:
+ # grid_sample feature-map
+ feat_map = F.interpolate(x_d2, scale_factor=2, mode='bilinear', align_corners=True)
+ init_pred = F.interpolate(out_res8, scale_factor=2, mode='bilinear', align_corners=True)
+ feat_map = torch.cat([feat_map, init_pred], dim=1) # (B, 512+4, H, W)
+ B, _, H, W = feat_map.shape
+
+ # try all pixels
+ out_res4 = self.out_conv_res4(feat_map.view(B, 512 + 4, -1)) # (B, 4, N)
+ out_res4 = norm_normalize(out_res4) # (B, 4, N) - normalized
+ out_res4 = out_res4.view(B, 4, H, W)
+ samples_pred_res4 = point_coords_res4 = None
+
+ ################################################################################################################
+ # out_res2
+ ################################################################################################################
+
+ if mode == 'train':
+
+ # upsampling ... out_res4: [2, 4, 120, 160] -> out_res4_res2: [2, 4, 240, 320]
+ out_res4_res2 = F.interpolate(out_res4, scale_factor=2, mode='bilinear', align_corners=True)
+ B, _, H, W = out_res4_res2.shape
+
+ # samples: [B, 1, N, 2]
+ point_coords_res2, rows_int, cols_int = sample_points(out_res4_res2.detach(), gt_norm_mask,
+ sampling_ratio=self.sampling_ratio,
+ beta=self.importance_ratio)
+
+ # output (needed for evaluation / visualization)
+ out_res2 = out_res4_res2
+
+ # grid_sample feature-map
+ feat_res2 = F.grid_sample(x_d3, point_coords_res2, mode='bilinear', align_corners=True) # (B, 256, 1, N)
+ init_pred = F.grid_sample(out_res4, point_coords_res2, mode='bilinear', align_corners=True) # (B, 4, 1, N)
+ feat_res2 = torch.cat([feat_res2, init_pred], dim=1) # (B, 256+4, 1, N)
+
+ # prediction (needed to compute loss)
+ samples_pred_res2 = self.out_conv_res2(feat_res2[:, :, 0, :]) # (B, 4, N)
+ samples_pred_res2 = norm_normalize(samples_pred_res2) # (B, 4, N) - normalized
+
+ for i in range(B):
+ out_res2[i, :, rows_int[i, :], cols_int[i, :]] = samples_pred_res2[i, :, :]
+
+ else:
+ # grid_sample feature-map
+ feat_map = F.interpolate(x_d3, scale_factor=2, mode='bilinear', align_corners=True)
+ init_pred = F.interpolate(out_res4, scale_factor=2, mode='bilinear', align_corners=True)
+ feat_map = torch.cat([feat_map, init_pred], dim=1) # (B, 512+4, H, W)
+ B, _, H, W = feat_map.shape
+
+ out_res2 = self.out_conv_res2(feat_map.view(B, 256 + 4, -1)) # (B, 4, N)
+ out_res2 = norm_normalize(out_res2) # (B, 4, N) - normalized
+ out_res2 = out_res2.view(B, 4, H, W)
+ samples_pred_res2 = point_coords_res2 = None
+
+ ################################################################################################################
+ # out_res1
+ ################################################################################################################
+
+ if mode == 'train':
+ # upsampling ... out_res4: [2, 4, 120, 160] -> out_res4_res2: [2, 4, 240, 320]
+ out_res2_res1 = F.interpolate(out_res2, scale_factor=2, mode='bilinear', align_corners=True)
+ B, _, H, W = out_res2_res1.shape
+
+ # samples: [B, 1, N, 2]
+ point_coords_res1, rows_int, cols_int = sample_points(out_res2_res1.detach(), gt_norm_mask,
+ sampling_ratio=self.sampling_ratio,
+ beta=self.importance_ratio)
+
+ # output (needed for evaluation / visualization)
+ out_res1 = out_res2_res1
+
+ # grid_sample feature-map
+ feat_res1 = F.grid_sample(x_d4, point_coords_res1, mode='bilinear', align_corners=True) # (B, 128, 1, N)
+ init_pred = F.grid_sample(out_res2, point_coords_res1, mode='bilinear', align_corners=True) # (B, 4, 1, N)
+ feat_res1 = torch.cat([feat_res1, init_pred], dim=1) # (B, 128+4, 1, N)
+
+ # prediction (needed to compute loss)
+ samples_pred_res1 = self.out_conv_res1(feat_res1[:, :, 0, :]) # (B, 4, N)
+ samples_pred_res1 = norm_normalize(samples_pred_res1) # (B, 4, N) - normalized
+
+ for i in range(B):
+ out_res1[i, :, rows_int[i, :], cols_int[i, :]] = samples_pred_res1[i, :, :]
+
+ else:
+ # grid_sample feature-map
+ feat_map = F.interpolate(x_d4, scale_factor=2, mode='bilinear', align_corners=True)
+ init_pred = F.interpolate(out_res2, scale_factor=2, mode='bilinear', align_corners=True)
+ feat_map = torch.cat([feat_map, init_pred], dim=1) # (B, 512+4, H, W)
+ B, _, H, W = feat_map.shape
+
+ out_res1 = self.out_conv_res1(feat_map.view(B, 128 + 4, -1)) # (B, 4, N)
+ out_res1 = norm_normalize(out_res1) # (B, 4, N) - normalized
+ out_res1 = out_res1.view(B, 4, H, W)
+ samples_pred_res1 = point_coords_res1 = None
+
+ return [out_res8, out_res4, out_res2, out_res1], \
+ [out_res8, samples_pred_res4, samples_pred_res2, samples_pred_res1], \
+ [None, point_coords_res4, point_coords_res2, point_coords_res1]
+
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/.gitignore b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..f04e5fff91094d9b9c662bba977d762bf71516ac
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/.gitignore
@@ -0,0 +1,109 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# pytorch stuff
+*.pth
+*.onnx
+*.pb
+
+trained_models/
+.fuse_hidden*
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/BENCHMARK.md b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/BENCHMARK.md
new file mode 100644
index 0000000000000000000000000000000000000000..6ead7171ce5a5bbd2702f6b5c825dc9808ba5658
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/BENCHMARK.md
@@ -0,0 +1,555 @@
+# Model Performance Benchmarks
+
+All benchmarks run as per:
+
+```
+python onnx_export.py --model mobilenetv3_100 ./mobilenetv3_100.onnx
+python onnx_optimize.py ./mobilenetv3_100.onnx --output mobilenetv3_100-opt.onnx
+python onnx_to_caffe.py ./mobilenetv3_100.onnx --c2-prefix mobilenetv3
+python onnx_to_caffe.py ./mobilenetv3_100-opt.onnx --c2-prefix mobilenetv3-opt
+python caffe2_benchmark.py --c2-init ./mobilenetv3.init.pb --c2-predict ./mobilenetv3.predict.pb
+python caffe2_benchmark.py --c2-init ./mobilenetv3-opt.init.pb --c2-predict ./mobilenetv3-opt.predict.pb
+```
+
+## EfficientNet-B0
+
+### Unoptimized
+```
+Main run finished. Milliseconds per iter: 49.2862. Iters per second: 20.2897
+Time per operator type:
+ 29.7378 ms. 60.5145%. Conv
+ 12.1785 ms. 24.7824%. Sigmoid
+ 3.62811 ms. 7.38297%. SpatialBN
+ 2.98444 ms. 6.07314%. Mul
+ 0.326902 ms. 0.665225%. AveragePool
+ 0.197317 ms. 0.401528%. FC
+ 0.0852877 ms. 0.173555%. Add
+ 0.0032607 ms. 0.00663532%. Squeeze
+ 49.1416 ms in Total
+FLOP per operator type:
+ 0.76907 GFLOP. 95.2696%. Conv
+ 0.0269508 GFLOP. 3.33857%. SpatialBN
+ 0.00846444 GFLOP. 1.04855%. Mul
+ 0.002561 GFLOP. 0.317248%. FC
+ 0.000210112 GFLOP. 0.0260279%. Add
+ 0.807256 GFLOP in Total
+Feature Memory Read per operator type:
+ 58.5253 MB. 43.0891%. Mul
+ 43.2015 MB. 31.807%. Conv
+ 27.2869 MB. 20.0899%. SpatialBN
+ 5.12912 MB. 3.77631%. FC
+ 1.6809 MB. 1.23756%. Add
+ 135.824 MB in Total
+Feature Memory Written per operator type:
+ 33.8578 MB. 38.1965%. Mul
+ 26.9881 MB. 30.4465%. Conv
+ 26.9508 MB. 30.4044%. SpatialBN
+ 0.840448 MB. 0.948147%. Add
+ 0.004 MB. 0.00451258%. FC
+ 88.6412 MB in Total
+Parameter Memory per operator type:
+ 15.8248 MB. 74.9391%. Conv
+ 5.124 MB. 24.265%. FC
+ 0.168064 MB. 0.795877%. SpatialBN
+ 0 MB. 0%. Add
+ 0 MB. 0%. Mul
+ 21.1168 MB in Total
+```
+### Optimized
+```
+Main run finished. Milliseconds per iter: 46.0838. Iters per second: 21.6996
+Time per operator type:
+ 29.776 ms. 65.002%. Conv
+ 12.2803 ms. 26.8084%. Sigmoid
+ 3.15073 ms. 6.87815%. Mul
+ 0.328651 ms. 0.717456%. AveragePool
+ 0.186237 ms. 0.406563%. FC
+ 0.0832429 ms. 0.181722%. Add
+ 0.0026184 ms. 0.00571606%. Squeeze
+ 45.8078 ms in Total
+FLOP per operator type:
+ 0.76907 GFLOP. 98.5601%. Conv
+ 0.00846444 GFLOP. 1.08476%. Mul
+ 0.002561 GFLOP. 0.328205%. FC
+ 0.000210112 GFLOP. 0.0269269%. Add
+ 0.780305 GFLOP in Total
+Feature Memory Read per operator type:
+ 58.5253 MB. 53.8803%. Mul
+ 43.2855 MB. 39.8501%. Conv
+ 5.12912 MB. 4.72204%. FC
+ 1.6809 MB. 1.54749%. Add
+ 108.621 MB in Total
+Feature Memory Written per operator type:
+ 33.8578 MB. 54.8834%. Mul
+ 26.9881 MB. 43.7477%. Conv
+ 0.840448 MB. 1.36237%. Add
+ 0.004 MB. 0.00648399%. FC
+ 61.6904 MB in Total
+Parameter Memory per operator type:
+ 15.8248 MB. 75.5403%. Conv
+ 5.124 MB. 24.4597%. FC
+ 0 MB. 0%. Add
+ 0 MB. 0%. Mul
+ 20.9488 MB in Total
+```
+
+## EfficientNet-B1
+### Optimized
+```
+Main run finished. Milliseconds per iter: 71.8102. Iters per second: 13.9256
+Time per operator type:
+ 45.7915 ms. 66.3206%. Conv
+ 17.8718 ms. 25.8841%. Sigmoid
+ 4.44132 ms. 6.43244%. Mul
+ 0.51001 ms. 0.738658%. AveragePool
+ 0.233283 ms. 0.337868%. Add
+ 0.194986 ms. 0.282402%. FC
+ 0.00268255 ms. 0.00388519%. Squeeze
+ 69.0456 ms in Total
+FLOP per operator type:
+ 1.37105 GFLOP. 98.7673%. Conv
+ 0.0138759 GFLOP. 0.99959%. Mul
+ 0.002561 GFLOP. 0.184489%. FC
+ 0.000674432 GFLOP. 0.0485847%. Add
+ 1.38816 GFLOP in Total
+Feature Memory Read per operator type:
+ 94.624 MB. 54.0789%. Mul
+ 69.8255 MB. 39.9062%. Conv
+ 5.39546 MB. 3.08357%. Add
+ 5.12912 MB. 2.93136%. FC
+ 174.974 MB in Total
+Feature Memory Written per operator type:
+ 55.5035 MB. 54.555%. Mul
+ 43.5333 MB. 42.7894%. Conv
+ 2.69773 MB. 2.65163%. Add
+ 0.004 MB. 0.00393165%. FC
+ 101.739 MB in Total
+Parameter Memory per operator type:
+ 25.7479 MB. 83.4024%. Conv
+ 5.124 MB. 16.5976%. FC
+ 0 MB. 0%. Add
+ 0 MB. 0%. Mul
+ 30.8719 MB in Total
+```
+
+## EfficientNet-B2
+### Optimized
+```
+Main run finished. Milliseconds per iter: 92.28. Iters per second: 10.8366
+Time per operator type:
+ 61.4627 ms. 67.5845%. Conv
+ 22.7458 ms. 25.0113%. Sigmoid
+ 5.59931 ms. 6.15701%. Mul
+ 0.642567 ms. 0.706568%. AveragePool
+ 0.272795 ms. 0.299965%. Add
+ 0.216178 ms. 0.237709%. FC
+ 0.00268895 ms. 0.00295677%. Squeeze
+ 90.942 ms in Total
+FLOP per operator type:
+ 1.98431 GFLOP. 98.9343%. Conv
+ 0.0177039 GFLOP. 0.882686%. Mul
+ 0.002817 GFLOP. 0.140451%. FC
+ 0.000853984 GFLOP. 0.0425782%. Add
+ 2.00568 GFLOP in Total
+Feature Memory Read per operator type:
+ 120.609 MB. 54.9637%. Mul
+ 86.3512 MB. 39.3519%. Conv
+ 6.83187 MB. 3.11341%. Add
+ 5.64163 MB. 2.571%. FC
+ 219.433 MB in Total
+Feature Memory Written per operator type:
+ 70.8155 MB. 54.6573%. Mul
+ 55.3273 MB. 42.7031%. Conv
+ 3.41594 MB. 2.63651%. Add
+ 0.004 MB. 0.00308731%. FC
+ 129.563 MB in Total
+Parameter Memory per operator type:
+ 30.4721 MB. 84.3913%. Conv
+ 5.636 MB. 15.6087%. FC
+ 0 MB. 0%. Add
+ 0 MB. 0%. Mul
+ 36.1081 MB in Total
+```
+
+## MixNet-M
+### Optimized
+```
+Main run finished. Milliseconds per iter: 63.1122. Iters per second: 15.8448
+Time per operator type:
+ 48.1139 ms. 75.2052%. Conv
+ 7.1341 ms. 11.1511%. Sigmoid
+ 2.63706 ms. 4.12189%. SpatialBN
+ 1.73186 ms. 2.70701%. Mul
+ 1.38707 ms. 2.16809%. Split
+ 1.29322 ms. 2.02139%. Concat
+ 1.00093 ms. 1.56452%. Relu
+ 0.235309 ms. 0.367803%. Add
+ 0.221579 ms. 0.346343%. FC
+ 0.219315 ms. 0.342803%. AveragePool
+ 0.00250145 ms. 0.00390993%. Squeeze
+ 63.9768 ms in Total
+FLOP per operator type:
+ 0.675273 GFLOP. 95.5827%. Conv
+ 0.0221072 GFLOP. 3.12921%. SpatialBN
+ 0.00538445 GFLOP. 0.762152%. Mul
+ 0.003073 GFLOP. 0.434973%. FC
+ 0.000642488 GFLOP. 0.0909421%. Add
+ 0 GFLOP. 0%. Concat
+ 0 GFLOP. 0%. Relu
+ 0.70648 GFLOP in Total
+Feature Memory Read per operator type:
+ 46.8424 MB. 30.502%. Conv
+ 36.8626 MB. 24.0036%. Mul
+ 22.3152 MB. 14.5309%. SpatialBN
+ 22.1074 MB. 14.3955%. Concat
+ 14.1496 MB. 9.21372%. Relu
+ 6.15414 MB. 4.00735%. FC
+ 5.1399 MB. 3.34692%. Add
+ 153.571 MB in Total
+Feature Memory Written per operator type:
+ 32.7672 MB. 28.4331%. Conv
+ 22.1072 MB. 19.1831%. Concat
+ 22.1072 MB. 19.1831%. SpatialBN
+ 21.5378 MB. 18.689%. Mul
+ 14.1496 MB. 12.2781%. Relu
+ 2.56995 MB. 2.23003%. Add
+ 0.004 MB. 0.00347092%. FC
+ 115.243 MB in Total
+Parameter Memory per operator type:
+ 13.7059 MB. 68.674%. Conv
+ 6.148 MB. 30.8049%. FC
+ 0.104 MB. 0.521097%. SpatialBN
+ 0 MB. 0%. Add
+ 0 MB. 0%. Concat
+ 0 MB. 0%. Mul
+ 0 MB. 0%. Relu
+ 19.9579 MB in Total
+```
+
+## TF MobileNet-V3 Large 1.0
+
+### Optimized
+```
+Main run finished. Milliseconds per iter: 22.0495. Iters per second: 45.3525
+Time per operator type:
+ 17.437 ms. 80.0087%. Conv
+ 1.27662 ms. 5.8577%. Add
+ 1.12759 ms. 5.17387%. Div
+ 0.701155 ms. 3.21721%. Mul
+ 0.562654 ms. 2.58171%. Relu
+ 0.431144 ms. 1.97828%. Clip
+ 0.156902 ms. 0.719936%. FC
+ 0.0996858 ms. 0.457402%. AveragePool
+ 0.00112455 ms. 0.00515993%. Flatten
+ 21.7939 ms in Total
+FLOP per operator type:
+ 0.43062 GFLOP. 98.1484%. Conv
+ 0.002561 GFLOP. 0.583713%. FC
+ 0.00210867 GFLOP. 0.480616%. Mul
+ 0.00193868 GFLOP. 0.441871%. Add
+ 0.00151532 GFLOP. 0.345377%. Div
+ 0 GFLOP. 0%. Relu
+ 0.438743 GFLOP in Total
+Feature Memory Read per operator type:
+ 34.7967 MB. 43.9391%. Conv
+ 14.496 MB. 18.3046%. Mul
+ 9.44828 MB. 11.9307%. Add
+ 9.26157 MB. 11.6949%. Relu
+ 6.0614 MB. 7.65395%. Div
+ 5.12912 MB. 6.47673%. FC
+ 79.193 MB in Total
+Feature Memory Written per operator type:
+ 17.6247 MB. 35.8656%. Conv
+ 9.26157 MB. 18.847%. Relu
+ 8.43469 MB. 17.1643%. Mul
+ 7.75472 MB. 15.7806%. Add
+ 6.06128 MB. 12.3345%. Div
+ 0.004 MB. 0.00813985%. FC
+ 49.1409 MB in Total
+Parameter Memory per operator type:
+ 16.6851 MB. 76.5052%. Conv
+ 5.124 MB. 23.4948%. FC
+ 0 MB. 0%. Add
+ 0 MB. 0%. Div
+ 0 MB. 0%. Mul
+ 0 MB. 0%. Relu
+ 21.8091 MB in Total
+```
+
+## MobileNet-V3 (RW)
+
+### Unoptimized
+```
+Main run finished. Milliseconds per iter: 24.8316. Iters per second: 40.2712
+Time per operator type:
+ 15.9266 ms. 69.2624%. Conv
+ 2.36551 ms. 10.2873%. SpatialBN
+ 1.39102 ms. 6.04936%. Add
+ 1.30327 ms. 5.66773%. Div
+ 0.737014 ms. 3.20517%. Mul
+ 0.639697 ms. 2.78195%. Relu
+ 0.375681 ms. 1.63378%. Clip
+ 0.153126 ms. 0.665921%. FC
+ 0.0993787 ms. 0.432184%. AveragePool
+ 0.0032632 ms. 0.0141912%. Squeeze
+ 22.9946 ms in Total
+FLOP per operator type:
+ 0.430616 GFLOP. 94.4041%. Conv
+ 0.0175992 GFLOP. 3.85829%. SpatialBN
+ 0.002561 GFLOP. 0.561449%. FC
+ 0.00210961 GFLOP. 0.46249%. Mul
+ 0.00173891 GFLOP. 0.381223%. Add
+ 0.00151626 GFLOP. 0.33241%. Div
+ 0 GFLOP. 0%. Relu
+ 0.456141 GFLOP in Total
+Feature Memory Read per operator type:
+ 34.7354 MB. 36.4363%. Conv
+ 17.7944 MB. 18.6658%. SpatialBN
+ 14.5035 MB. 15.2137%. Mul
+ 9.25778 MB. 9.71113%. Relu
+ 7.84641 MB. 8.23064%. Add
+ 6.06516 MB. 6.36216%. Div
+ 5.12912 MB. 5.38029%. FC
+ 95.3317 MB in Total
+Feature Memory Written per operator type:
+ 17.6246 MB. 26.7264%. Conv
+ 17.5992 MB. 26.6878%. SpatialBN
+ 9.25778 MB. 14.0387%. Relu
+ 8.43843 MB. 12.7962%. Mul
+ 6.95565 MB. 10.5477%. Add
+ 6.06502 MB. 9.19713%. Div
+ 0.004 MB. 0.00606568%. FC
+ 65.9447 MB in Total
+Parameter Memory per operator type:
+ 16.6778 MB. 76.1564%. Conv
+ 5.124 MB. 23.3979%. FC
+ 0.0976 MB. 0.445674%. SpatialBN
+ 0 MB. 0%. Add
+ 0 MB. 0%. Div
+ 0 MB. 0%. Mul
+ 0 MB. 0%. Relu
+ 21.8994 MB in Total
+
+```
+### Optimized
+
+```
+Main run finished. Milliseconds per iter: 22.0981. Iters per second: 45.2527
+Time per operator type:
+ 17.146 ms. 78.8965%. Conv
+ 1.38453 ms. 6.37084%. Add
+ 1.30991 ms. 6.02749%. Div
+ 0.685417 ms. 3.15391%. Mul
+ 0.532589 ms. 2.45068%. Relu
+ 0.418263 ms. 1.92461%. Clip
+ 0.15128 ms. 0.696106%. FC
+ 0.102065 ms. 0.469648%. AveragePool
+ 0.0022143 ms. 0.010189%. Squeeze
+ 21.7323 ms in Total
+FLOP per operator type:
+ 0.430616 GFLOP. 98.1927%. Conv
+ 0.002561 GFLOP. 0.583981%. FC
+ 0.00210961 GFLOP. 0.481051%. Mul
+ 0.00173891 GFLOP. 0.396522%. Add
+ 0.00151626 GFLOP. 0.34575%. Div
+ 0 GFLOP. 0%. Relu
+ 0.438542 GFLOP in Total
+Feature Memory Read per operator type:
+ 34.7842 MB. 44.833%. Conv
+ 14.5035 MB. 18.6934%. Mul
+ 9.25778 MB. 11.9323%. Relu
+ 7.84641 MB. 10.1132%. Add
+ 6.06516 MB. 7.81733%. Div
+ 5.12912 MB. 6.61087%. FC
+ 77.5861 MB in Total
+Feature Memory Written per operator type:
+ 17.6246 MB. 36.4556%. Conv
+ 9.25778 MB. 19.1492%. Relu
+ 8.43843 MB. 17.4544%. Mul
+ 6.95565 MB. 14.3874%. Add
+ 6.06502 MB. 12.5452%. Div
+ 0.004 MB. 0.00827378%. FC
+ 48.3455 MB in Total
+Parameter Memory per operator type:
+ 16.6778 MB. 76.4973%. Conv
+ 5.124 MB. 23.5027%. FC
+ 0 MB. 0%. Add
+ 0 MB. 0%. Div
+ 0 MB. 0%. Mul
+ 0 MB. 0%. Relu
+ 21.8018 MB in Total
+
+```
+
+## MnasNet-A1
+
+### Unoptimized
+```
+Main run finished. Milliseconds per iter: 30.0892. Iters per second: 33.2345
+Time per operator type:
+ 24.4656 ms. 79.0905%. Conv
+ 4.14958 ms. 13.4144%. SpatialBN
+ 1.60598 ms. 5.19169%. Relu
+ 0.295219 ms. 0.95436%. Mul
+ 0.187609 ms. 0.606486%. FC
+ 0.120556 ms. 0.389724%. AveragePool
+ 0.09036 ms. 0.292109%. Add
+ 0.015727 ms. 0.050841%. Sigmoid
+ 0.00306205 ms. 0.00989875%. Squeeze
+ 30.9337 ms in Total
+FLOP per operator type:
+ 0.620598 GFLOP. 95.6434%. Conv
+ 0.0248873 GFLOP. 3.8355%. SpatialBN
+ 0.002561 GFLOP. 0.394688%. FC
+ 0.000597408 GFLOP. 0.0920695%. Mul
+ 0.000222656 GFLOP. 0.0343146%. Add
+ 0 GFLOP. 0%. Relu
+ 0.648867 GFLOP in Total
+Feature Memory Read per operator type:
+ 35.5457 MB. 38.4109%. Conv
+ 25.1552 MB. 27.1829%. SpatialBN
+ 22.5235 MB. 24.339%. Relu
+ 5.12912 MB. 5.54256%. FC
+ 2.40586 MB. 2.59978%. Mul
+ 1.78125 MB. 1.92483%. Add
+ 92.5406 MB in Total
+Feature Memory Written per operator type:
+ 24.9042 MB. 32.9424%. Conv
+ 24.8873 MB. 32.92%. SpatialBN
+ 22.5235 MB. 29.7932%. Relu
+ 2.38963 MB. 3.16092%. Mul
+ 0.890624 MB. 1.17809%. Add
+ 0.004 MB. 0.00529106%. FC
+ 75.5993 MB in Total
+Parameter Memory per operator type:
+ 10.2732 MB. 66.1459%. Conv
+ 5.124 MB. 32.9917%. FC
+ 0.133952 MB. 0.86247%. SpatialBN
+ 0 MB. 0%. Add
+ 0 MB. 0%. Mul
+ 0 MB. 0%. Relu
+ 15.5312 MB in Total
+```
+
+### Optimized
+```
+Main run finished. Milliseconds per iter: 24.2367. Iters per second: 41.2597
+Time per operator type:
+ 22.0547 ms. 91.1375%. Conv
+ 1.49096 ms. 6.16116%. Relu
+ 0.253417 ms. 1.0472%. Mul
+ 0.18506 ms. 0.76473%. FC
+ 0.112942 ms. 0.466717%. AveragePool
+ 0.086769 ms. 0.358559%. Add
+ 0.0127889 ms. 0.0528479%. Sigmoid
+ 0.0027346 ms. 0.0113003%. Squeeze
+ 24.1994 ms in Total
+FLOP per operator type:
+ 0.620598 GFLOP. 99.4581%. Conv
+ 0.002561 GFLOP. 0.41043%. FC
+ 0.000597408 GFLOP. 0.0957417%. Mul
+ 0.000222656 GFLOP. 0.0356832%. Add
+ 0 GFLOP. 0%. Relu
+ 0.623979 GFLOP in Total
+Feature Memory Read per operator type:
+ 35.6127 MB. 52.7968%. Conv
+ 22.5235 MB. 33.3917%. Relu
+ 5.12912 MB. 7.60406%. FC
+ 2.40586 MB. 3.56675%. Mul
+ 1.78125 MB. 2.64075%. Add
+ 67.4524 MB in Total
+Feature Memory Written per operator type:
+ 24.9042 MB. 49.1092%. Conv
+ 22.5235 MB. 44.4145%. Relu
+ 2.38963 MB. 4.71216%. Mul
+ 0.890624 MB. 1.75624%. Add
+ 0.004 MB. 0.00788768%. FC
+ 50.712 MB in Total
+Parameter Memory per operator type:
+ 10.2732 MB. 66.7213%. Conv
+ 5.124 MB. 33.2787%. FC
+ 0 MB. 0%. Add
+ 0 MB. 0%. Mul
+ 0 MB. 0%. Relu
+ 15.3972 MB in Total
+```
+## MnasNet-B1
+
+### Unoptimized
+```
+Main run finished. Milliseconds per iter: 28.3109. Iters per second: 35.322
+Time per operator type:
+ 29.1121 ms. 83.3081%. Conv
+ 4.14959 ms. 11.8746%. SpatialBN
+ 1.35823 ms. 3.88675%. Relu
+ 0.186188 ms. 0.532802%. FC
+ 0.116244 ms. 0.332647%. Add
+ 0.018641 ms. 0.0533437%. AveragePool
+ 0.0040904 ms. 0.0117052%. Squeeze
+ 34.9451 ms in Total
+FLOP per operator type:
+ 0.626272 GFLOP. 96.2088%. Conv
+ 0.0218266 GFLOP. 3.35303%. SpatialBN
+ 0.002561 GFLOP. 0.393424%. FC
+ 0.000291648 GFLOP. 0.0448034%. Add
+ 0 GFLOP. 0%. Relu
+ 0.650951 GFLOP in Total
+Feature Memory Read per operator type:
+ 34.4354 MB. 41.3788%. Conv
+ 22.1299 MB. 26.5921%. SpatialBN
+ 19.1923 MB. 23.0622%. Relu
+ 5.12912 MB. 6.16333%. FC
+ 2.33318 MB. 2.80364%. Add
+ 83.2199 MB in Total
+Feature Memory Written per operator type:
+ 21.8266 MB. 34.0955%. Conv
+ 21.8266 MB. 34.0955%. SpatialBN
+ 19.1923 MB. 29.9805%. Relu
+ 1.16659 MB. 1.82234%. Add
+ 0.004 MB. 0.00624844%. FC
+ 64.016 MB in Total
+Parameter Memory per operator type:
+ 12.2576 MB. 69.9104%. Conv
+ 5.124 MB. 29.2245%. FC
+ 0.15168 MB. 0.865099%. SpatialBN
+ 0 MB. 0%. Add
+ 0 MB. 0%. Relu
+ 17.5332 MB in Total
+```
+
+### Optimized
+```
+Main run finished. Milliseconds per iter: 26.6364. Iters per second: 37.5426
+Time per operator type:
+ 24.9888 ms. 94.0962%. Conv
+ 1.26147 ms. 4.75011%. Relu
+ 0.176234 ms. 0.663619%. FC
+ 0.113309 ms. 0.426672%. Add
+ 0.0138708 ms. 0.0522311%. AveragePool
+ 0.00295685 ms. 0.0111341%. Squeeze
+ 26.5566 ms in Total
+FLOP per operator type:
+ 0.626272 GFLOP. 99.5466%. Conv
+ 0.002561 GFLOP. 0.407074%. FC
+ 0.000291648 GFLOP. 0.0463578%. Add
+ 0 GFLOP. 0%. Relu
+ 0.629124 GFLOP in Total
+Feature Memory Read per operator type:
+ 34.5112 MB. 56.4224%. Conv
+ 19.1923 MB. 31.3775%. Relu
+ 5.12912 MB. 8.3856%. FC
+ 2.33318 MB. 3.81452%. Add
+ 61.1658 MB in Total
+Feature Memory Written per operator type:
+ 21.8266 MB. 51.7346%. Conv
+ 19.1923 MB. 45.4908%. Relu
+ 1.16659 MB. 2.76513%. Add
+ 0.004 MB. 0.00948104%. FC
+ 42.1895 MB in Total
+Parameter Memory per operator type:
+ 12.2576 MB. 70.5205%. Conv
+ 5.124 MB. 29.4795%. FC
+ 0 MB. 0%. Add
+ 0 MB. 0%. Relu
+ 17.3816 MB in Total
+```
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/LICENSE b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..80e7d15508202f3262a50db27f5198460d7f509f
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2020 Ross Wightman
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/README.md b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..463368280d6a5015060eb73d20fe6512f8e04c50
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/README.md
@@ -0,0 +1,323 @@
+# (Generic) EfficientNets for PyTorch
+
+A 'generic' implementation of EfficientNet, MixNet, MobileNetV3, etc. that covers most of the compute/parameter efficient architectures derived from the MobileNet V1/V2 block sequence, including those found via automated neural architecture search.
+
+All models are implemented by GenEfficientNet or MobileNetV3 classes, with string based architecture definitions to configure the block layouts (idea from [here](https://github.com/tensorflow/tpu/blob/master/models/official/mnasnet/mnasnet_models.py))
+
+## What's New
+
+### Aug 19, 2020
+* Add updated PyTorch trained EfficientNet-B3 weights trained by myself with `timm` (82.1 top-1)
+* Add PyTorch trained EfficientNet-Lite0 contributed by [@hal-314](https://github.com/hal-314) (75.5 top-1)
+* Update ONNX and Caffe2 export / utility scripts to work with latest PyTorch / ONNX
+* ONNX runtime based validation script added
+* activations (mostly) brought in sync with `timm` equivalents
+
+
+### April 5, 2020
+* Add some newly trained MobileNet-V2 models trained with latest h-params, rand augment. They compare quite favourably to EfficientNet-Lite
+ * 3.5M param MobileNet-V2 100 @ 73%
+ * 4.5M param MobileNet-V2 110d @ 75%
+ * 6.1M param MobileNet-V2 140 @ 76.5%
+ * 5.8M param MobileNet-V2 120d @ 77.3%
+
+### March 23, 2020
+ * Add EfficientNet-Lite models w/ weights ported from [Tensorflow TPU](https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet/lite)
+ * Add PyTorch trained MobileNet-V3 Large weights with 75.77% top-1
+ * IMPORTANT CHANGE (if training from scratch) - weight init changed to better match Tensorflow impl, set `fix_group_fanout=False` in `initialize_weight_goog` for old behavior
+
+### Feb 12, 2020
+ * Add EfficientNet-L2 and B0-B7 NoisyStudent weights ported from [Tensorflow TPU](https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet)
+ * Port new EfficientNet-B8 (RandAugment) weights from TF TPU, these are different than the B8 AdvProp, different input normalization.
+ * Add RandAugment PyTorch trained EfficientNet-ES (EdgeTPU-Small) weights with 78.1 top-1. Trained by [Andrew Lavin](https://github.com/andravin)
+
+### Jan 22, 2020
+ * Update weights for EfficientNet B0, B2, B3 and MixNet-XL with latest RandAugment trained weights. Trained with (https://github.com/rwightman/pytorch-image-models)
+ * Fix torchscript compatibility for PyTorch 1.4, add torchscript support for MixedConv2d using ModuleDict
+ * Test models, torchscript, onnx export with PyTorch 1.4 -- no issues
+
+### Nov 22, 2019
+ * New top-1 high! Ported official TF EfficientNet AdvProp (https://arxiv.org/abs/1911.09665) weights and B8 model spec. Created a new set of `ap` models since they use a different
+ preprocessing (Inception mean/std) from the original EfficientNet base/AA/RA weights.
+
+### Nov 15, 2019
+ * Ported official TF MobileNet-V3 float32 large/small/minimalistic weights
+ * Modifications to MobileNet-V3 model and components to support some additional config needed for differences between TF MobileNet-V3 and mine
+
+### Oct 30, 2019
+ * Many of the models will now work with torch.jit.script, MixNet being the biggest exception
+ * Improved interface for enabling torchscript or ONNX export compatible modes (via config)
+ * Add JIT optimized mem-efficient Swish/Mish autograd.fn in addition to memory-efficient autgrad.fn
+ * Activation factory to select best version of activation by name or override one globally
+ * Add pretrained checkpoint load helper that handles input conv and classifier changes
+
+### Oct 27, 2019
+ * Add CondConv EfficientNet variants ported from https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet/condconv
+ * Add RandAug weights for TF EfficientNet B5 and B7 from https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet
+ * Bring over MixNet-XL model and depth scaling algo from my pytorch-image-models code base
+ * Switch activations and global pooling to modules
+ * Add memory-efficient Swish/Mish impl
+ * Add as_sequential() method to all models and allow as an argument in entrypoint fns
+ * Move MobileNetV3 into own file since it has a different head
+ * Remove ChamNet, MobileNet V2/V1 since they will likely never be used here
+
+## Models
+
+Implemented models include:
+ * EfficientNet NoisyStudent (B0-B7, L2) (https://arxiv.org/abs/1911.04252)
+ * EfficientNet AdvProp (B0-B8) (https://arxiv.org/abs/1911.09665)
+ * EfficientNet (B0-B8) (https://arxiv.org/abs/1905.11946)
+ * EfficientNet-EdgeTPU (S, M, L) (https://ai.googleblog.com/2019/08/efficientnet-edgetpu-creating.html)
+ * EfficientNet-CondConv (https://arxiv.org/abs/1904.04971)
+ * EfficientNet-Lite (https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet/lite)
+ * MixNet (https://arxiv.org/abs/1907.09595)
+ * MNASNet B1, A1 (Squeeze-Excite), and Small (https://arxiv.org/abs/1807.11626)
+ * MobileNet-V3 (https://arxiv.org/abs/1905.02244)
+ * FBNet-C (https://arxiv.org/abs/1812.03443)
+ * Single-Path NAS (https://arxiv.org/abs/1904.02877)
+
+I originally implemented and trained some these models with code [here](https://github.com/rwightman/pytorch-image-models), this repository contains just the GenEfficientNet models, validation, and associated ONNX/Caffe2 export code.
+
+## Pretrained
+
+I've managed to train several of the models to accuracies close to or above the originating papers and official impl. My training code is here: https://github.com/rwightman/pytorch-image-models
+
+
+|Model | Prec@1 (Err) | Prec@5 (Err) | Param#(M) | MAdds(M) | Image Scaling | Resolution | Crop |
+|---|---|---|---|---|---|---|---|
+| efficientnet_b3 | 82.240 (17.760) | 96.116 (3.884) | 12.23 | TBD | bicubic | 320 | 1.0 |
+| efficientnet_b3 | 82.076 (17.924) | 96.020 (3.980) | 12.23 | TBD | bicubic | 300 | 0.904 |
+| mixnet_xl | 81.074 (18.926) | 95.282 (4.718) | 11.90 | TBD | bicubic | 256 | 1.0 |
+| efficientnet_b2 | 80.612 (19.388) | 95.318 (4.682) | 9.1 | TBD | bicubic | 288 | 1.0 |
+| mixnet_xl | 80.476 (19.524) | 94.936 (5.064) | 11.90 | TBD | bicubic | 224 | 0.875 |
+| efficientnet_b2 | 80.288 (19.712) | 95.166 (4.834) | 9.1 | 1003 | bicubic | 260 | 0.890 |
+| mixnet_l | 78.976 (21.024 | 94.184 (5.816) | 7.33 | TBD | bicubic | 224 | 0.875 |
+| efficientnet_b1 | 78.692 (21.308) | 94.086 (5.914) | 7.8 | 694 | bicubic | 240 | 0.882 |
+| efficientnet_es | 78.066 (21.934) | 93.926 (6.074) | 5.44 | TBD | bicubic | 224 | 0.875 |
+| efficientnet_b0 | 77.698 (22.302) | 93.532 (6.468) | 5.3 | 390 | bicubic | 224 | 0.875 |
+| mobilenetv2_120d | 77.294 (22.706 | 93.502 (6.498) | 5.8 | TBD | bicubic | 224 | 0.875 |
+| mixnet_m | 77.256 (22.744) | 93.418 (6.582) | 5.01 | 353 | bicubic | 224 | 0.875 |
+| mobilenetv2_140 | 76.524 (23.476) | 92.990 (7.010) | 6.1 | TBD | bicubic | 224 | 0.875 |
+| mixnet_s | 75.988 (24.012) | 92.794 (7.206) | 4.13 | TBD | bicubic | 224 | 0.875 |
+| mobilenetv3_large_100 | 75.766 (24.234) | 92.542 (7.458) | 5.5 | TBD | bicubic | 224 | 0.875 |
+| mobilenetv3_rw | 75.634 (24.366) | 92.708 (7.292) | 5.5 | 219 | bicubic | 224 | 0.875 |
+| efficientnet_lite0 | 75.472 (24.528) | 92.520 (7.480) | 4.65 | TBD | bicubic | 224 | 0.875 |
+| mnasnet_a1 | 75.448 (24.552) | 92.604 (7.396) | 3.9 | 312 | bicubic | 224 | 0.875 |
+| fbnetc_100 | 75.124 (24.876) | 92.386 (7.614) | 5.6 | 385 | bilinear | 224 | 0.875 |
+| mobilenetv2_110d | 75.052 (24.948) | 92.180 (7.820) | 4.5 | TBD | bicubic | 224 | 0.875 |
+| mnasnet_b1 | 74.658 (25.342) | 92.114 (7.886) | 4.4 | 315 | bicubic | 224 | 0.875 |
+| spnasnet_100 | 74.084 (25.916) | 91.818 (8.182) | 4.4 | TBD | bilinear | 224 | 0.875 |
+| mobilenetv2_100 | 72.978 (27.022) | 91.016 (8.984) | 3.5 | TBD | bicubic | 224 | 0.875 |
+
+
+More pretrained models to come...
+
+
+## Ported Weights
+
+The weights ported from Tensorflow checkpoints for the EfficientNet models do pretty much match accuracy in Tensorflow once a SAME convolution padding equivalent is added, and the same crop factors, image scaling, etc (see table) are used via cmd line args.
+
+**IMPORTANT:**
+* Tensorflow ported weights for EfficientNet AdvProp (AP), EfficientNet EdgeTPU, EfficientNet-CondConv, EfficientNet-Lite, and MobileNet-V3 models use Inception style (0.5, 0.5, 0.5) for mean and std.
+* Enabling the Tensorflow preprocessing pipeline with `--tf-preprocessing` at validation time will improve scores by 0.1-0.5%, very close to original TF impl.
+
+To run validation for tf_efficientnet_b5:
+`python validate.py /path/to/imagenet/validation/ --model tf_efficientnet_b5 -b 64 --img-size 456 --crop-pct 0.934 --interpolation bicubic`
+
+To run validation w/ TF preprocessing for tf_efficientnet_b5:
+`python validate.py /path/to/imagenet/validation/ --model tf_efficientnet_b5 -b 64 --img-size 456 --tf-preprocessing`
+
+To run validation for a model with Inception preprocessing, ie EfficientNet-B8 AdvProp:
+`python validate.py /path/to/imagenet/validation/ --model tf_efficientnet_b8_ap -b 48 --num-gpu 2 --img-size 672 --crop-pct 0.954 --mean 0.5 --std 0.5`
+
+|Model | Prec@1 (Err) | Prec@5 (Err) | Param # | Image Scaling | Image Size | Crop |
+|---|---|---|---|---|---|---|
+| tf_efficientnet_l2_ns *tfp | 88.352 (11.648) | 98.652 (1.348) | 480 | bicubic | 800 | N/A |
+| tf_efficientnet_l2_ns | TBD | TBD | 480 | bicubic | 800 | 0.961 |
+| tf_efficientnet_l2_ns_475 | 88.234 (11.766) | 98.546 (1.454) | 480 | bicubic | 475 | 0.936 |
+| tf_efficientnet_l2_ns_475 *tfp | 88.172 (11.828) | 98.566 (1.434) | 480 | bicubic | 475 | N/A |
+| tf_efficientnet_b7_ns *tfp | 86.844 (13.156) | 98.084 (1.916) | 66.35 | bicubic | 600 | N/A |
+| tf_efficientnet_b7_ns | 86.840 (13.160) | 98.094 (1.906) | 66.35 | bicubic | 600 | N/A |
+| tf_efficientnet_b6_ns | 86.452 (13.548) | 97.882 (2.118) | 43.04 | bicubic | 528 | N/A |
+| tf_efficientnet_b6_ns *tfp | 86.444 (13.556) | 97.880 (2.120) | 43.04 | bicubic | 528 | N/A |
+| tf_efficientnet_b5_ns *tfp | 86.064 (13.936) | 97.746 (2.254) | 30.39 | bicubic | 456 | N/A |
+| tf_efficientnet_b5_ns | 86.088 (13.912) | 97.752 (2.248) | 30.39 | bicubic | 456 | N/A |
+| tf_efficientnet_b8_ap *tfp | 85.436 (14.564) | 97.272 (2.728) | 87.4 | bicubic | 672 | N/A |
+| tf_efficientnet_b8 *tfp | 85.384 (14.616) | 97.394 (2.606) | 87.4 | bicubic | 672 | N/A |
+| tf_efficientnet_b8 | 85.370 (14.630) | 97.390 (2.610) | 87.4 | bicubic | 672 | 0.954 |
+| tf_efficientnet_b8_ap | 85.368 (14.632) | 97.294 (2.706) | 87.4 | bicubic | 672 | 0.954 |
+| tf_efficientnet_b4_ns *tfp | 85.298 (14.702) | 97.504 (2.496) | 19.34 | bicubic | 380 | N/A |
+| tf_efficientnet_b4_ns | 85.162 (14.838) | 97.470 (2.530) | 19.34 | bicubic | 380 | 0.922 |
+| tf_efficientnet_b7_ap *tfp | 85.154 (14.846) | 97.244 (2.756) | 66.35 | bicubic | 600 | N/A |
+| tf_efficientnet_b7_ap | 85.118 (14.882) | 97.252 (2.748) | 66.35 | bicubic | 600 | 0.949 |
+| tf_efficientnet_b7 *tfp | 84.940 (15.060) | 97.214 (2.786) | 66.35 | bicubic | 600 | N/A |
+| tf_efficientnet_b7 | 84.932 (15.068) | 97.208 (2.792) | 66.35 | bicubic | 600 | 0.949 |
+| tf_efficientnet_b6_ap | 84.786 (15.214) | 97.138 (2.862) | 43.04 | bicubic | 528 | 0.942 |
+| tf_efficientnet_b6_ap *tfp | 84.760 (15.240) | 97.124 (2.876) | 43.04 | bicubic | 528 | N/A |
+| tf_efficientnet_b5_ap *tfp | 84.276 (15.724) | 96.932 (3.068) | 30.39 | bicubic | 456 | N/A |
+| tf_efficientnet_b5_ap | 84.254 (15.746) | 96.976 (3.024) | 30.39 | bicubic | 456 | 0.934 |
+| tf_efficientnet_b6 *tfp | 84.140 (15.860) | 96.852 (3.148) | 43.04 | bicubic | 528 | N/A |
+| tf_efficientnet_b6 | 84.110 (15.890) | 96.886 (3.114) | 43.04 | bicubic | 528 | 0.942 |
+| tf_efficientnet_b3_ns *tfp | 84.054 (15.946) | 96.918 (3.082) | 12.23 | bicubic | 300 | N/A |
+| tf_efficientnet_b3_ns | 84.048 (15.952) | 96.910 (3.090) | 12.23 | bicubic | 300 | .904 |
+| tf_efficientnet_b5 *tfp | 83.822 (16.178) | 96.756 (3.244) | 30.39 | bicubic | 456 | N/A |
+| tf_efficientnet_b5 | 83.812 (16.188) | 96.748 (3.252) | 30.39 | bicubic | 456 | 0.934 |
+| tf_efficientnet_b4_ap *tfp | 83.278 (16.722) | 96.376 (3.624) | 19.34 | bicubic | 380 | N/A |
+| tf_efficientnet_b4_ap | 83.248 (16.752) | 96.388 (3.612) | 19.34 | bicubic | 380 | 0.922 |
+| tf_efficientnet_b4 | 83.022 (16.978) | 96.300 (3.700) | 19.34 | bicubic | 380 | 0.922 |
+| tf_efficientnet_b4 *tfp | 82.948 (17.052) | 96.308 (3.692) | 19.34 | bicubic | 380 | N/A |
+| tf_efficientnet_b2_ns *tfp | 82.436 (17.564) | 96.268 (3.732) | 9.11 | bicubic | 260 | N/A |
+| tf_efficientnet_b2_ns | 82.380 (17.620) | 96.248 (3.752) | 9.11 | bicubic | 260 | 0.89 |
+| tf_efficientnet_b3_ap *tfp | 81.882 (18.118) | 95.662 (4.338) | 12.23 | bicubic | 300 | N/A |
+| tf_efficientnet_b3_ap | 81.828 (18.172) | 95.624 (4.376) | 12.23 | bicubic | 300 | 0.904 |
+| tf_efficientnet_b3 | 81.636 (18.364) | 95.718 (4.282) | 12.23 | bicubic | 300 | 0.904 |
+| tf_efficientnet_b3 *tfp | 81.576 (18.424) | 95.662 (4.338) | 12.23 | bicubic | 300 | N/A |
+| tf_efficientnet_lite4 | 81.528 (18.472) | 95.668 (4.332) | 13.00 | bilinear | 380 | 0.92 |
+| tf_efficientnet_b1_ns *tfp | 81.514 (18.486) | 95.776 (4.224) | 7.79 | bicubic | 240 | N/A |
+| tf_efficientnet_lite4 *tfp | 81.502 (18.498) | 95.676 (4.324) | 13.00 | bilinear | 380 | N/A |
+| tf_efficientnet_b1_ns | 81.388 (18.612) | 95.738 (4.262) | 7.79 | bicubic | 240 | 0.88 |
+| tf_efficientnet_el | 80.534 (19.466) | 95.190 (4.810) | 10.59 | bicubic | 300 | 0.904 |
+| tf_efficientnet_el *tfp | 80.476 (19.524) | 95.200 (4.800) | 10.59 | bicubic | 300 | N/A |
+| tf_efficientnet_b2_ap *tfp | 80.420 (19.580) | 95.040 (4.960) | 9.11 | bicubic | 260 | N/A |
+| tf_efficientnet_b2_ap | 80.306 (19.694) | 95.028 (4.972) | 9.11 | bicubic | 260 | 0.890 |
+| tf_efficientnet_b2 *tfp | 80.188 (19.812) | 94.974 (5.026) | 9.11 | bicubic | 260 | N/A |
+| tf_efficientnet_b2 | 80.086 (19.914) | 94.908 (5.092) | 9.11 | bicubic | 260 | 0.890 |
+| tf_efficientnet_lite3 | 79.812 (20.188) | 94.914 (5.086) | 8.20 | bilinear | 300 | 0.904 |
+| tf_efficientnet_lite3 *tfp | 79.734 (20.266) | 94.838 (5.162) | 8.20 | bilinear | 300 | N/A |
+| tf_efficientnet_b1_ap *tfp | 79.532 (20.468) | 94.378 (5.622) | 7.79 | bicubic | 240 | N/A |
+| tf_efficientnet_cc_b1_8e *tfp | 79.464 (20.536)| 94.492 (5.508) | 39.7 | bicubic | 240 | 0.88 |
+| tf_efficientnet_cc_b1_8e | 79.298 (20.702) | 94.364 (5.636) | 39.7 | bicubic | 240 | 0.88 |
+| tf_efficientnet_b1_ap | 79.278 (20.722) | 94.308 (5.692) | 7.79 | bicubic | 240 | 0.88 |
+| tf_efficientnet_b1 *tfp | 79.172 (20.828) | 94.450 (5.550) | 7.79 | bicubic | 240 | N/A |
+| tf_efficientnet_em *tfp | 78.958 (21.042) | 94.458 (5.542) | 6.90 | bicubic | 240 | N/A |
+| tf_efficientnet_b0_ns *tfp | 78.806 (21.194) | 94.496 (5.504) | 5.29 | bicubic | 224 | N/A |
+| tf_mixnet_l *tfp | 78.846 (21.154) | 94.212 (5.788) | 7.33 | bilinear | 224 | N/A |
+| tf_efficientnet_b1 | 78.826 (21.174) | 94.198 (5.802) | 7.79 | bicubic | 240 | 0.88 |
+| tf_mixnet_l | 78.770 (21.230) | 94.004 (5.996) | 7.33 | bicubic | 224 | 0.875 |
+| tf_efficientnet_em | 78.742 (21.258) | 94.332 (5.668) | 6.90 | bicubic | 240 | 0.875 |
+| tf_efficientnet_b0_ns | 78.658 (21.342) | 94.376 (5.624) | 5.29 | bicubic | 224 | 0.875 |
+| tf_efficientnet_cc_b0_8e *tfp | 78.314 (21.686) | 93.790 (6.210) | 24.0 | bicubic | 224 | 0.875 |
+| tf_efficientnet_cc_b0_8e | 77.908 (22.092) | 93.656 (6.344) | 24.0 | bicubic | 224 | 0.875 |
+| tf_efficientnet_cc_b0_4e *tfp | 77.746 (22.254) | 93.552 (6.448) | 13.3 | bicubic | 224 | 0.875 |
+| tf_efficientnet_cc_b0_4e | 77.304 (22.696) | 93.332 (6.668) | 13.3 | bicubic | 224 | 0.875 |
+| tf_efficientnet_es *tfp | 77.616 (22.384) | 93.750 (6.250) | 5.44 | bicubic | 224 | N/A |
+| tf_efficientnet_lite2 *tfp | 77.544 (22.456) | 93.800 (6.200) | 6.09 | bilinear | 260 | N/A |
+| tf_efficientnet_lite2 | 77.460 (22.540) | 93.746 (6.254) | 6.09 | bicubic | 260 | 0.89 |
+| tf_efficientnet_b0_ap *tfp | 77.514 (22.486) | 93.576 (6.424) | 5.29 | bicubic | 224 | N/A |
+| tf_efficientnet_es | 77.264 (22.736) | 93.600 (6.400) | 5.44 | bicubic | 224 | N/A |
+| tf_efficientnet_b0 *tfp | 77.258 (22.742) | 93.478 (6.522) | 5.29 | bicubic | 224 | N/A |
+| tf_efficientnet_b0_ap | 77.084 (22.916) | 93.254 (6.746) | 5.29 | bicubic | 224 | 0.875 |
+| tf_mixnet_m *tfp | 77.072 (22.928) | 93.368 (6.632) | 5.01 | bilinear | 224 | N/A |
+| tf_mixnet_m | 76.950 (23.050) | 93.156 (6.844) | 5.01 | bicubic | 224 | 0.875 |
+| tf_efficientnet_b0 | 76.848 (23.152) | 93.228 (6.772) | 5.29 | bicubic | 224 | 0.875 |
+| tf_efficientnet_lite1 *tfp | 76.764 (23.236) | 93.326 (6.674) | 5.42 | bilinear | 240 | N/A |
+| tf_efficientnet_lite1 | 76.638 (23.362) | 93.232 (6.768) | 5.42 | bicubic | 240 | 0.882 |
+| tf_mixnet_s *tfp | 75.800 (24.200) | 92.788 (7.212) | 4.13 | bilinear | 224 | N/A |
+| tf_mobilenetv3_large_100 *tfp | 75.768 (24.232) | 92.710 (7.290) | 5.48 | bilinear | 224 | N/A |
+| tf_mixnet_s | 75.648 (24.352) | 92.636 (7.364) | 4.13 | bicubic | 224 | 0.875 |
+| tf_mobilenetv3_large_100 | 75.516 (24.484) | 92.600 (7.400) | 5.48 | bilinear | 224 | 0.875 |
+| tf_efficientnet_lite0 *tfp | 75.074 (24.926) | 92.314 (7.686) | 4.65 | bilinear | 224 | N/A |
+| tf_efficientnet_lite0 | 74.842 (25.158) | 92.170 (7.830) | 4.65 | bicubic | 224 | 0.875 |
+| tf_mobilenetv3_large_075 *tfp | 73.730 (26.270) | 91.616 (8.384) | 3.99 | bilinear | 224 |N/A |
+| tf_mobilenetv3_large_075 | 73.442 (26.558) | 91.352 (8.648) | 3.99 | bilinear | 224 | 0.875 |
+| tf_mobilenetv3_large_minimal_100 *tfp | 72.678 (27.322) | 90.860 (9.140) | 3.92 | bilinear | 224 | N/A |
+| tf_mobilenetv3_large_minimal_100 | 72.244 (27.756) | 90.636 (9.364) | 3.92 | bilinear | 224 | 0.875 |
+| tf_mobilenetv3_small_100 *tfp | 67.918 (32.082) | 87.958 (12.042 | 2.54 | bilinear | 224 | N/A |
+| tf_mobilenetv3_small_100 | 67.918 (32.082) | 87.662 (12.338) | 2.54 | bilinear | 224 | 0.875 |
+| tf_mobilenetv3_small_075 *tfp | 66.142 (33.858) | 86.498 (13.502) | 2.04 | bilinear | 224 | N/A |
+| tf_mobilenetv3_small_075 | 65.718 (34.282) | 86.136 (13.864) | 2.04 | bilinear | 224 | 0.875 |
+| tf_mobilenetv3_small_minimal_100 *tfp | 63.378 (36.622) | 84.802 (15.198) | 2.04 | bilinear | 224 | N/A |
+| tf_mobilenetv3_small_minimal_100 | 62.898 (37.102) | 84.230 (15.770) | 2.04 | bilinear | 224 | 0.875 |
+
+
+*tfp models validated with `tf-preprocessing` pipeline
+
+Google tf and tflite weights ported from official Tensorflow repositories
+* https://github.com/tensorflow/tpu/tree/master/models/official/mnasnet
+* https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet
+* https://github.com/tensorflow/models/tree/master/research/slim/nets/mobilenet
+
+## Usage
+
+### Environment
+
+All development and testing has been done in Conda Python 3 environments on Linux x86-64 systems, specifically Python 3.6.x, 3.7.x, 3.8.x.
+
+Users have reported that a Python 3 Anaconda install in Windows works. I have not verified this myself.
+
+PyTorch versions 1.4, 1.5, 1.6 have been tested with this code.
+
+I've tried to keep the dependencies minimal, the setup is as per the PyTorch default install instructions for Conda:
+```
+conda create -n torch-env
+conda activate torch-env
+conda install -c pytorch pytorch torchvision cudatoolkit=10.2
+```
+
+### PyTorch Hub
+
+Models can be accessed via the PyTorch Hub API
+
+```
+>>> torch.hub.list('rwightman/gen-efficientnet-pytorch')
+['efficientnet_b0', ...]
+>>> model = torch.hub.load('rwightman/gen-efficientnet-pytorch', 'efficientnet_b0', pretrained=True)
+>>> model.eval()
+>>> output = model(torch.randn(1,3,224,224))
+```
+
+### Pip
+This package can be installed via pip.
+
+Install (after conda env/install):
+```
+pip install geffnet
+```
+
+Eval use:
+```
+>>> import geffnet
+>>> m = geffnet.create_model('mobilenetv3_large_100', pretrained=True)
+>>> m.eval()
+```
+
+Train use:
+```
+>>> import geffnet
+>>> # models can also be created by using the entrypoint directly
+>>> m = geffnet.efficientnet_b2(pretrained=True, drop_rate=0.25, drop_connect_rate=0.2)
+>>> m.train()
+```
+
+Create in a nn.Sequential container, for fast.ai, etc:
+```
+>>> import geffnet
+>>> m = geffnet.mixnet_l(pretrained=True, drop_rate=0.25, drop_connect_rate=0.2, as_sequential=True)
+```
+
+### Exporting
+
+Scripts are included to
+* export models to ONNX (`onnx_export.py`)
+* optimized ONNX graph (`onnx_optimize.py` or `onnx_validate.py` w/ `--onnx-output-opt` arg)
+* validate with ONNX runtime (`onnx_validate.py`)
+* convert ONNX model to Caffe2 (`onnx_to_caffe.py`)
+* validate in Caffe2 (`caffe2_validate.py`)
+* benchmark in Caffe2 w/ FLOPs, parameters output (`caffe2_benchmark.py`)
+
+As an example, to export the MobileNet-V3 pretrained model and then run an Imagenet validation:
+```
+python onnx_export.py --model mobilenetv3_large_100 ./mobilenetv3_100.onnx
+python onnx_validate.py /imagenet/validation/ --onnx-input ./mobilenetv3_100.onnx
+```
+
+These scripts were tested to be working as of PyTorch 1.6 and ONNX 1.7 w/ ONNX runtime 1.4. Caffe2 compatible
+export now requires additional args mentioned in the export script (not needed in earlier versions).
+
+#### Export Notes
+1. The TF ported weights with the 'SAME' conv padding activated cannot be exported to ONNX unless `_EXPORTABLE` flag in `config.py` is set to True. Use `config.set_exportable(True)` as in the `onnx_export.py` script.
+2. TF ported models with 'SAME' padding will have the padding fixed at export time to the resolution used for export. Even though dynamic padding is supported in opset >= 11, I can't get it working.
+3. ONNX optimize facility doesn't work reliably in PyTorch 1.6 / ONNX 1.7. Fortunately, the onnxruntime based inference is working very well now and includes on the fly optimization.
+3. ONNX / Caffe2 export/import frequently breaks with different PyTorch and ONNX version releases. Please check their respective issue trackers before filing issues here.
+
+
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/caffe2_benchmark.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/caffe2_benchmark.py
new file mode 100644
index 0000000000000000000000000000000000000000..93f28a1e63d9f7287ca02997c7991fe66dd0aeb9
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/caffe2_benchmark.py
@@ -0,0 +1,65 @@
+""" Caffe2 validation script
+
+This script runs Caffe2 benchmark on exported ONNX model.
+It is a useful tool for reporting model FLOPS.
+
+Copyright 2020 Ross Wightman
+"""
+import argparse
+from caffe2.python import core, workspace, model_helper
+from caffe2.proto import caffe2_pb2
+
+
+parser = argparse.ArgumentParser(description='Caffe2 Model Benchmark')
+parser.add_argument('--c2-prefix', default='', type=str, metavar='NAME',
+ help='caffe2 model pb name prefix')
+parser.add_argument('--c2-init', default='', type=str, metavar='PATH',
+ help='caffe2 model init .pb')
+parser.add_argument('--c2-predict', default='', type=str, metavar='PATH',
+ help='caffe2 model predict .pb')
+parser.add_argument('-b', '--batch-size', default=1, type=int,
+ metavar='N', help='mini-batch size (default: 1)')
+parser.add_argument('--img-size', default=224, type=int,
+ metavar='N', help='Input image dimension, uses model default if empty')
+
+
+def main():
+ args = parser.parse_args()
+ args.gpu_id = 0
+ if args.c2_prefix:
+ args.c2_init = args.c2_prefix + '.init.pb'
+ args.c2_predict = args.c2_prefix + '.predict.pb'
+
+ model = model_helper.ModelHelper(name="le_net", init_params=False)
+
+ # Bring in the init net from init_net.pb
+ init_net_proto = caffe2_pb2.NetDef()
+ with open(args.c2_init, "rb") as f:
+ init_net_proto.ParseFromString(f.read())
+ model.param_init_net = core.Net(init_net_proto)
+
+ # bring in the predict net from predict_net.pb
+ predict_net_proto = caffe2_pb2.NetDef()
+ with open(args.c2_predict, "rb") as f:
+ predict_net_proto.ParseFromString(f.read())
+ model.net = core.Net(predict_net_proto)
+
+ # CUDA performance not impressive
+ #device_opts = core.DeviceOption(caffe2_pb2.PROTO_CUDA, args.gpu_id)
+ #model.net.RunAllOnGPU(gpu_id=args.gpu_id, use_cudnn=True)
+ #model.param_init_net.RunAllOnGPU(gpu_id=args.gpu_id, use_cudnn=True)
+
+ input_blob = model.net.external_inputs[0]
+ model.param_init_net.GaussianFill(
+ [],
+ input_blob.GetUnscopedName(),
+ shape=(args.batch_size, 3, args.img_size, args.img_size),
+ mean=0.0,
+ std=1.0)
+ workspace.RunNetOnce(model.param_init_net)
+ workspace.CreateNet(model.net, overwrite=True)
+ workspace.BenchmarkNet(model.net.Proto().name, 5, 20, True)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/caffe2_validate.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/caffe2_validate.py
new file mode 100644
index 0000000000000000000000000000000000000000..7cfaab38c095663fe32e4addbdf06b57bcb53614
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/caffe2_validate.py
@@ -0,0 +1,138 @@
+""" Caffe2 validation script
+
+This script is created to verify exported ONNX models running in Caffe2
+It utilizes the same PyTorch dataloader/processing pipeline for a
+fair comparison against the originals.
+
+Copyright 2020 Ross Wightman
+"""
+import argparse
+import numpy as np
+from caffe2.python import core, workspace, model_helper
+from caffe2.proto import caffe2_pb2
+from data import create_loader, resolve_data_config, Dataset
+from utils import AverageMeter
+import time
+
+parser = argparse.ArgumentParser(description='Caffe2 ImageNet Validation')
+parser.add_argument('data', metavar='DIR',
+ help='path to dataset')
+parser.add_argument('--c2-prefix', default='', type=str, metavar='NAME',
+ help='caffe2 model pb name prefix')
+parser.add_argument('--c2-init', default='', type=str, metavar='PATH',
+ help='caffe2 model init .pb')
+parser.add_argument('--c2-predict', default='', type=str, metavar='PATH',
+ help='caffe2 model predict .pb')
+parser.add_argument('-j', '--workers', default=2, type=int, metavar='N',
+ help='number of data loading workers (default: 2)')
+parser.add_argument('-b', '--batch-size', default=256, type=int,
+ metavar='N', help='mini-batch size (default: 256)')
+parser.add_argument('--img-size', default=None, type=int,
+ metavar='N', help='Input image dimension, uses model default if empty')
+parser.add_argument('--mean', type=float, nargs='+', default=None, metavar='MEAN',
+ help='Override mean pixel value of dataset')
+parser.add_argument('--std', type=float, nargs='+', default=None, metavar='STD',
+ help='Override std deviation of of dataset')
+parser.add_argument('--crop-pct', type=float, default=None, metavar='PCT',
+ help='Override default crop pct of 0.875')
+parser.add_argument('--interpolation', default='', type=str, metavar='NAME',
+ help='Image resize interpolation type (overrides model)')
+parser.add_argument('--tf-preprocessing', dest='tf_preprocessing', action='store_true',
+ help='use tensorflow mnasnet preporcessing')
+parser.add_argument('--print-freq', '-p', default=10, type=int,
+ metavar='N', help='print frequency (default: 10)')
+
+
+def main():
+ args = parser.parse_args()
+ args.gpu_id = 0
+ if args.c2_prefix:
+ args.c2_init = args.c2_prefix + '.init.pb'
+ args.c2_predict = args.c2_prefix + '.predict.pb'
+
+ model = model_helper.ModelHelper(name="validation_net", init_params=False)
+
+ # Bring in the init net from init_net.pb
+ init_net_proto = caffe2_pb2.NetDef()
+ with open(args.c2_init, "rb") as f:
+ init_net_proto.ParseFromString(f.read())
+ model.param_init_net = core.Net(init_net_proto)
+
+ # bring in the predict net from predict_net.pb
+ predict_net_proto = caffe2_pb2.NetDef()
+ with open(args.c2_predict, "rb") as f:
+ predict_net_proto.ParseFromString(f.read())
+ model.net = core.Net(predict_net_proto)
+
+ data_config = resolve_data_config(None, args)
+ loader = create_loader(
+ Dataset(args.data, load_bytes=args.tf_preprocessing),
+ input_size=data_config['input_size'],
+ batch_size=args.batch_size,
+ use_prefetcher=False,
+ interpolation=data_config['interpolation'],
+ mean=data_config['mean'],
+ std=data_config['std'],
+ num_workers=args.workers,
+ crop_pct=data_config['crop_pct'],
+ tensorflow_preprocessing=args.tf_preprocessing)
+
+ # this is so obvious, wonderful interface
+ input_blob = model.net.external_inputs[0]
+ output_blob = model.net.external_outputs[0]
+
+ if True:
+ device_opts = None
+ else:
+ # CUDA is crashing, no idea why, awesome error message, give it a try for kicks
+ device_opts = core.DeviceOption(caffe2_pb2.PROTO_CUDA, args.gpu_id)
+ model.net.RunAllOnGPU(gpu_id=args.gpu_id, use_cudnn=True)
+ model.param_init_net.RunAllOnGPU(gpu_id=args.gpu_id, use_cudnn=True)
+
+ model.param_init_net.GaussianFill(
+ [], input_blob.GetUnscopedName(),
+ shape=(1,) + data_config['input_size'], mean=0.0, std=1.0)
+ workspace.RunNetOnce(model.param_init_net)
+ workspace.CreateNet(model.net, overwrite=True)
+
+ batch_time = AverageMeter()
+ top1 = AverageMeter()
+ top5 = AverageMeter()
+ end = time.time()
+ for i, (input, target) in enumerate(loader):
+ # run the net and return prediction
+ caffe2_in = input.data.numpy()
+ workspace.FeedBlob(input_blob, caffe2_in, device_opts)
+ workspace.RunNet(model.net, num_iter=1)
+ output = workspace.FetchBlob(output_blob)
+
+ # measure accuracy and record loss
+ prec1, prec5 = accuracy_np(output.data, target.numpy())
+ top1.update(prec1.item(), input.size(0))
+ top5.update(prec5.item(), input.size(0))
+
+ # measure elapsed time
+ batch_time.update(time.time() - end)
+ end = time.time()
+
+ if i % args.print_freq == 0:
+ print('Test: [{0}/{1}]\t'
+ 'Time {batch_time.val:.3f} ({batch_time.avg:.3f}, {rate_avg:.3f}/s, {ms_avg:.3f} ms/sample) \t'
+ 'Prec@1 {top1.val:.3f} ({top1.avg:.3f})\t'
+ 'Prec@5 {top5.val:.3f} ({top5.avg:.3f})'.format(
+ i, len(loader), batch_time=batch_time, rate_avg=input.size(0) / batch_time.avg,
+ ms_avg=100 * batch_time.avg / input.size(0), top1=top1, top5=top5))
+
+ print(' * Prec@1 {top1.avg:.3f} ({top1a:.3f}) Prec@5 {top5.avg:.3f} ({top5a:.3f})'.format(
+ top1=top1, top1a=100-top1.avg, top5=top5, top5a=100.-top5.avg))
+
+
+def accuracy_np(output, target):
+ max_indices = np.argsort(output, axis=1)[:, ::-1]
+ top5 = 100 * np.equal(max_indices[:, :5], target[:, np.newaxis]).sum(axis=1).mean()
+ top1 = 100 * np.equal(max_indices[:, 0], target).mean()
+ return top1, top5
+
+
+if __name__ == '__main__':
+ main()
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..2e441a5838d1e972823b9668ac8d459445f6f6ce
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/__init__.py
@@ -0,0 +1,5 @@
+from .gen_efficientnet import *
+from .mobilenetv3 import *
+from .model_factory import create_model
+from .config import is_exportable, is_scriptable, set_exportable, set_scriptable
+from .activations import *
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..813421a743ffc33b8eb53ebf62dd4a03d831b654
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/__init__.py
@@ -0,0 +1,137 @@
+from geffnet import config
+from geffnet.activations.activations_me import *
+from geffnet.activations.activations_jit import *
+from geffnet.activations.activations import *
+import torch
+
+_has_silu = 'silu' in dir(torch.nn.functional)
+
+_ACT_FN_DEFAULT = dict(
+ silu=F.silu if _has_silu else swish,
+ swish=F.silu if _has_silu else swish,
+ mish=mish,
+ relu=F.relu,
+ relu6=F.relu6,
+ sigmoid=sigmoid,
+ tanh=tanh,
+ hard_sigmoid=hard_sigmoid,
+ hard_swish=hard_swish,
+)
+
+_ACT_FN_JIT = dict(
+ silu=F.silu if _has_silu else swish_jit,
+ swish=F.silu if _has_silu else swish_jit,
+ mish=mish_jit,
+)
+
+_ACT_FN_ME = dict(
+ silu=F.silu if _has_silu else swish_me,
+ swish=F.silu if _has_silu else swish_me,
+ mish=mish_me,
+ hard_swish=hard_swish_me,
+ hard_sigmoid_jit=hard_sigmoid_me,
+)
+
+_ACT_LAYER_DEFAULT = dict(
+ silu=nn.SiLU if _has_silu else Swish,
+ swish=nn.SiLU if _has_silu else Swish,
+ mish=Mish,
+ relu=nn.ReLU,
+ relu6=nn.ReLU6,
+ sigmoid=Sigmoid,
+ tanh=Tanh,
+ hard_sigmoid=HardSigmoid,
+ hard_swish=HardSwish,
+)
+
+_ACT_LAYER_JIT = dict(
+ silu=nn.SiLU if _has_silu else SwishJit,
+ swish=nn.SiLU if _has_silu else SwishJit,
+ mish=MishJit,
+)
+
+_ACT_LAYER_ME = dict(
+ silu=nn.SiLU if _has_silu else SwishMe,
+ swish=nn.SiLU if _has_silu else SwishMe,
+ mish=MishMe,
+ hard_swish=HardSwishMe,
+ hard_sigmoid=HardSigmoidMe
+)
+
+_OVERRIDE_FN = dict()
+_OVERRIDE_LAYER = dict()
+
+
+def add_override_act_fn(name, fn):
+ global _OVERRIDE_FN
+ _OVERRIDE_FN[name] = fn
+
+
+def update_override_act_fn(overrides):
+ assert isinstance(overrides, dict)
+ global _OVERRIDE_FN
+ _OVERRIDE_FN.update(overrides)
+
+
+def clear_override_act_fn():
+ global _OVERRIDE_FN
+ _OVERRIDE_FN = dict()
+
+
+def add_override_act_layer(name, fn):
+ _OVERRIDE_LAYER[name] = fn
+
+
+def update_override_act_layer(overrides):
+ assert isinstance(overrides, dict)
+ global _OVERRIDE_LAYER
+ _OVERRIDE_LAYER.update(overrides)
+
+
+def clear_override_act_layer():
+ global _OVERRIDE_LAYER
+ _OVERRIDE_LAYER = dict()
+
+
+def get_act_fn(name='relu'):
+ """ Activation Function Factory
+ Fetching activation fns by name with this function allows export or torch script friendly
+ functions to be returned dynamically based on current config.
+ """
+ if name in _OVERRIDE_FN:
+ return _OVERRIDE_FN[name]
+ use_me = not (config.is_exportable() or config.is_scriptable() or config.is_no_jit())
+ if use_me and name in _ACT_FN_ME:
+ # If not exporting or scripting the model, first look for a memory optimized version
+ # activation with custom autograd, then fallback to jit scripted, then a Python or Torch builtin
+ return _ACT_FN_ME[name]
+ if config.is_exportable() and name in ('silu', 'swish'):
+ # FIXME PyTorch SiLU doesn't ONNX export, this is a temp hack
+ return swish
+ use_jit = not (config.is_exportable() or config.is_no_jit())
+ # NOTE: export tracing should work with jit scripted components, but I keep running into issues
+ if use_jit and name in _ACT_FN_JIT: # jit scripted models should be okay for export/scripting
+ return _ACT_FN_JIT[name]
+ return _ACT_FN_DEFAULT[name]
+
+
+def get_act_layer(name='relu'):
+ """ Activation Layer Factory
+ Fetching activation layers by name with this function allows export or torch script friendly
+ functions to be returned dynamically based on current config.
+ """
+ if name in _OVERRIDE_LAYER:
+ return _OVERRIDE_LAYER[name]
+ use_me = not (config.is_exportable() or config.is_scriptable() or config.is_no_jit())
+ if use_me and name in _ACT_LAYER_ME:
+ return _ACT_LAYER_ME[name]
+ if config.is_exportable() and name in ('silu', 'swish'):
+ # FIXME PyTorch SiLU doesn't ONNX export, this is a temp hack
+ return Swish
+ use_jit = not (config.is_exportable() or config.is_no_jit())
+ # NOTE: export tracing should work with jit scripted components, but I keep running into issues
+ if use_jit and name in _ACT_FN_JIT: # jit scripted models should be okay for export/scripting
+ return _ACT_LAYER_JIT[name]
+ return _ACT_LAYER_DEFAULT[name]
+
+
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/activations.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/activations.py
new file mode 100644
index 0000000000000000000000000000000000000000..bdea692d1397673b2513d898c33edbcb37d94240
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/activations.py
@@ -0,0 +1,102 @@
+""" Activations
+
+A collection of activations fn and modules with a common interface so that they can
+easily be swapped. All have an `inplace` arg even if not used.
+
+Copyright 2020 Ross Wightman
+"""
+from torch import nn as nn
+from torch.nn import functional as F
+
+
+def swish(x, inplace: bool = False):
+ """Swish - Described originally as SiLU (https://arxiv.org/abs/1702.03118v3)
+ and also as Swish (https://arxiv.org/abs/1710.05941).
+
+ TODO Rename to SiLU with addition to PyTorch
+ """
+ return x.mul_(x.sigmoid()) if inplace else x.mul(x.sigmoid())
+
+
+class Swish(nn.Module):
+ def __init__(self, inplace: bool = False):
+ super(Swish, self).__init__()
+ self.inplace = inplace
+
+ def forward(self, x):
+ return swish(x, self.inplace)
+
+
+def mish(x, inplace: bool = False):
+ """Mish: A Self Regularized Non-Monotonic Neural Activation Function - https://arxiv.org/abs/1908.08681
+ """
+ return x.mul(F.softplus(x).tanh())
+
+
+class Mish(nn.Module):
+ def __init__(self, inplace: bool = False):
+ super(Mish, self).__init__()
+ self.inplace = inplace
+
+ def forward(self, x):
+ return mish(x, self.inplace)
+
+
+def sigmoid(x, inplace: bool = False):
+ return x.sigmoid_() if inplace else x.sigmoid()
+
+
+# PyTorch has this, but not with a consistent inplace argmument interface
+class Sigmoid(nn.Module):
+ def __init__(self, inplace: bool = False):
+ super(Sigmoid, self).__init__()
+ self.inplace = inplace
+
+ def forward(self, x):
+ return x.sigmoid_() if self.inplace else x.sigmoid()
+
+
+def tanh(x, inplace: bool = False):
+ return x.tanh_() if inplace else x.tanh()
+
+
+# PyTorch has this, but not with a consistent inplace argmument interface
+class Tanh(nn.Module):
+ def __init__(self, inplace: bool = False):
+ super(Tanh, self).__init__()
+ self.inplace = inplace
+
+ def forward(self, x):
+ return x.tanh_() if self.inplace else x.tanh()
+
+
+def hard_swish(x, inplace: bool = False):
+ inner = F.relu6(x + 3.).div_(6.)
+ return x.mul_(inner) if inplace else x.mul(inner)
+
+
+class HardSwish(nn.Module):
+ def __init__(self, inplace: bool = False):
+ super(HardSwish, self).__init__()
+ self.inplace = inplace
+
+ def forward(self, x):
+ return hard_swish(x, self.inplace)
+
+
+def hard_sigmoid(x, inplace: bool = False):
+ if inplace:
+ return x.add_(3.).clamp_(0., 6.).div_(6.)
+ else:
+ return F.relu6(x + 3.) / 6.
+
+
+class HardSigmoid(nn.Module):
+ def __init__(self, inplace: bool = False):
+ super(HardSigmoid, self).__init__()
+ self.inplace = inplace
+
+ def forward(self, x):
+ return hard_sigmoid(x, self.inplace)
+
+
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/activations_jit.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/activations_jit.py
new file mode 100644
index 0000000000000000000000000000000000000000..7176b05e779787528a47f20d55d64d4a0f219360
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/activations_jit.py
@@ -0,0 +1,79 @@
+""" Activations (jit)
+
+A collection of jit-scripted activations fn and modules with a common interface so that they can
+easily be swapped. All have an `inplace` arg even if not used.
+
+All jit scripted activations are lacking in-place variations on purpose, scripted kernel fusion does not
+currently work across in-place op boundaries, thus performance is equal to or less than the non-scripted
+versions if they contain in-place ops.
+
+Copyright 2020 Ross Wightman
+"""
+
+import torch
+from torch import nn as nn
+from torch.nn import functional as F
+
+__all__ = ['swish_jit', 'SwishJit', 'mish_jit', 'MishJit',
+ 'hard_sigmoid_jit', 'HardSigmoidJit', 'hard_swish_jit', 'HardSwishJit']
+
+
+@torch.jit.script
+def swish_jit(x, inplace: bool = False):
+ """Swish - Described originally as SiLU (https://arxiv.org/abs/1702.03118v3)
+ and also as Swish (https://arxiv.org/abs/1710.05941).
+
+ TODO Rename to SiLU with addition to PyTorch
+ """
+ return x.mul(x.sigmoid())
+
+
+@torch.jit.script
+def mish_jit(x, _inplace: bool = False):
+ """Mish: A Self Regularized Non-Monotonic Neural Activation Function - https://arxiv.org/abs/1908.08681
+ """
+ return x.mul(F.softplus(x).tanh())
+
+
+class SwishJit(nn.Module):
+ def __init__(self, inplace: bool = False):
+ super(SwishJit, self).__init__()
+
+ def forward(self, x):
+ return swish_jit(x)
+
+
+class MishJit(nn.Module):
+ def __init__(self, inplace: bool = False):
+ super(MishJit, self).__init__()
+
+ def forward(self, x):
+ return mish_jit(x)
+
+
+@torch.jit.script
+def hard_sigmoid_jit(x, inplace: bool = False):
+ # return F.relu6(x + 3.) / 6.
+ return (x + 3).clamp(min=0, max=6).div(6.) # clamp seems ever so slightly faster?
+
+
+class HardSigmoidJit(nn.Module):
+ def __init__(self, inplace: bool = False):
+ super(HardSigmoidJit, self).__init__()
+
+ def forward(self, x):
+ return hard_sigmoid_jit(x)
+
+
+@torch.jit.script
+def hard_swish_jit(x, inplace: bool = False):
+ # return x * (F.relu6(x + 3.) / 6)
+ return x * (x + 3).clamp(min=0, max=6).div(6.) # clamp seems ever so slightly faster?
+
+
+class HardSwishJit(nn.Module):
+ def __init__(self, inplace: bool = False):
+ super(HardSwishJit, self).__init__()
+
+ def forward(self, x):
+ return hard_swish_jit(x)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/activations_me.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/activations_me.py
new file mode 100644
index 0000000000000000000000000000000000000000..e91df5a50fdbe40bc386e2541a4fda743ad95e9a
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/activations_me.py
@@ -0,0 +1,174 @@
+""" Activations (memory-efficient w/ custom autograd)
+
+A collection of activations fn and modules with a common interface so that they can
+easily be swapped. All have an `inplace` arg even if not used.
+
+These activations are not compatible with jit scripting or ONNX export of the model, please use either
+the JIT or basic versions of the activations.
+
+Copyright 2020 Ross Wightman
+"""
+
+import torch
+from torch import nn as nn
+from torch.nn import functional as F
+
+
+__all__ = ['swish_me', 'SwishMe', 'mish_me', 'MishMe',
+ 'hard_sigmoid_me', 'HardSigmoidMe', 'hard_swish_me', 'HardSwishMe']
+
+
+@torch.jit.script
+def swish_jit_fwd(x):
+ return x.mul(torch.sigmoid(x))
+
+
+@torch.jit.script
+def swish_jit_bwd(x, grad_output):
+ x_sigmoid = torch.sigmoid(x)
+ return grad_output * (x_sigmoid * (1 + x * (1 - x_sigmoid)))
+
+
+class SwishJitAutoFn(torch.autograd.Function):
+ """ torch.jit.script optimised Swish w/ memory-efficient checkpoint
+ Inspired by conversation btw Jeremy Howard & Adam Pazske
+ https://twitter.com/jeremyphoward/status/1188251041835315200
+
+ Swish - Described originally as SiLU (https://arxiv.org/abs/1702.03118v3)
+ and also as Swish (https://arxiv.org/abs/1710.05941).
+
+ TODO Rename to SiLU with addition to PyTorch
+ """
+
+ @staticmethod
+ def forward(ctx, x):
+ ctx.save_for_backward(x)
+ return swish_jit_fwd(x)
+
+ @staticmethod
+ def backward(ctx, grad_output):
+ x = ctx.saved_tensors[0]
+ return swish_jit_bwd(x, grad_output)
+
+
+def swish_me(x, inplace=False):
+ return SwishJitAutoFn.apply(x)
+
+
+class SwishMe(nn.Module):
+ def __init__(self, inplace: bool = False):
+ super(SwishMe, self).__init__()
+
+ def forward(self, x):
+ return SwishJitAutoFn.apply(x)
+
+
+@torch.jit.script
+def mish_jit_fwd(x):
+ return x.mul(torch.tanh(F.softplus(x)))
+
+
+@torch.jit.script
+def mish_jit_bwd(x, grad_output):
+ x_sigmoid = torch.sigmoid(x)
+ x_tanh_sp = F.softplus(x).tanh()
+ return grad_output.mul(x_tanh_sp + x * x_sigmoid * (1 - x_tanh_sp * x_tanh_sp))
+
+
+class MishJitAutoFn(torch.autograd.Function):
+ """ Mish: A Self Regularized Non-Monotonic Neural Activation Function - https://arxiv.org/abs/1908.08681
+ A memory efficient, jit scripted variant of Mish
+ """
+ @staticmethod
+ def forward(ctx, x):
+ ctx.save_for_backward(x)
+ return mish_jit_fwd(x)
+
+ @staticmethod
+ def backward(ctx, grad_output):
+ x = ctx.saved_tensors[0]
+ return mish_jit_bwd(x, grad_output)
+
+
+def mish_me(x, inplace=False):
+ return MishJitAutoFn.apply(x)
+
+
+class MishMe(nn.Module):
+ def __init__(self, inplace: bool = False):
+ super(MishMe, self).__init__()
+
+ def forward(self, x):
+ return MishJitAutoFn.apply(x)
+
+
+@torch.jit.script
+def hard_sigmoid_jit_fwd(x, inplace: bool = False):
+ return (x + 3).clamp(min=0, max=6).div(6.)
+
+
+@torch.jit.script
+def hard_sigmoid_jit_bwd(x, grad_output):
+ m = torch.ones_like(x) * ((x >= -3.) & (x <= 3.)) / 6.
+ return grad_output * m
+
+
+class HardSigmoidJitAutoFn(torch.autograd.Function):
+ @staticmethod
+ def forward(ctx, x):
+ ctx.save_for_backward(x)
+ return hard_sigmoid_jit_fwd(x)
+
+ @staticmethod
+ def backward(ctx, grad_output):
+ x = ctx.saved_tensors[0]
+ return hard_sigmoid_jit_bwd(x, grad_output)
+
+
+def hard_sigmoid_me(x, inplace: bool = False):
+ return HardSigmoidJitAutoFn.apply(x)
+
+
+class HardSigmoidMe(nn.Module):
+ def __init__(self, inplace: bool = False):
+ super(HardSigmoidMe, self).__init__()
+
+ def forward(self, x):
+ return HardSigmoidJitAutoFn.apply(x)
+
+
+@torch.jit.script
+def hard_swish_jit_fwd(x):
+ return x * (x + 3).clamp(min=0, max=6).div(6.)
+
+
+@torch.jit.script
+def hard_swish_jit_bwd(x, grad_output):
+ m = torch.ones_like(x) * (x >= 3.)
+ m = torch.where((x >= -3.) & (x <= 3.), x / 3. + .5, m)
+ return grad_output * m
+
+
+class HardSwishJitAutoFn(torch.autograd.Function):
+ """A memory efficient, jit-scripted HardSwish activation"""
+ @staticmethod
+ def forward(ctx, x):
+ ctx.save_for_backward(x)
+ return hard_swish_jit_fwd(x)
+
+ @staticmethod
+ def backward(ctx, grad_output):
+ x = ctx.saved_tensors[0]
+ return hard_swish_jit_bwd(x, grad_output)
+
+
+def hard_swish_me(x, inplace=False):
+ return HardSwishJitAutoFn.apply(x)
+
+
+class HardSwishMe(nn.Module):
+ def __init__(self, inplace: bool = False):
+ super(HardSwishMe, self).__init__()
+
+ def forward(self, x):
+ return HardSwishJitAutoFn.apply(x)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/config.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/config.py
new file mode 100644
index 0000000000000000000000000000000000000000..27d5307fd9ee0246f1e35f41520f17385d23f1dd
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/config.py
@@ -0,0 +1,123 @@
+""" Global layer config state
+"""
+from typing import Any, Optional
+
+__all__ = [
+ 'is_exportable', 'is_scriptable', 'is_no_jit', 'layer_config_kwargs',
+ 'set_exportable', 'set_scriptable', 'set_no_jit', 'set_layer_config'
+]
+
+# Set to True if prefer to have layers with no jit optimization (includes activations)
+_NO_JIT = False
+
+# Set to True if prefer to have activation layers with no jit optimization
+# NOTE not currently used as no difference between no_jit and no_activation jit as only layers obeying
+# the jit flags so far are activations. This will change as more layers are updated and/or added.
+_NO_ACTIVATION_JIT = False
+
+# Set to True if exporting a model with Same padding via ONNX
+_EXPORTABLE = False
+
+# Set to True if wanting to use torch.jit.script on a model
+_SCRIPTABLE = False
+
+
+def is_no_jit():
+ return _NO_JIT
+
+
+class set_no_jit:
+ def __init__(self, mode: bool) -> None:
+ global _NO_JIT
+ self.prev = _NO_JIT
+ _NO_JIT = mode
+
+ def __enter__(self) -> None:
+ pass
+
+ def __exit__(self, *args: Any) -> bool:
+ global _NO_JIT
+ _NO_JIT = self.prev
+ return False
+
+
+def is_exportable():
+ return _EXPORTABLE
+
+
+class set_exportable:
+ def __init__(self, mode: bool) -> None:
+ global _EXPORTABLE
+ self.prev = _EXPORTABLE
+ _EXPORTABLE = mode
+
+ def __enter__(self) -> None:
+ pass
+
+ def __exit__(self, *args: Any) -> bool:
+ global _EXPORTABLE
+ _EXPORTABLE = self.prev
+ return False
+
+
+def is_scriptable():
+ return _SCRIPTABLE
+
+
+class set_scriptable:
+ def __init__(self, mode: bool) -> None:
+ global _SCRIPTABLE
+ self.prev = _SCRIPTABLE
+ _SCRIPTABLE = mode
+
+ def __enter__(self) -> None:
+ pass
+
+ def __exit__(self, *args: Any) -> bool:
+ global _SCRIPTABLE
+ _SCRIPTABLE = self.prev
+ return False
+
+
+class set_layer_config:
+ """ Layer config context manager that allows setting all layer config flags at once.
+ If a flag arg is None, it will not change the current value.
+ """
+ def __init__(
+ self,
+ scriptable: Optional[bool] = None,
+ exportable: Optional[bool] = None,
+ no_jit: Optional[bool] = None,
+ no_activation_jit: Optional[bool] = None):
+ global _SCRIPTABLE
+ global _EXPORTABLE
+ global _NO_JIT
+ global _NO_ACTIVATION_JIT
+ self.prev = _SCRIPTABLE, _EXPORTABLE, _NO_JIT, _NO_ACTIVATION_JIT
+ if scriptable is not None:
+ _SCRIPTABLE = scriptable
+ if exportable is not None:
+ _EXPORTABLE = exportable
+ if no_jit is not None:
+ _NO_JIT = no_jit
+ if no_activation_jit is not None:
+ _NO_ACTIVATION_JIT = no_activation_jit
+
+ def __enter__(self) -> None:
+ pass
+
+ def __exit__(self, *args: Any) -> bool:
+ global _SCRIPTABLE
+ global _EXPORTABLE
+ global _NO_JIT
+ global _NO_ACTIVATION_JIT
+ _SCRIPTABLE, _EXPORTABLE, _NO_JIT, _NO_ACTIVATION_JIT = self.prev
+ return False
+
+
+def layer_config_kwargs(kwargs):
+ """ Consume config kwargs and return contextmgr obj """
+ return set_layer_config(
+ scriptable=kwargs.pop('scriptable', None),
+ exportable=kwargs.pop('exportable', None),
+ no_jit=kwargs.pop('no_jit', None))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/conv2d_layers.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/conv2d_layers.py
new file mode 100644
index 0000000000000000000000000000000000000000..d8467460c4b36e54c83ce2dcd3ebe91d3432cad2
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/conv2d_layers.py
@@ -0,0 +1,304 @@
+""" Conv2D w/ SAME padding, CondConv, MixedConv
+
+A collection of conv layers and padding helpers needed by EfficientNet, MixNet, and
+MobileNetV3 models that maintain weight compatibility with original Tensorflow models.
+
+Copyright 2020 Ross Wightman
+"""
+import collections.abc
+import math
+from functools import partial
+from itertools import repeat
+from typing import Tuple, Optional
+
+import numpy as np
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+
+from .config import *
+
+
+# From PyTorch internals
+def _ntuple(n):
+ def parse(x):
+ if isinstance(x, collections.abc.Iterable):
+ return x
+ return tuple(repeat(x, n))
+ return parse
+
+
+_single = _ntuple(1)
+_pair = _ntuple(2)
+_triple = _ntuple(3)
+_quadruple = _ntuple(4)
+
+
+def _is_static_pad(kernel_size, stride=1, dilation=1, **_):
+ return stride == 1 and (dilation * (kernel_size - 1)) % 2 == 0
+
+
+def _get_padding(kernel_size, stride=1, dilation=1, **_):
+ padding = ((stride - 1) + dilation * (kernel_size - 1)) // 2
+ return padding
+
+
+def _calc_same_pad(i: int, k: int, s: int, d: int):
+ return max((-(i // -s) - 1) * s + (k - 1) * d + 1 - i, 0)
+
+
+def _same_pad_arg(input_size, kernel_size, stride, dilation):
+ ih, iw = input_size
+ kh, kw = kernel_size
+ pad_h = _calc_same_pad(ih, kh, stride[0], dilation[0])
+ pad_w = _calc_same_pad(iw, kw, stride[1], dilation[1])
+ return [pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2]
+
+
+def _split_channels(num_chan, num_groups):
+ split = [num_chan // num_groups for _ in range(num_groups)]
+ split[0] += num_chan - sum(split)
+ return split
+
+
+def conv2d_same(
+ x, weight: torch.Tensor, bias: Optional[torch.Tensor] = None, stride: Tuple[int, int] = (1, 1),
+ padding: Tuple[int, int] = (0, 0), dilation: Tuple[int, int] = (1, 1), groups: int = 1):
+ ih, iw = x.size()[-2:]
+ kh, kw = weight.size()[-2:]
+ pad_h = _calc_same_pad(ih, kh, stride[0], dilation[0])
+ pad_w = _calc_same_pad(iw, kw, stride[1], dilation[1])
+ x = F.pad(x, [pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2])
+ return F.conv2d(x, weight, bias, stride, (0, 0), dilation, groups)
+
+
+class Conv2dSame(nn.Conv2d):
+ """ Tensorflow like 'SAME' convolution wrapper for 2D convolutions
+ """
+
+ # pylint: disable=unused-argument
+ def __init__(self, in_channels, out_channels, kernel_size, stride=1,
+ padding=0, dilation=1, groups=1, bias=True):
+ super(Conv2dSame, self).__init__(
+ in_channels, out_channels, kernel_size, stride, 0, dilation, groups, bias)
+
+ def forward(self, x):
+ return conv2d_same(x, self.weight, self.bias, self.stride, self.padding, self.dilation, self.groups)
+
+
+class Conv2dSameExport(nn.Conv2d):
+ """ ONNX export friendly Tensorflow like 'SAME' convolution wrapper for 2D convolutions
+
+ NOTE: This does not currently work with torch.jit.script
+ """
+
+ # pylint: disable=unused-argument
+ def __init__(self, in_channels, out_channels, kernel_size, stride=1,
+ padding=0, dilation=1, groups=1, bias=True):
+ super(Conv2dSameExport, self).__init__(
+ in_channels, out_channels, kernel_size, stride, 0, dilation, groups, bias)
+ self.pad = None
+ self.pad_input_size = (0, 0)
+
+ def forward(self, x):
+ input_size = x.size()[-2:]
+ if self.pad is None:
+ pad_arg = _same_pad_arg(input_size, self.weight.size()[-2:], self.stride, self.dilation)
+ self.pad = nn.ZeroPad2d(pad_arg)
+ self.pad_input_size = input_size
+
+ if self.pad is not None:
+ x = self.pad(x)
+ return F.conv2d(
+ x, self.weight, self.bias, self.stride, self.padding, self.dilation, self.groups)
+
+
+def get_padding_value(padding, kernel_size, **kwargs):
+ dynamic = False
+ if isinstance(padding, str):
+ # for any string padding, the padding will be calculated for you, one of three ways
+ padding = padding.lower()
+ if padding == 'same':
+ # TF compatible 'SAME' padding, has a performance and GPU memory allocation impact
+ if _is_static_pad(kernel_size, **kwargs):
+ # static case, no extra overhead
+ padding = _get_padding(kernel_size, **kwargs)
+ else:
+ # dynamic padding
+ padding = 0
+ dynamic = True
+ elif padding == 'valid':
+ # 'VALID' padding, same as padding=0
+ padding = 0
+ else:
+ # Default to PyTorch style 'same'-ish symmetric padding
+ padding = _get_padding(kernel_size, **kwargs)
+ return padding, dynamic
+
+
+def create_conv2d_pad(in_chs, out_chs, kernel_size, **kwargs):
+ padding = kwargs.pop('padding', '')
+ kwargs.setdefault('bias', False)
+ padding, is_dynamic = get_padding_value(padding, kernel_size, **kwargs)
+ if is_dynamic:
+ if is_exportable():
+ assert not is_scriptable()
+ return Conv2dSameExport(in_chs, out_chs, kernel_size, **kwargs)
+ else:
+ return Conv2dSame(in_chs, out_chs, kernel_size, **kwargs)
+ else:
+ return nn.Conv2d(in_chs, out_chs, kernel_size, padding=padding, **kwargs)
+
+
+class MixedConv2d(nn.ModuleDict):
+ """ Mixed Grouped Convolution
+ Based on MDConv and GroupedConv in MixNet impl:
+ https://github.com/tensorflow/tpu/blob/master/models/official/mnasnet/mixnet/custom_layers.py
+ """
+
+ def __init__(self, in_channels, out_channels, kernel_size=3,
+ stride=1, padding='', dilation=1, depthwise=False, **kwargs):
+ super(MixedConv2d, self).__init__()
+
+ kernel_size = kernel_size if isinstance(kernel_size, list) else [kernel_size]
+ num_groups = len(kernel_size)
+ in_splits = _split_channels(in_channels, num_groups)
+ out_splits = _split_channels(out_channels, num_groups)
+ self.in_channels = sum(in_splits)
+ self.out_channels = sum(out_splits)
+ for idx, (k, in_ch, out_ch) in enumerate(zip(kernel_size, in_splits, out_splits)):
+ conv_groups = out_ch if depthwise else 1
+ self.add_module(
+ str(idx),
+ create_conv2d_pad(
+ in_ch, out_ch, k, stride=stride,
+ padding=padding, dilation=dilation, groups=conv_groups, **kwargs)
+ )
+ self.splits = in_splits
+
+ def forward(self, x):
+ x_split = torch.split(x, self.splits, 1)
+ x_out = [conv(x_split[i]) for i, conv in enumerate(self.values())]
+ x = torch.cat(x_out, 1)
+ return x
+
+
+def get_condconv_initializer(initializer, num_experts, expert_shape):
+ def condconv_initializer(weight):
+ """CondConv initializer function."""
+ num_params = np.prod(expert_shape)
+ if (len(weight.shape) != 2 or weight.shape[0] != num_experts or
+ weight.shape[1] != num_params):
+ raise (ValueError(
+ 'CondConv variables must have shape [num_experts, num_params]'))
+ for i in range(num_experts):
+ initializer(weight[i].view(expert_shape))
+ return condconv_initializer
+
+
+class CondConv2d(nn.Module):
+ """ Conditional Convolution
+ Inspired by: https://github.com/tensorflow/tpu/blob/master/models/official/efficientnet/condconv/condconv_layers.py
+
+ Grouped convolution hackery for parallel execution of the per-sample kernel filters inspired by this discussion:
+ https://github.com/pytorch/pytorch/issues/17983
+ """
+ __constants__ = ['bias', 'in_channels', 'out_channels', 'dynamic_padding']
+
+ def __init__(self, in_channels, out_channels, kernel_size=3,
+ stride=1, padding='', dilation=1, groups=1, bias=False, num_experts=4):
+ super(CondConv2d, self).__init__()
+
+ self.in_channels = in_channels
+ self.out_channels = out_channels
+ self.kernel_size = _pair(kernel_size)
+ self.stride = _pair(stride)
+ padding_val, is_padding_dynamic = get_padding_value(
+ padding, kernel_size, stride=stride, dilation=dilation)
+ self.dynamic_padding = is_padding_dynamic # if in forward to work with torchscript
+ self.padding = _pair(padding_val)
+ self.dilation = _pair(dilation)
+ self.groups = groups
+ self.num_experts = num_experts
+
+ self.weight_shape = (self.out_channels, self.in_channels // self.groups) + self.kernel_size
+ weight_num_param = 1
+ for wd in self.weight_shape:
+ weight_num_param *= wd
+ self.weight = torch.nn.Parameter(torch.Tensor(self.num_experts, weight_num_param))
+
+ if bias:
+ self.bias_shape = (self.out_channels,)
+ self.bias = torch.nn.Parameter(torch.Tensor(self.num_experts, self.out_channels))
+ else:
+ self.register_parameter('bias', None)
+
+ self.reset_parameters()
+
+ def reset_parameters(self):
+ init_weight = get_condconv_initializer(
+ partial(nn.init.kaiming_uniform_, a=math.sqrt(5)), self.num_experts, self.weight_shape)
+ init_weight(self.weight)
+ if self.bias is not None:
+ fan_in = np.prod(self.weight_shape[1:])
+ bound = 1 / math.sqrt(fan_in)
+ init_bias = get_condconv_initializer(
+ partial(nn.init.uniform_, a=-bound, b=bound), self.num_experts, self.bias_shape)
+ init_bias(self.bias)
+
+ def forward(self, x, routing_weights):
+ B, C, H, W = x.shape
+ weight = torch.matmul(routing_weights, self.weight)
+ new_weight_shape = (B * self.out_channels, self.in_channels // self.groups) + self.kernel_size
+ weight = weight.view(new_weight_shape)
+ bias = None
+ if self.bias is not None:
+ bias = torch.matmul(routing_weights, self.bias)
+ bias = bias.view(B * self.out_channels)
+ # move batch elements with channels so each batch element can be efficiently convolved with separate kernel
+ x = x.view(1, B * C, H, W)
+ if self.dynamic_padding:
+ out = conv2d_same(
+ x, weight, bias, stride=self.stride, padding=self.padding,
+ dilation=self.dilation, groups=self.groups * B)
+ else:
+ out = F.conv2d(
+ x, weight, bias, stride=self.stride, padding=self.padding,
+ dilation=self.dilation, groups=self.groups * B)
+ out = out.permute([1, 0, 2, 3]).view(B, self.out_channels, out.shape[-2], out.shape[-1])
+
+ # Literal port (from TF definition)
+ # x = torch.split(x, 1, 0)
+ # weight = torch.split(weight, 1, 0)
+ # if self.bias is not None:
+ # bias = torch.matmul(routing_weights, self.bias)
+ # bias = torch.split(bias, 1, 0)
+ # else:
+ # bias = [None] * B
+ # out = []
+ # for xi, wi, bi in zip(x, weight, bias):
+ # wi = wi.view(*self.weight_shape)
+ # if bi is not None:
+ # bi = bi.view(*self.bias_shape)
+ # out.append(self.conv_fn(
+ # xi, wi, bi, stride=self.stride, padding=self.padding,
+ # dilation=self.dilation, groups=self.groups))
+ # out = torch.cat(out, 0)
+ return out
+
+
+def select_conv2d(in_chs, out_chs, kernel_size, **kwargs):
+ assert 'groups' not in kwargs # only use 'depthwise' bool arg
+ if isinstance(kernel_size, list):
+ assert 'num_experts' not in kwargs # MixNet + CondConv combo not supported currently
+ # We're going to use only lists for defining the MixedConv2d kernel groups,
+ # ints, tuples, other iterables will continue to pass to normal conv and specify h, w.
+ m = MixedConv2d(in_chs, out_chs, kernel_size, **kwargs)
+ else:
+ depthwise = kwargs.pop('depthwise', False)
+ groups = out_chs if depthwise else 1
+ if 'num_experts' in kwargs and kwargs['num_experts'] > 0:
+ m = CondConv2d(in_chs, out_chs, kernel_size, groups=groups, **kwargs)
+ else:
+ m = create_conv2d_pad(in_chs, out_chs, kernel_size, groups=groups, **kwargs)
+ return m
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/efficientnet_builder.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/efficientnet_builder.py
new file mode 100644
index 0000000000000000000000000000000000000000..95dd63d400e70d70664c5a433a2772363f865e61
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/efficientnet_builder.py
@@ -0,0 +1,683 @@
+""" EfficientNet / MobileNetV3 Blocks and Builder
+
+Copyright 2020 Ross Wightman
+"""
+import re
+from copy import deepcopy
+
+from .conv2d_layers import *
+from geffnet.activations import *
+
+__all__ = ['get_bn_args_tf', 'resolve_bn_args', 'resolve_se_args', 'resolve_act_layer', 'make_divisible',
+ 'round_channels', 'drop_connect', 'SqueezeExcite', 'ConvBnAct', 'DepthwiseSeparableConv',
+ 'InvertedResidual', 'CondConvResidual', 'EdgeResidual', 'EfficientNetBuilder', 'decode_arch_def',
+ 'initialize_weight_default', 'initialize_weight_goog', 'BN_MOMENTUM_TF_DEFAULT', 'BN_EPS_TF_DEFAULT'
+]
+
+# Defaults used for Google/Tensorflow training of mobile networks /w RMSprop as per
+# papers and TF reference implementations. PT momentum equiv for TF decay is (1 - TF decay)
+# NOTE: momentum varies btw .99 and .9997 depending on source
+# .99 in official TF TPU impl
+# .9997 (/w .999 in search space) for paper
+#
+# PyTorch defaults are momentum = .1, eps = 1e-5
+#
+BN_MOMENTUM_TF_DEFAULT = 1 - 0.99
+BN_EPS_TF_DEFAULT = 1e-3
+_BN_ARGS_TF = dict(momentum=BN_MOMENTUM_TF_DEFAULT, eps=BN_EPS_TF_DEFAULT)
+
+
+def get_bn_args_tf():
+ return _BN_ARGS_TF.copy()
+
+
+def resolve_bn_args(kwargs):
+ bn_args = get_bn_args_tf() if kwargs.pop('bn_tf', False) else {}
+ bn_momentum = kwargs.pop('bn_momentum', None)
+ if bn_momentum is not None:
+ bn_args['momentum'] = bn_momentum
+ bn_eps = kwargs.pop('bn_eps', None)
+ if bn_eps is not None:
+ bn_args['eps'] = bn_eps
+ return bn_args
+
+
+_SE_ARGS_DEFAULT = dict(
+ gate_fn=sigmoid,
+ act_layer=None, # None == use containing block's activation layer
+ reduce_mid=False,
+ divisor=1)
+
+
+def resolve_se_args(kwargs, in_chs, act_layer=None):
+ se_kwargs = kwargs.copy() if kwargs is not None else {}
+ # fill in args that aren't specified with the defaults
+ for k, v in _SE_ARGS_DEFAULT.items():
+ se_kwargs.setdefault(k, v)
+ # some models, like MobilNetV3, calculate SE reduction chs from the containing block's mid_ch instead of in_ch
+ if not se_kwargs.pop('reduce_mid'):
+ se_kwargs['reduced_base_chs'] = in_chs
+ # act_layer override, if it remains None, the containing block's act_layer will be used
+ if se_kwargs['act_layer'] is None:
+ assert act_layer is not None
+ se_kwargs['act_layer'] = act_layer
+ return se_kwargs
+
+
+def resolve_act_layer(kwargs, default='relu'):
+ act_layer = kwargs.pop('act_layer', default)
+ if isinstance(act_layer, str):
+ act_layer = get_act_layer(act_layer)
+ return act_layer
+
+
+def make_divisible(v: int, divisor: int = 8, min_value: int = None):
+ min_value = min_value or divisor
+ new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)
+ if new_v < 0.9 * v: # ensure round down does not go down by more than 10%.
+ new_v += divisor
+ return new_v
+
+
+def round_channels(channels, multiplier=1.0, divisor=8, channel_min=None):
+ """Round number of filters based on depth multiplier."""
+ if not multiplier:
+ return channels
+ channels *= multiplier
+ return make_divisible(channels, divisor, channel_min)
+
+
+def drop_connect(inputs, training: bool = False, drop_connect_rate: float = 0.):
+ """Apply drop connect."""
+ if not training:
+ return inputs
+
+ keep_prob = 1 - drop_connect_rate
+ random_tensor = keep_prob + torch.rand(
+ (inputs.size()[0], 1, 1, 1), dtype=inputs.dtype, device=inputs.device)
+ random_tensor.floor_() # binarize
+ output = inputs.div(keep_prob) * random_tensor
+ return output
+
+
+class SqueezeExcite(nn.Module):
+
+ def __init__(self, in_chs, se_ratio=0.25, reduced_base_chs=None, act_layer=nn.ReLU, gate_fn=sigmoid, divisor=1):
+ super(SqueezeExcite, self).__init__()
+ reduced_chs = make_divisible((reduced_base_chs or in_chs) * se_ratio, divisor)
+ self.conv_reduce = nn.Conv2d(in_chs, reduced_chs, 1, bias=True)
+ self.act1 = act_layer(inplace=True)
+ self.conv_expand = nn.Conv2d(reduced_chs, in_chs, 1, bias=True)
+ self.gate_fn = gate_fn
+
+ def forward(self, x):
+ x_se = x.mean((2, 3), keepdim=True)
+ x_se = self.conv_reduce(x_se)
+ x_se = self.act1(x_se)
+ x_se = self.conv_expand(x_se)
+ x = x * self.gate_fn(x_se)
+ return x
+
+
+class ConvBnAct(nn.Module):
+ def __init__(self, in_chs, out_chs, kernel_size,
+ stride=1, pad_type='', act_layer=nn.ReLU, norm_layer=nn.BatchNorm2d, norm_kwargs=None):
+ super(ConvBnAct, self).__init__()
+ assert stride in [1, 2]
+ norm_kwargs = norm_kwargs or {}
+ self.conv = select_conv2d(in_chs, out_chs, kernel_size, stride=stride, padding=pad_type)
+ self.bn1 = norm_layer(out_chs, **norm_kwargs)
+ self.act1 = act_layer(inplace=True)
+
+ def forward(self, x):
+ x = self.conv(x)
+ x = self.bn1(x)
+ x = self.act1(x)
+ return x
+
+
+class DepthwiseSeparableConv(nn.Module):
+ """ DepthwiseSeparable block
+ Used for DS convs in MobileNet-V1 and in the place of IR blocks with an expansion
+ factor of 1.0. This is an alternative to having a IR with optional first pw conv.
+ """
+ def __init__(self, in_chs, out_chs, dw_kernel_size=3,
+ stride=1, pad_type='', act_layer=nn.ReLU, noskip=False,
+ pw_kernel_size=1, pw_act=False, se_ratio=0., se_kwargs=None,
+ norm_layer=nn.BatchNorm2d, norm_kwargs=None, drop_connect_rate=0.):
+ super(DepthwiseSeparableConv, self).__init__()
+ assert stride in [1, 2]
+ norm_kwargs = norm_kwargs or {}
+ self.has_residual = (stride == 1 and in_chs == out_chs) and not noskip
+ self.drop_connect_rate = drop_connect_rate
+
+ self.conv_dw = select_conv2d(
+ in_chs, in_chs, dw_kernel_size, stride=stride, padding=pad_type, depthwise=True)
+ self.bn1 = norm_layer(in_chs, **norm_kwargs)
+ self.act1 = act_layer(inplace=True)
+
+ # Squeeze-and-excitation
+ if se_ratio is not None and se_ratio > 0.:
+ se_kwargs = resolve_se_args(se_kwargs, in_chs, act_layer)
+ self.se = SqueezeExcite(in_chs, se_ratio=se_ratio, **se_kwargs)
+ else:
+ self.se = nn.Identity()
+
+ self.conv_pw = select_conv2d(in_chs, out_chs, pw_kernel_size, padding=pad_type)
+ self.bn2 = norm_layer(out_chs, **norm_kwargs)
+ self.act2 = act_layer(inplace=True) if pw_act else nn.Identity()
+
+ def forward(self, x):
+ residual = x
+
+ x = self.conv_dw(x)
+ x = self.bn1(x)
+ x = self.act1(x)
+
+ x = self.se(x)
+
+ x = self.conv_pw(x)
+ x = self.bn2(x)
+ x = self.act2(x)
+
+ if self.has_residual:
+ if self.drop_connect_rate > 0.:
+ x = drop_connect(x, self.training, self.drop_connect_rate)
+ x += residual
+ return x
+
+
+class InvertedResidual(nn.Module):
+ """ Inverted residual block w/ optional SE"""
+
+ def __init__(self, in_chs, out_chs, dw_kernel_size=3,
+ stride=1, pad_type='', act_layer=nn.ReLU, noskip=False,
+ exp_ratio=1.0, exp_kernel_size=1, pw_kernel_size=1,
+ se_ratio=0., se_kwargs=None, norm_layer=nn.BatchNorm2d, norm_kwargs=None,
+ conv_kwargs=None, drop_connect_rate=0.):
+ super(InvertedResidual, self).__init__()
+ norm_kwargs = norm_kwargs or {}
+ conv_kwargs = conv_kwargs or {}
+ mid_chs: int = make_divisible(in_chs * exp_ratio)
+ self.has_residual = (in_chs == out_chs and stride == 1) and not noskip
+ self.drop_connect_rate = drop_connect_rate
+
+ # Point-wise expansion
+ self.conv_pw = select_conv2d(in_chs, mid_chs, exp_kernel_size, padding=pad_type, **conv_kwargs)
+ self.bn1 = norm_layer(mid_chs, **norm_kwargs)
+ self.act1 = act_layer(inplace=True)
+
+ # Depth-wise convolution
+ self.conv_dw = select_conv2d(
+ mid_chs, mid_chs, dw_kernel_size, stride=stride, padding=pad_type, depthwise=True, **conv_kwargs)
+ self.bn2 = norm_layer(mid_chs, **norm_kwargs)
+ self.act2 = act_layer(inplace=True)
+
+ # Squeeze-and-excitation
+ if se_ratio is not None and se_ratio > 0.:
+ se_kwargs = resolve_se_args(se_kwargs, in_chs, act_layer)
+ self.se = SqueezeExcite(mid_chs, se_ratio=se_ratio, **se_kwargs)
+ else:
+ self.se = nn.Identity() # for jit.script compat
+
+ # Point-wise linear projection
+ self.conv_pwl = select_conv2d(mid_chs, out_chs, pw_kernel_size, padding=pad_type, **conv_kwargs)
+ self.bn3 = norm_layer(out_chs, **norm_kwargs)
+
+ def forward(self, x):
+ residual = x
+
+ # Point-wise expansion
+ x = self.conv_pw(x)
+ x = self.bn1(x)
+ x = self.act1(x)
+
+ # Depth-wise convolution
+ x = self.conv_dw(x)
+ x = self.bn2(x)
+ x = self.act2(x)
+
+ # Squeeze-and-excitation
+ x = self.se(x)
+
+ # Point-wise linear projection
+ x = self.conv_pwl(x)
+ x = self.bn3(x)
+
+ if self.has_residual:
+ if self.drop_connect_rate > 0.:
+ x = drop_connect(x, self.training, self.drop_connect_rate)
+ x += residual
+ return x
+
+
+class CondConvResidual(InvertedResidual):
+ """ Inverted residual block w/ CondConv routing"""
+
+ def __init__(self, in_chs, out_chs, dw_kernel_size=3,
+ stride=1, pad_type='', act_layer=nn.ReLU, noskip=False,
+ exp_ratio=1.0, exp_kernel_size=1, pw_kernel_size=1,
+ se_ratio=0., se_kwargs=None, norm_layer=nn.BatchNorm2d, norm_kwargs=None,
+ num_experts=0, drop_connect_rate=0.):
+
+ self.num_experts = num_experts
+ conv_kwargs = dict(num_experts=self.num_experts)
+
+ super(CondConvResidual, self).__init__(
+ in_chs, out_chs, dw_kernel_size=dw_kernel_size, stride=stride, pad_type=pad_type,
+ act_layer=act_layer, noskip=noskip, exp_ratio=exp_ratio, exp_kernel_size=exp_kernel_size,
+ pw_kernel_size=pw_kernel_size, se_ratio=se_ratio, se_kwargs=se_kwargs,
+ norm_layer=norm_layer, norm_kwargs=norm_kwargs, conv_kwargs=conv_kwargs,
+ drop_connect_rate=drop_connect_rate)
+
+ self.routing_fn = nn.Linear(in_chs, self.num_experts)
+
+ def forward(self, x):
+ residual = x
+
+ # CondConv routing
+ pooled_inputs = F.adaptive_avg_pool2d(x, 1).flatten(1)
+ routing_weights = torch.sigmoid(self.routing_fn(pooled_inputs))
+
+ # Point-wise expansion
+ x = self.conv_pw(x, routing_weights)
+ x = self.bn1(x)
+ x = self.act1(x)
+
+ # Depth-wise convolution
+ x = self.conv_dw(x, routing_weights)
+ x = self.bn2(x)
+ x = self.act2(x)
+
+ # Squeeze-and-excitation
+ x = self.se(x)
+
+ # Point-wise linear projection
+ x = self.conv_pwl(x, routing_weights)
+ x = self.bn3(x)
+
+ if self.has_residual:
+ if self.drop_connect_rate > 0.:
+ x = drop_connect(x, self.training, self.drop_connect_rate)
+ x += residual
+ return x
+
+
+class EdgeResidual(nn.Module):
+ """ EdgeTPU Residual block with expansion convolution followed by pointwise-linear w/ stride"""
+
+ def __init__(self, in_chs, out_chs, exp_kernel_size=3, exp_ratio=1.0, fake_in_chs=0,
+ stride=1, pad_type='', act_layer=nn.ReLU, noskip=False, pw_kernel_size=1,
+ se_ratio=0., se_kwargs=None, norm_layer=nn.BatchNorm2d, norm_kwargs=None, drop_connect_rate=0.):
+ super(EdgeResidual, self).__init__()
+ norm_kwargs = norm_kwargs or {}
+ mid_chs = make_divisible(fake_in_chs * exp_ratio) if fake_in_chs > 0 else make_divisible(in_chs * exp_ratio)
+ self.has_residual = (in_chs == out_chs and stride == 1) and not noskip
+ self.drop_connect_rate = drop_connect_rate
+
+ # Expansion convolution
+ self.conv_exp = select_conv2d(in_chs, mid_chs, exp_kernel_size, padding=pad_type)
+ self.bn1 = norm_layer(mid_chs, **norm_kwargs)
+ self.act1 = act_layer(inplace=True)
+
+ # Squeeze-and-excitation
+ if se_ratio is not None and se_ratio > 0.:
+ se_kwargs = resolve_se_args(se_kwargs, in_chs, act_layer)
+ self.se = SqueezeExcite(mid_chs, se_ratio=se_ratio, **se_kwargs)
+ else:
+ self.se = nn.Identity()
+
+ # Point-wise linear projection
+ self.conv_pwl = select_conv2d(mid_chs, out_chs, pw_kernel_size, stride=stride, padding=pad_type)
+ self.bn2 = nn.BatchNorm2d(out_chs, **norm_kwargs)
+
+ def forward(self, x):
+ residual = x
+
+ # Expansion convolution
+ x = self.conv_exp(x)
+ x = self.bn1(x)
+ x = self.act1(x)
+
+ # Squeeze-and-excitation
+ x = self.se(x)
+
+ # Point-wise linear projection
+ x = self.conv_pwl(x)
+ x = self.bn2(x)
+
+ if self.has_residual:
+ if self.drop_connect_rate > 0.:
+ x = drop_connect(x, self.training, self.drop_connect_rate)
+ x += residual
+
+ return x
+
+
+class EfficientNetBuilder:
+ """ Build Trunk Blocks for Efficient/Mobile Networks
+
+ This ended up being somewhat of a cross between
+ https://github.com/tensorflow/tpu/blob/master/models/official/mnasnet/mnasnet_models.py
+ and
+ https://github.com/facebookresearch/maskrcnn-benchmark/blob/master/maskrcnn_benchmark/modeling/backbone/fbnet_builder.py
+
+ """
+
+ def __init__(self, channel_multiplier=1.0, channel_divisor=8, channel_min=None,
+ pad_type='', act_layer=None, se_kwargs=None,
+ norm_layer=nn.BatchNorm2d, norm_kwargs=None, drop_connect_rate=0.):
+ self.channel_multiplier = channel_multiplier
+ self.channel_divisor = channel_divisor
+ self.channel_min = channel_min
+ self.pad_type = pad_type
+ self.act_layer = act_layer
+ self.se_kwargs = se_kwargs
+ self.norm_layer = norm_layer
+ self.norm_kwargs = norm_kwargs
+ self.drop_connect_rate = drop_connect_rate
+
+ # updated during build
+ self.in_chs = None
+ self.block_idx = 0
+ self.block_count = 0
+
+ def _round_channels(self, chs):
+ return round_channels(chs, self.channel_multiplier, self.channel_divisor, self.channel_min)
+
+ def _make_block(self, ba):
+ bt = ba.pop('block_type')
+ ba['in_chs'] = self.in_chs
+ ba['out_chs'] = self._round_channels(ba['out_chs'])
+ if 'fake_in_chs' in ba and ba['fake_in_chs']:
+ # FIXME this is a hack to work around mismatch in origin impl input filters for EdgeTPU
+ ba['fake_in_chs'] = self._round_channels(ba['fake_in_chs'])
+ ba['norm_layer'] = self.norm_layer
+ ba['norm_kwargs'] = self.norm_kwargs
+ ba['pad_type'] = self.pad_type
+ # block act fn overrides the model default
+ ba['act_layer'] = ba['act_layer'] if ba['act_layer'] is not None else self.act_layer
+ assert ba['act_layer'] is not None
+ if bt == 'ir':
+ ba['drop_connect_rate'] = self.drop_connect_rate * self.block_idx / self.block_count
+ ba['se_kwargs'] = self.se_kwargs
+ if ba.get('num_experts', 0) > 0:
+ block = CondConvResidual(**ba)
+ else:
+ block = InvertedResidual(**ba)
+ elif bt == 'ds' or bt == 'dsa':
+ ba['drop_connect_rate'] = self.drop_connect_rate * self.block_idx / self.block_count
+ ba['se_kwargs'] = self.se_kwargs
+ block = DepthwiseSeparableConv(**ba)
+ elif bt == 'er':
+ ba['drop_connect_rate'] = self.drop_connect_rate * self.block_idx / self.block_count
+ ba['se_kwargs'] = self.se_kwargs
+ block = EdgeResidual(**ba)
+ elif bt == 'cn':
+ block = ConvBnAct(**ba)
+ else:
+ assert False, 'Uknkown block type (%s) while building model.' % bt
+ self.in_chs = ba['out_chs'] # update in_chs for arg of next block
+ return block
+
+ def _make_stack(self, stack_args):
+ blocks = []
+ # each stack (stage) contains a list of block arguments
+ for i, ba in enumerate(stack_args):
+ if i >= 1:
+ # only the first block in any stack can have a stride > 1
+ ba['stride'] = 1
+ block = self._make_block(ba)
+ blocks.append(block)
+ self.block_idx += 1 # incr global idx (across all stacks)
+ return nn.Sequential(*blocks)
+
+ def __call__(self, in_chs, block_args):
+ """ Build the blocks
+ Args:
+ in_chs: Number of input-channels passed to first block
+ block_args: A list of lists, outer list defines stages, inner
+ list contains strings defining block configuration(s)
+ Return:
+ List of block stacks (each stack wrapped in nn.Sequential)
+ """
+ self.in_chs = in_chs
+ self.block_count = sum([len(x) for x in block_args])
+ self.block_idx = 0
+ blocks = []
+ # outer list of block_args defines the stacks ('stages' by some conventions)
+ for stack_idx, stack in enumerate(block_args):
+ assert isinstance(stack, list)
+ stack = self._make_stack(stack)
+ blocks.append(stack)
+ return blocks
+
+
+def _parse_ksize(ss):
+ if ss.isdigit():
+ return int(ss)
+ else:
+ return [int(k) for k in ss.split('.')]
+
+
+def _decode_block_str(block_str):
+ """ Decode block definition string
+
+ Gets a list of block arg (dicts) through a string notation of arguments.
+ E.g. ir_r2_k3_s2_e1_i32_o16_se0.25_noskip
+
+ All args can exist in any order with the exception of the leading string which
+ is assumed to indicate the block type.
+
+ leading string - block type (
+ ir = InvertedResidual, ds = DepthwiseSep, dsa = DeptwhiseSep with pw act, cn = ConvBnAct)
+ r - number of repeat blocks,
+ k - kernel size,
+ s - strides (1-9),
+ e - expansion ratio,
+ c - output channels,
+ se - squeeze/excitation ratio
+ n - activation fn ('re', 'r6', 'hs', or 'sw')
+ Args:
+ block_str: a string representation of block arguments.
+ Returns:
+ A list of block args (dicts)
+ Raises:
+ ValueError: if the string def not properly specified (TODO)
+ """
+ assert isinstance(block_str, str)
+ ops = block_str.split('_')
+ block_type = ops[0] # take the block type off the front
+ ops = ops[1:]
+ options = {}
+ noskip = False
+ for op in ops:
+ # string options being checked on individual basis, combine if they grow
+ if op == 'noskip':
+ noskip = True
+ elif op.startswith('n'):
+ # activation fn
+ key = op[0]
+ v = op[1:]
+ if v == 're':
+ value = get_act_layer('relu')
+ elif v == 'r6':
+ value = get_act_layer('relu6')
+ elif v == 'hs':
+ value = get_act_layer('hard_swish')
+ elif v == 'sw':
+ value = get_act_layer('swish')
+ else:
+ continue
+ options[key] = value
+ else:
+ # all numeric options
+ splits = re.split(r'(\d.*)', op)
+ if len(splits) >= 2:
+ key, value = splits[:2]
+ options[key] = value
+
+ # if act_layer is None, the model default (passed to model init) will be used
+ act_layer = options['n'] if 'n' in options else None
+ exp_kernel_size = _parse_ksize(options['a']) if 'a' in options else 1
+ pw_kernel_size = _parse_ksize(options['p']) if 'p' in options else 1
+ fake_in_chs = int(options['fc']) if 'fc' in options else 0 # FIXME hack to deal with in_chs issue in TPU def
+
+ num_repeat = int(options['r'])
+ # each type of block has different valid arguments, fill accordingly
+ if block_type == 'ir':
+ block_args = dict(
+ block_type=block_type,
+ dw_kernel_size=_parse_ksize(options['k']),
+ exp_kernel_size=exp_kernel_size,
+ pw_kernel_size=pw_kernel_size,
+ out_chs=int(options['c']),
+ exp_ratio=float(options['e']),
+ se_ratio=float(options['se']) if 'se' in options else None,
+ stride=int(options['s']),
+ act_layer=act_layer,
+ noskip=noskip,
+ )
+ if 'cc' in options:
+ block_args['num_experts'] = int(options['cc'])
+ elif block_type == 'ds' or block_type == 'dsa':
+ block_args = dict(
+ block_type=block_type,
+ dw_kernel_size=_parse_ksize(options['k']),
+ pw_kernel_size=pw_kernel_size,
+ out_chs=int(options['c']),
+ se_ratio=float(options['se']) if 'se' in options else None,
+ stride=int(options['s']),
+ act_layer=act_layer,
+ pw_act=block_type == 'dsa',
+ noskip=block_type == 'dsa' or noskip,
+ )
+ elif block_type == 'er':
+ block_args = dict(
+ block_type=block_type,
+ exp_kernel_size=_parse_ksize(options['k']),
+ pw_kernel_size=pw_kernel_size,
+ out_chs=int(options['c']),
+ exp_ratio=float(options['e']),
+ fake_in_chs=fake_in_chs,
+ se_ratio=float(options['se']) if 'se' in options else None,
+ stride=int(options['s']),
+ act_layer=act_layer,
+ noskip=noskip,
+ )
+ elif block_type == 'cn':
+ block_args = dict(
+ block_type=block_type,
+ kernel_size=int(options['k']),
+ out_chs=int(options['c']),
+ stride=int(options['s']),
+ act_layer=act_layer,
+ )
+ else:
+ assert False, 'Unknown block type (%s)' % block_type
+
+ return block_args, num_repeat
+
+
+def _scale_stage_depth(stack_args, repeats, depth_multiplier=1.0, depth_trunc='ceil'):
+ """ Per-stage depth scaling
+ Scales the block repeats in each stage. This depth scaling impl maintains
+ compatibility with the EfficientNet scaling method, while allowing sensible
+ scaling for other models that may have multiple block arg definitions in each stage.
+ """
+
+ # We scale the total repeat count for each stage, there may be multiple
+ # block arg defs per stage so we need to sum.
+ num_repeat = sum(repeats)
+ if depth_trunc == 'round':
+ # Truncating to int by rounding allows stages with few repeats to remain
+ # proportionally smaller for longer. This is a good choice when stage definitions
+ # include single repeat stages that we'd prefer to keep that way as long as possible
+ num_repeat_scaled = max(1, round(num_repeat * depth_multiplier))
+ else:
+ # The default for EfficientNet truncates repeats to int via 'ceil'.
+ # Any multiplier > 1.0 will result in an increased depth for every stage.
+ num_repeat_scaled = int(math.ceil(num_repeat * depth_multiplier))
+
+ # Proportionally distribute repeat count scaling to each block definition in the stage.
+ # Allocation is done in reverse as it results in the first block being less likely to be scaled.
+ # The first block makes less sense to repeat in most of the arch definitions.
+ repeats_scaled = []
+ for r in repeats[::-1]:
+ rs = max(1, round((r / num_repeat * num_repeat_scaled)))
+ repeats_scaled.append(rs)
+ num_repeat -= r
+ num_repeat_scaled -= rs
+ repeats_scaled = repeats_scaled[::-1]
+
+ # Apply the calculated scaling to each block arg in the stage
+ sa_scaled = []
+ for ba, rep in zip(stack_args, repeats_scaled):
+ sa_scaled.extend([deepcopy(ba) for _ in range(rep)])
+ return sa_scaled
+
+
+def decode_arch_def(arch_def, depth_multiplier=1.0, depth_trunc='ceil', experts_multiplier=1, fix_first_last=False):
+ arch_args = []
+ for stack_idx, block_strings in enumerate(arch_def):
+ assert isinstance(block_strings, list)
+ stack_args = []
+ repeats = []
+ for block_str in block_strings:
+ assert isinstance(block_str, str)
+ ba, rep = _decode_block_str(block_str)
+ if ba.get('num_experts', 0) > 0 and experts_multiplier > 1:
+ ba['num_experts'] *= experts_multiplier
+ stack_args.append(ba)
+ repeats.append(rep)
+ if fix_first_last and (stack_idx == 0 or stack_idx == len(arch_def) - 1):
+ arch_args.append(_scale_stage_depth(stack_args, repeats, 1.0, depth_trunc))
+ else:
+ arch_args.append(_scale_stage_depth(stack_args, repeats, depth_multiplier, depth_trunc))
+ return arch_args
+
+
+def initialize_weight_goog(m, n='', fix_group_fanout=True):
+ # weight init as per Tensorflow Official impl
+ # https://github.com/tensorflow/tpu/blob/master/models/official/mnasnet/mnasnet_model.py
+ if isinstance(m, CondConv2d):
+ fan_out = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
+ if fix_group_fanout:
+ fan_out //= m.groups
+ init_weight_fn = get_condconv_initializer(
+ lambda w: w.data.normal_(0, math.sqrt(2.0 / fan_out)), m.num_experts, m.weight_shape)
+ init_weight_fn(m.weight)
+ if m.bias is not None:
+ m.bias.data.zero_()
+ elif isinstance(m, nn.Conv2d):
+ fan_out = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
+ if fix_group_fanout:
+ fan_out //= m.groups
+ m.weight.data.normal_(0, math.sqrt(2.0 / fan_out))
+ if m.bias is not None:
+ m.bias.data.zero_()
+ elif isinstance(m, nn.BatchNorm2d):
+ m.weight.data.fill_(1.0)
+ m.bias.data.zero_()
+ elif isinstance(m, nn.Linear):
+ fan_out = m.weight.size(0) # fan-out
+ fan_in = 0
+ if 'routing_fn' in n:
+ fan_in = m.weight.size(1)
+ init_range = 1.0 / math.sqrt(fan_in + fan_out)
+ m.weight.data.uniform_(-init_range, init_range)
+ m.bias.data.zero_()
+
+
+def initialize_weight_default(m, n=''):
+ if isinstance(m, CondConv2d):
+ init_fn = get_condconv_initializer(partial(
+ nn.init.kaiming_normal_, mode='fan_out', nonlinearity='relu'), m.num_experts, m.weight_shape)
+ init_fn(m.weight)
+ elif isinstance(m, nn.Conv2d):
+ nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
+ elif isinstance(m, nn.BatchNorm2d):
+ m.weight.data.fill_(1.0)
+ m.bias.data.zero_()
+ elif isinstance(m, nn.Linear):
+ nn.init.kaiming_uniform_(m.weight, mode='fan_in', nonlinearity='linear')
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/gen_efficientnet.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/gen_efficientnet.py
new file mode 100644
index 0000000000000000000000000000000000000000..cd170d4cc5bed6ca82b61539902b470d3320c691
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/gen_efficientnet.py
@@ -0,0 +1,1450 @@
+""" Generic Efficient Networks
+
+A generic MobileNet class with building blocks to support a variety of models:
+
+* EfficientNet (B0-B8, L2 + Tensorflow pretrained AutoAug/RandAug/AdvProp/NoisyStudent ports)
+ - EfficientNet: Rethinking Model Scaling for CNNs - https://arxiv.org/abs/1905.11946
+ - CondConv: Conditionally Parameterized Convolutions for Efficient Inference - https://arxiv.org/abs/1904.04971
+ - Adversarial Examples Improve Image Recognition - https://arxiv.org/abs/1911.09665
+ - Self-training with Noisy Student improves ImageNet classification - https://arxiv.org/abs/1911.04252
+
+* EfficientNet-Lite
+
+* MixNet (Small, Medium, and Large)
+ - MixConv: Mixed Depthwise Convolutional Kernels - https://arxiv.org/abs/1907.09595
+
+* MNasNet B1, A1 (SE), Small
+ - MnasNet: Platform-Aware Neural Architecture Search for Mobile - https://arxiv.org/abs/1807.11626
+
+* FBNet-C
+ - FBNet: Hardware-Aware Efficient ConvNet Design via Differentiable NAS - https://arxiv.org/abs/1812.03443
+
+* Single-Path NAS Pixel1
+ - Single-Path NAS: Designing Hardware-Efficient ConvNets - https://arxiv.org/abs/1904.02877
+
+* And likely more...
+
+Hacked together by / Copyright 2020 Ross Wightman
+"""
+import torch.nn as nn
+import torch.nn.functional as F
+
+from .config import layer_config_kwargs, is_scriptable
+from .conv2d_layers import select_conv2d
+from .helpers import load_pretrained
+from .efficientnet_builder import *
+
+__all__ = ['GenEfficientNet', 'mnasnet_050', 'mnasnet_075', 'mnasnet_100', 'mnasnet_b1', 'mnasnet_140',
+ 'semnasnet_050', 'semnasnet_075', 'semnasnet_100', 'mnasnet_a1', 'semnasnet_140', 'mnasnet_small',
+ 'mobilenetv2_100', 'mobilenetv2_140', 'mobilenetv2_110d', 'mobilenetv2_120d',
+ 'fbnetc_100', 'spnasnet_100', 'efficientnet_b0', 'efficientnet_b1', 'efficientnet_b2', 'efficientnet_b3',
+ 'efficientnet_b4', 'efficientnet_b5', 'efficientnet_b6', 'efficientnet_b7', 'efficientnet_b8',
+ 'efficientnet_l2', 'efficientnet_es', 'efficientnet_em', 'efficientnet_el',
+ 'efficientnet_cc_b0_4e', 'efficientnet_cc_b0_8e', 'efficientnet_cc_b1_8e',
+ 'efficientnet_lite0', 'efficientnet_lite1', 'efficientnet_lite2', 'efficientnet_lite3', 'efficientnet_lite4',
+ 'tf_efficientnet_b0', 'tf_efficientnet_b1', 'tf_efficientnet_b2', 'tf_efficientnet_b3',
+ 'tf_efficientnet_b4', 'tf_efficientnet_b5', 'tf_efficientnet_b6', 'tf_efficientnet_b7', 'tf_efficientnet_b8',
+ 'tf_efficientnet_b0_ap', 'tf_efficientnet_b1_ap', 'tf_efficientnet_b2_ap', 'tf_efficientnet_b3_ap',
+ 'tf_efficientnet_b4_ap', 'tf_efficientnet_b5_ap', 'tf_efficientnet_b6_ap', 'tf_efficientnet_b7_ap',
+ 'tf_efficientnet_b8_ap', 'tf_efficientnet_b0_ns', 'tf_efficientnet_b1_ns', 'tf_efficientnet_b2_ns',
+ 'tf_efficientnet_b3_ns', 'tf_efficientnet_b4_ns', 'tf_efficientnet_b5_ns', 'tf_efficientnet_b6_ns',
+ 'tf_efficientnet_b7_ns', 'tf_efficientnet_l2_ns', 'tf_efficientnet_l2_ns_475',
+ 'tf_efficientnet_es', 'tf_efficientnet_em', 'tf_efficientnet_el',
+ 'tf_efficientnet_cc_b0_4e', 'tf_efficientnet_cc_b0_8e', 'tf_efficientnet_cc_b1_8e',
+ 'tf_efficientnet_lite0', 'tf_efficientnet_lite1', 'tf_efficientnet_lite2', 'tf_efficientnet_lite3',
+ 'tf_efficientnet_lite4',
+ 'mixnet_s', 'mixnet_m', 'mixnet_l', 'mixnet_xl', 'tf_mixnet_s', 'tf_mixnet_m', 'tf_mixnet_l']
+
+
+model_urls = {
+ 'mnasnet_050': None,
+ 'mnasnet_075': None,
+ 'mnasnet_100':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/mnasnet_b1-74cb7081.pth',
+ 'mnasnet_140': None,
+ 'mnasnet_small': None,
+
+ 'semnasnet_050': None,
+ 'semnasnet_075': None,
+ 'semnasnet_100':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/mnasnet_a1-d9418771.pth',
+ 'semnasnet_140': None,
+
+ 'mobilenetv2_100':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/mobilenetv2_100_ra-b33bc2c4.pth',
+ 'mobilenetv2_110d':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/mobilenetv2_110d_ra-77090ade.pth',
+ 'mobilenetv2_120d':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/mobilenetv2_120d_ra-5987e2ed.pth',
+ 'mobilenetv2_140':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/mobilenetv2_140_ra-21a4e913.pth',
+
+ 'fbnetc_100':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/fbnetc_100-c345b898.pth',
+ 'spnasnet_100':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/spnasnet_100-048bc3f4.pth',
+
+ 'efficientnet_b0':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/efficientnet_b0_ra-3dd342df.pth',
+ 'efficientnet_b1':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/efficientnet_b1-533bc792.pth',
+ 'efficientnet_b2':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/efficientnet_b2_ra-bcdf34b7.pth',
+ 'efficientnet_b3':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/efficientnet_b3_ra2-cf984f9c.pth',
+ 'efficientnet_b4': None,
+ 'efficientnet_b5': None,
+ 'efficientnet_b6': None,
+ 'efficientnet_b7': None,
+ 'efficientnet_b8': None,
+ 'efficientnet_l2': None,
+
+ 'efficientnet_es':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/efficientnet_es_ra-f111e99c.pth',
+ 'efficientnet_em': None,
+ 'efficientnet_el': None,
+
+ 'efficientnet_cc_b0_4e': None,
+ 'efficientnet_cc_b0_8e': None,
+ 'efficientnet_cc_b1_8e': None,
+
+ 'efficientnet_lite0': 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/efficientnet_lite0_ra-37913777.pth',
+ 'efficientnet_lite1': None,
+ 'efficientnet_lite2': None,
+ 'efficientnet_lite3': None,
+ 'efficientnet_lite4': None,
+
+ 'tf_efficientnet_b0':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b0_aa-827b6e33.pth',
+ 'tf_efficientnet_b1':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b1_aa-ea7a6ee0.pth',
+ 'tf_efficientnet_b2':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b2_aa-60c94f97.pth',
+ 'tf_efficientnet_b3':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b3_aa-84b4657e.pth',
+ 'tf_efficientnet_b4':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b4_aa-818f208c.pth',
+ 'tf_efficientnet_b5':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b5_ra-9a3e5369.pth',
+ 'tf_efficientnet_b6':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b6_aa-80ba17e4.pth',
+ 'tf_efficientnet_b7':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b7_ra-6c08e654.pth',
+ 'tf_efficientnet_b8':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b8_ra-572d5dd9.pth',
+
+ 'tf_efficientnet_b0_ap':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b0_ap-f262efe1.pth',
+ 'tf_efficientnet_b1_ap':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b1_ap-44ef0a3d.pth',
+ 'tf_efficientnet_b2_ap':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b2_ap-2f8e7636.pth',
+ 'tf_efficientnet_b3_ap':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b3_ap-aad25bdd.pth',
+ 'tf_efficientnet_b4_ap':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b4_ap-dedb23e6.pth',
+ 'tf_efficientnet_b5_ap':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b5_ap-9e82fae8.pth',
+ 'tf_efficientnet_b6_ap':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b6_ap-4ffb161f.pth',
+ 'tf_efficientnet_b7_ap':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b7_ap-ddb28fec.pth',
+ 'tf_efficientnet_b8_ap':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b8_ap-00e169fa.pth',
+
+ 'tf_efficientnet_b0_ns':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b0_ns-c0e6a31c.pth',
+ 'tf_efficientnet_b1_ns':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b1_ns-99dd0c41.pth',
+ 'tf_efficientnet_b2_ns':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b2_ns-00306e48.pth',
+ 'tf_efficientnet_b3_ns':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b3_ns-9d44bf68.pth',
+ 'tf_efficientnet_b4_ns':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b4_ns-d6313a46.pth',
+ 'tf_efficientnet_b5_ns':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b5_ns-6f26d0cf.pth',
+ 'tf_efficientnet_b6_ns':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b6_ns-51548356.pth',
+ 'tf_efficientnet_b7_ns':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b7_ns-1dbc32de.pth',
+ 'tf_efficientnet_l2_ns_475':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_l2_ns_475-bebbd00a.pth',
+ 'tf_efficientnet_l2_ns':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_l2_ns-df73bb44.pth',
+
+ 'tf_efficientnet_es':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_es-ca1afbfe.pth',
+ 'tf_efficientnet_em':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_em-e78cfe58.pth',
+ 'tf_efficientnet_el':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_el-5143854e.pth',
+
+ 'tf_efficientnet_cc_b0_4e':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_cc_b0_4e-4362b6b2.pth',
+ 'tf_efficientnet_cc_b0_8e':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_cc_b0_8e-66184a25.pth',
+ 'tf_efficientnet_cc_b1_8e':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_cc_b1_8e-f7c79ae1.pth',
+
+ 'tf_efficientnet_lite0':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_lite0-0aa007d2.pth',
+ 'tf_efficientnet_lite1':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_lite1-bde8b488.pth',
+ 'tf_efficientnet_lite2':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_lite2-dcccb7df.pth',
+ 'tf_efficientnet_lite3':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_lite3-b733e338.pth',
+ 'tf_efficientnet_lite4':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_lite4-741542c3.pth',
+
+ 'mixnet_s': 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/mixnet_s-a907afbc.pth',
+ 'mixnet_m': 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/mixnet_m-4647fc68.pth',
+ 'mixnet_l': 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/mixnet_l-5a9a2ed8.pth',
+ 'mixnet_xl': 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/mixnet_xl_ra-aac3c00c.pth',
+
+ 'tf_mixnet_s':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_mixnet_s-89d3354b.pth',
+ 'tf_mixnet_m':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_mixnet_m-0f4d8805.pth',
+ 'tf_mixnet_l':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_mixnet_l-6c92e0c8.pth',
+}
+
+
+class GenEfficientNet(nn.Module):
+ """ Generic EfficientNets
+
+ An implementation of mobile optimized networks that covers:
+ * EfficientNet (B0-B8, L2, CondConv, EdgeTPU)
+ * MixNet (Small, Medium, and Large, XL)
+ * MNASNet A1, B1, and small
+ * FBNet C
+ * Single-Path NAS Pixel1
+ """
+
+ def __init__(self, block_args, num_classes=1000, in_chans=3, num_features=1280, stem_size=32, fix_stem=False,
+ channel_multiplier=1.0, channel_divisor=8, channel_min=None,
+ pad_type='', act_layer=nn.ReLU, drop_rate=0., drop_connect_rate=0.,
+ se_kwargs=None, norm_layer=nn.BatchNorm2d, norm_kwargs=None,
+ weight_init='goog'):
+ super(GenEfficientNet, self).__init__()
+ self.drop_rate = drop_rate
+
+ if not fix_stem:
+ stem_size = round_channels(stem_size, channel_multiplier, channel_divisor, channel_min)
+ self.conv_stem = select_conv2d(in_chans, stem_size, 3, stride=2, padding=pad_type)
+ self.bn1 = norm_layer(stem_size, **norm_kwargs)
+ self.act1 = act_layer(inplace=True)
+ in_chs = stem_size
+
+ builder = EfficientNetBuilder(
+ channel_multiplier, channel_divisor, channel_min,
+ pad_type, act_layer, se_kwargs, norm_layer, norm_kwargs, drop_connect_rate)
+ self.blocks = nn.Sequential(*builder(in_chs, block_args))
+ in_chs = builder.in_chs
+
+ self.conv_head = select_conv2d(in_chs, num_features, 1, padding=pad_type)
+ self.bn2 = norm_layer(num_features, **norm_kwargs)
+ self.act2 = act_layer(inplace=True)
+ self.global_pool = nn.AdaptiveAvgPool2d(1)
+ self.classifier = nn.Linear(num_features, num_classes)
+
+ for n, m in self.named_modules():
+ if weight_init == 'goog':
+ initialize_weight_goog(m, n)
+ else:
+ initialize_weight_default(m, n)
+
+ def features(self, x):
+ x = self.conv_stem(x)
+ x = self.bn1(x)
+ x = self.act1(x)
+ x = self.blocks(x)
+ x = self.conv_head(x)
+ x = self.bn2(x)
+ x = self.act2(x)
+ return x
+
+ def as_sequential(self):
+ layers = [self.conv_stem, self.bn1, self.act1]
+ layers.extend(self.blocks)
+ layers.extend([
+ self.conv_head, self.bn2, self.act2,
+ self.global_pool, nn.Flatten(), nn.Dropout(self.drop_rate), self.classifier])
+ return nn.Sequential(*layers)
+
+ def forward(self, x):
+ x = self.features(x)
+ x = self.global_pool(x)
+ x = x.flatten(1)
+ if self.drop_rate > 0.:
+ x = F.dropout(x, p=self.drop_rate, training=self.training)
+ return self.classifier(x)
+
+
+def _create_model(model_kwargs, variant, pretrained=False):
+ as_sequential = model_kwargs.pop('as_sequential', False)
+ model = GenEfficientNet(**model_kwargs)
+ if pretrained:
+ load_pretrained(model, model_urls[variant])
+ if as_sequential:
+ model = model.as_sequential()
+ return model
+
+
+def _gen_mnasnet_a1(variant, channel_multiplier=1.0, pretrained=False, **kwargs):
+ """Creates a mnasnet-a1 model.
+
+ Ref impl: https://github.com/tensorflow/tpu/tree/master/models/official/mnasnet
+ Paper: https://arxiv.org/pdf/1807.11626.pdf.
+
+ Args:
+ channel_multiplier: multiplier to number of channels per layer.
+ """
+ arch_def = [
+ # stage 0, 112x112 in
+ ['ds_r1_k3_s1_e1_c16_noskip'],
+ # stage 1, 112x112 in
+ ['ir_r2_k3_s2_e6_c24'],
+ # stage 2, 56x56 in
+ ['ir_r3_k5_s2_e3_c40_se0.25'],
+ # stage 3, 28x28 in
+ ['ir_r4_k3_s2_e6_c80'],
+ # stage 4, 14x14in
+ ['ir_r2_k3_s1_e6_c112_se0.25'],
+ # stage 5, 14x14in
+ ['ir_r3_k5_s2_e6_c160_se0.25'],
+ # stage 6, 7x7 in
+ ['ir_r1_k3_s1_e6_c320'],
+ ]
+ with layer_config_kwargs(kwargs):
+ model_kwargs = dict(
+ block_args=decode_arch_def(arch_def),
+ stem_size=32,
+ channel_multiplier=channel_multiplier,
+ act_layer=resolve_act_layer(kwargs, 'relu'),
+ norm_kwargs=resolve_bn_args(kwargs),
+ **kwargs
+ )
+ model = _create_model(model_kwargs, variant, pretrained)
+ return model
+
+
+def _gen_mnasnet_b1(variant, channel_multiplier=1.0, pretrained=False, **kwargs):
+ """Creates a mnasnet-b1 model.
+
+ Ref impl: https://github.com/tensorflow/tpu/tree/master/models/official/mnasnet
+ Paper: https://arxiv.org/pdf/1807.11626.pdf.
+
+ Args:
+ channel_multiplier: multiplier to number of channels per layer.
+ """
+ arch_def = [
+ # stage 0, 112x112 in
+ ['ds_r1_k3_s1_c16_noskip'],
+ # stage 1, 112x112 in
+ ['ir_r3_k3_s2_e3_c24'],
+ # stage 2, 56x56 in
+ ['ir_r3_k5_s2_e3_c40'],
+ # stage 3, 28x28 in
+ ['ir_r3_k5_s2_e6_c80'],
+ # stage 4, 14x14in
+ ['ir_r2_k3_s1_e6_c96'],
+ # stage 5, 14x14in
+ ['ir_r4_k5_s2_e6_c192'],
+ # stage 6, 7x7 in
+ ['ir_r1_k3_s1_e6_c320_noskip']
+ ]
+ with layer_config_kwargs(kwargs):
+ model_kwargs = dict(
+ block_args=decode_arch_def(arch_def),
+ stem_size=32,
+ channel_multiplier=channel_multiplier,
+ act_layer=resolve_act_layer(kwargs, 'relu'),
+ norm_kwargs=resolve_bn_args(kwargs),
+ **kwargs
+ )
+ model = _create_model(model_kwargs, variant, pretrained)
+ return model
+
+
+def _gen_mnasnet_small(variant, channel_multiplier=1.0, pretrained=False, **kwargs):
+ """Creates a mnasnet-b1 model.
+
+ Ref impl: https://github.com/tensorflow/tpu/tree/master/models/official/mnasnet
+ Paper: https://arxiv.org/pdf/1807.11626.pdf.
+
+ Args:
+ channel_multiplier: multiplier to number of channels per layer.
+ """
+ arch_def = [
+ ['ds_r1_k3_s1_c8'],
+ ['ir_r1_k3_s2_e3_c16'],
+ ['ir_r2_k3_s2_e6_c16'],
+ ['ir_r4_k5_s2_e6_c32_se0.25'],
+ ['ir_r3_k3_s1_e6_c32_se0.25'],
+ ['ir_r3_k5_s2_e6_c88_se0.25'],
+ ['ir_r1_k3_s1_e6_c144']
+ ]
+ with layer_config_kwargs(kwargs):
+ model_kwargs = dict(
+ block_args=decode_arch_def(arch_def),
+ stem_size=8,
+ channel_multiplier=channel_multiplier,
+ act_layer=resolve_act_layer(kwargs, 'relu'),
+ norm_kwargs=resolve_bn_args(kwargs),
+ **kwargs
+ )
+ model = _create_model(model_kwargs, variant, pretrained)
+ return model
+
+
+def _gen_mobilenet_v2(
+ variant, channel_multiplier=1.0, depth_multiplier=1.0, fix_stem_head=False, pretrained=False, **kwargs):
+ """ Generate MobileNet-V2 network
+ Ref impl: https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet_v2.py
+ Paper: https://arxiv.org/abs/1801.04381
+ """
+ arch_def = [
+ ['ds_r1_k3_s1_c16'],
+ ['ir_r2_k3_s2_e6_c24'],
+ ['ir_r3_k3_s2_e6_c32'],
+ ['ir_r4_k3_s2_e6_c64'],
+ ['ir_r3_k3_s1_e6_c96'],
+ ['ir_r3_k3_s2_e6_c160'],
+ ['ir_r1_k3_s1_e6_c320'],
+ ]
+ with layer_config_kwargs(kwargs):
+ model_kwargs = dict(
+ block_args=decode_arch_def(arch_def, depth_multiplier=depth_multiplier, fix_first_last=fix_stem_head),
+ num_features=1280 if fix_stem_head else round_channels(1280, channel_multiplier, 8, None),
+ stem_size=32,
+ fix_stem=fix_stem_head,
+ channel_multiplier=channel_multiplier,
+ norm_kwargs=resolve_bn_args(kwargs),
+ act_layer=nn.ReLU6,
+ **kwargs
+ )
+ model = _create_model(model_kwargs, variant, pretrained)
+ return model
+
+
+def _gen_fbnetc(variant, channel_multiplier=1.0, pretrained=False, **kwargs):
+ """ FBNet-C
+
+ Paper: https://arxiv.org/abs/1812.03443
+ Ref Impl: https://github.com/facebookresearch/maskrcnn-benchmark/blob/master/maskrcnn_benchmark/modeling/backbone/fbnet_modeldef.py
+
+ NOTE: the impl above does not relate to the 'C' variant here, that was derived from paper,
+ it was used to confirm some building block details
+ """
+ arch_def = [
+ ['ir_r1_k3_s1_e1_c16'],
+ ['ir_r1_k3_s2_e6_c24', 'ir_r2_k3_s1_e1_c24'],
+ ['ir_r1_k5_s2_e6_c32', 'ir_r1_k5_s1_e3_c32', 'ir_r1_k5_s1_e6_c32', 'ir_r1_k3_s1_e6_c32'],
+ ['ir_r1_k5_s2_e6_c64', 'ir_r1_k5_s1_e3_c64', 'ir_r2_k5_s1_e6_c64'],
+ ['ir_r3_k5_s1_e6_c112', 'ir_r1_k5_s1_e3_c112'],
+ ['ir_r4_k5_s2_e6_c184'],
+ ['ir_r1_k3_s1_e6_c352'],
+ ]
+ with layer_config_kwargs(kwargs):
+ model_kwargs = dict(
+ block_args=decode_arch_def(arch_def),
+ stem_size=16,
+ num_features=1984, # paper suggests this, but is not 100% clear
+ channel_multiplier=channel_multiplier,
+ act_layer=resolve_act_layer(kwargs, 'relu'),
+ norm_kwargs=resolve_bn_args(kwargs),
+ **kwargs
+ )
+ model = _create_model(model_kwargs, variant, pretrained)
+ return model
+
+
+def _gen_spnasnet(variant, channel_multiplier=1.0, pretrained=False, **kwargs):
+ """Creates the Single-Path NAS model from search targeted for Pixel1 phone.
+
+ Paper: https://arxiv.org/abs/1904.02877
+
+ Args:
+ channel_multiplier: multiplier to number of channels per layer.
+ """
+ arch_def = [
+ # stage 0, 112x112 in
+ ['ds_r1_k3_s1_c16_noskip'],
+ # stage 1, 112x112 in
+ ['ir_r3_k3_s2_e3_c24'],
+ # stage 2, 56x56 in
+ ['ir_r1_k5_s2_e6_c40', 'ir_r3_k3_s1_e3_c40'],
+ # stage 3, 28x28 in
+ ['ir_r1_k5_s2_e6_c80', 'ir_r3_k3_s1_e3_c80'],
+ # stage 4, 14x14in
+ ['ir_r1_k5_s1_e6_c96', 'ir_r3_k5_s1_e3_c96'],
+ # stage 5, 14x14in
+ ['ir_r4_k5_s2_e6_c192'],
+ # stage 6, 7x7 in
+ ['ir_r1_k3_s1_e6_c320_noskip']
+ ]
+ with layer_config_kwargs(kwargs):
+ model_kwargs = dict(
+ block_args=decode_arch_def(arch_def),
+ stem_size=32,
+ channel_multiplier=channel_multiplier,
+ act_layer=resolve_act_layer(kwargs, 'relu'),
+ norm_kwargs=resolve_bn_args(kwargs),
+ **kwargs
+ )
+ model = _create_model(model_kwargs, variant, pretrained)
+ return model
+
+
+def _gen_efficientnet(variant, channel_multiplier=1.0, depth_multiplier=1.0, pretrained=False, **kwargs):
+ """Creates an EfficientNet model.
+
+ Ref impl: https://github.com/tensorflow/tpu/blob/master/models/official/efficientnet/efficientnet_model.py
+ Paper: https://arxiv.org/abs/1905.11946
+
+ EfficientNet params
+ name: (channel_multiplier, depth_multiplier, resolution, dropout_rate)
+ 'efficientnet-b0': (1.0, 1.0, 224, 0.2),
+ 'efficientnet-b1': (1.0, 1.1, 240, 0.2),
+ 'efficientnet-b2': (1.1, 1.2, 260, 0.3),
+ 'efficientnet-b3': (1.2, 1.4, 300, 0.3),
+ 'efficientnet-b4': (1.4, 1.8, 380, 0.4),
+ 'efficientnet-b5': (1.6, 2.2, 456, 0.4),
+ 'efficientnet-b6': (1.8, 2.6, 528, 0.5),
+ 'efficientnet-b7': (2.0, 3.1, 600, 0.5),
+ 'efficientnet-b8': (2.2, 3.6, 672, 0.5),
+
+ Args:
+ channel_multiplier: multiplier to number of channels per layer
+ depth_multiplier: multiplier to number of repeats per stage
+
+ """
+ arch_def = [
+ ['ds_r1_k3_s1_e1_c16_se0.25'],
+ ['ir_r2_k3_s2_e6_c24_se0.25'],
+ ['ir_r2_k5_s2_e6_c40_se0.25'],
+ ['ir_r3_k3_s2_e6_c80_se0.25'],
+ ['ir_r3_k5_s1_e6_c112_se0.25'],
+ ['ir_r4_k5_s2_e6_c192_se0.25'],
+ ['ir_r1_k3_s1_e6_c320_se0.25'],
+ ]
+ with layer_config_kwargs(kwargs):
+ model_kwargs = dict(
+ block_args=decode_arch_def(arch_def, depth_multiplier),
+ num_features=round_channels(1280, channel_multiplier, 8, None),
+ stem_size=32,
+ channel_multiplier=channel_multiplier,
+ act_layer=resolve_act_layer(kwargs, 'swish'),
+ norm_kwargs=resolve_bn_args(kwargs),
+ **kwargs,
+ )
+ model = _create_model(model_kwargs, variant, pretrained)
+ return model
+
+
+def _gen_efficientnet_edge(variant, channel_multiplier=1.0, depth_multiplier=1.0, pretrained=False, **kwargs):
+ arch_def = [
+ # NOTE `fc` is present to override a mismatch between stem channels and in chs not
+ # present in other models
+ ['er_r1_k3_s1_e4_c24_fc24_noskip'],
+ ['er_r2_k3_s2_e8_c32'],
+ ['er_r4_k3_s2_e8_c48'],
+ ['ir_r5_k5_s2_e8_c96'],
+ ['ir_r4_k5_s1_e8_c144'],
+ ['ir_r2_k5_s2_e8_c192'],
+ ]
+ with layer_config_kwargs(kwargs):
+ model_kwargs = dict(
+ block_args=decode_arch_def(arch_def, depth_multiplier),
+ num_features=round_channels(1280, channel_multiplier, 8, None),
+ stem_size=32,
+ channel_multiplier=channel_multiplier,
+ act_layer=resolve_act_layer(kwargs, 'relu'),
+ norm_kwargs=resolve_bn_args(kwargs),
+ **kwargs,
+ )
+ model = _create_model(model_kwargs, variant, pretrained)
+ return model
+
+
+def _gen_efficientnet_condconv(
+ variant, channel_multiplier=1.0, depth_multiplier=1.0, experts_multiplier=1, pretrained=False, **kwargs):
+ """Creates an efficientnet-condconv model."""
+ arch_def = [
+ ['ds_r1_k3_s1_e1_c16_se0.25'],
+ ['ir_r2_k3_s2_e6_c24_se0.25'],
+ ['ir_r2_k5_s2_e6_c40_se0.25'],
+ ['ir_r3_k3_s2_e6_c80_se0.25'],
+ ['ir_r3_k5_s1_e6_c112_se0.25_cc4'],
+ ['ir_r4_k5_s2_e6_c192_se0.25_cc4'],
+ ['ir_r1_k3_s1_e6_c320_se0.25_cc4'],
+ ]
+ with layer_config_kwargs(kwargs):
+ model_kwargs = dict(
+ block_args=decode_arch_def(arch_def, depth_multiplier, experts_multiplier=experts_multiplier),
+ num_features=round_channels(1280, channel_multiplier, 8, None),
+ stem_size=32,
+ channel_multiplier=channel_multiplier,
+ act_layer=resolve_act_layer(kwargs, 'swish'),
+ norm_kwargs=resolve_bn_args(kwargs),
+ **kwargs,
+ )
+ model = _create_model(model_kwargs, variant, pretrained)
+ return model
+
+
+def _gen_efficientnet_lite(variant, channel_multiplier=1.0, depth_multiplier=1.0, pretrained=False, **kwargs):
+ """Creates an EfficientNet-Lite model.
+
+ Ref impl: https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet/lite
+ Paper: https://arxiv.org/abs/1905.11946
+
+ EfficientNet params
+ name: (channel_multiplier, depth_multiplier, resolution, dropout_rate)
+ 'efficientnet-lite0': (1.0, 1.0, 224, 0.2),
+ 'efficientnet-lite1': (1.0, 1.1, 240, 0.2),
+ 'efficientnet-lite2': (1.1, 1.2, 260, 0.3),
+ 'efficientnet-lite3': (1.2, 1.4, 280, 0.3),
+ 'efficientnet-lite4': (1.4, 1.8, 300, 0.3),
+
+ Args:
+ channel_multiplier: multiplier to number of channels per layer
+ depth_multiplier: multiplier to number of repeats per stage
+ """
+ arch_def = [
+ ['ds_r1_k3_s1_e1_c16'],
+ ['ir_r2_k3_s2_e6_c24'],
+ ['ir_r2_k5_s2_e6_c40'],
+ ['ir_r3_k3_s2_e6_c80'],
+ ['ir_r3_k5_s1_e6_c112'],
+ ['ir_r4_k5_s2_e6_c192'],
+ ['ir_r1_k3_s1_e6_c320'],
+ ]
+ with layer_config_kwargs(kwargs):
+ model_kwargs = dict(
+ block_args=decode_arch_def(arch_def, depth_multiplier, fix_first_last=True),
+ num_features=1280,
+ stem_size=32,
+ fix_stem=True,
+ channel_multiplier=channel_multiplier,
+ act_layer=nn.ReLU6,
+ norm_kwargs=resolve_bn_args(kwargs),
+ **kwargs,
+ )
+ model = _create_model(model_kwargs, variant, pretrained)
+ return model
+
+
+def _gen_mixnet_s(variant, channel_multiplier=1.0, pretrained=False, **kwargs):
+ """Creates a MixNet Small model.
+
+ Ref impl: https://github.com/tensorflow/tpu/tree/master/models/official/mnasnet/mixnet
+ Paper: https://arxiv.org/abs/1907.09595
+ """
+ arch_def = [
+ # stage 0, 112x112 in
+ ['ds_r1_k3_s1_e1_c16'], # relu
+ # stage 1, 112x112 in
+ ['ir_r1_k3_a1.1_p1.1_s2_e6_c24', 'ir_r1_k3_a1.1_p1.1_s1_e3_c24'], # relu
+ # stage 2, 56x56 in
+ ['ir_r1_k3.5.7_s2_e6_c40_se0.5_nsw', 'ir_r3_k3.5_a1.1_p1.1_s1_e6_c40_se0.5_nsw'], # swish
+ # stage 3, 28x28 in
+ ['ir_r1_k3.5.7_p1.1_s2_e6_c80_se0.25_nsw', 'ir_r2_k3.5_p1.1_s1_e6_c80_se0.25_nsw'], # swish
+ # stage 4, 14x14in
+ ['ir_r1_k3.5.7_a1.1_p1.1_s1_e6_c120_se0.5_nsw', 'ir_r2_k3.5.7.9_a1.1_p1.1_s1_e3_c120_se0.5_nsw'], # swish
+ # stage 5, 14x14in
+ ['ir_r1_k3.5.7.9.11_s2_e6_c200_se0.5_nsw', 'ir_r2_k3.5.7.9_p1.1_s1_e6_c200_se0.5_nsw'], # swish
+ # 7x7
+ ]
+ with layer_config_kwargs(kwargs):
+ model_kwargs = dict(
+ block_args=decode_arch_def(arch_def),
+ num_features=1536,
+ stem_size=16,
+ channel_multiplier=channel_multiplier,
+ act_layer=resolve_act_layer(kwargs, 'relu'),
+ norm_kwargs=resolve_bn_args(kwargs),
+ **kwargs
+ )
+ model = _create_model(model_kwargs, variant, pretrained)
+ return model
+
+
+def _gen_mixnet_m(variant, channel_multiplier=1.0, depth_multiplier=1.0, pretrained=False, **kwargs):
+ """Creates a MixNet Medium-Large model.
+
+ Ref impl: https://github.com/tensorflow/tpu/tree/master/models/official/mnasnet/mixnet
+ Paper: https://arxiv.org/abs/1907.09595
+ """
+ arch_def = [
+ # stage 0, 112x112 in
+ ['ds_r1_k3_s1_e1_c24'], # relu
+ # stage 1, 112x112 in
+ ['ir_r1_k3.5.7_a1.1_p1.1_s2_e6_c32', 'ir_r1_k3_a1.1_p1.1_s1_e3_c32'], # relu
+ # stage 2, 56x56 in
+ ['ir_r1_k3.5.7.9_s2_e6_c40_se0.5_nsw', 'ir_r3_k3.5_a1.1_p1.1_s1_e6_c40_se0.5_nsw'], # swish
+ # stage 3, 28x28 in
+ ['ir_r1_k3.5.7_s2_e6_c80_se0.25_nsw', 'ir_r3_k3.5.7.9_a1.1_p1.1_s1_e6_c80_se0.25_nsw'], # swish
+ # stage 4, 14x14in
+ ['ir_r1_k3_s1_e6_c120_se0.5_nsw', 'ir_r3_k3.5.7.9_a1.1_p1.1_s1_e3_c120_se0.5_nsw'], # swish
+ # stage 5, 14x14in
+ ['ir_r1_k3.5.7.9_s2_e6_c200_se0.5_nsw', 'ir_r3_k3.5.7.9_p1.1_s1_e6_c200_se0.5_nsw'], # swish
+ # 7x7
+ ]
+ with layer_config_kwargs(kwargs):
+ model_kwargs = dict(
+ block_args=decode_arch_def(arch_def, depth_multiplier, depth_trunc='round'),
+ num_features=1536,
+ stem_size=24,
+ channel_multiplier=channel_multiplier,
+ act_layer=resolve_act_layer(kwargs, 'relu'),
+ norm_kwargs=resolve_bn_args(kwargs),
+ **kwargs
+ )
+ model = _create_model(model_kwargs, variant, pretrained)
+ return model
+
+
+def mnasnet_050(pretrained=False, **kwargs):
+ """ MNASNet B1, depth multiplier of 0.5. """
+ model = _gen_mnasnet_b1('mnasnet_050', 0.5, pretrained=pretrained, **kwargs)
+ return model
+
+
+def mnasnet_075(pretrained=False, **kwargs):
+ """ MNASNet B1, depth multiplier of 0.75. """
+ model = _gen_mnasnet_b1('mnasnet_075', 0.75, pretrained=pretrained, **kwargs)
+ return model
+
+
+def mnasnet_100(pretrained=False, **kwargs):
+ """ MNASNet B1, depth multiplier of 1.0. """
+ model = _gen_mnasnet_b1('mnasnet_100', 1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def mnasnet_b1(pretrained=False, **kwargs):
+ """ MNASNet B1, depth multiplier of 1.0. """
+ return mnasnet_100(pretrained, **kwargs)
+
+
+def mnasnet_140(pretrained=False, **kwargs):
+ """ MNASNet B1, depth multiplier of 1.4 """
+ model = _gen_mnasnet_b1('mnasnet_140', 1.4, pretrained=pretrained, **kwargs)
+ return model
+
+
+def semnasnet_050(pretrained=False, **kwargs):
+ """ MNASNet A1 (w/ SE), depth multiplier of 0.5 """
+ model = _gen_mnasnet_a1('semnasnet_050', 0.5, pretrained=pretrained, **kwargs)
+ return model
+
+
+def semnasnet_075(pretrained=False, **kwargs):
+ """ MNASNet A1 (w/ SE), depth multiplier of 0.75. """
+ model = _gen_mnasnet_a1('semnasnet_075', 0.75, pretrained=pretrained, **kwargs)
+ return model
+
+
+def semnasnet_100(pretrained=False, **kwargs):
+ """ MNASNet A1 (w/ SE), depth multiplier of 1.0. """
+ model = _gen_mnasnet_a1('semnasnet_100', 1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def mnasnet_a1(pretrained=False, **kwargs):
+ """ MNASNet A1 (w/ SE), depth multiplier of 1.0. """
+ return semnasnet_100(pretrained, **kwargs)
+
+
+def semnasnet_140(pretrained=False, **kwargs):
+ """ MNASNet A1 (w/ SE), depth multiplier of 1.4. """
+ model = _gen_mnasnet_a1('semnasnet_140', 1.4, pretrained=pretrained, **kwargs)
+ return model
+
+
+def mnasnet_small(pretrained=False, **kwargs):
+ """ MNASNet Small, depth multiplier of 1.0. """
+ model = _gen_mnasnet_small('mnasnet_small', 1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def mobilenetv2_100(pretrained=False, **kwargs):
+ """ MobileNet V2 w/ 1.0 channel multiplier """
+ model = _gen_mobilenet_v2('mobilenetv2_100', 1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def mobilenetv2_140(pretrained=False, **kwargs):
+ """ MobileNet V2 w/ 1.4 channel multiplier """
+ model = _gen_mobilenet_v2('mobilenetv2_140', 1.4, pretrained=pretrained, **kwargs)
+ return model
+
+
+def mobilenetv2_110d(pretrained=False, **kwargs):
+ """ MobileNet V2 w/ 1.1 channel, 1.2 depth multipliers"""
+ model = _gen_mobilenet_v2(
+ 'mobilenetv2_110d', 1.1, depth_multiplier=1.2, fix_stem_head=True, pretrained=pretrained, **kwargs)
+ return model
+
+
+def mobilenetv2_120d(pretrained=False, **kwargs):
+ """ MobileNet V2 w/ 1.2 channel, 1.4 depth multipliers """
+ model = _gen_mobilenet_v2(
+ 'mobilenetv2_120d', 1.2, depth_multiplier=1.4, fix_stem_head=True, pretrained=pretrained, **kwargs)
+ return model
+
+
+def fbnetc_100(pretrained=False, **kwargs):
+ """ FBNet-C """
+ if pretrained:
+ # pretrained model trained with non-default BN epsilon
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ model = _gen_fbnetc('fbnetc_100', 1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def spnasnet_100(pretrained=False, **kwargs):
+ """ Single-Path NAS Pixel1"""
+ model = _gen_spnasnet('spnasnet_100', 1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def efficientnet_b0(pretrained=False, **kwargs):
+ """ EfficientNet-B0 """
+ # NOTE for train set drop_rate=0.2, drop_connect_rate=0.2
+ model = _gen_efficientnet(
+ 'efficientnet_b0', channel_multiplier=1.0, depth_multiplier=1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def efficientnet_b1(pretrained=False, **kwargs):
+ """ EfficientNet-B1 """
+ # NOTE for train set drop_rate=0.2, drop_connect_rate=0.2
+ model = _gen_efficientnet(
+ 'efficientnet_b1', channel_multiplier=1.0, depth_multiplier=1.1, pretrained=pretrained, **kwargs)
+ return model
+
+
+def efficientnet_b2(pretrained=False, **kwargs):
+ """ EfficientNet-B2 """
+ # NOTE for train set drop_rate=0.3, drop_connect_rate=0.2
+ model = _gen_efficientnet(
+ 'efficientnet_b2', channel_multiplier=1.1, depth_multiplier=1.2, pretrained=pretrained, **kwargs)
+ return model
+
+
+def efficientnet_b3(pretrained=False, **kwargs):
+ """ EfficientNet-B3 """
+ # NOTE for train set drop_rate=0.3, drop_connect_rate=0.2
+ model = _gen_efficientnet(
+ 'efficientnet_b3', channel_multiplier=1.2, depth_multiplier=1.4, pretrained=pretrained, **kwargs)
+ return model
+
+
+def efficientnet_b4(pretrained=False, **kwargs):
+ """ EfficientNet-B4 """
+ # NOTE for train set drop_rate=0.4, drop_connect_rate=0.2
+ model = _gen_efficientnet(
+ 'efficientnet_b4', channel_multiplier=1.4, depth_multiplier=1.8, pretrained=pretrained, **kwargs)
+ return model
+
+
+def efficientnet_b5(pretrained=False, **kwargs):
+ """ EfficientNet-B5 """
+ # NOTE for train set drop_rate=0.4, drop_connect_rate=0.2
+ model = _gen_efficientnet(
+ 'efficientnet_b5', channel_multiplier=1.6, depth_multiplier=2.2, pretrained=pretrained, **kwargs)
+ return model
+
+
+def efficientnet_b6(pretrained=False, **kwargs):
+ """ EfficientNet-B6 """
+ # NOTE for train set drop_rate=0.5, drop_connect_rate=0.2
+ model = _gen_efficientnet(
+ 'efficientnet_b6', channel_multiplier=1.8, depth_multiplier=2.6, pretrained=pretrained, **kwargs)
+ return model
+
+
+def efficientnet_b7(pretrained=False, **kwargs):
+ """ EfficientNet-B7 """
+ # NOTE for train set drop_rate=0.5, drop_connect_rate=0.2
+ model = _gen_efficientnet(
+ 'efficientnet_b7', channel_multiplier=2.0, depth_multiplier=3.1, pretrained=pretrained, **kwargs)
+ return model
+
+
+def efficientnet_b8(pretrained=False, **kwargs):
+ """ EfficientNet-B8 """
+ # NOTE for train set drop_rate=0.5, drop_connect_rate=0.2
+ model = _gen_efficientnet(
+ 'efficientnet_b8', channel_multiplier=2.2, depth_multiplier=3.6, pretrained=pretrained, **kwargs)
+ return model
+
+
+def efficientnet_l2(pretrained=False, **kwargs):
+ """ EfficientNet-L2. """
+ # NOTE for train, drop_rate should be 0.5
+ model = _gen_efficientnet(
+ 'efficientnet_l2', channel_multiplier=4.3, depth_multiplier=5.3, pretrained=pretrained, **kwargs)
+ return model
+
+
+def efficientnet_es(pretrained=False, **kwargs):
+ """ EfficientNet-Edge Small. """
+ model = _gen_efficientnet_edge(
+ 'efficientnet_es', channel_multiplier=1.0, depth_multiplier=1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def efficientnet_em(pretrained=False, **kwargs):
+ """ EfficientNet-Edge-Medium. """
+ model = _gen_efficientnet_edge(
+ 'efficientnet_em', channel_multiplier=1.0, depth_multiplier=1.1, pretrained=pretrained, **kwargs)
+ return model
+
+
+def efficientnet_el(pretrained=False, **kwargs):
+ """ EfficientNet-Edge-Large. """
+ model = _gen_efficientnet_edge(
+ 'efficientnet_el', channel_multiplier=1.2, depth_multiplier=1.4, pretrained=pretrained, **kwargs)
+ return model
+
+
+def efficientnet_cc_b0_4e(pretrained=False, **kwargs):
+ """ EfficientNet-CondConv-B0 w/ 8 Experts """
+ # NOTE for train set drop_rate=0.25, drop_connect_rate=0.2
+ model = _gen_efficientnet_condconv(
+ 'efficientnet_cc_b0_4e', channel_multiplier=1.0, depth_multiplier=1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def efficientnet_cc_b0_8e(pretrained=False, **kwargs):
+ """ EfficientNet-CondConv-B0 w/ 8 Experts """
+ # NOTE for train set drop_rate=0.25, drop_connect_rate=0.2
+ model = _gen_efficientnet_condconv(
+ 'efficientnet_cc_b0_8e', channel_multiplier=1.0, depth_multiplier=1.0, experts_multiplier=2,
+ pretrained=pretrained, **kwargs)
+ return model
+
+
+def efficientnet_cc_b1_8e(pretrained=False, **kwargs):
+ """ EfficientNet-CondConv-B1 w/ 8 Experts """
+ # NOTE for train set drop_rate=0.25, drop_connect_rate=0.2
+ model = _gen_efficientnet_condconv(
+ 'efficientnet_cc_b1_8e', channel_multiplier=1.0, depth_multiplier=1.1, experts_multiplier=2,
+ pretrained=pretrained, **kwargs)
+ return model
+
+
+def efficientnet_lite0(pretrained=False, **kwargs):
+ """ EfficientNet-Lite0 """
+ model = _gen_efficientnet_lite(
+ 'efficientnet_lite0', channel_multiplier=1.0, depth_multiplier=1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def efficientnet_lite1(pretrained=False, **kwargs):
+ """ EfficientNet-Lite1 """
+ model = _gen_efficientnet_lite(
+ 'efficientnet_lite1', channel_multiplier=1.0, depth_multiplier=1.1, pretrained=pretrained, **kwargs)
+ return model
+
+
+def efficientnet_lite2(pretrained=False, **kwargs):
+ """ EfficientNet-Lite2 """
+ model = _gen_efficientnet_lite(
+ 'efficientnet_lite2', channel_multiplier=1.1, depth_multiplier=1.2, pretrained=pretrained, **kwargs)
+ return model
+
+
+def efficientnet_lite3(pretrained=False, **kwargs):
+ """ EfficientNet-Lite3 """
+ model = _gen_efficientnet_lite(
+ 'efficientnet_lite3', channel_multiplier=1.2, depth_multiplier=1.4, pretrained=pretrained, **kwargs)
+ return model
+
+
+def efficientnet_lite4(pretrained=False, **kwargs):
+ """ EfficientNet-Lite4 """
+ model = _gen_efficientnet_lite(
+ 'efficientnet_lite4', channel_multiplier=1.4, depth_multiplier=1.8, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_b0(pretrained=False, **kwargs):
+ """ EfficientNet-B0 AutoAug. Tensorflow compatible variant """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_b0', channel_multiplier=1.0, depth_multiplier=1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_b1(pretrained=False, **kwargs):
+ """ EfficientNet-B1 AutoAug. Tensorflow compatible variant """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_b1', channel_multiplier=1.0, depth_multiplier=1.1, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_b2(pretrained=False, **kwargs):
+ """ EfficientNet-B2 AutoAug. Tensorflow compatible variant """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_b2', channel_multiplier=1.1, depth_multiplier=1.2, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_b3(pretrained=False, **kwargs):
+ """ EfficientNet-B3 AutoAug. Tensorflow compatible variant """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_b3', channel_multiplier=1.2, depth_multiplier=1.4, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_b4(pretrained=False, **kwargs):
+ """ EfficientNet-B4 AutoAug. Tensorflow compatible variant """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_b4', channel_multiplier=1.4, depth_multiplier=1.8, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_b5(pretrained=False, **kwargs):
+ """ EfficientNet-B5 RandAug. Tensorflow compatible variant """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_b5', channel_multiplier=1.6, depth_multiplier=2.2, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_b6(pretrained=False, **kwargs):
+ """ EfficientNet-B6 AutoAug. Tensorflow compatible variant """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_b6', channel_multiplier=1.8, depth_multiplier=2.6, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_b7(pretrained=False, **kwargs):
+ """ EfficientNet-B7 RandAug. Tensorflow compatible variant """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_b7', channel_multiplier=2.0, depth_multiplier=3.1, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_b8(pretrained=False, **kwargs):
+ """ EfficientNet-B8 RandAug. Tensorflow compatible variant """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_b8', channel_multiplier=2.2, depth_multiplier=3.6, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_b0_ap(pretrained=False, **kwargs):
+ """ EfficientNet-B0 AdvProp. Tensorflow compatible variant
+ Paper: Adversarial Examples Improve Image Recognition (https://arxiv.org/abs/1911.09665)
+ """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_b0_ap', channel_multiplier=1.0, depth_multiplier=1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_b1_ap(pretrained=False, **kwargs):
+ """ EfficientNet-B1 AdvProp. Tensorflow compatible variant
+ Paper: Adversarial Examples Improve Image Recognition (https://arxiv.org/abs/1911.09665)
+ """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_b1_ap', channel_multiplier=1.0, depth_multiplier=1.1, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_b2_ap(pretrained=False, **kwargs):
+ """ EfficientNet-B2 AdvProp. Tensorflow compatible variant
+ Paper: Adversarial Examples Improve Image Recognition (https://arxiv.org/abs/1911.09665)
+ """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_b2_ap', channel_multiplier=1.1, depth_multiplier=1.2, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_b3_ap(pretrained=False, **kwargs):
+ """ EfficientNet-B3 AdvProp. Tensorflow compatible variant
+ Paper: Adversarial Examples Improve Image Recognition (https://arxiv.org/abs/1911.09665)
+ """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_b3_ap', channel_multiplier=1.2, depth_multiplier=1.4, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_b4_ap(pretrained=False, **kwargs):
+ """ EfficientNet-B4 AdvProp. Tensorflow compatible variant
+ Paper: Adversarial Examples Improve Image Recognition (https://arxiv.org/abs/1911.09665)
+ """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_b4_ap', channel_multiplier=1.4, depth_multiplier=1.8, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_b5_ap(pretrained=False, **kwargs):
+ """ EfficientNet-B5 AdvProp. Tensorflow compatible variant
+ Paper: Adversarial Examples Improve Image Recognition (https://arxiv.org/abs/1911.09665)
+ """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_b5_ap', channel_multiplier=1.6, depth_multiplier=2.2, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_b6_ap(pretrained=False, **kwargs):
+ """ EfficientNet-B6 AdvProp. Tensorflow compatible variant
+ Paper: Adversarial Examples Improve Image Recognition (https://arxiv.org/abs/1911.09665)
+ """
+ # NOTE for train, drop_rate should be 0.5
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_b6_ap', channel_multiplier=1.8, depth_multiplier=2.6, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_b7_ap(pretrained=False, **kwargs):
+ """ EfficientNet-B7 AdvProp. Tensorflow compatible variant
+ Paper: Adversarial Examples Improve Image Recognition (https://arxiv.org/abs/1911.09665)
+ """
+ # NOTE for train, drop_rate should be 0.5
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_b7_ap', channel_multiplier=2.0, depth_multiplier=3.1, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_b8_ap(pretrained=False, **kwargs):
+ """ EfficientNet-B8 AdvProp. Tensorflow compatible variant
+ Paper: Adversarial Examples Improve Image Recognition (https://arxiv.org/abs/1911.09665)
+ """
+ # NOTE for train, drop_rate should be 0.5
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_b8_ap', channel_multiplier=2.2, depth_multiplier=3.6, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_b0_ns(pretrained=False, **kwargs):
+ """ EfficientNet-B0 NoisyStudent. Tensorflow compatible variant
+ Paper: Self-training with Noisy Student improves ImageNet classification (https://arxiv.org/abs/1911.04252)
+ """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_b0_ns', channel_multiplier=1.0, depth_multiplier=1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_b1_ns(pretrained=False, **kwargs):
+ """ EfficientNet-B1 NoisyStudent. Tensorflow compatible variant
+ Paper: Self-training with Noisy Student improves ImageNet classification (https://arxiv.org/abs/1911.04252)
+ """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_b1_ns', channel_multiplier=1.0, depth_multiplier=1.1, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_b2_ns(pretrained=False, **kwargs):
+ """ EfficientNet-B2 NoisyStudent. Tensorflow compatible variant
+ Paper: Self-training with Noisy Student improves ImageNet classification (https://arxiv.org/abs/1911.04252)
+ """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_b2_ns', channel_multiplier=1.1, depth_multiplier=1.2, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_b3_ns(pretrained=False, **kwargs):
+ """ EfficientNet-B3 NoisyStudent. Tensorflow compatible variant
+ Paper: Self-training with Noisy Student improves ImageNet classification (https://arxiv.org/abs/1911.04252)
+ """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_b3_ns', channel_multiplier=1.2, depth_multiplier=1.4, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_b4_ns(pretrained=False, **kwargs):
+ """ EfficientNet-B4 NoisyStudent. Tensorflow compatible variant
+ Paper: Self-training with Noisy Student improves ImageNet classification (https://arxiv.org/abs/1911.04252)
+ """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_b4_ns', channel_multiplier=1.4, depth_multiplier=1.8, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_b5_ns(pretrained=False, **kwargs):
+ """ EfficientNet-B5 NoisyStudent. Tensorflow compatible variant
+ Paper: Self-training with Noisy Student improves ImageNet classification (https://arxiv.org/abs/1911.04252)
+ """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_b5_ns', channel_multiplier=1.6, depth_multiplier=2.2, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_b6_ns(pretrained=False, **kwargs):
+ """ EfficientNet-B6 NoisyStudent. Tensorflow compatible variant
+ Paper: Self-training with Noisy Student improves ImageNet classification (https://arxiv.org/abs/1911.04252)
+ """
+ # NOTE for train, drop_rate should be 0.5
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_b6_ns', channel_multiplier=1.8, depth_multiplier=2.6, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_b7_ns(pretrained=False, **kwargs):
+ """ EfficientNet-B7 NoisyStudent. Tensorflow compatible variant
+ Paper: Self-training with Noisy Student improves ImageNet classification (https://arxiv.org/abs/1911.04252)
+ """
+ # NOTE for train, drop_rate should be 0.5
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_b7_ns', channel_multiplier=2.0, depth_multiplier=3.1, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_l2_ns_475(pretrained=False, **kwargs):
+ """ EfficientNet-L2 NoisyStudent @ 475x475. Tensorflow compatible variant
+ Paper: Self-training with Noisy Student improves ImageNet classification (https://arxiv.org/abs/1911.04252)
+ """
+ # NOTE for train, drop_rate should be 0.5
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_l2_ns_475', channel_multiplier=4.3, depth_multiplier=5.3, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_l2_ns(pretrained=False, **kwargs):
+ """ EfficientNet-L2 NoisyStudent. Tensorflow compatible variant
+ Paper: Self-training with Noisy Student improves ImageNet classification (https://arxiv.org/abs/1911.04252)
+ """
+ # NOTE for train, drop_rate should be 0.5
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet(
+ 'tf_efficientnet_l2_ns', channel_multiplier=4.3, depth_multiplier=5.3, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_es(pretrained=False, **kwargs):
+ """ EfficientNet-Edge Small. Tensorflow compatible variant """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet_edge(
+ 'tf_efficientnet_es', channel_multiplier=1.0, depth_multiplier=1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_em(pretrained=False, **kwargs):
+ """ EfficientNet-Edge-Medium. Tensorflow compatible variant """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet_edge(
+ 'tf_efficientnet_em', channel_multiplier=1.0, depth_multiplier=1.1, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_el(pretrained=False, **kwargs):
+ """ EfficientNet-Edge-Large. Tensorflow compatible variant """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet_edge(
+ 'tf_efficientnet_el', channel_multiplier=1.2, depth_multiplier=1.4, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_cc_b0_4e(pretrained=False, **kwargs):
+ """ EfficientNet-CondConv-B0 w/ 4 Experts """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet_condconv(
+ 'tf_efficientnet_cc_b0_4e', channel_multiplier=1.0, depth_multiplier=1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_cc_b0_8e(pretrained=False, **kwargs):
+ """ EfficientNet-CondConv-B0 w/ 8 Experts """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet_condconv(
+ 'tf_efficientnet_cc_b0_8e', channel_multiplier=1.0, depth_multiplier=1.0, experts_multiplier=2,
+ pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_cc_b1_8e(pretrained=False, **kwargs):
+ """ EfficientNet-CondConv-B1 w/ 8 Experts """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet_condconv(
+ 'tf_efficientnet_cc_b1_8e', channel_multiplier=1.0, depth_multiplier=1.1, experts_multiplier=2,
+ pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_lite0(pretrained=False, **kwargs):
+ """ EfficientNet-Lite0. Tensorflow compatible variant """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet_lite(
+ 'tf_efficientnet_lite0', channel_multiplier=1.0, depth_multiplier=1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_lite1(pretrained=False, **kwargs):
+ """ EfficientNet-Lite1. Tensorflow compatible variant """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet_lite(
+ 'tf_efficientnet_lite1', channel_multiplier=1.0, depth_multiplier=1.1, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_lite2(pretrained=False, **kwargs):
+ """ EfficientNet-Lite2. Tensorflow compatible variant """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet_lite(
+ 'tf_efficientnet_lite2', channel_multiplier=1.1, depth_multiplier=1.2, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_lite3(pretrained=False, **kwargs):
+ """ EfficientNet-Lite3. Tensorflow compatible variant """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet_lite(
+ 'tf_efficientnet_lite3', channel_multiplier=1.2, depth_multiplier=1.4, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_efficientnet_lite4(pretrained=False, **kwargs):
+ """ EfficientNet-Lite4. Tensorflow compatible variant """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_efficientnet_lite(
+ 'tf_efficientnet_lite4', channel_multiplier=1.4, depth_multiplier=1.8, pretrained=pretrained, **kwargs)
+ return model
+
+
+def mixnet_s(pretrained=False, **kwargs):
+ """Creates a MixNet Small model.
+ """
+ # NOTE for train set drop_rate=0.2
+ model = _gen_mixnet_s(
+ 'mixnet_s', channel_multiplier=1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def mixnet_m(pretrained=False, **kwargs):
+ """Creates a MixNet Medium model.
+ """
+ # NOTE for train set drop_rate=0.25
+ model = _gen_mixnet_m(
+ 'mixnet_m', channel_multiplier=1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def mixnet_l(pretrained=False, **kwargs):
+ """Creates a MixNet Large model.
+ """
+ # NOTE for train set drop_rate=0.25
+ model = _gen_mixnet_m(
+ 'mixnet_l', channel_multiplier=1.3, pretrained=pretrained, **kwargs)
+ return model
+
+
+def mixnet_xl(pretrained=False, **kwargs):
+ """Creates a MixNet Extra-Large model.
+ Not a paper spec, experimental def by RW w/ depth scaling.
+ """
+ # NOTE for train set drop_rate=0.25, drop_connect_rate=0.2
+ model = _gen_mixnet_m(
+ 'mixnet_xl', channel_multiplier=1.6, depth_multiplier=1.2, pretrained=pretrained, **kwargs)
+ return model
+
+
+def mixnet_xxl(pretrained=False, **kwargs):
+ """Creates a MixNet Double Extra Large model.
+ Not a paper spec, experimental def by RW w/ depth scaling.
+ """
+ # NOTE for train set drop_rate=0.3, drop_connect_rate=0.2
+ model = _gen_mixnet_m(
+ 'mixnet_xxl', channel_multiplier=2.4, depth_multiplier=1.3, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_mixnet_s(pretrained=False, **kwargs):
+ """Creates a MixNet Small model. Tensorflow compatible variant
+ """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_mixnet_s(
+ 'tf_mixnet_s', channel_multiplier=1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_mixnet_m(pretrained=False, **kwargs):
+ """Creates a MixNet Medium model. Tensorflow compatible variant
+ """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_mixnet_m(
+ 'tf_mixnet_m', channel_multiplier=1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_mixnet_l(pretrained=False, **kwargs):
+ """Creates a MixNet Large model. Tensorflow compatible variant
+ """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_mixnet_m(
+ 'tf_mixnet_l', channel_multiplier=1.3, pretrained=pretrained, **kwargs)
+ return model
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/helpers.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/helpers.py
new file mode 100644
index 0000000000000000000000000000000000000000..3f83a07d690c7ad681c777c19b1e7a5bb95da007
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/helpers.py
@@ -0,0 +1,71 @@
+""" Checkpoint loading / state_dict helpers
+Copyright 2020 Ross Wightman
+"""
+import torch
+import os
+from collections import OrderedDict
+try:
+ from torch.hub import load_state_dict_from_url
+except ImportError:
+ from torch.utils.model_zoo import load_url as load_state_dict_from_url
+
+
+def load_checkpoint(model, checkpoint_path):
+ if checkpoint_path and os.path.isfile(checkpoint_path):
+ print("=> Loading checkpoint '{}'".format(checkpoint_path))
+ checkpoint = torch.load(checkpoint_path)
+ if isinstance(checkpoint, dict) and 'state_dict' in checkpoint:
+ new_state_dict = OrderedDict()
+ for k, v in checkpoint['state_dict'].items():
+ if k.startswith('module'):
+ name = k[7:] # remove `module.`
+ else:
+ name = k
+ new_state_dict[name] = v
+ model.load_state_dict(new_state_dict)
+ else:
+ model.load_state_dict(checkpoint)
+ print("=> Loaded checkpoint '{}'".format(checkpoint_path))
+ else:
+ print("=> Error: No checkpoint found at '{}'".format(checkpoint_path))
+ raise FileNotFoundError()
+
+
+def load_pretrained(model, url, filter_fn=None, strict=True):
+ if not url:
+ print("=> Warning: Pretrained model URL is empty, using random initialization.")
+ return
+
+ state_dict = load_state_dict_from_url(url, progress=False, map_location='cpu')
+
+ input_conv = 'conv_stem'
+ classifier = 'classifier'
+ in_chans = getattr(model, input_conv).weight.shape[1]
+ num_classes = getattr(model, classifier).weight.shape[0]
+
+ input_conv_weight = input_conv + '.weight'
+ pretrained_in_chans = state_dict[input_conv_weight].shape[1]
+ if in_chans != pretrained_in_chans:
+ if in_chans == 1:
+ print('=> Converting pretrained input conv {} from {} to 1 channel'.format(
+ input_conv_weight, pretrained_in_chans))
+ conv1_weight = state_dict[input_conv_weight]
+ state_dict[input_conv_weight] = conv1_weight.sum(dim=1, keepdim=True)
+ else:
+ print('=> Discarding pretrained input conv {} since input channel count != {}'.format(
+ input_conv_weight, pretrained_in_chans))
+ del state_dict[input_conv_weight]
+ strict = False
+
+ classifier_weight = classifier + '.weight'
+ pretrained_num_classes = state_dict[classifier_weight].shape[0]
+ if num_classes != pretrained_num_classes:
+ print('=> Discarding pretrained classifier since num_classes != {}'.format(pretrained_num_classes))
+ del state_dict[classifier_weight]
+ del state_dict[classifier + '.bias']
+ strict = False
+
+ if filter_fn is not None:
+ state_dict = filter_fn(state_dict)
+
+ model.load_state_dict(state_dict, strict=strict)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/mobilenetv3.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/mobilenetv3.py
new file mode 100644
index 0000000000000000000000000000000000000000..b5966c28f7207e98ee50745b1bc8f3663c650f9d
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/mobilenetv3.py
@@ -0,0 +1,364 @@
+""" MobileNet-V3
+
+A PyTorch impl of MobileNet-V3, compatible with TF weights from official impl.
+
+Paper: Searching for MobileNetV3 - https://arxiv.org/abs/1905.02244
+
+Hacked together by / Copyright 2020 Ross Wightman
+"""
+import torch.nn as nn
+import torch.nn.functional as F
+
+from .activations import get_act_fn, get_act_layer, HardSwish
+from .config import layer_config_kwargs
+from .conv2d_layers import select_conv2d
+from .helpers import load_pretrained
+from .efficientnet_builder import *
+
+__all__ = ['mobilenetv3_rw', 'mobilenetv3_large_075', 'mobilenetv3_large_100', 'mobilenetv3_large_minimal_100',
+ 'mobilenetv3_small_075', 'mobilenetv3_small_100', 'mobilenetv3_small_minimal_100',
+ 'tf_mobilenetv3_large_075', 'tf_mobilenetv3_large_100', 'tf_mobilenetv3_large_minimal_100',
+ 'tf_mobilenetv3_small_075', 'tf_mobilenetv3_small_100', 'tf_mobilenetv3_small_minimal_100']
+
+model_urls = {
+ 'mobilenetv3_rw':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/mobilenetv3_100-35495452.pth',
+ 'mobilenetv3_large_075': None,
+ 'mobilenetv3_large_100':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/mobilenetv3_large_100_ra-f55367f5.pth',
+ 'mobilenetv3_large_minimal_100': None,
+ 'mobilenetv3_small_075': None,
+ 'mobilenetv3_small_100': None,
+ 'mobilenetv3_small_minimal_100': None,
+ 'tf_mobilenetv3_large_075':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_mobilenetv3_large_075-150ee8b0.pth',
+ 'tf_mobilenetv3_large_100':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_mobilenetv3_large_100-427764d5.pth',
+ 'tf_mobilenetv3_large_minimal_100':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_mobilenetv3_large_minimal_100-8596ae28.pth',
+ 'tf_mobilenetv3_small_075':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_mobilenetv3_small_075-da427f52.pth',
+ 'tf_mobilenetv3_small_100':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_mobilenetv3_small_100-37f49e2b.pth',
+ 'tf_mobilenetv3_small_minimal_100':
+ 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_mobilenetv3_small_minimal_100-922a7843.pth',
+}
+
+
+class MobileNetV3(nn.Module):
+ """ MobileNet-V3
+
+ A this model utilizes the MobileNet-v3 specific 'efficient head', where global pooling is done before the
+ head convolution without a final batch-norm layer before the classifier.
+
+ Paper: https://arxiv.org/abs/1905.02244
+ """
+
+ def __init__(self, block_args, num_classes=1000, in_chans=3, stem_size=16, num_features=1280, head_bias=True,
+ channel_multiplier=1.0, pad_type='', act_layer=HardSwish, drop_rate=0., drop_connect_rate=0.,
+ se_kwargs=None, norm_layer=nn.BatchNorm2d, norm_kwargs=None, weight_init='goog'):
+ super(MobileNetV3, self).__init__()
+ self.drop_rate = drop_rate
+
+ stem_size = round_channels(stem_size, channel_multiplier)
+ self.conv_stem = select_conv2d(in_chans, stem_size, 3, stride=2, padding=pad_type)
+ self.bn1 = nn.BatchNorm2d(stem_size, **norm_kwargs)
+ self.act1 = act_layer(inplace=True)
+ in_chs = stem_size
+
+ builder = EfficientNetBuilder(
+ channel_multiplier, pad_type=pad_type, act_layer=act_layer, se_kwargs=se_kwargs,
+ norm_layer=norm_layer, norm_kwargs=norm_kwargs, drop_connect_rate=drop_connect_rate)
+ self.blocks = nn.Sequential(*builder(in_chs, block_args))
+ in_chs = builder.in_chs
+
+ self.global_pool = nn.AdaptiveAvgPool2d(1)
+ self.conv_head = select_conv2d(in_chs, num_features, 1, padding=pad_type, bias=head_bias)
+ self.act2 = act_layer(inplace=True)
+ self.classifier = nn.Linear(num_features, num_classes)
+
+ for m in self.modules():
+ if weight_init == 'goog':
+ initialize_weight_goog(m)
+ else:
+ initialize_weight_default(m)
+
+ def as_sequential(self):
+ layers = [self.conv_stem, self.bn1, self.act1]
+ layers.extend(self.blocks)
+ layers.extend([
+ self.global_pool, self.conv_head, self.act2,
+ nn.Flatten(), nn.Dropout(self.drop_rate), self.classifier])
+ return nn.Sequential(*layers)
+
+ def features(self, x):
+ x = self.conv_stem(x)
+ x = self.bn1(x)
+ x = self.act1(x)
+ x = self.blocks(x)
+ x = self.global_pool(x)
+ x = self.conv_head(x)
+ x = self.act2(x)
+ return x
+
+ def forward(self, x):
+ x = self.features(x)
+ x = x.flatten(1)
+ if self.drop_rate > 0.:
+ x = F.dropout(x, p=self.drop_rate, training=self.training)
+ return self.classifier(x)
+
+
+def _create_model(model_kwargs, variant, pretrained=False):
+ as_sequential = model_kwargs.pop('as_sequential', False)
+ model = MobileNetV3(**model_kwargs)
+ if pretrained and model_urls[variant]:
+ load_pretrained(model, model_urls[variant])
+ if as_sequential:
+ model = model.as_sequential()
+ return model
+
+
+def _gen_mobilenet_v3_rw(variant, channel_multiplier=1.0, pretrained=False, **kwargs):
+ """Creates a MobileNet-V3 model (RW variant).
+
+ Paper: https://arxiv.org/abs/1905.02244
+
+ This was my first attempt at reproducing the MobileNet-V3 from paper alone. It came close to the
+ eventual Tensorflow reference impl but has a few differences:
+ 1. This model has no bias on the head convolution
+ 2. This model forces no residual (noskip) on the first DWS block, this is different than MnasNet
+ 3. This model always uses ReLU for the SE activation layer, other models in the family inherit their act layer
+ from their parent block
+ 4. This model does not enforce divisible by 8 limitation on the SE reduction channel count
+
+ Overall the changes are fairly minor and result in a very small parameter count difference and no
+ top-1/5
+
+ Args:
+ channel_multiplier: multiplier to number of channels per layer.
+ """
+ arch_def = [
+ # stage 0, 112x112 in
+ ['ds_r1_k3_s1_e1_c16_nre_noskip'], # relu
+ # stage 1, 112x112 in
+ ['ir_r1_k3_s2_e4_c24_nre', 'ir_r1_k3_s1_e3_c24_nre'], # relu
+ # stage 2, 56x56 in
+ ['ir_r3_k5_s2_e3_c40_se0.25_nre'], # relu
+ # stage 3, 28x28 in
+ ['ir_r1_k3_s2_e6_c80', 'ir_r1_k3_s1_e2.5_c80', 'ir_r2_k3_s1_e2.3_c80'], # hard-swish
+ # stage 4, 14x14in
+ ['ir_r2_k3_s1_e6_c112_se0.25'], # hard-swish
+ # stage 5, 14x14in
+ ['ir_r3_k5_s2_e6_c160_se0.25'], # hard-swish
+ # stage 6, 7x7 in
+ ['cn_r1_k1_s1_c960'], # hard-swish
+ ]
+ with layer_config_kwargs(kwargs):
+ model_kwargs = dict(
+ block_args=decode_arch_def(arch_def),
+ head_bias=False, # one of my mistakes
+ channel_multiplier=channel_multiplier,
+ act_layer=resolve_act_layer(kwargs, 'hard_swish'),
+ se_kwargs=dict(gate_fn=get_act_fn('hard_sigmoid'), reduce_mid=True),
+ norm_kwargs=resolve_bn_args(kwargs),
+ **kwargs,
+ )
+ model = _create_model(model_kwargs, variant, pretrained)
+ return model
+
+
+def _gen_mobilenet_v3(variant, channel_multiplier=1.0, pretrained=False, **kwargs):
+ """Creates a MobileNet-V3 large/small/minimal models.
+
+ Ref impl: https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet_v3.py
+ Paper: https://arxiv.org/abs/1905.02244
+
+ Args:
+ channel_multiplier: multiplier to number of channels per layer.
+ """
+ if 'small' in variant:
+ num_features = 1024
+ if 'minimal' in variant:
+ act_layer = 'relu'
+ arch_def = [
+ # stage 0, 112x112 in
+ ['ds_r1_k3_s2_e1_c16'],
+ # stage 1, 56x56 in
+ ['ir_r1_k3_s2_e4.5_c24', 'ir_r1_k3_s1_e3.67_c24'],
+ # stage 2, 28x28 in
+ ['ir_r1_k3_s2_e4_c40', 'ir_r2_k3_s1_e6_c40'],
+ # stage 3, 14x14 in
+ ['ir_r2_k3_s1_e3_c48'],
+ # stage 4, 14x14in
+ ['ir_r3_k3_s2_e6_c96'],
+ # stage 6, 7x7 in
+ ['cn_r1_k1_s1_c576'],
+ ]
+ else:
+ act_layer = 'hard_swish'
+ arch_def = [
+ # stage 0, 112x112 in
+ ['ds_r1_k3_s2_e1_c16_se0.25_nre'], # relu
+ # stage 1, 56x56 in
+ ['ir_r1_k3_s2_e4.5_c24_nre', 'ir_r1_k3_s1_e3.67_c24_nre'], # relu
+ # stage 2, 28x28 in
+ ['ir_r1_k5_s2_e4_c40_se0.25', 'ir_r2_k5_s1_e6_c40_se0.25'], # hard-swish
+ # stage 3, 14x14 in
+ ['ir_r2_k5_s1_e3_c48_se0.25'], # hard-swish
+ # stage 4, 14x14in
+ ['ir_r3_k5_s2_e6_c96_se0.25'], # hard-swish
+ # stage 6, 7x7 in
+ ['cn_r1_k1_s1_c576'], # hard-swish
+ ]
+ else:
+ num_features = 1280
+ if 'minimal' in variant:
+ act_layer = 'relu'
+ arch_def = [
+ # stage 0, 112x112 in
+ ['ds_r1_k3_s1_e1_c16'],
+ # stage 1, 112x112 in
+ ['ir_r1_k3_s2_e4_c24', 'ir_r1_k3_s1_e3_c24'],
+ # stage 2, 56x56 in
+ ['ir_r3_k3_s2_e3_c40'],
+ # stage 3, 28x28 in
+ ['ir_r1_k3_s2_e6_c80', 'ir_r1_k3_s1_e2.5_c80', 'ir_r2_k3_s1_e2.3_c80'],
+ # stage 4, 14x14in
+ ['ir_r2_k3_s1_e6_c112'],
+ # stage 5, 14x14in
+ ['ir_r3_k3_s2_e6_c160'],
+ # stage 6, 7x7 in
+ ['cn_r1_k1_s1_c960'],
+ ]
+ else:
+ act_layer = 'hard_swish'
+ arch_def = [
+ # stage 0, 112x112 in
+ ['ds_r1_k3_s1_e1_c16_nre'], # relu
+ # stage 1, 112x112 in
+ ['ir_r1_k3_s2_e4_c24_nre', 'ir_r1_k3_s1_e3_c24_nre'], # relu
+ # stage 2, 56x56 in
+ ['ir_r3_k5_s2_e3_c40_se0.25_nre'], # relu
+ # stage 3, 28x28 in
+ ['ir_r1_k3_s2_e6_c80', 'ir_r1_k3_s1_e2.5_c80', 'ir_r2_k3_s1_e2.3_c80'], # hard-swish
+ # stage 4, 14x14in
+ ['ir_r2_k3_s1_e6_c112_se0.25'], # hard-swish
+ # stage 5, 14x14in
+ ['ir_r3_k5_s2_e6_c160_se0.25'], # hard-swish
+ # stage 6, 7x7 in
+ ['cn_r1_k1_s1_c960'], # hard-swish
+ ]
+ with layer_config_kwargs(kwargs):
+ model_kwargs = dict(
+ block_args=decode_arch_def(arch_def),
+ num_features=num_features,
+ stem_size=16,
+ channel_multiplier=channel_multiplier,
+ act_layer=resolve_act_layer(kwargs, act_layer),
+ se_kwargs=dict(
+ act_layer=get_act_layer('relu'), gate_fn=get_act_fn('hard_sigmoid'), reduce_mid=True, divisor=8),
+ norm_kwargs=resolve_bn_args(kwargs),
+ **kwargs,
+ )
+ model = _create_model(model_kwargs, variant, pretrained)
+ return model
+
+
+def mobilenetv3_rw(pretrained=False, **kwargs):
+ """ MobileNet-V3 RW
+ Attn: See note in gen function for this variant.
+ """
+ # NOTE for train set drop_rate=0.2
+ if pretrained:
+ # pretrained model trained with non-default BN epsilon
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ model = _gen_mobilenet_v3_rw('mobilenetv3_rw', 1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def mobilenetv3_large_075(pretrained=False, **kwargs):
+ """ MobileNet V3 Large 0.75"""
+ # NOTE for train set drop_rate=0.2
+ model = _gen_mobilenet_v3('mobilenetv3_large_075', 0.75, pretrained=pretrained, **kwargs)
+ return model
+
+
+def mobilenetv3_large_100(pretrained=False, **kwargs):
+ """ MobileNet V3 Large 1.0 """
+ # NOTE for train set drop_rate=0.2
+ model = _gen_mobilenet_v3('mobilenetv3_large_100', 1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def mobilenetv3_large_minimal_100(pretrained=False, **kwargs):
+ """ MobileNet V3 Large (Minimalistic) 1.0 """
+ # NOTE for train set drop_rate=0.2
+ model = _gen_mobilenet_v3('mobilenetv3_large_minimal_100', 1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def mobilenetv3_small_075(pretrained=False, **kwargs):
+ """ MobileNet V3 Small 0.75 """
+ model = _gen_mobilenet_v3('mobilenetv3_small_075', 0.75, pretrained=pretrained, **kwargs)
+ return model
+
+
+def mobilenetv3_small_100(pretrained=False, **kwargs):
+ """ MobileNet V3 Small 1.0 """
+ model = _gen_mobilenet_v3('mobilenetv3_small_100', 1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def mobilenetv3_small_minimal_100(pretrained=False, **kwargs):
+ """ MobileNet V3 Small (Minimalistic) 1.0 """
+ model = _gen_mobilenet_v3('mobilenetv3_small_minimal_100', 1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_mobilenetv3_large_075(pretrained=False, **kwargs):
+ """ MobileNet V3 Large 0.75. Tensorflow compat variant. """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_mobilenet_v3('tf_mobilenetv3_large_075', 0.75, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_mobilenetv3_large_100(pretrained=False, **kwargs):
+ """ MobileNet V3 Large 1.0. Tensorflow compat variant. """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_mobilenet_v3('tf_mobilenetv3_large_100', 1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_mobilenetv3_large_minimal_100(pretrained=False, **kwargs):
+ """ MobileNet V3 Large Minimalistic 1.0. Tensorflow compat variant. """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_mobilenet_v3('tf_mobilenetv3_large_minimal_100', 1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_mobilenetv3_small_075(pretrained=False, **kwargs):
+ """ MobileNet V3 Small 0.75. Tensorflow compat variant. """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_mobilenet_v3('tf_mobilenetv3_small_075', 0.75, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_mobilenetv3_small_100(pretrained=False, **kwargs):
+ """ MobileNet V3 Small 1.0. Tensorflow compat variant."""
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_mobilenet_v3('tf_mobilenetv3_small_100', 1.0, pretrained=pretrained, **kwargs)
+ return model
+
+
+def tf_mobilenetv3_small_minimal_100(pretrained=False, **kwargs):
+ """ MobileNet V3 Small Minimalistic 1.0. Tensorflow compat variant. """
+ kwargs['bn_eps'] = BN_EPS_TF_DEFAULT
+ kwargs['pad_type'] = 'same'
+ model = _gen_mobilenet_v3('tf_mobilenetv3_small_minimal_100', 1.0, pretrained=pretrained, **kwargs)
+ return model
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/model_factory.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/model_factory.py
new file mode 100644
index 0000000000000000000000000000000000000000..4d46ea8baedaf3d787826eb3bb314b4230514647
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/model_factory.py
@@ -0,0 +1,27 @@
+from .config import set_layer_config
+from .helpers import load_checkpoint
+
+from .gen_efficientnet import *
+from .mobilenetv3 import *
+
+
+def create_model(
+ model_name='mnasnet_100',
+ pretrained=None,
+ num_classes=1000,
+ in_chans=3,
+ checkpoint_path='',
+ **kwargs):
+
+ model_kwargs = dict(num_classes=num_classes, in_chans=in_chans, pretrained=pretrained, **kwargs)
+
+ if model_name in globals():
+ create_fn = globals()[model_name]
+ model = create_fn(**model_kwargs)
+ else:
+ raise RuntimeError('Unknown model (%s)' % model_name)
+
+ if checkpoint_path and not pretrained:
+ load_checkpoint(model, checkpoint_path)
+
+ return model
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/version.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/version.py
new file mode 100644
index 0000000000000000000000000000000000000000..a6221b3de7b1490c5e712e8b5fcc94c3d9d04295
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/geffnet/version.py
@@ -0,0 +1 @@
+__version__ = '1.0.2'
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/hubconf.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/hubconf.py
new file mode 100644
index 0000000000000000000000000000000000000000..45b17b99bbeba34596569e6e50f6e8a2ebc45c54
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/hubconf.py
@@ -0,0 +1,84 @@
+dependencies = ['torch', 'math']
+
+from geffnet import efficientnet_b0
+from geffnet import efficientnet_b1
+from geffnet import efficientnet_b2
+from geffnet import efficientnet_b3
+
+from geffnet import efficientnet_es
+
+from geffnet import efficientnet_lite0
+
+from geffnet import mixnet_s
+from geffnet import mixnet_m
+from geffnet import mixnet_l
+from geffnet import mixnet_xl
+
+from geffnet import mobilenetv2_100
+from geffnet import mobilenetv2_110d
+from geffnet import mobilenetv2_120d
+from geffnet import mobilenetv2_140
+
+from geffnet import mobilenetv3_large_100
+from geffnet import mobilenetv3_rw
+from geffnet import mnasnet_a1
+from geffnet import mnasnet_b1
+from geffnet import fbnetc_100
+from geffnet import spnasnet_100
+
+from geffnet import tf_efficientnet_b0
+from geffnet import tf_efficientnet_b1
+from geffnet import tf_efficientnet_b2
+from geffnet import tf_efficientnet_b3
+from geffnet import tf_efficientnet_b4
+from geffnet import tf_efficientnet_b5
+from geffnet import tf_efficientnet_b6
+from geffnet import tf_efficientnet_b7
+from geffnet import tf_efficientnet_b8
+
+from geffnet import tf_efficientnet_b0_ap
+from geffnet import tf_efficientnet_b1_ap
+from geffnet import tf_efficientnet_b2_ap
+from geffnet import tf_efficientnet_b3_ap
+from geffnet import tf_efficientnet_b4_ap
+from geffnet import tf_efficientnet_b5_ap
+from geffnet import tf_efficientnet_b6_ap
+from geffnet import tf_efficientnet_b7_ap
+from geffnet import tf_efficientnet_b8_ap
+
+from geffnet import tf_efficientnet_b0_ns
+from geffnet import tf_efficientnet_b1_ns
+from geffnet import tf_efficientnet_b2_ns
+from geffnet import tf_efficientnet_b3_ns
+from geffnet import tf_efficientnet_b4_ns
+from geffnet import tf_efficientnet_b5_ns
+from geffnet import tf_efficientnet_b6_ns
+from geffnet import tf_efficientnet_b7_ns
+from geffnet import tf_efficientnet_l2_ns_475
+from geffnet import tf_efficientnet_l2_ns
+
+from geffnet import tf_efficientnet_es
+from geffnet import tf_efficientnet_em
+from geffnet import tf_efficientnet_el
+
+from geffnet import tf_efficientnet_cc_b0_4e
+from geffnet import tf_efficientnet_cc_b0_8e
+from geffnet import tf_efficientnet_cc_b1_8e
+
+from geffnet import tf_efficientnet_lite0
+from geffnet import tf_efficientnet_lite1
+from geffnet import tf_efficientnet_lite2
+from geffnet import tf_efficientnet_lite3
+from geffnet import tf_efficientnet_lite4
+
+from geffnet import tf_mixnet_s
+from geffnet import tf_mixnet_m
+from geffnet import tf_mixnet_l
+
+from geffnet import tf_mobilenetv3_large_075
+from geffnet import tf_mobilenetv3_large_100
+from geffnet import tf_mobilenetv3_large_minimal_100
+from geffnet import tf_mobilenetv3_small_075
+from geffnet import tf_mobilenetv3_small_100
+from geffnet import tf_mobilenetv3_small_minimal_100
+
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/onnx_export.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/onnx_export.py
new file mode 100644
index 0000000000000000000000000000000000000000..7a5162ce214830df501bdb81edb66c095122f69d
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/onnx_export.py
@@ -0,0 +1,120 @@
+""" ONNX export script
+
+Export PyTorch models as ONNX graphs.
+
+This export script originally started as an adaptation of code snippets found at
+https://pytorch.org/tutorials/advanced/super_resolution_with_onnxruntime.html
+
+The default parameters work with PyTorch 1.6 and ONNX 1.7 and produce an optimal ONNX graph
+for hosting in the ONNX runtime (see onnx_validate.py). To export an ONNX model compatible
+with caffe2 (see caffe2_benchmark.py and caffe2_validate.py), the --keep-init and --aten-fallback
+flags are currently required.
+
+Older versions of PyTorch/ONNX (tested PyTorch 1.4, ONNX 1.5) do not need extra flags for
+caffe2 compatibility, but they produce a model that isn't as fast running on ONNX runtime.
+
+Most new release of PyTorch and ONNX cause some sort of breakage in the export / usage of ONNX models.
+Please do your research and search ONNX and PyTorch issue tracker before asking me. Thanks.
+
+Copyright 2020 Ross Wightman
+"""
+import argparse
+import torch
+import numpy as np
+
+import onnx
+import geffnet
+
+parser = argparse.ArgumentParser(description='PyTorch ImageNet Validation')
+parser.add_argument('output', metavar='ONNX_FILE',
+ help='output model filename')
+parser.add_argument('--model', '-m', metavar='MODEL', default='mobilenetv3_large_100',
+ help='model architecture (default: mobilenetv3_large_100)')
+parser.add_argument('--opset', type=int, default=10,
+ help='ONNX opset to use (default: 10)')
+parser.add_argument('--keep-init', action='store_true', default=False,
+ help='Keep initializers as input. Needed for Caffe2 compatible export in newer PyTorch/ONNX.')
+parser.add_argument('--aten-fallback', action='store_true', default=False,
+ help='Fallback to ATEN ops. Helps fix AdaptiveAvgPool issue with Caffe2 in newer PyTorch/ONNX.')
+parser.add_argument('--dynamic-size', action='store_true', default=False,
+ help='Export model width dynamic width/height. Not recommended for "tf" models with SAME padding.')
+parser.add_argument('-b', '--batch-size', default=1, type=int,
+ metavar='N', help='mini-batch size (default: 1)')
+parser.add_argument('--img-size', default=None, type=int,
+ metavar='N', help='Input image dimension, uses model default if empty')
+parser.add_argument('--mean', type=float, nargs='+', default=None, metavar='MEAN',
+ help='Override mean pixel value of dataset')
+parser.add_argument('--std', type=float, nargs='+', default=None, metavar='STD',
+ help='Override std deviation of of dataset')
+parser.add_argument('--num-classes', type=int, default=1000,
+ help='Number classes in dataset')
+parser.add_argument('--checkpoint', default='', type=str, metavar='PATH',
+ help='path to checkpoint (default: none)')
+
+
+def main():
+ args = parser.parse_args()
+
+ args.pretrained = True
+ if args.checkpoint:
+ args.pretrained = False
+
+ print("==> Creating PyTorch {} model".format(args.model))
+ # NOTE exportable=True flag disables autofn/jit scripted activations and uses Conv2dSameExport layers
+ # for models using SAME padding
+ model = geffnet.create_model(
+ args.model,
+ num_classes=args.num_classes,
+ in_chans=3,
+ pretrained=args.pretrained,
+ checkpoint_path=args.checkpoint,
+ exportable=True)
+
+ model.eval()
+
+ example_input = torch.randn((args.batch_size, 3, args.img_size or 224, args.img_size or 224), requires_grad=True)
+
+ # Run model once before export trace, sets padding for models with Conv2dSameExport. This means
+ # that the padding for models with Conv2dSameExport (most models with tf_ prefix) is fixed for
+ # the input img_size specified in this script.
+ # Opset >= 11 should allow for dynamic padding, however I cannot get it to work due to
+ # issues in the tracing of the dynamic padding or errors attempting to export the model after jit
+ # scripting it (an approach that should work). Perhaps in a future PyTorch or ONNX versions...
+ model(example_input)
+
+ print("==> Exporting model to ONNX format at '{}'".format(args.output))
+ input_names = ["input0"]
+ output_names = ["output0"]
+ dynamic_axes = {'input0': {0: 'batch'}, 'output0': {0: 'batch'}}
+ if args.dynamic_size:
+ dynamic_axes['input0'][2] = 'height'
+ dynamic_axes['input0'][3] = 'width'
+ if args.aten_fallback:
+ export_type = torch.onnx.OperatorExportTypes.ONNX_ATEN_FALLBACK
+ else:
+ export_type = torch.onnx.OperatorExportTypes.ONNX
+
+ torch_out = torch.onnx._export(
+ model, example_input, args.output, export_params=True, verbose=True, input_names=input_names,
+ output_names=output_names, keep_initializers_as_inputs=args.keep_init, dynamic_axes=dynamic_axes,
+ opset_version=args.opset, operator_export_type=export_type)
+
+ print("==> Loading and checking exported model from '{}'".format(args.output))
+ onnx_model = onnx.load(args.output)
+ onnx.checker.check_model(onnx_model) # assuming throw on error
+ print("==> Passed")
+
+ if args.keep_init and args.aten_fallback:
+ import caffe2.python.onnx.backend as onnx_caffe2
+ # Caffe2 loading only works properly in newer PyTorch/ONNX combos when
+ # keep_initializers_as_inputs and aten_fallback are set to True.
+ print("==> Loading model into Caffe2 backend and comparing forward pass.".format(args.output))
+ caffe2_backend = onnx_caffe2.prepare(onnx_model)
+ B = {onnx_model.graph.input[0].name: x.data.numpy()}
+ c2_out = caffe2_backend.run(B)[0]
+ np.testing.assert_almost_equal(torch_out.data.numpy(), c2_out, decimal=5)
+ print("==> Passed")
+
+
+if __name__ == '__main__':
+ main()
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/onnx_optimize.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/onnx_optimize.py
new file mode 100644
index 0000000000000000000000000000000000000000..ee20bbf9f0f9473370489512eb96ca0b570b5388
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/onnx_optimize.py
@@ -0,0 +1,84 @@
+""" ONNX optimization script
+
+Run ONNX models through the optimizer to prune unneeded nodes, fuse batchnorm layers into conv, etc.
+
+NOTE: This isn't working consistently in recent PyTorch/ONNX combos (ie PyTorch 1.6 and ONNX 1.7),
+it seems time to switch to using the onnxruntime online optimizer (can also be saved for offline).
+
+Copyright 2020 Ross Wightman
+"""
+import argparse
+import warnings
+
+import onnx
+from onnx import optimizer
+
+
+parser = argparse.ArgumentParser(description="Optimize ONNX model")
+
+parser.add_argument("model", help="The ONNX model")
+parser.add_argument("--output", required=True, help="The optimized model output filename")
+
+
+def traverse_graph(graph, prefix=''):
+ content = []
+ indent = prefix + ' '
+ graphs = []
+ num_nodes = 0
+ for node in graph.node:
+ pn, gs = onnx.helper.printable_node(node, indent, subgraphs=True)
+ assert isinstance(gs, list)
+ content.append(pn)
+ graphs.extend(gs)
+ num_nodes += 1
+ for g in graphs:
+ g_count, g_str = traverse_graph(g)
+ content.append('\n' + g_str)
+ num_nodes += g_count
+ return num_nodes, '\n'.join(content)
+
+
+def main():
+ args = parser.parse_args()
+ onnx_model = onnx.load(args.model)
+ num_original_nodes, original_graph_str = traverse_graph(onnx_model.graph)
+
+ # Optimizer passes to perform
+ passes = [
+ #'eliminate_deadend',
+ 'eliminate_identity',
+ 'eliminate_nop_dropout',
+ 'eliminate_nop_pad',
+ 'eliminate_nop_transpose',
+ 'eliminate_unused_initializer',
+ 'extract_constant_to_initializer',
+ 'fuse_add_bias_into_conv',
+ 'fuse_bn_into_conv',
+ 'fuse_consecutive_concats',
+ 'fuse_consecutive_reduce_unsqueeze',
+ 'fuse_consecutive_squeezes',
+ 'fuse_consecutive_transposes',
+ #'fuse_matmul_add_bias_into_gemm',
+ 'fuse_pad_into_conv',
+ #'fuse_transpose_into_gemm',
+ #'lift_lexical_references',
+ ]
+
+ # Apply the optimization on the original serialized model
+ # WARNING I've had issues with optimizer in recent versions of PyTorch / ONNX causing
+ # 'duplicate definition of name' errors, see: https://github.com/onnx/onnx/issues/2401
+ # It may be better to rely on onnxruntime optimizations, see onnx_validate.py script.
+ warnings.warn("I've had issues with optimizer in recent versions of PyTorch / ONNX."
+ "Try onnxruntime optimization if this doesn't work.")
+ optimized_model = optimizer.optimize(onnx_model, passes)
+
+ num_optimized_nodes, optimzied_graph_str = traverse_graph(optimized_model.graph)
+ print('==> The model after optimization:\n{}\n'.format(optimzied_graph_str))
+ print('==> The optimized model has {} nodes, the original had {}.'.format(num_optimized_nodes, num_original_nodes))
+
+ # Save the ONNX model
+ onnx.save(optimized_model, args.output)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/onnx_to_caffe.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/onnx_to_caffe.py
new file mode 100644
index 0000000000000000000000000000000000000000..44399aafababcdf6b84147a0613eb0909730db4b
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/onnx_to_caffe.py
@@ -0,0 +1,27 @@
+import argparse
+
+import onnx
+from caffe2.python.onnx.backend import Caffe2Backend
+
+
+parser = argparse.ArgumentParser(description="Convert ONNX to Caffe2")
+
+parser.add_argument("model", help="The ONNX model")
+parser.add_argument("--c2-prefix", required=True,
+ help="The output file prefix for the caffe2 model init and predict file. ")
+
+
+def main():
+ args = parser.parse_args()
+ onnx_model = onnx.load(args.model)
+ caffe2_init, caffe2_predict = Caffe2Backend.onnx_graph_to_caffe2_net(onnx_model)
+ caffe2_init_str = caffe2_init.SerializeToString()
+ with open(args.c2_prefix + '.init.pb', "wb") as f:
+ f.write(caffe2_init_str)
+ caffe2_predict_str = caffe2_predict.SerializeToString()
+ with open(args.c2_prefix + '.predict.pb', "wb") as f:
+ f.write(caffe2_predict_str)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/onnx_validate.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/onnx_validate.py
new file mode 100644
index 0000000000000000000000000000000000000000..ab3e4fb141b6ef660dcc5b447fd9f368a2ea19a0
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/onnx_validate.py
@@ -0,0 +1,112 @@
+""" ONNX-runtime validation script
+
+This script was created to verify accuracy and performance of exported ONNX
+models running with the onnxruntime. It utilizes the PyTorch dataloader/processing
+pipeline for a fair comparison against the originals.
+
+Copyright 2020 Ross Wightman
+"""
+import argparse
+import numpy as np
+import onnxruntime
+from data import create_loader, resolve_data_config, Dataset
+from utils import AverageMeter
+import time
+
+parser = argparse.ArgumentParser(description='Caffe2 ImageNet Validation')
+parser.add_argument('data', metavar='DIR',
+ help='path to dataset')
+parser.add_argument('--onnx-input', default='', type=str, metavar='PATH',
+ help='path to onnx model/weights file')
+parser.add_argument('--onnx-output-opt', default='', type=str, metavar='PATH',
+ help='path to output optimized onnx graph')
+parser.add_argument('--profile', action='store_true', default=False,
+ help='Enable profiler output.')
+parser.add_argument('-j', '--workers', default=2, type=int, metavar='N',
+ help='number of data loading workers (default: 2)')
+parser.add_argument('-b', '--batch-size', default=256, type=int,
+ metavar='N', help='mini-batch size (default: 256)')
+parser.add_argument('--img-size', default=None, type=int,
+ metavar='N', help='Input image dimension, uses model default if empty')
+parser.add_argument('--mean', type=float, nargs='+', default=None, metavar='MEAN',
+ help='Override mean pixel value of dataset')
+parser.add_argument('--std', type=float, nargs='+', default=None, metavar='STD',
+ help='Override std deviation of of dataset')
+parser.add_argument('--crop-pct', type=float, default=None, metavar='PCT',
+ help='Override default crop pct of 0.875')
+parser.add_argument('--interpolation', default='', type=str, metavar='NAME',
+ help='Image resize interpolation type (overrides model)')
+parser.add_argument('--tf-preprocessing', dest='tf_preprocessing', action='store_true',
+ help='use tensorflow mnasnet preporcessing')
+parser.add_argument('--print-freq', '-p', default=10, type=int,
+ metavar='N', help='print frequency (default: 10)')
+
+
+def main():
+ args = parser.parse_args()
+ args.gpu_id = 0
+
+ # Set graph optimization level
+ sess_options = onnxruntime.SessionOptions()
+ sess_options.graph_optimization_level = onnxruntime.GraphOptimizationLevel.ORT_ENABLE_ALL
+ if args.profile:
+ sess_options.enable_profiling = True
+ if args.onnx_output_opt:
+ sess_options.optimized_model_filepath = args.onnx_output_opt
+
+ session = onnxruntime.InferenceSession(args.onnx_input, sess_options)
+
+ data_config = resolve_data_config(None, args)
+ loader = create_loader(
+ Dataset(args.data, load_bytes=args.tf_preprocessing),
+ input_size=data_config['input_size'],
+ batch_size=args.batch_size,
+ use_prefetcher=False,
+ interpolation=data_config['interpolation'],
+ mean=data_config['mean'],
+ std=data_config['std'],
+ num_workers=args.workers,
+ crop_pct=data_config['crop_pct'],
+ tensorflow_preprocessing=args.tf_preprocessing)
+
+ input_name = session.get_inputs()[0].name
+
+ batch_time = AverageMeter()
+ top1 = AverageMeter()
+ top5 = AverageMeter()
+ end = time.time()
+ for i, (input, target) in enumerate(loader):
+ # run the net and return prediction
+ output = session.run([], {input_name: input.data.numpy()})
+ output = output[0]
+
+ # measure accuracy and record loss
+ prec1, prec5 = accuracy_np(output, target.numpy())
+ top1.update(prec1.item(), input.size(0))
+ top5.update(prec5.item(), input.size(0))
+
+ # measure elapsed time
+ batch_time.update(time.time() - end)
+ end = time.time()
+
+ if i % args.print_freq == 0:
+ print('Test: [{0}/{1}]\t'
+ 'Time {batch_time.val:.3f} ({batch_time.avg:.3f}, {rate_avg:.3f}/s, {ms_avg:.3f} ms/sample) \t'
+ 'Prec@1 {top1.val:.3f} ({top1.avg:.3f})\t'
+ 'Prec@5 {top5.val:.3f} ({top5.avg:.3f})'.format(
+ i, len(loader), batch_time=batch_time, rate_avg=input.size(0) / batch_time.avg,
+ ms_avg=100 * batch_time.avg / input.size(0), top1=top1, top5=top5))
+
+ print(' * Prec@1 {top1.avg:.3f} ({top1a:.3f}) Prec@5 {top5.avg:.3f} ({top5a:.3f})'.format(
+ top1=top1, top1a=100-top1.avg, top5=top5, top5a=100.-top5.avg))
+
+
+def accuracy_np(output, target):
+ max_indices = np.argsort(output, axis=1)[:, ::-1]
+ top5 = 100 * np.equal(max_indices[:, :5], target[:, np.newaxis]).sum(axis=1).mean()
+ top1 = 100 * np.equal(max_indices[:, 0], target).mean()
+ return top1, top5
+
+
+if __name__ == '__main__':
+ main()
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/requirements.txt b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ac3ffc13bae15f9b11f7cbe3705760056ecd7f13
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/requirements.txt
@@ -0,0 +1,2 @@
+torch>=1.2.0
+torchvision>=0.4.0
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/setup.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/setup.py
new file mode 100644
index 0000000000000000000000000000000000000000..023e4c30f98164595964423e3a83eefaf7ffdad6
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/setup.py
@@ -0,0 +1,47 @@
+""" Setup
+"""
+from setuptools import setup, find_packages
+from codecs import open
+from os import path
+
+here = path.abspath(path.dirname(__file__))
+
+# Get the long description from the README file
+with open(path.join(here, 'README.md'), encoding='utf-8') as f:
+ long_description = f.read()
+
+exec(open('geffnet/version.py').read())
+setup(
+ name='geffnet',
+ version=__version__,
+ description='(Generic) EfficientNets for PyTorch',
+ long_description=long_description,
+ long_description_content_type='text/markdown',
+ url='https://github.com/rwightman/gen-efficientnet-pytorch',
+ author='Ross Wightman',
+ author_email='hello@rwightman.com',
+ classifiers=[
+ # How mature is this project? Common values are
+ # 3 - Alpha
+ # 4 - Beta
+ # 5 - Production/Stable
+ 'Development Status :: 3 - Alpha',
+ 'Intended Audience :: Education',
+ 'Intended Audience :: Science/Research',
+ 'License :: OSI Approved :: Apache Software License',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
+ 'Topic :: Scientific/Engineering',
+ 'Topic :: Scientific/Engineering :: Artificial Intelligence',
+ 'Topic :: Software Development',
+ 'Topic :: Software Development :: Libraries',
+ 'Topic :: Software Development :: Libraries :: Python Modules',
+ ],
+
+ # Note that this is a string of words separated by whitespace, not a list.
+ keywords='pytorch pretrained models efficientnet mixnet mobilenetv3 mnasnet',
+ packages=find_packages(exclude=['data']),
+ install_requires=['torch >= 1.4', 'torchvision'],
+ python_requires='>=3.6',
+)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/utils.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..d327e8bd8120c5cd09ae6c15c3991ccbe27f6c1f
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/utils.py
@@ -0,0 +1,52 @@
+import os
+
+
+class AverageMeter:
+ """Computes and stores the average and current value"""
+ def __init__(self):
+ self.reset()
+
+ def reset(self):
+ self.val = 0
+ self.avg = 0
+ self.sum = 0
+ self.count = 0
+
+ def update(self, val, n=1):
+ self.val = val
+ self.sum += val * n
+ self.count += n
+ self.avg = self.sum / self.count
+
+
+def accuracy(output, target, topk=(1,)):
+ """Computes the precision@k for the specified values of k"""
+ maxk = max(topk)
+ batch_size = target.size(0)
+
+ _, pred = output.topk(maxk, 1, True, True)
+ pred = pred.t()
+ correct = pred.eq(target.view(1, -1).expand_as(pred))
+
+ res = []
+ for k in topk:
+ correct_k = correct[:k].reshape(-1).float().sum(0)
+ res.append(correct_k.mul_(100.0 / batch_size))
+ return res
+
+
+def get_outdir(path, *paths, inc=False):
+ outdir = os.path.join(path, *paths)
+ if not os.path.exists(outdir):
+ os.makedirs(outdir)
+ elif inc:
+ count = 1
+ outdir_inc = outdir + '-' + str(count)
+ while os.path.exists(outdir_inc):
+ count = count + 1
+ outdir_inc = outdir + '-' + str(count)
+ assert count < 100
+ outdir = outdir_inc
+ os.makedirs(outdir)
+ return outdir
+
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/validate.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/validate.py
new file mode 100644
index 0000000000000000000000000000000000000000..5fd44fbb3165ef81ef81251b6299f6aaa80bf2c2
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/efficientnet_repo/validate.py
@@ -0,0 +1,166 @@
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import argparse
+import time
+import torch
+import torch.nn as nn
+import torch.nn.parallel
+from contextlib import suppress
+
+import geffnet
+from data import Dataset, create_loader, resolve_data_config
+from utils import accuracy, AverageMeter
+
+has_native_amp = False
+try:
+ if getattr(torch.cuda.amp, 'autocast') is not None:
+ has_native_amp = True
+except AttributeError:
+ pass
+
+torch.backends.cudnn.benchmark = True
+
+parser = argparse.ArgumentParser(description='PyTorch ImageNet Validation')
+parser.add_argument('data', metavar='DIR',
+ help='path to dataset')
+parser.add_argument('--model', '-m', metavar='MODEL', default='spnasnet1_00',
+ help='model architecture (default: dpn92)')
+parser.add_argument('-j', '--workers', default=4, type=int, metavar='N',
+ help='number of data loading workers (default: 2)')
+parser.add_argument('-b', '--batch-size', default=256, type=int,
+ metavar='N', help='mini-batch size (default: 256)')
+parser.add_argument('--img-size', default=None, type=int,
+ metavar='N', help='Input image dimension, uses model default if empty')
+parser.add_argument('--mean', type=float, nargs='+', default=None, metavar='MEAN',
+ help='Override mean pixel value of dataset')
+parser.add_argument('--std', type=float, nargs='+', default=None, metavar='STD',
+ help='Override std deviation of of dataset')
+parser.add_argument('--crop-pct', type=float, default=None, metavar='PCT',
+ help='Override default crop pct of 0.875')
+parser.add_argument('--interpolation', default='', type=str, metavar='NAME',
+ help='Image resize interpolation type (overrides model)')
+parser.add_argument('--num-classes', type=int, default=1000,
+ help='Number classes in dataset')
+parser.add_argument('--print-freq', '-p', default=10, type=int,
+ metavar='N', help='print frequency (default: 10)')
+parser.add_argument('--checkpoint', default='', type=str, metavar='PATH',
+ help='path to latest checkpoint (default: none)')
+parser.add_argument('--pretrained', dest='pretrained', action='store_true',
+ help='use pre-trained model')
+parser.add_argument('--torchscript', dest='torchscript', action='store_true',
+ help='convert model torchscript for inference')
+parser.add_argument('--num-gpu', type=int, default=1,
+ help='Number of GPUS to use')
+parser.add_argument('--tf-preprocessing', dest='tf_preprocessing', action='store_true',
+ help='use tensorflow mnasnet preporcessing')
+parser.add_argument('--no-cuda', dest='no_cuda', action='store_true',
+ help='')
+parser.add_argument('--channels-last', action='store_true', default=False,
+ help='Use channels_last memory layout')
+parser.add_argument('--amp', action='store_true', default=False,
+ help='Use native Torch AMP mixed precision.')
+
+
+def main():
+ args = parser.parse_args()
+
+ if not args.checkpoint and not args.pretrained:
+ args.pretrained = True
+
+ amp_autocast = suppress # do nothing
+ if args.amp:
+ if not has_native_amp:
+ print("Native Torch AMP is not available (requires torch >= 1.6), using FP32.")
+ else:
+ amp_autocast = torch.cuda.amp.autocast
+
+ # create model
+ model = geffnet.create_model(
+ args.model,
+ num_classes=args.num_classes,
+ in_chans=3,
+ pretrained=args.pretrained,
+ checkpoint_path=args.checkpoint,
+ scriptable=args.torchscript)
+
+ if args.channels_last:
+ model = model.to(memory_format=torch.channels_last)
+
+ if args.torchscript:
+ torch.jit.optimized_execution(True)
+ model = torch.jit.script(model)
+
+ print('Model %s created, param count: %d' %
+ (args.model, sum([m.numel() for m in model.parameters()])))
+
+ data_config = resolve_data_config(model, args)
+
+ criterion = nn.CrossEntropyLoss()
+
+ if not args.no_cuda:
+ if args.num_gpu > 1:
+ model = torch.nn.DataParallel(model, device_ids=list(range(args.num_gpu))).cuda()
+ else:
+ model = model.cuda()
+ criterion = criterion.cuda()
+
+ loader = create_loader(
+ Dataset(args.data, load_bytes=args.tf_preprocessing),
+ input_size=data_config['input_size'],
+ batch_size=args.batch_size,
+ use_prefetcher=not args.no_cuda,
+ interpolation=data_config['interpolation'],
+ mean=data_config['mean'],
+ std=data_config['std'],
+ num_workers=args.workers,
+ crop_pct=data_config['crop_pct'],
+ tensorflow_preprocessing=args.tf_preprocessing)
+
+ batch_time = AverageMeter()
+ losses = AverageMeter()
+ top1 = AverageMeter()
+ top5 = AverageMeter()
+
+ model.eval()
+ end = time.time()
+ with torch.no_grad():
+ for i, (input, target) in enumerate(loader):
+ if not args.no_cuda:
+ target = target.cuda()
+ input = input.cuda()
+ if args.channels_last:
+ input = input.contiguous(memory_format=torch.channels_last)
+
+ # compute output
+ with amp_autocast():
+ output = model(input)
+ loss = criterion(output, target)
+
+ # measure accuracy and record loss
+ prec1, prec5 = accuracy(output.data, target, topk=(1, 5))
+ losses.update(loss.item(), input.size(0))
+ top1.update(prec1.item(), input.size(0))
+ top5.update(prec5.item(), input.size(0))
+
+ # measure elapsed time
+ batch_time.update(time.time() - end)
+ end = time.time()
+
+ if i % args.print_freq == 0:
+ print('Test: [{0}/{1}]\t'
+ 'Time {batch_time.val:.3f} ({batch_time.avg:.3f}, {rate_avg:.3f}/s) \t'
+ 'Loss {loss.val:.4f} ({loss.avg:.4f})\t'
+ 'Prec@1 {top1.val:.3f} ({top1.avg:.3f})\t'
+ 'Prec@5 {top5.val:.3f} ({top5.avg:.3f})'.format(
+ i, len(loader), batch_time=batch_time,
+ rate_avg=input.size(0) / batch_time.avg,
+ loss=losses, top1=top1, top5=top5))
+
+ print(' * Prec@1 {top1.avg:.3f} ({top1a:.3f}) Prec@5 {top5.avg:.3f} ({top5a:.3f})'.format(
+ top1=top1, top1a=100-top1.avg, top5=top5, top5a=100.-top5.avg))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/encoder.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/encoder.py
new file mode 100644
index 0000000000000000000000000000000000000000..7f7149ca3c0cf2b6e019105af7e645cfbb3eda11
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/encoder.py
@@ -0,0 +1,34 @@
+import os
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+
+
+class Encoder(nn.Module):
+ def __init__(self):
+ super(Encoder, self).__init__()
+
+ basemodel_name = 'tf_efficientnet_b5_ap'
+ print('Loading base model ()...'.format(basemodel_name), end='')
+ repo_path = os.path.join(os.path.dirname(__file__), 'efficientnet_repo')
+ basemodel = torch.hub.load(repo_path, basemodel_name, pretrained=False, source='local')
+ print('Done.')
+
+ # Remove last layer
+ print('Removing last two layers (global_pool & classifier).')
+ basemodel.global_pool = nn.Identity()
+ basemodel.classifier = nn.Identity()
+
+ self.original_model = basemodel
+
+ def forward(self, x):
+ features = [x]
+ for k, v in self.original_model._modules.items():
+ if (k == 'blocks'):
+ for ki, vi in v._modules.items():
+ features.append(vi(features[-1]))
+ else:
+ features.append(v(features[-1]))
+ return features
+
+
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/submodules.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/submodules.py
new file mode 100644
index 0000000000000000000000000000000000000000..409733351bd6ab5d191c800aff1bc05bfa4cb6f8
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/normalbae/nets/submodules/submodules.py
@@ -0,0 +1,140 @@
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+
+
+########################################################################################################################
+
+
+# Upsample + BatchNorm
+class UpSampleBN(nn.Module):
+ def __init__(self, skip_input, output_features):
+ super(UpSampleBN, self).__init__()
+
+ self._net = nn.Sequential(nn.Conv2d(skip_input, output_features, kernel_size=3, stride=1, padding=1),
+ nn.BatchNorm2d(output_features),
+ nn.LeakyReLU(),
+ nn.Conv2d(output_features, output_features, kernel_size=3, stride=1, padding=1),
+ nn.BatchNorm2d(output_features),
+ nn.LeakyReLU())
+
+ def forward(self, x, concat_with):
+ up_x = F.interpolate(x, size=[concat_with.size(2), concat_with.size(3)], mode='bilinear', align_corners=True)
+ f = torch.cat([up_x, concat_with], dim=1)
+ return self._net(f)
+
+
+# Upsample + GroupNorm + Weight Standardization
+class UpSampleGN(nn.Module):
+ def __init__(self, skip_input, output_features):
+ super(UpSampleGN, self).__init__()
+
+ self._net = nn.Sequential(Conv2d(skip_input, output_features, kernel_size=3, stride=1, padding=1),
+ nn.GroupNorm(8, output_features),
+ nn.LeakyReLU(),
+ Conv2d(output_features, output_features, kernel_size=3, stride=1, padding=1),
+ nn.GroupNorm(8, output_features),
+ nn.LeakyReLU())
+
+ def forward(self, x, concat_with):
+ up_x = F.interpolate(x, size=[concat_with.size(2), concat_with.size(3)], mode='bilinear', align_corners=True)
+ f = torch.cat([up_x, concat_with], dim=1)
+ return self._net(f)
+
+
+# Conv2d with weight standardization
+class Conv2d(nn.Conv2d):
+ def __init__(self, in_channels, out_channels, kernel_size, stride=1,
+ padding=0, dilation=1, groups=1, bias=True):
+ super(Conv2d, self).__init__(in_channels, out_channels, kernel_size, stride,
+ padding, dilation, groups, bias)
+
+ def forward(self, x):
+ weight = self.weight
+ weight_mean = weight.mean(dim=1, keepdim=True).mean(dim=2,
+ keepdim=True).mean(dim=3, keepdim=True)
+ weight = weight - weight_mean
+ std = weight.view(weight.size(0), -1).std(dim=1).view(-1, 1, 1, 1) + 1e-5
+ weight = weight / std.expand_as(weight)
+ return F.conv2d(x, weight, self.bias, self.stride,
+ self.padding, self.dilation, self.groups)
+
+
+# normalize
+def norm_normalize(norm_out):
+ min_kappa = 0.01
+ norm_x, norm_y, norm_z, kappa = torch.split(norm_out, 1, dim=1)
+ norm = torch.sqrt(norm_x ** 2.0 + norm_y ** 2.0 + norm_z ** 2.0) + 1e-10
+ kappa = F.elu(kappa) + 1.0 + min_kappa
+ final_out = torch.cat([norm_x / norm, norm_y / norm, norm_z / norm, kappa], dim=1)
+ return final_out
+
+
+# uncertainty-guided sampling (only used during training)
+@torch.no_grad()
+def sample_points(init_normal, gt_norm_mask, sampling_ratio, beta):
+ device = init_normal.device
+ B, _, H, W = init_normal.shape
+ N = int(sampling_ratio * H * W)
+ beta = beta
+
+ # uncertainty map
+ uncertainty_map = -1 * init_normal[:, 3, :, :] # B, H, W
+
+ # gt_invalid_mask (B, H, W)
+ if gt_norm_mask is not None:
+ gt_invalid_mask = F.interpolate(gt_norm_mask.float(), size=[H, W], mode='nearest')
+ gt_invalid_mask = gt_invalid_mask[:, 0, :, :] < 0.5
+ uncertainty_map[gt_invalid_mask] = -1e4
+
+ # (B, H*W)
+ _, idx = uncertainty_map.view(B, -1).sort(1, descending=True)
+
+ # importance sampling
+ if int(beta * N) > 0:
+ importance = idx[:, :int(beta * N)] # B, beta*N
+
+ # remaining
+ remaining = idx[:, int(beta * N):] # B, H*W - beta*N
+
+ # coverage
+ num_coverage = N - int(beta * N)
+
+ if num_coverage <= 0:
+ samples = importance
+ else:
+ coverage_list = []
+ for i in range(B):
+ idx_c = torch.randperm(remaining.size()[1]) # shuffles "H*W - beta*N"
+ coverage_list.append(remaining[i, :][idx_c[:num_coverage]].view(1, -1)) # 1, N-beta*N
+ coverage = torch.cat(coverage_list, dim=0) # B, N-beta*N
+ samples = torch.cat((importance, coverage), dim=1) # B, N
+
+ else:
+ # remaining
+ remaining = idx[:, :] # B, H*W
+
+ # coverage
+ num_coverage = N
+
+ coverage_list = []
+ for i in range(B):
+ idx_c = torch.randperm(remaining.size()[1]) # shuffles "H*W - beta*N"
+ coverage_list.append(remaining[i, :][idx_c[:num_coverage]].view(1, -1)) # 1, N-beta*N
+ coverage = torch.cat(coverage_list, dim=0) # B, N-beta*N
+ samples = coverage
+
+ # point coordinates
+ rows_int = samples // W # 0 for first row, H-1 for last row
+ rows_float = rows_int / float(H-1) # 0 to 1.0
+ rows_float = (rows_float * 2.0) - 1.0 # -1.0 to 1.0
+
+ cols_int = samples % W # 0 for first column, W-1 for last column
+ cols_float = cols_int / float(W-1) # 0 to 1.0
+ cols_float = (cols_float * 2.0) - 1.0 # -1.0 to 1.0
+
+ point_coords = torch.zeros(B, 1, N, 2)
+ point_coords[:, 0, :, 0] = cols_float # x coord
+ point_coords[:, 0, :, 1] = rows_float # y coord
+ point_coords = point_coords.to(device)
+ return point_coords, rows_int, cols_int
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/oneformer/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/oneformer/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..ebed08adabe6f0bf1d253c67b5c641e07ce9a175
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/oneformer/__init__.py
@@ -0,0 +1,49 @@
+import os
+from .api import make_detectron2_model, semantic_run
+from pathlib import Path
+import warnings
+from controlnet_aux.util import HWC3, common_input_validate, resize_image_with_pad, annotator_ckpts_path, custom_hf_download
+import numpy as np
+import cv2
+from PIL import Image
+
+DEFAULT_CONFIGS = {
+ "coco": {
+ "name": "150_16_swin_l_oneformer_coco_100ep.pth",
+ "config": os.path.join(os.path.dirname(__file__), 'configs/coco/oneformer_swin_large_IN21k_384_bs16_100ep.yaml')
+ },
+ "ade20k": {
+ "name": "250_16_swin_l_oneformer_ade20k_160k.pth",
+ "config": os.path.join(os.path.dirname(__file__), 'configs/ade20k/oneformer_swin_large_IN21k_384_bs16_160k.yaml')
+ }
+}
+class OneformerSegmentor:
+ def __init__(self, model, metadata):
+ self.model = model
+ self.metadata = metadata
+
+ def to(self, device):
+ self.model.model.to(device)
+ return self
+
+ @classmethod
+ def from_pretrained(cls, pretrained_model_or_path, filename=None, cache_dir=annotator_ckpts_path, config_path = None):
+ filename = filename or "250_16_swin_l_oneformer_ade20k_160k.pth"
+ config_path = config_path or DEFAULT_CONFIGS["ade20k" if "ade20k" in filename else "coco"]["config"]
+ model_path = custom_hf_download(pretrained_model_or_path, filename, cache_dir=cache_dir)
+
+ model, metadata = make_detectron2_model(config_path, model_path)
+
+ return cls(model, metadata)
+
+ def __call__(self, input_image=None, detect_resolution=512, output_type=None, upscale_method="INTER_CUBIC", **kwargs):
+ input_image, output_type = common_input_validate(input_image, output_type, **kwargs)
+ input_image, remove_pad = resize_image_with_pad(input_image, detect_resolution, upscale_method)
+
+ detected_map = semantic_run(input_image, self.model, self.metadata)
+ detected_map = remove_pad(HWC3(detected_map))
+
+ if output_type == "pil":
+ detected_map = Image.fromarray(detected_map)
+
+ return detected_map
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/oneformer/api.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/oneformer/api.py
new file mode 100644
index 0000000000000000000000000000000000000000..3502e9404416867ade0063f00bfc44955435a75c
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/oneformer/api.py
@@ -0,0 +1,39 @@
+import os
+os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
+
+import torch
+
+from custom_detectron2.config import get_cfg
+from custom_detectron2.projects.deeplab import add_deeplab_config
+from custom_detectron2.data import MetadataCatalog
+
+from custom_oneformer import (
+ add_oneformer_config,
+ add_common_config,
+ add_swin_config,
+ add_dinat_config,
+)
+
+from custom_oneformer.demo.defaults import DefaultPredictor
+from custom_oneformer.demo.visualizer import Visualizer, ColorMode
+
+
+def make_detectron2_model(config_path, ckpt_path):
+ cfg = get_cfg()
+ add_deeplab_config(cfg)
+ add_common_config(cfg)
+ add_swin_config(cfg)
+ add_oneformer_config(cfg)
+ add_dinat_config(cfg)
+ cfg.merge_from_file(config_path)
+ cfg.MODEL.WEIGHTS = ckpt_path
+ cfg.freeze()
+ metadata = MetadataCatalog.get(cfg.DATASETS.TEST_PANOPTIC[0] if len(cfg.DATASETS.TEST_PANOPTIC) else "__unused")
+ return DefaultPredictor(cfg), metadata
+
+
+def semantic_run(img, predictor, metadata):
+ predictions = predictor(img[:, :, ::-1], "semantic") # Predictor of OneFormer must use BGR image !!!
+ visualizer_map = Visualizer(img, is_img=False, metadata=metadata, instance_mode=ColorMode.IMAGE)
+ out_map = visualizer_map.draw_sem_seg(predictions["sem_seg"].argmax(dim=0).cpu(), alpha=1, is_text=False).get_image()
+ return out_map
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/oneformer/configs/ade20k/Base-ADE20K-UnifiedSegmentation.yaml b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/oneformer/configs/ade20k/Base-ADE20K-UnifiedSegmentation.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..31eab45b878433fc844a13dbdd54f97c936d9b89
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/oneformer/configs/ade20k/Base-ADE20K-UnifiedSegmentation.yaml
@@ -0,0 +1,68 @@
+MODEL:
+ BACKBONE:
+ FREEZE_AT: 0
+ NAME: "build_resnet_backbone"
+ WEIGHTS: "detectron2://ImageNetPretrained/torchvision/R-50.pkl"
+ PIXEL_MEAN: [123.675, 116.280, 103.530]
+ PIXEL_STD: [58.395, 57.120, 57.375]
+ RESNETS:
+ DEPTH: 50
+ STEM_TYPE: "basic" # not used
+ STEM_OUT_CHANNELS: 64
+ STRIDE_IN_1X1: False
+ OUT_FEATURES: ["res2", "res3", "res4", "res5"]
+ # NORM: "SyncBN"
+ RES5_MULTI_GRID: [1, 1, 1] # not used
+DATASETS:
+ TRAIN: ("ade20k_panoptic_train",)
+ TEST_PANOPTIC: ("ade20k_panoptic_val",)
+ TEST_INSTANCE: ("ade20k_instance_val",)
+ TEST_SEMANTIC: ("ade20k_sem_seg_val",)
+SOLVER:
+ IMS_PER_BATCH: 16
+ BASE_LR: 0.0001
+ MAX_ITER: 160000
+ WARMUP_FACTOR: 1.0
+ WARMUP_ITERS: 0
+ WEIGHT_DECAY: 0.05
+ OPTIMIZER: "ADAMW"
+ LR_SCHEDULER_NAME: "WarmupPolyLR"
+ BACKBONE_MULTIPLIER: 0.1
+ CLIP_GRADIENTS:
+ ENABLED: True
+ CLIP_TYPE: "full_model"
+ CLIP_VALUE: 0.01
+ NORM_TYPE: 2.0
+ AMP:
+ ENABLED: True
+INPUT:
+ MIN_SIZE_TRAIN: !!python/object/apply:eval ["[int(x * 0.1 * 512) for x in range(5, 21)]"]
+ MIN_SIZE_TRAIN_SAMPLING: "choice"
+ MIN_SIZE_TEST: 512
+ MAX_SIZE_TRAIN: 2048
+ MAX_SIZE_TEST: 2048
+ CROP:
+ ENABLED: True
+ TYPE: "absolute"
+ SIZE: (512, 512)
+ SINGLE_CATEGORY_MAX_AREA: 1.0
+ COLOR_AUG_SSD: True
+ SIZE_DIVISIBILITY: 512 # used in dataset mapper
+ FORMAT: "RGB"
+ DATASET_MAPPER_NAME: "oneformer_unified"
+ MAX_SEQ_LEN: 77
+ TASK_SEQ_LEN: 77
+ TASK_PROB:
+ SEMANTIC: 0.33
+ INSTANCE: 0.66
+TEST:
+ EVAL_PERIOD: 5000
+ AUG:
+ ENABLED: False
+ MIN_SIZES: [256, 384, 512, 640, 768, 896]
+ MAX_SIZE: 3584
+ FLIP: True
+DATALOADER:
+ FILTER_EMPTY_ANNOTATIONS: True
+ NUM_WORKERS: 4
+VERSION: 2
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/oneformer/configs/ade20k/oneformer_R50_bs16_160k.yaml b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/oneformer/configs/ade20k/oneformer_R50_bs16_160k.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..770ffc81907f8d7c7520e079b1c46060707254b8
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/oneformer/configs/ade20k/oneformer_R50_bs16_160k.yaml
@@ -0,0 +1,58 @@
+_BASE_: Base-ADE20K-UnifiedSegmentation.yaml
+MODEL:
+ META_ARCHITECTURE: "OneFormer"
+ SEM_SEG_HEAD:
+ NAME: "OneFormerHead"
+ IGNORE_VALUE: 255
+ NUM_CLASSES: 150
+ LOSS_WEIGHT: 1.0
+ CONVS_DIM: 256
+ MASK_DIM: 256
+ NORM: "GN"
+ # pixel decoder
+ PIXEL_DECODER_NAME: "MSDeformAttnPixelDecoder"
+ IN_FEATURES: ["res2", "res3", "res4", "res5"]
+ DEFORMABLE_TRANSFORMER_ENCODER_IN_FEATURES: ["res3", "res4", "res5"]
+ COMMON_STRIDE: 4
+ TRANSFORMER_ENC_LAYERS: 6
+ ONE_FORMER:
+ TRANSFORMER_DECODER_NAME: "ContrastiveMultiScaleMaskedTransformerDecoder"
+ TRANSFORMER_IN_FEATURE: "multi_scale_pixel_decoder"
+ DEEP_SUPERVISION: True
+ NO_OBJECT_WEIGHT: 0.1
+ CLASS_WEIGHT: 2.0
+ MASK_WEIGHT: 5.0
+ DICE_WEIGHT: 5.0
+ CONTRASTIVE_WEIGHT: 0.5
+ CONTRASTIVE_TEMPERATURE: 0.07
+ HIDDEN_DIM: 256
+ NUM_OBJECT_QUERIES: 150
+ USE_TASK_NORM: True
+ NHEADS: 8
+ DROPOUT: 0.1
+ DIM_FEEDFORWARD: 2048
+ ENC_LAYERS: 0
+ PRE_NORM: False
+ ENFORCE_INPUT_PROJ: False
+ SIZE_DIVISIBILITY: 32
+ CLASS_DEC_LAYERS: 2
+ DEC_LAYERS: 10 # 9 decoder layers, add one for the loss on learnable query
+ TRAIN_NUM_POINTS: 12544
+ OVERSAMPLE_RATIO: 3.0
+ IMPORTANCE_SAMPLE_RATIO: 0.75
+ TEXT_ENCODER:
+ WIDTH: 256
+ CONTEXT_LENGTH: 77
+ NUM_LAYERS: 6
+ VOCAB_SIZE: 49408
+ PROJ_NUM_LAYERS: 2
+ N_CTX: 16
+ TEST:
+ SEMANTIC_ON: True
+ INSTANCE_ON: True
+ PANOPTIC_ON: True
+ OVERLAP_THRESHOLD: 0.8
+ OBJECT_MASK_THRESHOLD: 0.8
+ TASK: "panoptic"
+TEST:
+ DETECTIONS_PER_IMAGE: 150
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/oneformer/configs/ade20k/oneformer_swin_large_IN21k_384_bs16_160k.yaml b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/oneformer/configs/ade20k/oneformer_swin_large_IN21k_384_bs16_160k.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..69c44ade144e4504077c0fe04fa8bb3491a679ed
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/oneformer/configs/ade20k/oneformer_swin_large_IN21k_384_bs16_160k.yaml
@@ -0,0 +1,40 @@
+_BASE_: oneformer_R50_bs16_160k.yaml
+MODEL:
+ BACKBONE:
+ NAME: "D2SwinTransformer"
+ SWIN:
+ EMBED_DIM: 192
+ DEPTHS: [2, 2, 18, 2]
+ NUM_HEADS: [6, 12, 24, 48]
+ WINDOW_SIZE: 12
+ APE: False
+ DROP_PATH_RATE: 0.3
+ PATCH_NORM: True
+ PRETRAIN_IMG_SIZE: 384
+ WEIGHTS: "swin_large_patch4_window12_384_22k.pkl"
+ PIXEL_MEAN: [123.675, 116.280, 103.530]
+ PIXEL_STD: [58.395, 57.120, 57.375]
+ ONE_FORMER:
+ NUM_OBJECT_QUERIES: 250
+INPUT:
+ MIN_SIZE_TRAIN: !!python/object/apply:eval ["[int(x * 0.1 * 640) for x in range(5, 21)]"]
+ MIN_SIZE_TRAIN_SAMPLING: "choice"
+ MIN_SIZE_TEST: 640
+ MAX_SIZE_TRAIN: 2560
+ MAX_SIZE_TEST: 2560
+ CROP:
+ ENABLED: True
+ TYPE: "absolute"
+ SIZE: (640, 640)
+ SINGLE_CATEGORY_MAX_AREA: 1.0
+ COLOR_AUG_SSD: True
+ SIZE_DIVISIBILITY: 640 # used in dataset mapper
+ FORMAT: "RGB"
+TEST:
+ DETECTIONS_PER_IMAGE: 250
+ EVAL_PERIOD: 5000
+ AUG:
+ ENABLED: False
+ MIN_SIZES: [320, 480, 640, 800, 960, 1120]
+ MAX_SIZE: 4480
+ FLIP: True
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/oneformer/configs/coco/Base-COCO-UnifiedSegmentation.yaml b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/oneformer/configs/coco/Base-COCO-UnifiedSegmentation.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..ccd24f348f9bc7d60dcdc4b74d887708e57cb8a8
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/oneformer/configs/coco/Base-COCO-UnifiedSegmentation.yaml
@@ -0,0 +1,54 @@
+MODEL:
+ BACKBONE:
+ FREEZE_AT: 0
+ NAME: "build_resnet_backbone"
+ WEIGHTS: "detectron2://ImageNetPretrained/torchvision/R-50.pkl"
+ PIXEL_MEAN: [123.675, 116.280, 103.530]
+ PIXEL_STD: [58.395, 57.120, 57.375]
+ RESNETS:
+ DEPTH: 50
+ STEM_TYPE: "basic" # not used
+ STEM_OUT_CHANNELS: 64
+ STRIDE_IN_1X1: False
+ OUT_FEATURES: ["res2", "res3", "res4", "res5"]
+ # NORM: "SyncBN"
+ RES5_MULTI_GRID: [1, 1, 1] # not used
+DATASETS:
+ TRAIN: ("coco_2017_train_panoptic_with_sem_seg",)
+ TEST_PANOPTIC: ("coco_2017_val_panoptic_with_sem_seg",) # to evaluate instance and semantic performance as well
+ TEST_INSTANCE: ("coco_2017_val",)
+ TEST_SEMANTIC: ("coco_2017_val_panoptic_with_sem_seg",)
+SOLVER:
+ IMS_PER_BATCH: 16
+ BASE_LR: 0.0001
+ STEPS: (327778, 355092)
+ MAX_ITER: 368750
+ WARMUP_FACTOR: 1.0
+ WARMUP_ITERS: 10
+ WEIGHT_DECAY: 0.05
+ OPTIMIZER: "ADAMW"
+ BACKBONE_MULTIPLIER: 0.1
+ CLIP_GRADIENTS:
+ ENABLED: True
+ CLIP_TYPE: "full_model"
+ CLIP_VALUE: 0.01
+ NORM_TYPE: 2.0
+ AMP:
+ ENABLED: True
+INPUT:
+ IMAGE_SIZE: 1024
+ MIN_SCALE: 0.1
+ MAX_SCALE: 2.0
+ FORMAT: "RGB"
+ DATASET_MAPPER_NAME: "coco_unified_lsj"
+ MAX_SEQ_LEN: 77
+ TASK_SEQ_LEN: 77
+ TASK_PROB:
+ SEMANTIC: 0.33
+ INSTANCE: 0.66
+TEST:
+ EVAL_PERIOD: 5000
+DATALOADER:
+ FILTER_EMPTY_ANNOTATIONS: True
+ NUM_WORKERS: 4
+VERSION: 2
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/oneformer/configs/coco/oneformer_R50_bs16_50ep.yaml b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/oneformer/configs/coco/oneformer_R50_bs16_50ep.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f768c8fa8b5e4fc1121e65e050053e0d8870cd73
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/oneformer/configs/coco/oneformer_R50_bs16_50ep.yaml
@@ -0,0 +1,59 @@
+_BASE_: Base-COCO-UnifiedSegmentation.yaml
+MODEL:
+ META_ARCHITECTURE: "OneFormer"
+ SEM_SEG_HEAD:
+ NAME: "OneFormerHead"
+ IGNORE_VALUE: 255
+ NUM_CLASSES: 133
+ LOSS_WEIGHT: 1.0
+ CONVS_DIM: 256
+ MASK_DIM: 256
+ NORM: "GN"
+ # pixel decoder
+ PIXEL_DECODER_NAME: "MSDeformAttnPixelDecoder"
+ IN_FEATURES: ["res2", "res3", "res4", "res5"]
+ DEFORMABLE_TRANSFORMER_ENCODER_IN_FEATURES: ["res3", "res4", "res5"]
+ COMMON_STRIDE: 4
+ TRANSFORMER_ENC_LAYERS: 6
+ ONE_FORMER:
+ TRANSFORMER_DECODER_NAME: "ContrastiveMultiScaleMaskedTransformerDecoder"
+ TRANSFORMER_IN_FEATURE: "multi_scale_pixel_decoder"
+ DEEP_SUPERVISION: True
+ NO_OBJECT_WEIGHT: 0.1
+ CLASS_WEIGHT: 2.0
+ MASK_WEIGHT: 5.0
+ DICE_WEIGHT: 5.0
+ CONTRASTIVE_WEIGHT: 0.5
+ CONTRASTIVE_TEMPERATURE: 0.07
+ HIDDEN_DIM: 256
+ NUM_OBJECT_QUERIES: 150
+ USE_TASK_NORM: True
+ NHEADS: 8
+ DROPOUT: 0.1
+ DIM_FEEDFORWARD: 2048
+ ENC_LAYERS: 0
+ PRE_NORM: False
+ ENFORCE_INPUT_PROJ: False
+ SIZE_DIVISIBILITY: 32
+ CLASS_DEC_LAYERS: 2
+ DEC_LAYERS: 10 # 9 decoder layers, add one for the loss on learnable query
+ TRAIN_NUM_POINTS: 12544
+ OVERSAMPLE_RATIO: 3.0
+ IMPORTANCE_SAMPLE_RATIO: 0.75
+ TEXT_ENCODER:
+ WIDTH: 256
+ CONTEXT_LENGTH: 77
+ NUM_LAYERS: 6
+ VOCAB_SIZE: 49408
+ PROJ_NUM_LAYERS: 2
+ N_CTX: 16
+ TEST:
+ SEMANTIC_ON: True
+ INSTANCE_ON: True
+ PANOPTIC_ON: True
+ DETECTION_ON: False
+ OVERLAP_THRESHOLD: 0.8
+ OBJECT_MASK_THRESHOLD: 0.8
+ TASK: "panoptic"
+TEST:
+ DETECTIONS_PER_IMAGE: 150
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/oneformer/configs/coco/oneformer_swin_large_IN21k_384_bs16_100ep.yaml b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/oneformer/configs/coco/oneformer_swin_large_IN21k_384_bs16_100ep.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..faae655317c52d90b9f756417f8b1a1adcbe78f2
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/oneformer/configs/coco/oneformer_swin_large_IN21k_384_bs16_100ep.yaml
@@ -0,0 +1,25 @@
+_BASE_: oneformer_R50_bs16_50ep.yaml
+MODEL:
+ BACKBONE:
+ NAME: "D2SwinTransformer"
+ SWIN:
+ EMBED_DIM: 192
+ DEPTHS: [2, 2, 18, 2]
+ NUM_HEADS: [6, 12, 24, 48]
+ WINDOW_SIZE: 12
+ APE: False
+ DROP_PATH_RATE: 0.3
+ PATCH_NORM: True
+ PRETRAIN_IMG_SIZE: 384
+ WEIGHTS: "swin_large_patch4_window12_384_22k.pkl"
+ PIXEL_MEAN: [123.675, 116.280, 103.530]
+ PIXEL_STD: [58.395, 57.120, 57.375]
+ ONE_FORMER:
+ NUM_OBJECT_QUERIES: 150
+SOLVER:
+ STEPS: (655556, 735184)
+ MAX_ITER: 737500
+ AMP:
+ ENABLED: False
+TEST:
+ DETECTIONS_PER_IMAGE: 150
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/LICENSE b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..6f60b76d35fa1012809985780964a5068adce4fd
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/LICENSE
@@ -0,0 +1,108 @@
+OPENPOSE: MULTIPERSON KEYPOINT DETECTION
+SOFTWARE LICENSE AGREEMENT
+ACADEMIC OR NON-PROFIT ORGANIZATION NONCOMMERCIAL RESEARCH USE ONLY
+
+BY USING OR DOWNLOADING THE SOFTWARE, YOU ARE AGREEING TO THE TERMS OF THIS LICENSE AGREEMENT. IF YOU DO NOT AGREE WITH THESE TERMS, YOU MAY NOT USE OR DOWNLOAD THE SOFTWARE.
+
+This is a license agreement ("Agreement") between your academic institution or non-profit organization or self (called "Licensee" or "You" in this Agreement) and Carnegie Mellon University (called "Licensor" in this Agreement). All rights not specifically granted to you in this Agreement are reserved for Licensor.
+
+RESERVATION OF OWNERSHIP AND GRANT OF LICENSE:
+Licensor retains exclusive ownership of any copy of the Software (as defined below) licensed under this Agreement and hereby grants to Licensee a personal, non-exclusive,
+non-transferable license to use the Software for noncommercial research purposes, without the right to sublicense, pursuant to the terms and conditions of this Agreement. As used in this Agreement, the term "Software" means (i) the actual copy of all or any portion of code for program routines made accessible to Licensee by Licensor pursuant to this Agreement, inclusive of backups, updates, and/or merged copies permitted hereunder or subsequently supplied by Licensor, including all or any file structures, programming instructions, user interfaces and screen formats and sequences as well as any and all documentation and instructions related to it, and (ii) all or any derivatives and/or modifications created or made by You to any of the items specified in (i).
+
+CONFIDENTIALITY: Licensee acknowledges that the Software is proprietary to Licensor, and as such, Licensee agrees to receive all such materials in confidence and use the Software only in accordance with the terms of this Agreement. Licensee agrees to use reasonable effort to protect the Software from unauthorized use, reproduction, distribution, or publication.
+
+COPYRIGHT: The Software is owned by Licensor and is protected by United
+States copyright laws and applicable international treaties and/or conventions.
+
+PERMITTED USES: The Software may be used for your own noncommercial internal research purposes. You understand and agree that Licensor is not obligated to implement any suggestions and/or feedback you might provide regarding the Software, but to the extent Licensor does so, you are not entitled to any compensation related thereto.
+
+DERIVATIVES: You may create derivatives of or make modifications to the Software, however, You agree that all and any such derivatives and modifications will be owned by Licensor and become a part of the Software licensed to You under this Agreement. You may only use such derivatives and modifications for your own noncommercial internal research purposes, and you may not otherwise use, distribute or copy such derivatives and modifications in violation of this Agreement.
+
+BACKUPS: If Licensee is an organization, it may make that number of copies of the Software necessary for internal noncommercial use at a single site within its organization provided that all information appearing in or on the original labels, including the copyright and trademark notices are copied onto the labels of the copies.
+
+USES NOT PERMITTED: You may not distribute, copy or use the Software except as explicitly permitted herein. Licensee has not been granted any trademark license as part of this Agreement and may not use the name or mark “OpenPose", "Carnegie Mellon" or any renditions thereof without the prior written permission of Licensor.
+
+You may not sell, rent, lease, sublicense, lend, time-share or transfer, in whole or in part, or provide third parties access to prior or present versions (or any parts thereof) of the Software.
+
+ASSIGNMENT: You may not assign this Agreement or your rights hereunder without the prior written consent of Licensor. Any attempted assignment without such consent shall be null and void.
+
+TERM: The term of the license granted by this Agreement is from Licensee's acceptance of this Agreement by downloading the Software or by using the Software until terminated as provided below.
+
+The Agreement automatically terminates without notice if you fail to comply with any provision of this Agreement. Licensee may terminate this Agreement by ceasing using the Software. Upon any termination of this Agreement, Licensee will delete any and all copies of the Software. You agree that all provisions which operate to protect the proprietary rights of Licensor shall remain in force should breach occur and that the obligation of confidentiality described in this Agreement is binding in perpetuity and, as such, survives the term of the Agreement.
+
+FEE: Provided Licensee abides completely by the terms and conditions of this Agreement, there is no fee due to Licensor for Licensee's use of the Software in accordance with this Agreement.
+
+DISCLAIMER OF WARRANTIES: THE SOFTWARE IS PROVIDED "AS-IS" WITHOUT WARRANTY OF ANY KIND INCLUDING ANY WARRANTIES OF PERFORMANCE OR MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE OR PURPOSE OR OF NON-INFRINGEMENT. LICENSEE BEARS ALL RISK RELATING TO QUALITY AND PERFORMANCE OF THE SOFTWARE AND RELATED MATERIALS.
+
+SUPPORT AND MAINTENANCE: No Software support or training by the Licensor is provided as part of this Agreement.
+
+EXCLUSIVE REMEDY AND LIMITATION OF LIABILITY: To the maximum extent permitted under applicable law, Licensor shall not be liable for direct, indirect, special, incidental, or consequential damages or lost profits related to Licensee's use of and/or inability to use the Software, even if Licensor is advised of the possibility of such damage.
+
+EXPORT REGULATION: Licensee agrees to comply with any and all applicable
+U.S. export control laws, regulations, and/or other laws related to embargoes and sanction programs administered by the Office of Foreign Assets Control.
+
+SEVERABILITY: If any provision(s) of this Agreement shall be held to be invalid, illegal, or unenforceable by a court or other tribunal of competent jurisdiction, the validity, legality and enforceability of the remaining provisions shall not in any way be affected or impaired thereby.
+
+NO IMPLIED WAIVERS: No failure or delay by Licensor in enforcing any right or remedy under this Agreement shall be construed as a waiver of any future or other exercise of such right or remedy by Licensor.
+
+GOVERNING LAW: This Agreement shall be construed and enforced in accordance with the laws of the Commonwealth of Pennsylvania without reference to conflict of laws principles. You consent to the personal jurisdiction of the courts of this County and waive their rights to venue outside of Allegheny County, Pennsylvania.
+
+ENTIRE AGREEMENT AND AMENDMENTS: This Agreement constitutes the sole and entire agreement between Licensee and Licensor as to the matter set forth herein and supersedes any previous agreements, understandings, and arrangements between the parties relating hereto.
+
+
+
+************************************************************************
+
+THIRD-PARTY SOFTWARE NOTICES AND INFORMATION
+
+This project incorporates material from the project(s) listed below (collectively, "Third Party Code"). This Third Party Code is licensed to you under their original license terms set forth below. We reserves all other rights not expressly granted, whether by implication, estoppel or otherwise.
+
+1. Caffe, version 1.0.0, (https://github.com/BVLC/caffe/)
+
+COPYRIGHT
+
+All contributions by the University of California:
+Copyright (c) 2014-2017 The Regents of the University of California (Regents)
+All rights reserved.
+
+All other contributions:
+Copyright (c) 2014-2017, the respective contributors
+All rights reserved.
+
+Caffe uses a shared copyright model: each contributor holds copyright over
+their contributions to Caffe. The project versioning records all such
+contribution and copyright details. If a contributor wants to further mark
+their specific copyright on a particular contribution, they should indicate
+their copyright solely in the commit message of the change when it is
+committed.
+
+LICENSE
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+CONTRIBUTION AGREEMENT
+
+By contributing to the BVLC/caffe repository through pull-request, comment,
+or otherwise, the contributor releases their content to the
+license and copyright terms herein.
+
+************END OF THIRD-PARTY SOFTWARE NOTICES AND INFORMATION**********
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..029794f1dd345effce074bfba6ec3684baa9a912
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/__init__.py
@@ -0,0 +1,245 @@
+# Openpose
+# Original from CMU https://github.com/CMU-Perceptual-Computing-Lab/openpose
+# 2nd Edited by https://github.com/Hzzone/pytorch-openpose
+# 3rd Edited by ControlNet
+# 4th Edited by ControlNet (added face and correct hands)
+# 5th Edited by ControlNet (Improved JSON serialization/deserialization, and lots of bug fixs)
+# This preprocessor is licensed by CMU for non-commercial use only.
+
+
+import os
+
+os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
+
+import json
+import warnings
+from typing import Callable, List, NamedTuple, Tuple, Union
+
+import cv2
+import numpy as np
+import torch
+from huggingface_hub import hf_hub_download
+from PIL import Image
+
+from controlnet_aux.util import HWC3, common_input_validate, resize_image_with_pad, annotator_ckpts_path, custom_hf_download
+from . import util
+from .body import Body, BodyResult, Keypoint
+from .face import Face
+from .hand import Hand
+
+HandResult = List[Keypoint]
+FaceResult = List[Keypoint]
+
+class PoseResult(NamedTuple):
+ body: BodyResult
+ left_hand: Union[HandResult, None]
+ right_hand: Union[HandResult, None]
+ face: Union[FaceResult, None]
+
+def draw_poses(poses: List[PoseResult], H, W, draw_body=True, draw_hand=True, draw_face=True):
+ """
+ Draw the detected poses on an empty canvas.
+
+ Args:
+ poses (List[PoseResult]): A list of PoseResult objects containing the detected poses.
+ H (int): The height of the canvas.
+ W (int): The width of the canvas.
+ draw_body (bool, optional): Whether to draw body keypoints. Defaults to True.
+ draw_hand (bool, optional): Whether to draw hand keypoints. Defaults to True.
+ draw_face (bool, optional): Whether to draw face keypoints. Defaults to True.
+
+ Returns:
+ numpy.ndarray: A 3D numpy array representing the canvas with the drawn poses.
+ """
+ canvas = np.zeros(shape=(H, W, 3), dtype=np.uint8)
+
+ for pose in poses:
+ if draw_body:
+ canvas = util.draw_bodypose(canvas, pose.body.keypoints)
+
+ if draw_hand:
+ canvas = util.draw_handpose(canvas, pose.left_hand)
+ canvas = util.draw_handpose(canvas, pose.right_hand)
+
+ if draw_face:
+ canvas = util.draw_facepose(canvas, pose.face)
+
+ return canvas
+
+def encode_poses_as_json(poses: List[PoseResult], canvas_height: int, canvas_width: int) -> str:
+ """ Encode the pose as a JSON string following openpose JSON output format:
+ https://github.com/CMU-Perceptual-Computing-Lab/openpose/blob/master/doc/02_output.md
+ """
+ def compress_keypoints(keypoints: Union[List[Keypoint], None]) -> Union[List[float], None]:
+ if not keypoints:
+ return None
+
+ return [
+ value
+ for keypoint in keypoints
+ for value in (
+ [float(keypoint.x), float(keypoint.y), 1.0]
+ if keypoint is not None
+ else [0.0, 0.0, 0.0]
+ )
+ ]
+
+ return json.dumps({
+ 'people': [
+ {
+ 'pose_keypoints_2d': compress_keypoints(pose.body.keypoints),
+ "face_keypoints_2d": compress_keypoints(pose.face),
+ "hand_left_keypoints_2d": compress_keypoints(pose.left_hand),
+ "hand_right_keypoints_2d":compress_keypoints(pose.right_hand),
+ }
+ for pose in poses
+ ],
+ 'canvas_height': canvas_height,
+ 'canvas_width': canvas_width,
+ }, indent=4)
+
+class OpenposeDetector:
+ """
+ A class for detecting human poses in images using the Openpose model.
+
+ Attributes:
+ model_dir (str): Path to the directory where the pose models are stored.
+ """
+ def __init__(self, body_estimation, hand_estimation=None, face_estimation=None):
+ self.body_estimation = body_estimation
+ self.hand_estimation = hand_estimation
+ self.face_estimation = face_estimation
+
+ @classmethod
+ def from_pretrained(cls, pretrained_model_or_path, filename=None, hand_filename=None, face_filename=None, cache_dir=annotator_ckpts_path):
+ filename = filename or "body_pose_model.pth"
+ hand_filename = hand_filename or "hand_pose_model.pth"
+ face_filename = face_filename or "facenet.pth"
+
+ if pretrained_model_or_path == "lllyasviel/ControlNet":
+ subfolder = "annotator/ckpts"
+ face_pretrained_model_or_path = "lllyasviel/Annotators"
+
+ else:
+ subfolder = ''
+ face_pretrained_model_or_path = pretrained_model_or_path
+
+ body_model_path = custom_hf_download(pretrained_model_or_path, filename, cache_dir=cache_dir, subfolder=subfolder)
+ hand_model_path = custom_hf_download(pretrained_model_or_path, hand_filename, cache_dir=cache_dir, subfolder=subfolder)
+ face_model_path = custom_hf_download(face_pretrained_model_or_path, face_filename, cache_dir=cache_dir, subfolder=subfolder)
+
+ body_estimation = Body(body_model_path)
+ hand_estimation = Hand(hand_model_path)
+ face_estimation = Face(face_model_path)
+
+ return cls(body_estimation, hand_estimation, face_estimation)
+
+ def to(self, device):
+ self.body_estimation.to(device)
+ self.hand_estimation.to(device)
+ self.face_estimation.to(device)
+ return self
+
+ def detect_hands(self, body: BodyResult, oriImg) -> Tuple[Union[HandResult, None], Union[HandResult, None]]:
+ left_hand = None
+ right_hand = None
+ H, W, _ = oriImg.shape
+ for x, y, w, is_left in util.handDetect(body, oriImg):
+ peaks = self.hand_estimation(oriImg[y:y+w, x:x+w, :]).astype(np.float32)
+ if peaks.ndim == 2 and peaks.shape[1] == 2:
+ peaks[:, 0] = np.where(peaks[:, 0] < 1e-6, -1, peaks[:, 0] + x) / float(W)
+ peaks[:, 1] = np.where(peaks[:, 1] < 1e-6, -1, peaks[:, 1] + y) / float(H)
+
+ hand_result = [
+ Keypoint(x=peak[0], y=peak[1])
+ for peak in peaks
+ ]
+
+ if is_left:
+ left_hand = hand_result
+ else:
+ right_hand = hand_result
+
+ return left_hand, right_hand
+
+ def detect_face(self, body: BodyResult, oriImg) -> Union[FaceResult, None]:
+ face = util.faceDetect(body, oriImg)
+ if face is None:
+ return None
+
+ x, y, w = face
+ H, W, _ = oriImg.shape
+ heatmaps = self.face_estimation(oriImg[y:y+w, x:x+w, :])
+ peaks = self.face_estimation.compute_peaks_from_heatmaps(heatmaps).astype(np.float32)
+ if peaks.ndim == 2 and peaks.shape[1] == 2:
+ peaks[:, 0] = np.where(peaks[:, 0] < 1e-6, -1, peaks[:, 0] + x) / float(W)
+ peaks[:, 1] = np.where(peaks[:, 1] < 1e-6, -1, peaks[:, 1] + y) / float(H)
+ return [
+ Keypoint(x=peak[0], y=peak[1])
+ for peak in peaks
+ ]
+
+ return None
+
+ def detect_poses(self, oriImg, include_hand=False, include_face=False) -> List[PoseResult]:
+ """
+ Detect poses in the given image.
+ Args:
+ oriImg (numpy.ndarray): The input image for pose detection.
+ include_hand (bool, optional): Whether to include hand detection. Defaults to False.
+ include_face (bool, optional): Whether to include face detection. Defaults to False.
+
+ Returns:
+ List[PoseResult]: A list of PoseResult objects containing the detected poses.
+ """
+ oriImg = oriImg[:, :, ::-1].copy()
+ H, W, C = oriImg.shape
+ with torch.no_grad():
+ candidate, subset = self.body_estimation(oriImg)
+ bodies = self.body_estimation.format_body_result(candidate, subset)
+
+ results = []
+ for body in bodies:
+ left_hand, right_hand, face = (None,) * 3
+ if include_hand:
+ left_hand, right_hand = self.detect_hands(body, oriImg)
+ if include_face:
+ face = self.detect_face(body, oriImg)
+
+ results.append(PoseResult(BodyResult(
+ keypoints=[
+ Keypoint(
+ x=keypoint.x / float(W),
+ y=keypoint.y / float(H)
+ ) if keypoint is not None else None
+ for keypoint in body.keypoints
+ ],
+ total_score=body.total_score,
+ total_parts=body.total_parts
+ ), left_hand, right_hand, face))
+
+ return results
+
+ def __call__(self, input_image, detect_resolution=512, include_body=True, include_hand=False, include_face=False, hand_and_face=None, output_type="pil", image_and_json=False, upscale_method="INTER_CUBIC", **kwargs):
+ if hand_and_face is not None:
+ warnings.warn("hand_and_face is deprecated. Use include_hand and include_face instead.", DeprecationWarning)
+ include_hand = hand_and_face
+ include_face = hand_and_face
+
+ input_image, output_type = common_input_validate(input_image, output_type, **kwargs)
+
+ detected_map, remove_pad = resize_image_with_pad(input_image, detect_resolution, upscale_method)
+
+ poses = self.detect_poses(detected_map, include_hand=include_hand, include_face=include_face)
+ detected_map = remove_pad(detected_map)
+ canvas = draw_poses(poses, detected_map.shape[0], detected_map.shape[1], draw_body=include_body, draw_hand=include_hand, draw_face=include_face)
+
+ detected_map = HWC3(canvas)
+
+ if output_type == "pil":
+ detected_map = Image.fromarray(detected_map)
+
+ if image_and_json:
+ return (detected_map, encode_poses_as_json(poses, detected_map.shape[0], detected_map.shape[1]))
+
+ return detected_map
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/__pycache__/__init__.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c0cf82283c3e6a78bf0ccc915a2dc0f8b6c4cda6
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/__pycache__/__init__.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/__pycache__/body.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/__pycache__/body.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b7d6bdb4b1b7eab68636fb0478dd2826a042b017
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/__pycache__/body.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/__pycache__/face.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/__pycache__/face.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..dedc981ae268292ea978e70cf9f79dfb3b0597b0
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/__pycache__/face.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/__pycache__/hand.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/__pycache__/hand.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..692b5113eecac65495ccaea537b9169d3731639f
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/__pycache__/hand.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/__pycache__/model.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/__pycache__/model.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0aed4d41af6c7995079794dba4d4c8006dfff20d
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/__pycache__/model.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/__pycache__/util.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/__pycache__/util.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..3af24dc5a5a551643a44bfabe66ee57f787e29a2
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/__pycache__/util.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/body.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/body.py
new file mode 100644
index 0000000000000000000000000000000000000000..96f9299ad4aa735988668dc5b720ac5a08972848
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/body.py
@@ -0,0 +1,277 @@
+import math
+from typing import List, NamedTuple, Union
+
+import cv2
+import matplotlib.pyplot as plt
+import numpy as np
+import torch
+from scipy.ndimage.filters import gaussian_filter
+
+from . import util
+from .model import bodypose_model
+
+
+class Keypoint(NamedTuple):
+ x: float
+ y: float
+ score: float = 1.0
+ id: int = -1
+
+
+class BodyResult(NamedTuple):
+ # Note: Using `Union` instead of `|` operator as the ladder is a Python
+ # 3.10 feature.
+ # Annotator code should be Python 3.8 Compatible, as controlnet repo uses
+ # Python 3.8 environment.
+ # https://github.com/lllyasviel/ControlNet/blob/d3284fcd0972c510635a4f5abe2eeb71dc0de524/environment.yaml#L6
+ keypoints: List[Union[Keypoint, None]]
+ total_score: float
+ total_parts: int
+
+
+class Body(object):
+ def __init__(self, model_path):
+ self.model = bodypose_model()
+ model_dict = util.transfer(self.model, torch.load(model_path))
+ self.model.load_state_dict(model_dict)
+ self.model.eval()
+
+ def to(self, device):
+ self.model.to(device)
+ return self
+
+ def __call__(self, oriImg):
+ device = next(iter(self.model.parameters())).device
+ # scale_search = [0.5, 1.0, 1.5, 2.0]
+ scale_search = [0.5]
+ boxsize = 368
+ stride = 8
+ padValue = 128
+ thre1 = 0.1
+ thre2 = 0.05
+ multiplier = [x * boxsize / oriImg.shape[0] for x in scale_search]
+ heatmap_avg = np.zeros((oriImg.shape[0], oriImg.shape[1], 19))
+ paf_avg = np.zeros((oriImg.shape[0], oriImg.shape[1], 38))
+
+ for m in range(len(multiplier)):
+ scale = multiplier[m]
+ imageToTest = util.smart_resize_k(oriImg, fx=scale, fy=scale)
+ imageToTest_padded, pad = util.padRightDownCorner(imageToTest, stride, padValue)
+ im = np.transpose(np.float32(imageToTest_padded[:, :, :, np.newaxis]), (3, 2, 0, 1)) / 256 - 0.5
+ im = np.ascontiguousarray(im)
+
+ data = torch.from_numpy(im).float()
+ data = data.to(device)
+ # data = data.permute([2, 0, 1]).unsqueeze(0).float()
+ with torch.no_grad():
+ Mconv7_stage6_L1, Mconv7_stage6_L2 = self.model(data)
+ Mconv7_stage6_L1 = Mconv7_stage6_L1.cpu().numpy()
+ Mconv7_stage6_L2 = Mconv7_stage6_L2.cpu().numpy()
+
+ # extract outputs, resize, and remove padding
+ # heatmap = np.transpose(np.squeeze(net.blobs[output_blobs.keys()[1]].data), (1, 2, 0)) # output 1 is heatmaps
+ heatmap = np.transpose(np.squeeze(Mconv7_stage6_L2), (1, 2, 0)) # output 1 is heatmaps
+ heatmap = util.smart_resize_k(heatmap, fx=stride, fy=stride)
+ heatmap = heatmap[:imageToTest_padded.shape[0] - pad[2], :imageToTest_padded.shape[1] - pad[3], :]
+ heatmap = util.smart_resize(heatmap, (oriImg.shape[0], oriImg.shape[1]))
+
+ # paf = np.transpose(np.squeeze(net.blobs[output_blobs.keys()[0]].data), (1, 2, 0)) # output 0 is PAFs
+ paf = np.transpose(np.squeeze(Mconv7_stage6_L1), (1, 2, 0)) # output 0 is PAFs
+ paf = util.smart_resize_k(paf, fx=stride, fy=stride)
+ paf = paf[:imageToTest_padded.shape[0] - pad[2], :imageToTest_padded.shape[1] - pad[3], :]
+ paf = util.smart_resize(paf, (oriImg.shape[0], oriImg.shape[1]))
+
+ heatmap_avg += heatmap_avg + heatmap / len(multiplier)
+ paf_avg += + paf / len(multiplier)
+
+ all_peaks = []
+ peak_counter = 0
+
+ for part in range(18):
+ map_ori = heatmap_avg[:, :, part]
+ one_heatmap = gaussian_filter(map_ori, sigma=3)
+
+ map_left = np.zeros(one_heatmap.shape)
+ map_left[1:, :] = one_heatmap[:-1, :]
+ map_right = np.zeros(one_heatmap.shape)
+ map_right[:-1, :] = one_heatmap[1:, :]
+ map_up = np.zeros(one_heatmap.shape)
+ map_up[:, 1:] = one_heatmap[:, :-1]
+ map_down = np.zeros(one_heatmap.shape)
+ map_down[:, :-1] = one_heatmap[:, 1:]
+
+ peaks_binary = np.logical_and.reduce(
+ (one_heatmap >= map_left, one_heatmap >= map_right, one_heatmap >= map_up, one_heatmap >= map_down, one_heatmap > thre1))
+ peaks = list(zip(np.nonzero(peaks_binary)[1], np.nonzero(peaks_binary)[0])) # note reverse
+ peaks_with_score = [x + (map_ori[x[1], x[0]],) for x in peaks]
+ peak_id = range(peak_counter, peak_counter + len(peaks))
+ peaks_with_score_and_id = [peaks_with_score[i] + (peak_id[i],) for i in range(len(peak_id))]
+
+ all_peaks.append(peaks_with_score_and_id)
+ peak_counter += len(peaks)
+
+ # find connection in the specified sequence, center 29 is in the position 15
+ limbSeq = [[2, 3], [2, 6], [3, 4], [4, 5], [6, 7], [7, 8], [2, 9], [9, 10], \
+ [10, 11], [2, 12], [12, 13], [13, 14], [2, 1], [1, 15], [15, 17], \
+ [1, 16], [16, 18], [3, 17], [6, 18]]
+ # the middle joints heatmap correpondence
+ mapIdx = [[31, 32], [39, 40], [33, 34], [35, 36], [41, 42], [43, 44], [19, 20], [21, 22], \
+ [23, 24], [25, 26], [27, 28], [29, 30], [47, 48], [49, 50], [53, 54], [51, 52], \
+ [55, 56], [37, 38], [45, 46]]
+
+ connection_all = []
+ special_k = []
+ mid_num = 10
+
+ for k in range(len(mapIdx)):
+ score_mid = paf_avg[:, :, [x - 19 for x in mapIdx[k]]]
+ candA = all_peaks[limbSeq[k][0] - 1]
+ candB = all_peaks[limbSeq[k][1] - 1]
+ nA = len(candA)
+ nB = len(candB)
+ indexA, indexB = limbSeq[k]
+ if (nA != 0 and nB != 0):
+ connection_candidate = []
+ for i in range(nA):
+ for j in range(nB):
+ vec = np.subtract(candB[j][:2], candA[i][:2])
+ norm = math.sqrt(vec[0] * vec[0] + vec[1] * vec[1])
+ norm = max(0.001, norm)
+ vec = np.divide(vec, norm)
+
+ startend = list(zip(np.linspace(candA[i][0], candB[j][0], num=mid_num), \
+ np.linspace(candA[i][1], candB[j][1], num=mid_num)))
+
+ vec_x = np.array([score_mid[int(round(startend[I][1])), int(round(startend[I][0])), 0] \
+ for I in range(len(startend))])
+ vec_y = np.array([score_mid[int(round(startend[I][1])), int(round(startend[I][0])), 1] \
+ for I in range(len(startend))])
+
+ score_midpts = np.multiply(vec_x, vec[0]) + np.multiply(vec_y, vec[1])
+ score_with_dist_prior = sum(score_midpts) / len(score_midpts) + min(
+ 0.5 * oriImg.shape[0] / norm - 1, 0)
+ criterion1 = len(np.nonzero(score_midpts > thre2)[0]) > 0.8 * len(score_midpts)
+ criterion2 = score_with_dist_prior > 0
+ if criterion1 and criterion2:
+ connection_candidate.append(
+ [i, j, score_with_dist_prior, score_with_dist_prior + candA[i][2] + candB[j][2]])
+
+ connection_candidate = sorted(connection_candidate, key=lambda x: x[2], reverse=True)
+ connection = np.zeros((0, 5))
+ for c in range(len(connection_candidate)):
+ i, j, s = connection_candidate[c][0:3]
+ if (i not in connection[:, 3] and j not in connection[:, 4]):
+ connection = np.vstack([connection, [candA[i][3], candB[j][3], s, i, j]])
+ if (len(connection) >= min(nA, nB)):
+ break
+
+ connection_all.append(connection)
+ else:
+ special_k.append(k)
+ connection_all.append([])
+
+ # last number in each row is the total parts number of that person
+ # the second last number in each row is the score of the overall configuration
+ subset = -1 * np.ones((0, 20))
+ candidate = np.array([item for sublist in all_peaks for item in sublist])
+
+ for k in range(len(mapIdx)):
+ if k not in special_k:
+ partAs = connection_all[k][:, 0]
+ partBs = connection_all[k][:, 1]
+ indexA, indexB = np.array(limbSeq[k]) - 1
+
+ for i in range(len(connection_all[k])): # = 1:size(temp,1)
+ found = 0
+ subset_idx = [-1, -1]
+ for j in range(len(subset)): # 1:size(subset,1):
+ if subset[j][indexA] == partAs[i] or subset[j][indexB] == partBs[i]:
+ subset_idx[found] = j
+ found += 1
+
+ if found == 1:
+ j = subset_idx[0]
+ if subset[j][indexB] != partBs[i]:
+ subset[j][indexB] = partBs[i]
+ subset[j][-1] += 1
+ subset[j][-2] += candidate[partBs[i].astype(int), 2] + connection_all[k][i][2]
+ elif found == 2: # if found 2 and disjoint, merge them
+ j1, j2 = subset_idx
+ membership = ((subset[j1] >= 0).astype(int) + (subset[j2] >= 0).astype(int))[:-2]
+ if len(np.nonzero(membership == 2)[0]) == 0: # merge
+ subset[j1][:-2] += (subset[j2][:-2] + 1)
+ subset[j1][-2:] += subset[j2][-2:]
+ subset[j1][-2] += connection_all[k][i][2]
+ subset = np.delete(subset, j2, 0)
+ else: # as like found == 1
+ subset[j1][indexB] = partBs[i]
+ subset[j1][-1] += 1
+ subset[j1][-2] += candidate[partBs[i].astype(int), 2] + connection_all[k][i][2]
+
+ # if find no partA in the subset, create a new subset
+ elif not found and k < 17:
+ row = -1 * np.ones(20)
+ row[indexA] = partAs[i]
+ row[indexB] = partBs[i]
+ row[-1] = 2
+ row[-2] = sum(candidate[connection_all[k][i, :2].astype(int), 2]) + connection_all[k][i][2]
+ subset = np.vstack([subset, row])
+ # delete some rows of subset which has few parts occur
+ deleteIdx = []
+ for i in range(len(subset)):
+ if subset[i][-1] < 4 or subset[i][-2] / subset[i][-1] < 0.4:
+ deleteIdx.append(i)
+ subset = np.delete(subset, deleteIdx, axis=0)
+
+ # subset: n*20 array, 0-17 is the index in candidate, 18 is the total score, 19 is the total parts
+ # candidate: x, y, score, id
+ return candidate, subset
+
+ @staticmethod
+ def format_body_result(candidate: np.ndarray, subset: np.ndarray) -> List[BodyResult]:
+ """
+ Format the body results from the candidate and subset arrays into a list of BodyResult objects.
+
+ Args:
+ candidate (np.ndarray): An array of candidates containing the x, y coordinates, score, and id
+ for each body part.
+ subset (np.ndarray): An array of subsets containing indices to the candidate array for each
+ person detected. The last two columns of each row hold the total score and total parts
+ of the person.
+
+ Returns:
+ List[BodyResult]: A list of BodyResult objects, where each object represents a person with
+ detected keypoints, total score, and total parts.
+ """
+ return [
+ BodyResult(
+ keypoints=[
+ Keypoint(
+ x=candidate[candidate_index][0],
+ y=candidate[candidate_index][1],
+ score=candidate[candidate_index][2],
+ id=candidate[candidate_index][3]
+ ) if candidate_index != -1 else None
+ for candidate_index in person[:18].astype(int)
+ ],
+ total_score=person[18],
+ total_parts=person[19]
+ )
+ for person in subset
+ ]
+
+
+if __name__ == "__main__":
+ body_estimation = Body('../model/body_pose_model.pth')
+
+ test_image = '../images/ski.jpg'
+ oriImg = cv2.imread(test_image) # B,G,R order
+ candidate, subset = body_estimation(oriImg)
+ bodies = body_estimation.format_body_result(candidate, subset)
+
+ canvas = oriImg
+ for body in bodies:
+ canvas = util.draw_bodypose(canvas, body)
+
+ plt.imshow(canvas[:, :, [2, 1, 0]])
+ plt.show()
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/face.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/face.py
new file mode 100644
index 0000000000000000000000000000000000000000..41c7799af10b1f834369464862d41d8f967128c6
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/face.py
@@ -0,0 +1,364 @@
+import logging
+
+import numpy as np
+import torch
+import torch.nn.functional as F
+from torch.nn import Conv2d, MaxPool2d, Module, ReLU, init
+from torchvision.transforms import ToPILImage, ToTensor
+
+from . import util
+
+
+class FaceNet(Module):
+ """Model the cascading heatmaps. """
+ def __init__(self):
+ super(FaceNet, self).__init__()
+ # cnn to make feature map
+ self.relu = ReLU()
+ self.max_pooling_2d = MaxPool2d(kernel_size=2, stride=2)
+ self.conv1_1 = Conv2d(in_channels=3, out_channels=64,
+ kernel_size=3, stride=1, padding=1)
+ self.conv1_2 = Conv2d(
+ in_channels=64, out_channels=64, kernel_size=3, stride=1,
+ padding=1)
+ self.conv2_1 = Conv2d(
+ in_channels=64, out_channels=128, kernel_size=3, stride=1,
+ padding=1)
+ self.conv2_2 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=3, stride=1,
+ padding=1)
+ self.conv3_1 = Conv2d(
+ in_channels=128, out_channels=256, kernel_size=3, stride=1,
+ padding=1)
+ self.conv3_2 = Conv2d(
+ in_channels=256, out_channels=256, kernel_size=3, stride=1,
+ padding=1)
+ self.conv3_3 = Conv2d(
+ in_channels=256, out_channels=256, kernel_size=3, stride=1,
+ padding=1)
+ self.conv3_4 = Conv2d(
+ in_channels=256, out_channels=256, kernel_size=3, stride=1,
+ padding=1)
+ self.conv4_1 = Conv2d(
+ in_channels=256, out_channels=512, kernel_size=3, stride=1,
+ padding=1)
+ self.conv4_2 = Conv2d(
+ in_channels=512, out_channels=512, kernel_size=3, stride=1,
+ padding=1)
+ self.conv4_3 = Conv2d(
+ in_channels=512, out_channels=512, kernel_size=3, stride=1,
+ padding=1)
+ self.conv4_4 = Conv2d(
+ in_channels=512, out_channels=512, kernel_size=3, stride=1,
+ padding=1)
+ self.conv5_1 = Conv2d(
+ in_channels=512, out_channels=512, kernel_size=3, stride=1,
+ padding=1)
+ self.conv5_2 = Conv2d(
+ in_channels=512, out_channels=512, kernel_size=3, stride=1,
+ padding=1)
+ self.conv5_3_CPM = Conv2d(
+ in_channels=512, out_channels=128, kernel_size=3, stride=1,
+ padding=1)
+
+ # stage1
+ self.conv6_1_CPM = Conv2d(
+ in_channels=128, out_channels=512, kernel_size=1, stride=1,
+ padding=0)
+ self.conv6_2_CPM = Conv2d(
+ in_channels=512, out_channels=71, kernel_size=1, stride=1,
+ padding=0)
+
+ # stage2
+ self.Mconv1_stage2 = Conv2d(
+ in_channels=199, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv2_stage2 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv3_stage2 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv4_stage2 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv5_stage2 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv6_stage2 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=1, stride=1,
+ padding=0)
+ self.Mconv7_stage2 = Conv2d(
+ in_channels=128, out_channels=71, kernel_size=1, stride=1,
+ padding=0)
+
+ # stage3
+ self.Mconv1_stage3 = Conv2d(
+ in_channels=199, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv2_stage3 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv3_stage3 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv4_stage3 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv5_stage3 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv6_stage3 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=1, stride=1,
+ padding=0)
+ self.Mconv7_stage3 = Conv2d(
+ in_channels=128, out_channels=71, kernel_size=1, stride=1,
+ padding=0)
+
+ # stage4
+ self.Mconv1_stage4 = Conv2d(
+ in_channels=199, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv2_stage4 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv3_stage4 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv4_stage4 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv5_stage4 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv6_stage4 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=1, stride=1,
+ padding=0)
+ self.Mconv7_stage4 = Conv2d(
+ in_channels=128, out_channels=71, kernel_size=1, stride=1,
+ padding=0)
+
+ # stage5
+ self.Mconv1_stage5 = Conv2d(
+ in_channels=199, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv2_stage5 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv3_stage5 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv4_stage5 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv5_stage5 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv6_stage5 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=1, stride=1,
+ padding=0)
+ self.Mconv7_stage5 = Conv2d(
+ in_channels=128, out_channels=71, kernel_size=1, stride=1,
+ padding=0)
+
+ # stage6
+ self.Mconv1_stage6 = Conv2d(
+ in_channels=199, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv2_stage6 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv3_stage6 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv4_stage6 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv5_stage6 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=7, stride=1,
+ padding=3)
+ self.Mconv6_stage6 = Conv2d(
+ in_channels=128, out_channels=128, kernel_size=1, stride=1,
+ padding=0)
+ self.Mconv7_stage6 = Conv2d(
+ in_channels=128, out_channels=71, kernel_size=1, stride=1,
+ padding=0)
+
+ for m in self.modules():
+ if isinstance(m, Conv2d):
+ init.constant_(m.bias, 0)
+
+ def forward(self, x):
+ """Return a list of heatmaps."""
+ heatmaps = []
+
+ h = self.relu(self.conv1_1(x))
+ h = self.relu(self.conv1_2(h))
+ h = self.max_pooling_2d(h)
+ h = self.relu(self.conv2_1(h))
+ h = self.relu(self.conv2_2(h))
+ h = self.max_pooling_2d(h)
+ h = self.relu(self.conv3_1(h))
+ h = self.relu(self.conv3_2(h))
+ h = self.relu(self.conv3_3(h))
+ h = self.relu(self.conv3_4(h))
+ h = self.max_pooling_2d(h)
+ h = self.relu(self.conv4_1(h))
+ h = self.relu(self.conv4_2(h))
+ h = self.relu(self.conv4_3(h))
+ h = self.relu(self.conv4_4(h))
+ h = self.relu(self.conv5_1(h))
+ h = self.relu(self.conv5_2(h))
+ h = self.relu(self.conv5_3_CPM(h))
+ feature_map = h
+
+ # stage1
+ h = self.relu(self.conv6_1_CPM(h))
+ h = self.conv6_2_CPM(h)
+ heatmaps.append(h)
+
+ # stage2
+ h = torch.cat([h, feature_map], dim=1) # channel concat
+ h = self.relu(self.Mconv1_stage2(h))
+ h = self.relu(self.Mconv2_stage2(h))
+ h = self.relu(self.Mconv3_stage2(h))
+ h = self.relu(self.Mconv4_stage2(h))
+ h = self.relu(self.Mconv5_stage2(h))
+ h = self.relu(self.Mconv6_stage2(h))
+ h = self.Mconv7_stage2(h)
+ heatmaps.append(h)
+
+ # stage3
+ h = torch.cat([h, feature_map], dim=1) # channel concat
+ h = self.relu(self.Mconv1_stage3(h))
+ h = self.relu(self.Mconv2_stage3(h))
+ h = self.relu(self.Mconv3_stage3(h))
+ h = self.relu(self.Mconv4_stage3(h))
+ h = self.relu(self.Mconv5_stage3(h))
+ h = self.relu(self.Mconv6_stage3(h))
+ h = self.Mconv7_stage3(h)
+ heatmaps.append(h)
+
+ # stage4
+ h = torch.cat([h, feature_map], dim=1) # channel concat
+ h = self.relu(self.Mconv1_stage4(h))
+ h = self.relu(self.Mconv2_stage4(h))
+ h = self.relu(self.Mconv3_stage4(h))
+ h = self.relu(self.Mconv4_stage4(h))
+ h = self.relu(self.Mconv5_stage4(h))
+ h = self.relu(self.Mconv6_stage4(h))
+ h = self.Mconv7_stage4(h)
+ heatmaps.append(h)
+
+ # stage5
+ h = torch.cat([h, feature_map], dim=1) # channel concat
+ h = self.relu(self.Mconv1_stage5(h))
+ h = self.relu(self.Mconv2_stage5(h))
+ h = self.relu(self.Mconv3_stage5(h))
+ h = self.relu(self.Mconv4_stage5(h))
+ h = self.relu(self.Mconv5_stage5(h))
+ h = self.relu(self.Mconv6_stage5(h))
+ h = self.Mconv7_stage5(h)
+ heatmaps.append(h)
+
+ # stage6
+ h = torch.cat([h, feature_map], dim=1) # channel concat
+ h = self.relu(self.Mconv1_stage6(h))
+ h = self.relu(self.Mconv2_stage6(h))
+ h = self.relu(self.Mconv3_stage6(h))
+ h = self.relu(self.Mconv4_stage6(h))
+ h = self.relu(self.Mconv5_stage6(h))
+ h = self.relu(self.Mconv6_stage6(h))
+ h = self.Mconv7_stage6(h)
+ heatmaps.append(h)
+
+ return heatmaps
+
+
+LOG = logging.getLogger(__name__)
+TOTEN = ToTensor()
+TOPIL = ToPILImage()
+
+
+params = {
+ 'gaussian_sigma': 2.5,
+ 'inference_img_size': 736, # 368, 736, 1312
+ 'heatmap_peak_thresh': 0.1,
+ 'crop_scale': 1.5,
+ 'line_indices': [
+ [0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6],
+ [6, 7], [7, 8], [8, 9], [9, 10], [10, 11], [11, 12], [12, 13],
+ [13, 14], [14, 15], [15, 16],
+ [17, 18], [18, 19], [19, 20], [20, 21],
+ [22, 23], [23, 24], [24, 25], [25, 26],
+ [27, 28], [28, 29], [29, 30],
+ [31, 32], [32, 33], [33, 34], [34, 35],
+ [36, 37], [37, 38], [38, 39], [39, 40], [40, 41], [41, 36],
+ [42, 43], [43, 44], [44, 45], [45, 46], [46, 47], [47, 42],
+ [48, 49], [49, 50], [50, 51], [51, 52], [52, 53], [53, 54],
+ [54, 55], [55, 56], [56, 57], [57, 58], [58, 59], [59, 48],
+ [60, 61], [61, 62], [62, 63], [63, 64], [64, 65], [65, 66],
+ [66, 67], [67, 60]
+ ],
+}
+
+
+class Face(object):
+ """
+ The OpenPose face landmark detector model.
+
+ Args:
+ inference_size: set the size of the inference image size, suggested:
+ 368, 736, 1312, default 736
+ gaussian_sigma: blur the heatmaps, default 2.5
+ heatmap_peak_thresh: return landmark if over threshold, default 0.1
+
+ """
+ def __init__(self, face_model_path,
+ inference_size=None,
+ gaussian_sigma=None,
+ heatmap_peak_thresh=None):
+ self.inference_size = inference_size or params["inference_img_size"]
+ self.sigma = gaussian_sigma or params['gaussian_sigma']
+ self.threshold = heatmap_peak_thresh or params["heatmap_peak_thresh"]
+ self.model = FaceNet()
+ self.model.load_state_dict(torch.load(face_model_path))
+ self.model.eval()
+
+ def to(self, device):
+ self.model.to(device)
+ return self
+
+ def __call__(self, face_img):
+ device = next(iter(self.model.parameters())).device
+ H, W, C = face_img.shape
+
+ w_size = 384
+ x_data = torch.from_numpy(util.smart_resize(face_img, (w_size, w_size))).permute([2, 0, 1]) / 256.0 - 0.5
+
+ x_data = x_data.to(device)
+
+ with torch.no_grad():
+ hs = self.model(x_data[None, ...])
+ heatmaps = F.interpolate(
+ hs[-1],
+ (H, W),
+ mode='bilinear', align_corners=True).cpu().numpy()[0]
+ return heatmaps
+
+ def compute_peaks_from_heatmaps(self, heatmaps):
+ all_peaks = []
+ for part in range(heatmaps.shape[0]):
+ map_ori = heatmaps[part].copy()
+ binary = np.ascontiguousarray(map_ori > 0.05, dtype=np.uint8)
+
+ if np.sum(binary) == 0:
+ continue
+
+ positions = np.where(binary > 0.5)
+ intensities = map_ori[positions]
+ mi = np.argmax(intensities)
+ y, x = positions[0][mi], positions[1][mi]
+ all_peaks.append([x, y])
+
+ return np.array(all_peaks)
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/hand.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/hand.py
new file mode 100644
index 0000000000000000000000000000000000000000..1387c4238c8c3856bb9622edb9b4c883e26c1d59
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/hand.py
@@ -0,0 +1,90 @@
+import cv2
+import numpy as np
+import torch
+from scipy.ndimage.filters import gaussian_filter
+from skimage.measure import label
+
+from . import util
+from .model import handpose_model
+
+
+class Hand(object):
+ def __init__(self, model_path):
+ self.model = handpose_model()
+ model_dict = util.transfer(self.model, torch.load(model_path))
+ self.model.load_state_dict(model_dict)
+ self.model.eval()
+
+ def to(self, device):
+ self.model.to(device)
+ return self
+
+ def __call__(self, oriImgRaw):
+ device = next(iter(self.model.parameters())).device
+ scale_search = [0.5, 1.0, 1.5, 2.0]
+ # scale_search = [0.5]
+ boxsize = 368
+ stride = 8
+ padValue = 128
+ thre = 0.05
+ multiplier = [x * boxsize for x in scale_search]
+
+ wsize = 128
+ heatmap_avg = np.zeros((wsize, wsize, 22))
+
+ Hr, Wr, Cr = oriImgRaw.shape
+
+ oriImg = cv2.GaussianBlur(oriImgRaw, (0, 0), 0.8)
+
+ for m in range(len(multiplier)):
+ scale = multiplier[m]
+ imageToTest = util.smart_resize(oriImg, (scale, scale))
+
+ imageToTest_padded, pad = util.padRightDownCorner(imageToTest, stride, padValue)
+ im = np.transpose(np.float32(imageToTest_padded[:, :, :, np.newaxis]), (3, 2, 0, 1)) / 256 - 0.5
+ im = np.ascontiguousarray(im)
+
+ data = torch.from_numpy(im).float()
+ data = data.to(device)
+
+ with torch.no_grad():
+ output = self.model(data).cpu().numpy()
+
+ # extract outputs, resize, and remove padding
+ heatmap = np.transpose(np.squeeze(output), (1, 2, 0)) # output 1 is heatmaps
+ heatmap = util.smart_resize_k(heatmap, fx=stride, fy=stride)
+ heatmap = heatmap[:imageToTest_padded.shape[0] - pad[2], :imageToTest_padded.shape[1] - pad[3], :]
+ heatmap = util.smart_resize(heatmap, (wsize, wsize))
+
+ heatmap_avg += heatmap / len(multiplier)
+
+ all_peaks = []
+ for part in range(21):
+ map_ori = heatmap_avg[:, :, part]
+ one_heatmap = gaussian_filter(map_ori, sigma=3)
+ binary = np.ascontiguousarray(one_heatmap > thre, dtype=np.uint8)
+
+ if np.sum(binary) == 0:
+ all_peaks.append([0, 0])
+ continue
+ label_img, label_numbers = label(binary, return_num=True, connectivity=binary.ndim)
+ max_index = np.argmax([np.sum(map_ori[label_img == i]) for i in range(1, label_numbers + 1)]) + 1
+ label_img[label_img != max_index] = 0
+ map_ori[label_img == 0] = 0
+
+ y, x = util.npmax(map_ori)
+ y = int(float(y) * float(Hr) / float(wsize))
+ x = int(float(x) * float(Wr) / float(wsize))
+ all_peaks.append([x, y])
+ return np.array(all_peaks)
+
+if __name__ == "__main__":
+ hand_estimation = Hand('../model/hand_pose_model.pth')
+
+ # test_image = '../images/hand.jpg'
+ test_image = '../images/hand.jpg'
+ oriImg = cv2.imread(test_image) # B,G,R order
+ peaks = hand_estimation(oriImg)
+ canvas = util.draw_handpose(oriImg, peaks, True)
+ cv2.imshow('', canvas)
+ cv2.waitKey(0)
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/model.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/model.py
new file mode 100644
index 0000000000000000000000000000000000000000..6c3d47268986f8018b2c75307a7725d364b175fe
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/model.py
@@ -0,0 +1,217 @@
+import torch
+from collections import OrderedDict
+
+import torch
+import torch.nn as nn
+
+def make_layers(block, no_relu_layers):
+ layers = []
+ for layer_name, v in block.items():
+ if 'pool' in layer_name:
+ layer = nn.MaxPool2d(kernel_size=v[0], stride=v[1],
+ padding=v[2])
+ layers.append((layer_name, layer))
+ else:
+ conv2d = nn.Conv2d(in_channels=v[0], out_channels=v[1],
+ kernel_size=v[2], stride=v[3],
+ padding=v[4])
+ layers.append((layer_name, conv2d))
+ if layer_name not in no_relu_layers:
+ layers.append(('relu_'+layer_name, nn.ReLU(inplace=True)))
+
+ return nn.Sequential(OrderedDict(layers))
+
+class bodypose_model(nn.Module):
+ def __init__(self):
+ super(bodypose_model, self).__init__()
+
+ # these layers have no relu layer
+ no_relu_layers = ['conv5_5_CPM_L1', 'conv5_5_CPM_L2', 'Mconv7_stage2_L1',\
+ 'Mconv7_stage2_L2', 'Mconv7_stage3_L1', 'Mconv7_stage3_L2',\
+ 'Mconv7_stage4_L1', 'Mconv7_stage4_L2', 'Mconv7_stage5_L1',\
+ 'Mconv7_stage5_L2', 'Mconv7_stage6_L1', 'Mconv7_stage6_L1']
+ blocks = {}
+ block0 = OrderedDict([
+ ('conv1_1', [3, 64, 3, 1, 1]),
+ ('conv1_2', [64, 64, 3, 1, 1]),
+ ('pool1_stage1', [2, 2, 0]),
+ ('conv2_1', [64, 128, 3, 1, 1]),
+ ('conv2_2', [128, 128, 3, 1, 1]),
+ ('pool2_stage1', [2, 2, 0]),
+ ('conv3_1', [128, 256, 3, 1, 1]),
+ ('conv3_2', [256, 256, 3, 1, 1]),
+ ('conv3_3', [256, 256, 3, 1, 1]),
+ ('conv3_4', [256, 256, 3, 1, 1]),
+ ('pool3_stage1', [2, 2, 0]),
+ ('conv4_1', [256, 512, 3, 1, 1]),
+ ('conv4_2', [512, 512, 3, 1, 1]),
+ ('conv4_3_CPM', [512, 256, 3, 1, 1]),
+ ('conv4_4_CPM', [256, 128, 3, 1, 1])
+ ])
+
+
+ # Stage 1
+ block1_1 = OrderedDict([
+ ('conv5_1_CPM_L1', [128, 128, 3, 1, 1]),
+ ('conv5_2_CPM_L1', [128, 128, 3, 1, 1]),
+ ('conv5_3_CPM_L1', [128, 128, 3, 1, 1]),
+ ('conv5_4_CPM_L1', [128, 512, 1, 1, 0]),
+ ('conv5_5_CPM_L1', [512, 38, 1, 1, 0])
+ ])
+
+ block1_2 = OrderedDict([
+ ('conv5_1_CPM_L2', [128, 128, 3, 1, 1]),
+ ('conv5_2_CPM_L2', [128, 128, 3, 1, 1]),
+ ('conv5_3_CPM_L2', [128, 128, 3, 1, 1]),
+ ('conv5_4_CPM_L2', [128, 512, 1, 1, 0]),
+ ('conv5_5_CPM_L2', [512, 19, 1, 1, 0])
+ ])
+ blocks['block1_1'] = block1_1
+ blocks['block1_2'] = block1_2
+
+ self.model0 = make_layers(block0, no_relu_layers)
+
+ # Stages 2 - 6
+ for i in range(2, 7):
+ blocks['block%d_1' % i] = OrderedDict([
+ ('Mconv1_stage%d_L1' % i, [185, 128, 7, 1, 3]),
+ ('Mconv2_stage%d_L1' % i, [128, 128, 7, 1, 3]),
+ ('Mconv3_stage%d_L1' % i, [128, 128, 7, 1, 3]),
+ ('Mconv4_stage%d_L1' % i, [128, 128, 7, 1, 3]),
+ ('Mconv5_stage%d_L1' % i, [128, 128, 7, 1, 3]),
+ ('Mconv6_stage%d_L1' % i, [128, 128, 1, 1, 0]),
+ ('Mconv7_stage%d_L1' % i, [128, 38, 1, 1, 0])
+ ])
+
+ blocks['block%d_2' % i] = OrderedDict([
+ ('Mconv1_stage%d_L2' % i, [185, 128, 7, 1, 3]),
+ ('Mconv2_stage%d_L2' % i, [128, 128, 7, 1, 3]),
+ ('Mconv3_stage%d_L2' % i, [128, 128, 7, 1, 3]),
+ ('Mconv4_stage%d_L2' % i, [128, 128, 7, 1, 3]),
+ ('Mconv5_stage%d_L2' % i, [128, 128, 7, 1, 3]),
+ ('Mconv6_stage%d_L2' % i, [128, 128, 1, 1, 0]),
+ ('Mconv7_stage%d_L2' % i, [128, 19, 1, 1, 0])
+ ])
+
+ for k in blocks.keys():
+ blocks[k] = make_layers(blocks[k], no_relu_layers)
+
+ self.model1_1 = blocks['block1_1']
+ self.model2_1 = blocks['block2_1']
+ self.model3_1 = blocks['block3_1']
+ self.model4_1 = blocks['block4_1']
+ self.model5_1 = blocks['block5_1']
+ self.model6_1 = blocks['block6_1']
+
+ self.model1_2 = blocks['block1_2']
+ self.model2_2 = blocks['block2_2']
+ self.model3_2 = blocks['block3_2']
+ self.model4_2 = blocks['block4_2']
+ self.model5_2 = blocks['block5_2']
+ self.model6_2 = blocks['block6_2']
+
+
+ def forward(self, x):
+
+ out1 = self.model0(x)
+
+ out1_1 = self.model1_1(out1)
+ out1_2 = self.model1_2(out1)
+ out2 = torch.cat([out1_1, out1_2, out1], 1)
+
+ out2_1 = self.model2_1(out2)
+ out2_2 = self.model2_2(out2)
+ out3 = torch.cat([out2_1, out2_2, out1], 1)
+
+ out3_1 = self.model3_1(out3)
+ out3_2 = self.model3_2(out3)
+ out4 = torch.cat([out3_1, out3_2, out1], 1)
+
+ out4_1 = self.model4_1(out4)
+ out4_2 = self.model4_2(out4)
+ out5 = torch.cat([out4_1, out4_2, out1], 1)
+
+ out5_1 = self.model5_1(out5)
+ out5_2 = self.model5_2(out5)
+ out6 = torch.cat([out5_1, out5_2, out1], 1)
+
+ out6_1 = self.model6_1(out6)
+ out6_2 = self.model6_2(out6)
+
+ return out6_1, out6_2
+
+class handpose_model(nn.Module):
+ def __init__(self):
+ super(handpose_model, self).__init__()
+
+ # these layers have no relu layer
+ no_relu_layers = ['conv6_2_CPM', 'Mconv7_stage2', 'Mconv7_stage3',\
+ 'Mconv7_stage4', 'Mconv7_stage5', 'Mconv7_stage6']
+ # stage 1
+ block1_0 = OrderedDict([
+ ('conv1_1', [3, 64, 3, 1, 1]),
+ ('conv1_2', [64, 64, 3, 1, 1]),
+ ('pool1_stage1', [2, 2, 0]),
+ ('conv2_1', [64, 128, 3, 1, 1]),
+ ('conv2_2', [128, 128, 3, 1, 1]),
+ ('pool2_stage1', [2, 2, 0]),
+ ('conv3_1', [128, 256, 3, 1, 1]),
+ ('conv3_2', [256, 256, 3, 1, 1]),
+ ('conv3_3', [256, 256, 3, 1, 1]),
+ ('conv3_4', [256, 256, 3, 1, 1]),
+ ('pool3_stage1', [2, 2, 0]),
+ ('conv4_1', [256, 512, 3, 1, 1]),
+ ('conv4_2', [512, 512, 3, 1, 1]),
+ ('conv4_3', [512, 512, 3, 1, 1]),
+ ('conv4_4', [512, 512, 3, 1, 1]),
+ ('conv5_1', [512, 512, 3, 1, 1]),
+ ('conv5_2', [512, 512, 3, 1, 1]),
+ ('conv5_3_CPM', [512, 128, 3, 1, 1])
+ ])
+
+ block1_1 = OrderedDict([
+ ('conv6_1_CPM', [128, 512, 1, 1, 0]),
+ ('conv6_2_CPM', [512, 22, 1, 1, 0])
+ ])
+
+ blocks = {}
+ blocks['block1_0'] = block1_0
+ blocks['block1_1'] = block1_1
+
+ # stage 2-6
+ for i in range(2, 7):
+ blocks['block%d' % i] = OrderedDict([
+ ('Mconv1_stage%d' % i, [150, 128, 7, 1, 3]),
+ ('Mconv2_stage%d' % i, [128, 128, 7, 1, 3]),
+ ('Mconv3_stage%d' % i, [128, 128, 7, 1, 3]),
+ ('Mconv4_stage%d' % i, [128, 128, 7, 1, 3]),
+ ('Mconv5_stage%d' % i, [128, 128, 7, 1, 3]),
+ ('Mconv6_stage%d' % i, [128, 128, 1, 1, 0]),
+ ('Mconv7_stage%d' % i, [128, 22, 1, 1, 0])
+ ])
+
+ for k in blocks.keys():
+ blocks[k] = make_layers(blocks[k], no_relu_layers)
+
+ self.model1_0 = blocks['block1_0']
+ self.model1_1 = blocks['block1_1']
+ self.model2 = blocks['block2']
+ self.model3 = blocks['block3']
+ self.model4 = blocks['block4']
+ self.model5 = blocks['block5']
+ self.model6 = blocks['block6']
+
+ def forward(self, x):
+ out1_0 = self.model1_0(x)
+ out1_1 = self.model1_1(out1_0)
+ concat_stage2 = torch.cat([out1_1, out1_0], 1)
+ out_stage2 = self.model2(concat_stage2)
+ concat_stage3 = torch.cat([out_stage2, out1_0], 1)
+ out_stage3 = self.model3(concat_stage3)
+ concat_stage4 = torch.cat([out_stage3, out1_0], 1)
+ out_stage4 = self.model4(concat_stage4)
+ concat_stage5 = torch.cat([out_stage4, out1_0], 1)
+ out_stage5 = self.model5(concat_stage5)
+ concat_stage6 = torch.cat([out_stage5, out1_0], 1)
+ out_stage6 = self.model6(concat_stage6)
+ return out_stage6
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/util.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/util.py
new file mode 100644
index 0000000000000000000000000000000000000000..a0851ca409863dcee4bf731a47b472992569dd68
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/open_pose/util.py
@@ -0,0 +1,383 @@
+import math
+import numpy as np
+import matplotlib
+import cv2
+from typing import List, Tuple, Union
+
+from .body import BodyResult, Keypoint
+
+eps = 0.01
+
+
+def smart_resize(x, s):
+ Ht, Wt = s
+ if x.ndim == 2:
+ Ho, Wo = x.shape
+ Co = 1
+ else:
+ Ho, Wo, Co = x.shape
+ if Co == 3 or Co == 1:
+ k = float(Ht + Wt) / float(Ho + Wo)
+ return cv2.resize(x, (int(Wt), int(Ht)), interpolation=cv2.INTER_AREA if k < 1 else cv2.INTER_LANCZOS4)
+ else:
+ return np.stack([smart_resize(x[:, :, i], s) for i in range(Co)], axis=2)
+
+
+def smart_resize_k(x, fx, fy):
+ if x.ndim == 2:
+ Ho, Wo = x.shape
+ Co = 1
+ else:
+ Ho, Wo, Co = x.shape
+ Ht, Wt = Ho * fy, Wo * fx
+ if Co == 3 or Co == 1:
+ k = float(Ht + Wt) / float(Ho + Wo)
+ return cv2.resize(x, (int(Wt), int(Ht)), interpolation=cv2.INTER_AREA if k < 1 else cv2.INTER_LANCZOS4)
+ else:
+ return np.stack([smart_resize_k(x[:, :, i], fx, fy) for i in range(Co)], axis=2)
+
+
+def padRightDownCorner(img, stride, padValue):
+ h = img.shape[0]
+ w = img.shape[1]
+
+ pad = 4 * [None]
+ pad[0] = 0 # up
+ pad[1] = 0 # left
+ pad[2] = 0 if (h % stride == 0) else stride - (h % stride) # down
+ pad[3] = 0 if (w % stride == 0) else stride - (w % stride) # right
+
+ img_padded = img
+ pad_up = np.tile(img_padded[0:1, :, :]*0 + padValue, (pad[0], 1, 1))
+ img_padded = np.concatenate((pad_up, img_padded), axis=0)
+ pad_left = np.tile(img_padded[:, 0:1, :]*0 + padValue, (1, pad[1], 1))
+ img_padded = np.concatenate((pad_left, img_padded), axis=1)
+ pad_down = np.tile(img_padded[-2:-1, :, :]*0 + padValue, (pad[2], 1, 1))
+ img_padded = np.concatenate((img_padded, pad_down), axis=0)
+ pad_right = np.tile(img_padded[:, -2:-1, :]*0 + padValue, (1, pad[3], 1))
+ img_padded = np.concatenate((img_padded, pad_right), axis=1)
+
+ return img_padded, pad
+
+
+def transfer(model, model_weights):
+ transfered_model_weights = {}
+ for weights_name in model.state_dict().keys():
+ transfered_model_weights[weights_name] = model_weights['.'.join(weights_name.split('.')[1:])]
+ return transfered_model_weights
+
+
+def draw_bodypose(canvas: np.ndarray, keypoints: List[Keypoint]) -> np.ndarray:
+ """
+ Draw keypoints and limbs representing body pose on a given canvas.
+
+ Args:
+ canvas (np.ndarray): A 3D numpy array representing the canvas (image) on which to draw the body pose.
+ keypoints (List[Keypoint]): A list of Keypoint objects representing the body keypoints to be drawn.
+
+ Returns:
+ np.ndarray: A 3D numpy array representing the modified canvas with the drawn body pose.
+
+ Note:
+ The function expects the x and y coordinates of the keypoints to be normalized between 0 and 1.
+ """
+ H, W, C = canvas.shape
+ stickwidth = 4
+
+ limbSeq = [
+ [2, 3], [2, 6], [3, 4], [4, 5],
+ [6, 7], [7, 8], [2, 9], [9, 10],
+ [10, 11], [2, 12], [12, 13], [13, 14],
+ [2, 1], [1, 15], [15, 17], [1, 16],
+ [16, 18],
+ ]
+
+ colors = [[255, 0, 0], [255, 85, 0], [255, 170, 0], [255, 255, 0], [170, 255, 0], [85, 255, 0], [0, 255, 0], \
+ [0, 255, 85], [0, 255, 170], [0, 255, 255], [0, 170, 255], [0, 85, 255], [0, 0, 255], [85, 0, 255], \
+ [170, 0, 255], [255, 0, 255], [255, 0, 170], [255, 0, 85]]
+
+ for (k1_index, k2_index), color in zip(limbSeq, colors):
+ keypoint1 = keypoints[k1_index - 1]
+ keypoint2 = keypoints[k2_index - 1]
+
+ if keypoint1 is None or keypoint2 is None:
+ continue
+
+ Y = np.array([keypoint1.x, keypoint2.x]) * float(W)
+ X = np.array([keypoint1.y, keypoint2.y]) * float(H)
+ mX = np.mean(X)
+ mY = np.mean(Y)
+ length = ((X[0] - X[1]) ** 2 + (Y[0] - Y[1]) ** 2) ** 0.5
+ angle = math.degrees(math.atan2(X[0] - X[1], Y[0] - Y[1]))
+ polygon = cv2.ellipse2Poly((int(mY), int(mX)), (int(length / 2), stickwidth), int(angle), 0, 360, 1)
+ cv2.fillConvexPoly(canvas, polygon, [int(float(c) * 0.6) for c in color])
+
+ for keypoint, color in zip(keypoints, colors):
+ if keypoint is None:
+ continue
+
+ x, y = keypoint.x, keypoint.y
+ x = int(x * W)
+ y = int(y * H)
+ cv2.circle(canvas, (int(x), int(y)), 4, color, thickness=-1)
+
+ return canvas
+
+
+def draw_handpose(canvas: np.ndarray, keypoints: Union[List[Keypoint], None]) -> np.ndarray:
+ """
+ Draw keypoints and connections representing hand pose on a given canvas.
+
+ Args:
+ canvas (np.ndarray): A 3D numpy array representing the canvas (image) on which to draw the hand pose.
+ keypoints (List[Keypoint]| None): A list of Keypoint objects representing the hand keypoints to be drawn
+ or None if no keypoints are present.
+
+ Returns:
+ np.ndarray: A 3D numpy array representing the modified canvas with the drawn hand pose.
+
+ Note:
+ The function expects the x and y coordinates of the keypoints to be normalized between 0 and 1.
+ """
+ if not keypoints:
+ return canvas
+
+ H, W, C = canvas.shape
+
+ edges = [[0, 1], [1, 2], [2, 3], [3, 4], [0, 5], [5, 6], [6, 7], [7, 8], [0, 9], [9, 10], \
+ [10, 11], [11, 12], [0, 13], [13, 14], [14, 15], [15, 16], [0, 17], [17, 18], [18, 19], [19, 20]]
+
+ for ie, (e1, e2) in enumerate(edges):
+ k1 = keypoints[e1]
+ k2 = keypoints[e2]
+ if k1 is None or k2 is None:
+ continue
+
+ x1 = int(k1.x * W)
+ y1 = int(k1.y * H)
+ x2 = int(k2.x * W)
+ y2 = int(k2.y * H)
+ if x1 > eps and y1 > eps and x2 > eps and y2 > eps:
+ cv2.line(canvas, (x1, y1), (x2, y2), matplotlib.colors.hsv_to_rgb([ie / float(len(edges)), 1.0, 1.0]) * 255, thickness=2)
+
+ for keypoint in keypoints:
+ x, y = keypoint.x, keypoint.y
+ x = int(x * W)
+ y = int(y * H)
+ if x > eps and y > eps:
+ cv2.circle(canvas, (x, y), 4, (0, 0, 255), thickness=-1)
+ return canvas
+
+
+def draw_facepose(canvas: np.ndarray, keypoints: Union[List[Keypoint], None]) -> np.ndarray:
+ """
+ Draw keypoints representing face pose on a given canvas.
+
+ Args:
+ canvas (np.ndarray): A 3D numpy array representing the canvas (image) on which to draw the face pose.
+ keypoints (List[Keypoint]| None): A list of Keypoint objects representing the face keypoints to be drawn
+ or None if no keypoints are present.
+
+ Returns:
+ np.ndarray: A 3D numpy array representing the modified canvas with the drawn face pose.
+
+ Note:
+ The function expects the x and y coordinates of the keypoints to be normalized between 0 and 1.
+ """
+ if not keypoints:
+ return canvas
+
+ H, W, C = canvas.shape
+ for keypoint in keypoints:
+ x, y = keypoint.x, keypoint.y
+ x = int(x * W)
+ y = int(y * H)
+ if x > eps and y > eps:
+ cv2.circle(canvas, (x, y), 3, (255, 255, 255), thickness=-1)
+ return canvas
+
+
+# detect hand according to body pose keypoints
+# please refer to https://github.com/CMU-Perceptual-Computing-Lab/openpose/blob/master/src/openpose/hand/handDetector.cpp
+def handDetect(body: BodyResult, oriImg) -> List[Tuple[int, int, int, bool]]:
+ """
+ Detect hands in the input body pose keypoints and calculate the bounding box for each hand.
+
+ Args:
+ body (BodyResult): A BodyResult object containing the detected body pose keypoints.
+ oriImg (numpy.ndarray): A 3D numpy array representing the original input image.
+
+ Returns:
+ List[Tuple[int, int, int, bool]]: A list of tuples, each containing the coordinates (x, y) of the top-left
+ corner of the bounding box, the width (height) of the bounding box, and
+ a boolean flag indicating whether the hand is a left hand (True) or a
+ right hand (False).
+
+ Notes:
+ - The width and height of the bounding boxes are equal since the network requires squared input.
+ - The minimum bounding box size is 20 pixels.
+ """
+ ratioWristElbow = 0.33
+ detect_result = []
+ image_height, image_width = oriImg.shape[0:2]
+
+ keypoints = body.keypoints
+ # right hand: wrist 4, elbow 3, shoulder 2
+ # left hand: wrist 7, elbow 6, shoulder 5
+ left_shoulder = keypoints[5]
+ left_elbow = keypoints[6]
+ left_wrist = keypoints[7]
+ right_shoulder = keypoints[2]
+ right_elbow = keypoints[3]
+ right_wrist = keypoints[4]
+
+ # if any of three not detected
+ has_left = all(keypoint is not None for keypoint in (left_shoulder, left_elbow, left_wrist))
+ has_right = all(keypoint is not None for keypoint in (right_shoulder, right_elbow, right_wrist))
+ if not (has_left or has_right):
+ return []
+
+ hands = []
+ #left hand
+ if has_left:
+ hands.append([
+ left_shoulder.x, left_shoulder.y,
+ left_elbow.x, left_elbow.y,
+ left_wrist.x, left_wrist.y,
+ True
+ ])
+ # right hand
+ if has_right:
+ hands.append([
+ right_shoulder.x, right_shoulder.y,
+ right_elbow.x, right_elbow.y,
+ right_wrist.x, right_wrist.y,
+ False
+ ])
+
+ for x1, y1, x2, y2, x3, y3, is_left in hands:
+ # pos_hand = pos_wrist + ratio * (pos_wrist - pos_elbox) = (1 + ratio) * pos_wrist - ratio * pos_elbox
+ # handRectangle.x = posePtr[wrist*3] + ratioWristElbow * (posePtr[wrist*3] - posePtr[elbow*3]);
+ # handRectangle.y = posePtr[wrist*3+1] + ratioWristElbow * (posePtr[wrist*3+1] - posePtr[elbow*3+1]);
+ # const auto distanceWristElbow = getDistance(poseKeypoints, person, wrist, elbow);
+ # const auto distanceElbowShoulder = getDistance(poseKeypoints, person, elbow, shoulder);
+ # handRectangle.width = 1.5f * fastMax(distanceWristElbow, 0.9f * distanceElbowShoulder);
+ x = x3 + ratioWristElbow * (x3 - x2)
+ y = y3 + ratioWristElbow * (y3 - y2)
+ distanceWristElbow = math.sqrt((x3 - x2) ** 2 + (y3 - y2) ** 2)
+ distanceElbowShoulder = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
+ width = 1.5 * max(distanceWristElbow, 0.9 * distanceElbowShoulder)
+ # x-y refers to the center --> offset to topLeft point
+ # handRectangle.x -= handRectangle.width / 2.f;
+ # handRectangle.y -= handRectangle.height / 2.f;
+ x -= width / 2
+ y -= width / 2 # width = height
+ # overflow the image
+ if x < 0: x = 0
+ if y < 0: y = 0
+ width1 = width
+ width2 = width
+ if x + width > image_width: width1 = image_width - x
+ if y + width > image_height: width2 = image_height - y
+ width = min(width1, width2)
+ # the max hand box value is 20 pixels
+ if width >= 20:
+ detect_result.append((int(x), int(y), int(width), is_left))
+
+ '''
+ return value: [[x, y, w, True if left hand else False]].
+ width=height since the network require squared input.
+ x, y is the coordinate of top left
+ '''
+ return detect_result
+
+
+# Written by Lvmin
+def faceDetect(body: BodyResult, oriImg) -> Union[Tuple[int, int, int], None]:
+ """
+ Detect the face in the input body pose keypoints and calculate the bounding box for the face.
+
+ Args:
+ body (BodyResult): A BodyResult object containing the detected body pose keypoints.
+ oriImg (numpy.ndarray): A 3D numpy array representing the original input image.
+
+ Returns:
+ Tuple[int, int, int] | None: A tuple containing the coordinates (x, y) of the top-left corner of the
+ bounding box and the width (height) of the bounding box, or None if the
+ face is not detected or the bounding box width is less than 20 pixels.
+
+ Notes:
+ - The width and height of the bounding box are equal.
+ - The minimum bounding box size is 20 pixels.
+ """
+ # left right eye ear 14 15 16 17
+ image_height, image_width = oriImg.shape[0:2]
+
+ keypoints = body.keypoints
+ head = keypoints[0]
+ left_eye = keypoints[14]
+ right_eye = keypoints[15]
+ left_ear = keypoints[16]
+ right_ear = keypoints[17]
+
+ if head is None or all(keypoint is None for keypoint in (left_eye, right_eye, left_ear, right_ear)):
+ return None
+
+ width = 0.0
+ x0, y0 = head.x, head.y
+
+ if left_eye is not None:
+ x1, y1 = left_eye.x, left_eye.y
+ d = max(abs(x0 - x1), abs(y0 - y1))
+ width = max(width, d * 3.0)
+
+ if right_eye is not None:
+ x1, y1 = right_eye.x, right_eye.y
+ d = max(abs(x0 - x1), abs(y0 - y1))
+ width = max(width, d * 3.0)
+
+ if left_ear is not None:
+ x1, y1 = left_ear.x, left_ear.y
+ d = max(abs(x0 - x1), abs(y0 - y1))
+ width = max(width, d * 1.5)
+
+ if right_ear is not None:
+ x1, y1 = right_ear.x, right_ear.y
+ d = max(abs(x0 - x1), abs(y0 - y1))
+ width = max(width, d * 1.5)
+
+ x, y = x0, y0
+
+ x -= width
+ y -= width
+
+ if x < 0:
+ x = 0
+
+ if y < 0:
+ y = 0
+
+ width1 = width * 2
+ width2 = width * 2
+
+ if x + width > image_width:
+ width1 = image_width - x
+
+ if y + width > image_height:
+ width2 = image_height - y
+
+ width = min(width1, width2)
+
+ if width >= 20:
+ return int(x), int(y), int(width)
+ else:
+ return None
+
+
+# get max index of 2d array
+def npmax(array):
+ arrayindex = array.argmax(1)
+ arrayvalue = array.max(1)
+ i = arrayvalue.argmax()
+ j = arrayindex[i]
+ return i, j
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/pidi/LICENSE b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/pidi/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..913b6cf92c19d37b6ee4f7bc99c65f655e7f840c
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/pidi/LICENSE
@@ -0,0 +1,21 @@
+It is just for research purpose, and commercial use should be contacted with authors first.
+
+Copyright (c) 2021 Zhuo Su
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/pidi/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/pidi/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..6286358567c42ce282b99d8294877219c438842c
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/pidi/__init__.py
@@ -0,0 +1,65 @@
+import os
+import warnings
+
+import cv2
+import numpy as np
+import torch
+from einops import rearrange
+from PIL import Image
+
+from controlnet_aux.util import HWC3, nms, resize_image_with_pad, safe_step,common_input_validate, annotator_ckpts_path, custom_hf_download
+from .model import pidinet
+
+
+class PidiNetDetector:
+ def __init__(self, netNetwork):
+ self.netNetwork = netNetwork
+
+ @classmethod
+ def from_pretrained(cls, pretrained_model_or_path, filename=None, cache_dir=annotator_ckpts_path):
+ filename = filename or "table5_pidinet.pth"
+ model_path = custom_hf_download(pretrained_model_or_path, filename, cache_dir=cache_dir)
+
+ netNetwork = pidinet()
+ netNetwork.load_state_dict({k.replace('module.', ''): v for k, v in torch.load(model_path)['state_dict'].items()})
+ netNetwork.eval()
+
+ return cls(netNetwork)
+
+ def to(self, device):
+ self.netNetwork.to(device)
+ return self
+
+ def __call__(self, input_image, detect_resolution=512, safe=False, output_type="pil", scribble=False, apply_filter=False, upscale_method="INTER_CUBIC", **kwargs):
+ input_image, output_type = common_input_validate(input_image, output_type, **kwargs)
+ detected_map, remove_pad = resize_image_with_pad(input_image, detect_resolution, upscale_method)
+
+ device = next(iter(self.netNetwork.parameters())).device
+
+ detected_map = detected_map[:, :, ::-1].copy()
+ with torch.no_grad():
+ image_pidi = torch.from_numpy(detected_map).float().to(device)
+ image_pidi = image_pidi / 255.0
+ image_pidi = rearrange(image_pidi, 'h w c -> 1 c h w')
+ edge = self.netNetwork(image_pidi)[-1]
+ edge = edge.cpu().numpy()
+ if apply_filter:
+ edge = edge > 0.5
+ if safe:
+ edge = safe_step(edge)
+ edge = (edge * 255.0).clip(0, 255).astype(np.uint8)
+
+ detected_map = edge[0, 0]
+
+ if scribble:
+ detected_map = nms(detected_map, 127, 3.0)
+ detected_map = cv2.GaussianBlur(detected_map, (0, 0), 3.0)
+ detected_map[detected_map > 4] = 255
+ detected_map[detected_map < 255] = 0
+
+ detected_map = HWC3(remove_pad(detected_map))
+
+ if output_type == "pil":
+ detected_map = Image.fromarray(detected_map)
+
+ return detected_map
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/pidi/model.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/pidi/model.py
new file mode 100644
index 0000000000000000000000000000000000000000..16595b35a4f75a6d2b0e832e24b6e11706d77326
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/pidi/model.py
@@ -0,0 +1,681 @@
+"""
+Author: Zhuo Su, Wenzhe Liu
+Date: Feb 18, 2021
+"""
+
+import math
+
+import cv2
+import numpy as np
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+
+
+def img2tensor(imgs, bgr2rgb=True, float32=True):
+ """Numpy array to tensor.
+
+ Args:
+ imgs (list[ndarray] | ndarray): Input images.
+ bgr2rgb (bool): Whether to change bgr to rgb.
+ float32 (bool): Whether to change to float32.
+
+ Returns:
+ list[tensor] | tensor: Tensor images. If returned results only have
+ one element, just return tensor.
+ """
+
+ def _totensor(img, bgr2rgb, float32):
+ if img.shape[2] == 3 and bgr2rgb:
+ if img.dtype == 'float64':
+ img = img.astype('float32')
+ img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
+ img = torch.from_numpy(img.transpose(2, 0, 1))
+ if float32:
+ img = img.float()
+ return img
+
+ if isinstance(imgs, list):
+ return [_totensor(img, bgr2rgb, float32) for img in imgs]
+ else:
+ return _totensor(imgs, bgr2rgb, float32)
+
+nets = {
+ 'baseline': {
+ 'layer0': 'cv',
+ 'layer1': 'cv',
+ 'layer2': 'cv',
+ 'layer3': 'cv',
+ 'layer4': 'cv',
+ 'layer5': 'cv',
+ 'layer6': 'cv',
+ 'layer7': 'cv',
+ 'layer8': 'cv',
+ 'layer9': 'cv',
+ 'layer10': 'cv',
+ 'layer11': 'cv',
+ 'layer12': 'cv',
+ 'layer13': 'cv',
+ 'layer14': 'cv',
+ 'layer15': 'cv',
+ },
+ 'c-v15': {
+ 'layer0': 'cd',
+ 'layer1': 'cv',
+ 'layer2': 'cv',
+ 'layer3': 'cv',
+ 'layer4': 'cv',
+ 'layer5': 'cv',
+ 'layer6': 'cv',
+ 'layer7': 'cv',
+ 'layer8': 'cv',
+ 'layer9': 'cv',
+ 'layer10': 'cv',
+ 'layer11': 'cv',
+ 'layer12': 'cv',
+ 'layer13': 'cv',
+ 'layer14': 'cv',
+ 'layer15': 'cv',
+ },
+ 'a-v15': {
+ 'layer0': 'ad',
+ 'layer1': 'cv',
+ 'layer2': 'cv',
+ 'layer3': 'cv',
+ 'layer4': 'cv',
+ 'layer5': 'cv',
+ 'layer6': 'cv',
+ 'layer7': 'cv',
+ 'layer8': 'cv',
+ 'layer9': 'cv',
+ 'layer10': 'cv',
+ 'layer11': 'cv',
+ 'layer12': 'cv',
+ 'layer13': 'cv',
+ 'layer14': 'cv',
+ 'layer15': 'cv',
+ },
+ 'r-v15': {
+ 'layer0': 'rd',
+ 'layer1': 'cv',
+ 'layer2': 'cv',
+ 'layer3': 'cv',
+ 'layer4': 'cv',
+ 'layer5': 'cv',
+ 'layer6': 'cv',
+ 'layer7': 'cv',
+ 'layer8': 'cv',
+ 'layer9': 'cv',
+ 'layer10': 'cv',
+ 'layer11': 'cv',
+ 'layer12': 'cv',
+ 'layer13': 'cv',
+ 'layer14': 'cv',
+ 'layer15': 'cv',
+ },
+ 'cvvv4': {
+ 'layer0': 'cd',
+ 'layer1': 'cv',
+ 'layer2': 'cv',
+ 'layer3': 'cv',
+ 'layer4': 'cd',
+ 'layer5': 'cv',
+ 'layer6': 'cv',
+ 'layer7': 'cv',
+ 'layer8': 'cd',
+ 'layer9': 'cv',
+ 'layer10': 'cv',
+ 'layer11': 'cv',
+ 'layer12': 'cd',
+ 'layer13': 'cv',
+ 'layer14': 'cv',
+ 'layer15': 'cv',
+ },
+ 'avvv4': {
+ 'layer0': 'ad',
+ 'layer1': 'cv',
+ 'layer2': 'cv',
+ 'layer3': 'cv',
+ 'layer4': 'ad',
+ 'layer5': 'cv',
+ 'layer6': 'cv',
+ 'layer7': 'cv',
+ 'layer8': 'ad',
+ 'layer9': 'cv',
+ 'layer10': 'cv',
+ 'layer11': 'cv',
+ 'layer12': 'ad',
+ 'layer13': 'cv',
+ 'layer14': 'cv',
+ 'layer15': 'cv',
+ },
+ 'rvvv4': {
+ 'layer0': 'rd',
+ 'layer1': 'cv',
+ 'layer2': 'cv',
+ 'layer3': 'cv',
+ 'layer4': 'rd',
+ 'layer5': 'cv',
+ 'layer6': 'cv',
+ 'layer7': 'cv',
+ 'layer8': 'rd',
+ 'layer9': 'cv',
+ 'layer10': 'cv',
+ 'layer11': 'cv',
+ 'layer12': 'rd',
+ 'layer13': 'cv',
+ 'layer14': 'cv',
+ 'layer15': 'cv',
+ },
+ 'cccv4': {
+ 'layer0': 'cd',
+ 'layer1': 'cd',
+ 'layer2': 'cd',
+ 'layer3': 'cv',
+ 'layer4': 'cd',
+ 'layer5': 'cd',
+ 'layer6': 'cd',
+ 'layer7': 'cv',
+ 'layer8': 'cd',
+ 'layer9': 'cd',
+ 'layer10': 'cd',
+ 'layer11': 'cv',
+ 'layer12': 'cd',
+ 'layer13': 'cd',
+ 'layer14': 'cd',
+ 'layer15': 'cv',
+ },
+ 'aaav4': {
+ 'layer0': 'ad',
+ 'layer1': 'ad',
+ 'layer2': 'ad',
+ 'layer3': 'cv',
+ 'layer4': 'ad',
+ 'layer5': 'ad',
+ 'layer6': 'ad',
+ 'layer7': 'cv',
+ 'layer8': 'ad',
+ 'layer9': 'ad',
+ 'layer10': 'ad',
+ 'layer11': 'cv',
+ 'layer12': 'ad',
+ 'layer13': 'ad',
+ 'layer14': 'ad',
+ 'layer15': 'cv',
+ },
+ 'rrrv4': {
+ 'layer0': 'rd',
+ 'layer1': 'rd',
+ 'layer2': 'rd',
+ 'layer3': 'cv',
+ 'layer4': 'rd',
+ 'layer5': 'rd',
+ 'layer6': 'rd',
+ 'layer7': 'cv',
+ 'layer8': 'rd',
+ 'layer9': 'rd',
+ 'layer10': 'rd',
+ 'layer11': 'cv',
+ 'layer12': 'rd',
+ 'layer13': 'rd',
+ 'layer14': 'rd',
+ 'layer15': 'cv',
+ },
+ 'c16': {
+ 'layer0': 'cd',
+ 'layer1': 'cd',
+ 'layer2': 'cd',
+ 'layer3': 'cd',
+ 'layer4': 'cd',
+ 'layer5': 'cd',
+ 'layer6': 'cd',
+ 'layer7': 'cd',
+ 'layer8': 'cd',
+ 'layer9': 'cd',
+ 'layer10': 'cd',
+ 'layer11': 'cd',
+ 'layer12': 'cd',
+ 'layer13': 'cd',
+ 'layer14': 'cd',
+ 'layer15': 'cd',
+ },
+ 'a16': {
+ 'layer0': 'ad',
+ 'layer1': 'ad',
+ 'layer2': 'ad',
+ 'layer3': 'ad',
+ 'layer4': 'ad',
+ 'layer5': 'ad',
+ 'layer6': 'ad',
+ 'layer7': 'ad',
+ 'layer8': 'ad',
+ 'layer9': 'ad',
+ 'layer10': 'ad',
+ 'layer11': 'ad',
+ 'layer12': 'ad',
+ 'layer13': 'ad',
+ 'layer14': 'ad',
+ 'layer15': 'ad',
+ },
+ 'r16': {
+ 'layer0': 'rd',
+ 'layer1': 'rd',
+ 'layer2': 'rd',
+ 'layer3': 'rd',
+ 'layer4': 'rd',
+ 'layer5': 'rd',
+ 'layer6': 'rd',
+ 'layer7': 'rd',
+ 'layer8': 'rd',
+ 'layer9': 'rd',
+ 'layer10': 'rd',
+ 'layer11': 'rd',
+ 'layer12': 'rd',
+ 'layer13': 'rd',
+ 'layer14': 'rd',
+ 'layer15': 'rd',
+ },
+ 'carv4': {
+ 'layer0': 'cd',
+ 'layer1': 'ad',
+ 'layer2': 'rd',
+ 'layer3': 'cv',
+ 'layer4': 'cd',
+ 'layer5': 'ad',
+ 'layer6': 'rd',
+ 'layer7': 'cv',
+ 'layer8': 'cd',
+ 'layer9': 'ad',
+ 'layer10': 'rd',
+ 'layer11': 'cv',
+ 'layer12': 'cd',
+ 'layer13': 'ad',
+ 'layer14': 'rd',
+ 'layer15': 'cv',
+ },
+ }
+
+def createConvFunc(op_type):
+ assert op_type in ['cv', 'cd', 'ad', 'rd'], 'unknown op type: %s' % str(op_type)
+ if op_type == 'cv':
+ return F.conv2d
+
+ if op_type == 'cd':
+ def func(x, weights, bias=None, stride=1, padding=0, dilation=1, groups=1):
+ assert dilation in [1, 2], 'dilation for cd_conv should be in 1 or 2'
+ assert weights.size(2) == 3 and weights.size(3) == 3, 'kernel size for cd_conv should be 3x3'
+ assert padding == dilation, 'padding for cd_conv set wrong'
+
+ weights_c = weights.sum(dim=[2, 3], keepdim=True)
+ yc = F.conv2d(x, weights_c, stride=stride, padding=0, groups=groups)
+ y = F.conv2d(x, weights, bias, stride=stride, padding=padding, dilation=dilation, groups=groups)
+ return y - yc
+ return func
+ elif op_type == 'ad':
+ def func(x, weights, bias=None, stride=1, padding=0, dilation=1, groups=1):
+ assert dilation in [1, 2], 'dilation for ad_conv should be in 1 or 2'
+ assert weights.size(2) == 3 and weights.size(3) == 3, 'kernel size for ad_conv should be 3x3'
+ assert padding == dilation, 'padding for ad_conv set wrong'
+
+ shape = weights.shape
+ weights = weights.view(shape[0], shape[1], -1)
+ weights_conv = (weights - weights[:, :, [3, 0, 1, 6, 4, 2, 7, 8, 5]]).view(shape) # clock-wise
+ y = F.conv2d(x, weights_conv, bias, stride=stride, padding=padding, dilation=dilation, groups=groups)
+ return y
+ return func
+ elif op_type == 'rd':
+ def func(x, weights, bias=None, stride=1, padding=0, dilation=1, groups=1):
+ assert dilation in [1, 2], 'dilation for rd_conv should be in 1 or 2'
+ assert weights.size(2) == 3 and weights.size(3) == 3, 'kernel size for rd_conv should be 3x3'
+ padding = 2 * dilation
+
+ shape = weights.shape
+ if weights.is_cuda:
+ buffer = torch.cuda.FloatTensor(shape[0], shape[1], 5 * 5).fill_(0)
+ else:
+ buffer = torch.zeros(shape[0], shape[1], 5 * 5).to(weights.device)
+ weights = weights.view(shape[0], shape[1], -1)
+ buffer[:, :, [0, 2, 4, 10, 14, 20, 22, 24]] = weights[:, :, 1:]
+ buffer[:, :, [6, 7, 8, 11, 13, 16, 17, 18]] = -weights[:, :, 1:]
+ buffer[:, :, 12] = 0
+ buffer = buffer.view(shape[0], shape[1], 5, 5)
+ y = F.conv2d(x, buffer, bias, stride=stride, padding=padding, dilation=dilation, groups=groups)
+ return y
+ return func
+ else:
+ print('impossible to be here unless you force that')
+ return None
+
+class Conv2d(nn.Module):
+ def __init__(self, pdc, in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=False):
+ super(Conv2d, self).__init__()
+ if in_channels % groups != 0:
+ raise ValueError('in_channels must be divisible by groups')
+ if out_channels % groups != 0:
+ raise ValueError('out_channels must be divisible by groups')
+ self.in_channels = in_channels
+ self.out_channels = out_channels
+ self.kernel_size = kernel_size
+ self.stride = stride
+ self.padding = padding
+ self.dilation = dilation
+ self.groups = groups
+ self.weight = nn.Parameter(torch.Tensor(out_channels, in_channels // groups, kernel_size, kernel_size))
+ if bias:
+ self.bias = nn.Parameter(torch.Tensor(out_channels))
+ else:
+ self.register_parameter('bias', None)
+ self.reset_parameters()
+ self.pdc = pdc
+
+ def reset_parameters(self):
+ nn.init.kaiming_uniform_(self.weight, a=math.sqrt(5))
+ if self.bias is not None:
+ fan_in, _ = nn.init._calculate_fan_in_and_fan_out(self.weight)
+ bound = 1 / math.sqrt(fan_in)
+ nn.init.uniform_(self.bias, -bound, bound)
+
+ def forward(self, input):
+
+ return self.pdc(input, self.weight, self.bias, self.stride, self.padding, self.dilation, self.groups)
+
+class CSAM(nn.Module):
+ """
+ Compact Spatial Attention Module
+ """
+ def __init__(self, channels):
+ super(CSAM, self).__init__()
+
+ mid_channels = 4
+ self.relu1 = nn.ReLU()
+ self.conv1 = nn.Conv2d(channels, mid_channels, kernel_size=1, padding=0)
+ self.conv2 = nn.Conv2d(mid_channels, 1, kernel_size=3, padding=1, bias=False)
+ self.sigmoid = nn.Sigmoid()
+ nn.init.constant_(self.conv1.bias, 0)
+
+ def forward(self, x):
+ y = self.relu1(x)
+ y = self.conv1(y)
+ y = self.conv2(y)
+ y = self.sigmoid(y)
+
+ return x * y
+
+class CDCM(nn.Module):
+ """
+ Compact Dilation Convolution based Module
+ """
+ def __init__(self, in_channels, out_channels):
+ super(CDCM, self).__init__()
+
+ self.relu1 = nn.ReLU()
+ self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=1, padding=0)
+ self.conv2_1 = nn.Conv2d(out_channels, out_channels, kernel_size=3, dilation=5, padding=5, bias=False)
+ self.conv2_2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, dilation=7, padding=7, bias=False)
+ self.conv2_3 = nn.Conv2d(out_channels, out_channels, kernel_size=3, dilation=9, padding=9, bias=False)
+ self.conv2_4 = nn.Conv2d(out_channels, out_channels, kernel_size=3, dilation=11, padding=11, bias=False)
+ nn.init.constant_(self.conv1.bias, 0)
+
+ def forward(self, x):
+ x = self.relu1(x)
+ x = self.conv1(x)
+ x1 = self.conv2_1(x)
+ x2 = self.conv2_2(x)
+ x3 = self.conv2_3(x)
+ x4 = self.conv2_4(x)
+ return x1 + x2 + x3 + x4
+
+
+class MapReduce(nn.Module):
+ """
+ Reduce feature maps into a single edge map
+ """
+ def __init__(self, channels):
+ super(MapReduce, self).__init__()
+ self.conv = nn.Conv2d(channels, 1, kernel_size=1, padding=0)
+ nn.init.constant_(self.conv.bias, 0)
+
+ def forward(self, x):
+ return self.conv(x)
+
+
+class PDCBlock(nn.Module):
+ def __init__(self, pdc, inplane, ouplane, stride=1):
+ super(PDCBlock, self).__init__()
+ self.stride=stride
+
+ self.stride=stride
+ if self.stride > 1:
+ self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
+ self.shortcut = nn.Conv2d(inplane, ouplane, kernel_size=1, padding=0)
+ self.conv1 = Conv2d(pdc, inplane, inplane, kernel_size=3, padding=1, groups=inplane, bias=False)
+ self.relu2 = nn.ReLU()
+ self.conv2 = nn.Conv2d(inplane, ouplane, kernel_size=1, padding=0, bias=False)
+
+ def forward(self, x):
+ if self.stride > 1:
+ x = self.pool(x)
+ y = self.conv1(x)
+ y = self.relu2(y)
+ y = self.conv2(y)
+ if self.stride > 1:
+ x = self.shortcut(x)
+ y = y + x
+ return y
+
+class PDCBlock_converted(nn.Module):
+ """
+ CPDC, APDC can be converted to vanilla 3x3 convolution
+ RPDC can be converted to vanilla 5x5 convolution
+ """
+ def __init__(self, pdc, inplane, ouplane, stride=1):
+ super(PDCBlock_converted, self).__init__()
+ self.stride=stride
+
+ if self.stride > 1:
+ self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
+ self.shortcut = nn.Conv2d(inplane, ouplane, kernel_size=1, padding=0)
+ if pdc == 'rd':
+ self.conv1 = nn.Conv2d(inplane, inplane, kernel_size=5, padding=2, groups=inplane, bias=False)
+ else:
+ self.conv1 = nn.Conv2d(inplane, inplane, kernel_size=3, padding=1, groups=inplane, bias=False)
+ self.relu2 = nn.ReLU()
+ self.conv2 = nn.Conv2d(inplane, ouplane, kernel_size=1, padding=0, bias=False)
+
+ def forward(self, x):
+ if self.stride > 1:
+ x = self.pool(x)
+ y = self.conv1(x)
+ y = self.relu2(y)
+ y = self.conv2(y)
+ if self.stride > 1:
+ x = self.shortcut(x)
+ y = y + x
+ return y
+
+class PiDiNet(nn.Module):
+ def __init__(self, inplane, pdcs, dil=None, sa=False, convert=False):
+ super(PiDiNet, self).__init__()
+ self.sa = sa
+ if dil is not None:
+ assert isinstance(dil, int), 'dil should be an int'
+ self.dil = dil
+
+ self.fuseplanes = []
+
+ self.inplane = inplane
+ if convert:
+ if pdcs[0] == 'rd':
+ init_kernel_size = 5
+ init_padding = 2
+ else:
+ init_kernel_size = 3
+ init_padding = 1
+ self.init_block = nn.Conv2d(3, self.inplane,
+ kernel_size=init_kernel_size, padding=init_padding, bias=False)
+ block_class = PDCBlock_converted
+ else:
+ self.init_block = Conv2d(pdcs[0], 3, self.inplane, kernel_size=3, padding=1)
+ block_class = PDCBlock
+
+ self.block1_1 = block_class(pdcs[1], self.inplane, self.inplane)
+ self.block1_2 = block_class(pdcs[2], self.inplane, self.inplane)
+ self.block1_3 = block_class(pdcs[3], self.inplane, self.inplane)
+ self.fuseplanes.append(self.inplane) # C
+
+ inplane = self.inplane
+ self.inplane = self.inplane * 2
+ self.block2_1 = block_class(pdcs[4], inplane, self.inplane, stride=2)
+ self.block2_2 = block_class(pdcs[5], self.inplane, self.inplane)
+ self.block2_3 = block_class(pdcs[6], self.inplane, self.inplane)
+ self.block2_4 = block_class(pdcs[7], self.inplane, self.inplane)
+ self.fuseplanes.append(self.inplane) # 2C
+
+ inplane = self.inplane
+ self.inplane = self.inplane * 2
+ self.block3_1 = block_class(pdcs[8], inplane, self.inplane, stride=2)
+ self.block3_2 = block_class(pdcs[9], self.inplane, self.inplane)
+ self.block3_3 = block_class(pdcs[10], self.inplane, self.inplane)
+ self.block3_4 = block_class(pdcs[11], self.inplane, self.inplane)
+ self.fuseplanes.append(self.inplane) # 4C
+
+ self.block4_1 = block_class(pdcs[12], self.inplane, self.inplane, stride=2)
+ self.block4_2 = block_class(pdcs[13], self.inplane, self.inplane)
+ self.block4_3 = block_class(pdcs[14], self.inplane, self.inplane)
+ self.block4_4 = block_class(pdcs[15], self.inplane, self.inplane)
+ self.fuseplanes.append(self.inplane) # 4C
+
+ self.conv_reduces = nn.ModuleList()
+ if self.sa and self.dil is not None:
+ self.attentions = nn.ModuleList()
+ self.dilations = nn.ModuleList()
+ for i in range(4):
+ self.dilations.append(CDCM(self.fuseplanes[i], self.dil))
+ self.attentions.append(CSAM(self.dil))
+ self.conv_reduces.append(MapReduce(self.dil))
+ elif self.sa:
+ self.attentions = nn.ModuleList()
+ for i in range(4):
+ self.attentions.append(CSAM(self.fuseplanes[i]))
+ self.conv_reduces.append(MapReduce(self.fuseplanes[i]))
+ elif self.dil is not None:
+ self.dilations = nn.ModuleList()
+ for i in range(4):
+ self.dilations.append(CDCM(self.fuseplanes[i], self.dil))
+ self.conv_reduces.append(MapReduce(self.dil))
+ else:
+ for i in range(4):
+ self.conv_reduces.append(MapReduce(self.fuseplanes[i]))
+
+ self.classifier = nn.Conv2d(4, 1, kernel_size=1) # has bias
+ nn.init.constant_(self.classifier.weight, 0.25)
+ nn.init.constant_(self.classifier.bias, 0)
+
+ # print('initialization done')
+
+ def get_weights(self):
+ conv_weights = []
+ bn_weights = []
+ relu_weights = []
+ for pname, p in self.named_parameters():
+ if 'bn' in pname:
+ bn_weights.append(p)
+ elif 'relu' in pname:
+ relu_weights.append(p)
+ else:
+ conv_weights.append(p)
+
+ return conv_weights, bn_weights, relu_weights
+
+ def forward(self, x):
+ H, W = x.size()[2:]
+
+ x = self.init_block(x)
+
+ x1 = self.block1_1(x)
+ x1 = self.block1_2(x1)
+ x1 = self.block1_3(x1)
+
+ x2 = self.block2_1(x1)
+ x2 = self.block2_2(x2)
+ x2 = self.block2_3(x2)
+ x2 = self.block2_4(x2)
+
+ x3 = self.block3_1(x2)
+ x3 = self.block3_2(x3)
+ x3 = self.block3_3(x3)
+ x3 = self.block3_4(x3)
+
+ x4 = self.block4_1(x3)
+ x4 = self.block4_2(x4)
+ x4 = self.block4_3(x4)
+ x4 = self.block4_4(x4)
+
+ x_fuses = []
+ if self.sa and self.dil is not None:
+ for i, xi in enumerate([x1, x2, x3, x4]):
+ x_fuses.append(self.attentions[i](self.dilations[i](xi)))
+ elif self.sa:
+ for i, xi in enumerate([x1, x2, x3, x4]):
+ x_fuses.append(self.attentions[i](xi))
+ elif self.dil is not None:
+ for i, xi in enumerate([x1, x2, x3, x4]):
+ x_fuses.append(self.dilations[i](xi))
+ else:
+ x_fuses = [x1, x2, x3, x4]
+
+ e1 = self.conv_reduces[0](x_fuses[0])
+ e1 = F.interpolate(e1, (H, W), mode="bilinear", align_corners=False)
+
+ e2 = self.conv_reduces[1](x_fuses[1])
+ e2 = F.interpolate(e2, (H, W), mode="bilinear", align_corners=False)
+
+ e3 = self.conv_reduces[2](x_fuses[2])
+ e3 = F.interpolate(e3, (H, W), mode="bilinear", align_corners=False)
+
+ e4 = self.conv_reduces[3](x_fuses[3])
+ e4 = F.interpolate(e4, (H, W), mode="bilinear", align_corners=False)
+
+ outputs = [e1, e2, e3, e4]
+
+ output = self.classifier(torch.cat(outputs, dim=1))
+ #if not self.training:
+ # return torch.sigmoid(output)
+
+ outputs.append(output)
+ outputs = [torch.sigmoid(r) for r in outputs]
+ return outputs
+
+def config_model(model):
+ model_options = list(nets.keys())
+ assert model in model_options, \
+ 'unrecognized model, please choose from %s' % str(model_options)
+
+ # print(str(nets[model]))
+
+ pdcs = []
+ for i in range(16):
+ layer_name = 'layer%d' % i
+ op = nets[model][layer_name]
+ pdcs.append(createConvFunc(op))
+
+ return pdcs
+
+def pidinet():
+ pdcs = config_model('carv4')
+ dil = 24 #if args.dil else None
+ return PiDiNet(60, pdcs, dil=dil, sa=True)
+
+
+if __name__ == '__main__':
+ model = pidinet()
+ ckp = torch.load('table5_pidinet.pth')['state_dict']
+ model.load_state_dict({k.replace('module.',''):v for k, v in ckp.items()})
+ im = cv2.imread('examples/test_my/cat_v4.png')
+ im = img2tensor(im).unsqueeze(0)/255.
+ res = model(im)[-1]
+ res = res>0.5
+ res = res.float()
+ res = (res[0,0].cpu().data.numpy()*255.).astype(np.uint8)
+ print(res.shape)
+ cv2.imwrite('edge.png', res)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/processor.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/processor.py
new file mode 100644
index 0000000000000000000000000000000000000000..3d960c91c903c6bb10a002e38cd07415fff29a80
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/processor.py
@@ -0,0 +1,147 @@
+"""
+This file contains a Processor that can be used to process images with controlnet aux processors
+"""
+import io
+import logging
+from typing import Dict, Optional, Union
+
+from PIL import Image
+
+from controlnet_aux import (CannyDetector, ContentShuffleDetector, HEDdetector,
+ LeresDetector, LineartAnimeDetector,
+ LineartDetector, MediapipeFaceDetector,
+ MidasDetector, MLSDdetector, NormalBaeDetector,
+ OpenposeDetector, PidiNetDetector, ZoeDetector, TileDetector)
+
+LOGGER = logging.getLogger(__name__)
+
+
+MODELS = {
+ # checkpoint models
+ 'scribble_hed': {'class': HEDdetector, 'checkpoint': True},
+ 'softedge_hed': {'class': HEDdetector, 'checkpoint': True},
+ 'scribble_hedsafe': {'class': HEDdetector, 'checkpoint': True},
+ 'softedge_hedsafe': {'class': HEDdetector, 'checkpoint': True},
+ 'depth_midas': {'class': MidasDetector, 'checkpoint': True},
+ 'mlsd': {'class': MLSDdetector, 'checkpoint': True},
+ 'openpose': {'class': OpenposeDetector, 'checkpoint': True},
+ 'openpose_face': {'class': OpenposeDetector, 'checkpoint': True},
+ 'openpose_faceonly': {'class': OpenposeDetector, 'checkpoint': True},
+ 'openpose_full': {'class': OpenposeDetector, 'checkpoint': True},
+ 'openpose_hand': {'class': OpenposeDetector, 'checkpoint': True},
+ 'scribble_pidinet': {'class': PidiNetDetector, 'checkpoint': True},
+ 'softedge_pidinet': {'class': PidiNetDetector, 'checkpoint': True},
+ 'scribble_pidsafe': {'class': PidiNetDetector, 'checkpoint': True},
+ 'softedge_pidsafe': {'class': PidiNetDetector, 'checkpoint': True},
+ 'normal_bae': {'class': NormalBaeDetector, 'checkpoint': True},
+ 'lineart_coarse': {'class': LineartDetector, 'checkpoint': True},
+ 'lineart_realistic': {'class': LineartDetector, 'checkpoint': True},
+ 'lineart_anime': {'class': LineartAnimeDetector, 'checkpoint': True},
+ 'depth_zoe': {'class': ZoeDetector, 'checkpoint': True},
+ 'depth_leres': {'class': LeresDetector, 'checkpoint': True},
+ 'depth_leres++': {'class': LeresDetector, 'checkpoint': True},
+ # instantiate
+ 'shuffle': {'class': ContentShuffleDetector, 'checkpoint': False},
+ 'mediapipe_face': {'class': MediapipeFaceDetector, 'checkpoint': False},
+ 'canny': {'class': CannyDetector, 'checkpoint': False},
+ 'tile': {'class': TileDetector, 'checkpoint': False},
+}
+
+
+MODEL_PARAMS = {
+ 'scribble_hed': {'scribble': True},
+ 'softedge_hed': {'scribble': False},
+ 'scribble_hedsafe': {'scribble': True, 'safe': True},
+ 'softedge_hedsafe': {'scribble': False, 'safe': True},
+ 'depth_midas': {},
+ 'mlsd': {},
+ 'openpose': {'include_body': True, 'include_hand': False, 'include_face': False},
+ 'openpose_face': {'include_body': True, 'include_hand': False, 'include_face': True},
+ 'openpose_faceonly': {'include_body': False, 'include_hand': False, 'include_face': True},
+ 'openpose_full': {'include_body': True, 'include_hand': True, 'include_face': True},
+ 'openpose_hand': {'include_body': False, 'include_hand': True, 'include_face': False},
+ 'scribble_pidinet': {'safe': False, 'scribble': True},
+ 'softedge_pidinet': {'safe': False, 'scribble': False},
+ 'scribble_pidsafe': {'safe': True, 'scribble': True},
+ 'softedge_pidsafe': {'safe': True, 'scribble': False},
+ 'normal_bae': {},
+ 'lineart_realistic': {'coarse': False},
+ 'lineart_coarse': {'coarse': True},
+ 'lineart_anime': {},
+ 'canny': {},
+ 'shuffle': {},
+ 'depth_zoe': {},
+ 'depth_leres': {'boost': False},
+ 'depth_leres++': {'boost': True},
+ 'mediapipe_face': {},
+ 'tile': {},
+}
+
+CHOICES = f"Choices for the processor are {list(MODELS.keys())}"
+
+
+class Processor:
+ def __init__(self, processor_id: str, params: Optional[Dict] = None) -> None:
+ """Processor that can be used to process images with controlnet aux processors
+
+ Args:
+ processor_id (str): processor name, options are 'hed, midas, mlsd, openpose,
+ pidinet, normalbae, lineart, lineart_coarse, lineart_anime,
+ canny, content_shuffle, zoe, mediapipe_face, tile'
+ params (Optional[Dict]): parameters for the processor
+ """
+ LOGGER.info("Loading %s".format(processor_id))
+
+ if processor_id not in MODELS:
+ raise ValueError(f"{processor_id} is not a valid processor id. Please make sure to choose one of {', '.join(MODELS.keys())}")
+
+ self.processor_id = processor_id
+ self.processor = self.load_processor(self.processor_id)
+
+ # load default params
+ self.params = MODEL_PARAMS[self.processor_id]
+ # update with user params
+ if params:
+ self.params.update(params)
+
+ def load_processor(self, processor_id: str) -> 'Processor':
+ """Load controlnet aux processors
+
+ Args:
+ processor_id (str): processor name
+
+ Returns:
+ Processor: controlnet aux processor
+ """
+ processor = MODELS[processor_id]['class']
+
+ # check if the proecssor is a checkpoint model
+ if MODELS[processor_id]['checkpoint']:
+ processor = processor.from_pretrained("lllyasviel/Annotators")
+ else:
+ processor = processor()
+ return processor
+
+ def __call__(self, image: Union[Image.Image, bytes],
+ to_pil: bool = True) -> Union[Image.Image, bytes]:
+ """processes an image with a controlnet aux processor
+
+ Args:
+ image (Union[Image.Image, bytes]): input image in bytes or PIL Image
+ to_pil (bool): whether to return bytes or PIL Image
+
+ Returns:
+ Union[Image.Image, bytes]: processed image in bytes or PIL Image
+ """
+ # check if bytes or PIL Image
+ if isinstance(image, bytes):
+ image = Image.open(io.BytesIO(image)).convert("RGB")
+
+ processed_image = self.processor(image, **self.params)
+
+ if to_pil:
+ return processed_image
+ else:
+ output_bytes = io.BytesIO()
+ processed_image.save(output_bytes, format='JPEG')
+ return output_bytes.getvalue()
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..191a2185d40f5bc43996dd79d190dbeca22e8205
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/__init__.py
@@ -0,0 +1,74 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+# All rights reserved.
+
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+import os
+import warnings
+from typing import Union
+
+import cv2
+import numpy as np
+import torch
+from PIL import Image
+
+from controlnet_aux.util import HWC3, common_input_validate, resize_image_with_pad, annotator_ckpts_path, custom_hf_download
+from .automatic_mask_generator import SamAutomaticMaskGenerator
+from .build_sam import sam_model_registry
+
+
+class SamDetector:
+ def __init__(self, mask_generator: SamAutomaticMaskGenerator):
+ self.mask_generator = mask_generator
+
+ @classmethod
+ def from_pretrained(cls, pretrained_model_or_path, model_type="vit_h", filename="sam_vit_h_4b8939.pth", subfolder=None, cache_dir=annotator_ckpts_path):
+ """
+ Possible model_type : vit_h, vit_l, vit_b, vit_t
+ download weights from https://github.com/facebookresearch/segment-anything
+ """
+ model_path = custom_hf_download(pretrained_model_or_path, filename, cache_dir=cache_dir)
+
+ sam = sam_model_registry[model_type](checkpoint=model_path)
+ mask_generator = SamAutomaticMaskGenerator(sam)
+
+ return cls(mask_generator)
+
+ def to(self, device):
+ model = self.mask_generator.predictor.model.to(device)
+ model.train(False) #Update attention_bias in https://github.com/Fannovel16/comfyui_controlnet_aux/blob/main/src/controlnet_aux/segment_anything/modeling/tiny_vit_sam.py#L251
+ self.mask_generator = SamAutomaticMaskGenerator(model)
+ return self
+
+
+ def show_anns(self, anns):
+ if len(anns) == 0:
+ return
+ sorted_anns = sorted(anns, key=(lambda x: x['area']), reverse=True)
+ h, w = anns[0]['segmentation'].shape
+ final_img = Image.fromarray(np.zeros((h, w, 3), dtype=np.uint8), mode="RGB")
+ for ann in sorted_anns:
+ m = ann['segmentation']
+ img = np.empty((m.shape[0], m.shape[1], 3), dtype=np.uint8)
+ for i in range(3):
+ img[:,:,i] = np.random.randint(255, dtype=np.uint8)
+ final_img.paste(Image.fromarray(img, mode="RGB"), (0, 0), Image.fromarray(np.uint8(m*255)))
+
+ return np.array(final_img, dtype=np.uint8)
+
+ def __call__(self, input_image: Union[np.ndarray, Image.Image]=None, detect_resolution=512, output_type="pil", upscale_method="INTER_CUBIC", **kwargs) -> Image.Image:
+ input_image, output_type = common_input_validate(input_image, output_type, **kwargs)
+ input_image, remove_pad = resize_image_with_pad(input_image, detect_resolution, upscale_method)
+
+ # Generate Masks
+ masks = self.mask_generator.generate(input_image)
+ # Create map
+ map = self.show_anns(masks)
+
+ detected_map = HWC3(remove_pad(map))
+
+ if output_type == "pil":
+ detected_map = Image.fromarray(detected_map)
+
+ return detected_map
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/automatic_mask_generator.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/automatic_mask_generator.py
new file mode 100644
index 0000000000000000000000000000000000000000..d7ac3589d81890aca612023c85b46c0c8176195d
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/automatic_mask_generator.py
@@ -0,0 +1,372 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+# All rights reserved.
+
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+import numpy as np
+import torch
+from torchvision.ops.boxes import batched_nms, box_area # type: ignore
+
+from typing import Any, Dict, List, Optional, Tuple
+
+from .modeling import Sam
+from .predictor import SamPredictor
+from .utils.amg import (
+ MaskData,
+ area_from_rle,
+ batch_iterator,
+ batched_mask_to_box,
+ box_xyxy_to_xywh,
+ build_all_layer_point_grids,
+ calculate_stability_score,
+ coco_encode_rle,
+ generate_crop_boxes,
+ is_box_near_crop_edge,
+ mask_to_rle_pytorch,
+ remove_small_regions,
+ rle_to_mask,
+ uncrop_boxes_xyxy,
+ uncrop_masks,
+ uncrop_points,
+)
+
+
+class SamAutomaticMaskGenerator:
+ def __init__(
+ self,
+ model: Sam,
+ points_per_side: Optional[int] = 32,
+ points_per_batch: int = 64,
+ pred_iou_thresh: float = 0.88,
+ stability_score_thresh: float = 0.95,
+ stability_score_offset: float = 1.0,
+ box_nms_thresh: float = 0.7,
+ crop_n_layers: int = 0,
+ crop_nms_thresh: float = 0.7,
+ crop_overlap_ratio: float = 512 / 1500,
+ crop_n_points_downscale_factor: int = 1,
+ point_grids: Optional[List[np.ndarray]] = None,
+ min_mask_region_area: int = 0,
+ output_mode: str = "binary_mask",
+ ) -> None:
+ """
+ Using a SAM model, generates masks for the entire image.
+ Generates a grid of point prompts over the image, then filters
+ low quality and duplicate masks. The default settings are chosen
+ for SAM with a ViT-H backbone.
+
+ Arguments:
+ model (Sam): The SAM model to use for mask prediction.
+ points_per_side (int or None): The number of points to be sampled
+ along one side of the image. The total number of points is
+ points_per_side**2. If None, 'point_grids' must provide explicit
+ point sampling.
+ points_per_batch (int): Sets the number of points run simultaneously
+ by the model. Higher numbers may be faster but use more GPU memory.
+ pred_iou_thresh (float): A filtering threshold in [0,1], using the
+ model's predicted mask quality.
+ stability_score_thresh (float): A filtering threshold in [0,1], using
+ the stability of the mask under changes to the cutoff used to binarize
+ the model's mask predictions.
+ stability_score_offset (float): The amount to shift the cutoff when
+ calculated the stability score.
+ box_nms_thresh (float): The box IoU cutoff used by non-maximal
+ suppression to filter duplicate masks.
+ crop_n_layers (int): If >0, mask prediction will be run again on
+ crops of the image. Sets the number of layers to run, where each
+ layer has 2**i_layer number of image crops.
+ crop_nms_thresh (float): The box IoU cutoff used by non-maximal
+ suppression to filter duplicate masks between different crops.
+ crop_overlap_ratio (float): Sets the degree to which crops overlap.
+ In the first crop layer, crops will overlap by this fraction of
+ the image length. Later layers with more crops scale down this overlap.
+ crop_n_points_downscale_factor (int): The number of points-per-side
+ sampled in layer n is scaled down by crop_n_points_downscale_factor**n.
+ point_grids (list(np.ndarray) or None): A list over explicit grids
+ of points used for sampling, normalized to [0,1]. The nth grid in the
+ list is used in the nth crop layer. Exclusive with points_per_side.
+ min_mask_region_area (int): If >0, postprocessing will be applied
+ to remove disconnected regions and holes in masks with area smaller
+ than min_mask_region_area. Requires opencv.
+ output_mode (str): The form masks are returned in. Can be 'binary_mask',
+ 'uncompressed_rle', or 'coco_rle'. 'coco_rle' requires pycocotools.
+ For large resolutions, 'binary_mask' may consume large amounts of
+ memory.
+ """
+
+ assert (points_per_side is None) != (
+ point_grids is None
+ ), "Exactly one of points_per_side or point_grid must be provided."
+ if points_per_side is not None:
+ self.point_grids = build_all_layer_point_grids(
+ points_per_side,
+ crop_n_layers,
+ crop_n_points_downscale_factor,
+ )
+ elif point_grids is not None:
+ self.point_grids = point_grids
+ else:
+ raise ValueError("Can't have both points_per_side and point_grid be None.")
+
+ assert output_mode in [
+ "binary_mask",
+ "uncompressed_rle",
+ "coco_rle",
+ ], f"Unknown output_mode {output_mode}."
+ if output_mode == "coco_rle":
+ from custom_pycocotools import mask as mask_utils # type: ignore # noqa: F401
+
+ if min_mask_region_area > 0:
+ import cv2 # type: ignore # noqa: F401
+
+ self.predictor = SamPredictor(model)
+ self.points_per_batch = points_per_batch
+ self.pred_iou_thresh = pred_iou_thresh
+ self.stability_score_thresh = stability_score_thresh
+ self.stability_score_offset = stability_score_offset
+ self.box_nms_thresh = box_nms_thresh
+ self.crop_n_layers = crop_n_layers
+ self.crop_nms_thresh = crop_nms_thresh
+ self.crop_overlap_ratio = crop_overlap_ratio
+ self.crop_n_points_downscale_factor = crop_n_points_downscale_factor
+ self.min_mask_region_area = min_mask_region_area
+ self.output_mode = output_mode
+
+ @torch.no_grad()
+ def generate(self, image: np.ndarray) -> List[Dict[str, Any]]:
+ """
+ Generates masks for the given image.
+
+ Arguments:
+ image (np.ndarray): The image to generate masks for, in HWC uint8 format.
+
+ Returns:
+ list(dict(str, any)): A list over records for masks. Each record is
+ a dict containing the following keys:
+ segmentation (dict(str, any) or np.ndarray): The mask. If
+ output_mode='binary_mask', is an array of shape HW. Otherwise,
+ is a dictionary containing the RLE.
+ bbox (list(float)): The box around the mask, in XYWH format.
+ area (int): The area in pixels of the mask.
+ predicted_iou (float): The model's own prediction of the mask's
+ quality. This is filtered by the pred_iou_thresh parameter.
+ point_coords (list(list(float))): The point coordinates input
+ to the model to generate this mask.
+ stability_score (float): A measure of the mask's quality. This
+ is filtered on using the stability_score_thresh parameter.
+ crop_box (list(float)): The crop of the image used to generate
+ the mask, given in XYWH format.
+ """
+
+ # Generate masks
+ mask_data = self._generate_masks(image)
+
+ # Filter small disconnected regions and holes in masks
+ if self.min_mask_region_area > 0:
+ mask_data = self.postprocess_small_regions(
+ mask_data,
+ self.min_mask_region_area,
+ max(self.box_nms_thresh, self.crop_nms_thresh),
+ )
+
+ # Encode masks
+ if self.output_mode == "coco_rle":
+ mask_data["segmentations"] = [coco_encode_rle(rle) for rle in mask_data["rles"]]
+ elif self.output_mode == "binary_mask":
+ mask_data["segmentations"] = [rle_to_mask(rle) for rle in mask_data["rles"]]
+ else:
+ mask_data["segmentations"] = mask_data["rles"]
+
+ # Write mask records
+ curr_anns = []
+ for idx in range(len(mask_data["segmentations"])):
+ ann = {
+ "segmentation": mask_data["segmentations"][idx],
+ "area": area_from_rle(mask_data["rles"][idx]),
+ "bbox": box_xyxy_to_xywh(mask_data["boxes"][idx]).tolist(),
+ "predicted_iou": mask_data["iou_preds"][idx].item(),
+ "point_coords": [mask_data["points"][idx].tolist()],
+ "stability_score": mask_data["stability_score"][idx].item(),
+ "crop_box": box_xyxy_to_xywh(mask_data["crop_boxes"][idx]).tolist(),
+ }
+ curr_anns.append(ann)
+
+ return curr_anns
+
+ def _generate_masks(self, image: np.ndarray) -> MaskData:
+ orig_size = image.shape[:2]
+ crop_boxes, layer_idxs = generate_crop_boxes(
+ orig_size, self.crop_n_layers, self.crop_overlap_ratio
+ )
+
+ # Iterate over image crops
+ data = MaskData()
+ for crop_box, layer_idx in zip(crop_boxes, layer_idxs):
+ crop_data = self._process_crop(image, crop_box, layer_idx, orig_size)
+ data.cat(crop_data)
+
+ # Remove duplicate masks between crops
+ if len(crop_boxes) > 1:
+ # Prefer masks from smaller crops
+ scores = 1 / box_area(data["crop_boxes"])
+ scores = scores.to(data["boxes"].device)
+ keep_by_nms = batched_nms(
+ data["boxes"].float(),
+ scores,
+ torch.zeros_like(data["boxes"][:, 0]), # categories
+ iou_threshold=self.crop_nms_thresh,
+ )
+ data.filter(keep_by_nms)
+
+ data.to_numpy()
+ return data
+
+ def _process_crop(
+ self,
+ image: np.ndarray,
+ crop_box: List[int],
+ crop_layer_idx: int,
+ orig_size: Tuple[int, ...],
+ ) -> MaskData:
+ # Crop the image and calculate embeddings
+ x0, y0, x1, y1 = crop_box
+ cropped_im = image[y0:y1, x0:x1, :]
+ cropped_im_size = cropped_im.shape[:2]
+ self.predictor.set_image(cropped_im)
+
+ # Get points for this crop
+ points_scale = np.array(cropped_im_size)[None, ::-1]
+ points_for_image = self.point_grids[crop_layer_idx] * points_scale
+
+ # Generate masks for this crop in batches
+ data = MaskData()
+ for (points,) in batch_iterator(self.points_per_batch, points_for_image):
+ batch_data = self._process_batch(points, cropped_im_size, crop_box, orig_size)
+ data.cat(batch_data)
+ del batch_data
+ self.predictor.reset_image()
+
+ # Remove duplicates within this crop.
+ keep_by_nms = batched_nms(
+ data["boxes"].float(),
+ data["iou_preds"],
+ torch.zeros_like(data["boxes"][:, 0]), # categories
+ iou_threshold=self.box_nms_thresh,
+ )
+ data.filter(keep_by_nms)
+
+ # Return to the original image frame
+ data["boxes"] = uncrop_boxes_xyxy(data["boxes"], crop_box)
+ data["points"] = uncrop_points(data["points"], crop_box)
+ data["crop_boxes"] = torch.tensor([crop_box for _ in range(len(data["rles"]))])
+
+ return data
+
+ def _process_batch(
+ self,
+ points: np.ndarray,
+ im_size: Tuple[int, ...],
+ crop_box: List[int],
+ orig_size: Tuple[int, ...],
+ ) -> MaskData:
+ orig_h, orig_w = orig_size
+
+ # Run model on this batch
+ transformed_points = self.predictor.transform.apply_coords(points, im_size)
+ in_points = torch.as_tensor(transformed_points, device=self.predictor.device)
+ in_labels = torch.ones(in_points.shape[0], dtype=torch.int, device=in_points.device)
+ masks, iou_preds, _ = self.predictor.predict_torch(
+ in_points[:, None, :],
+ in_labels[:, None],
+ multimask_output=True,
+ return_logits=True,
+ )
+
+ # Serialize predictions and store in MaskData
+ data = MaskData(
+ masks=masks.flatten(0, 1),
+ iou_preds=iou_preds.flatten(0, 1),
+ points=torch.as_tensor(points.repeat(masks.shape[1], axis=0)),
+ )
+ del masks
+
+ # Filter by predicted IoU
+ if self.pred_iou_thresh > 0.0:
+ keep_mask = data["iou_preds"] > self.pred_iou_thresh
+ data.filter(keep_mask)
+
+ # Calculate stability score
+ data["stability_score"] = calculate_stability_score(
+ data["masks"], self.predictor.model.mask_threshold, self.stability_score_offset
+ )
+ if self.stability_score_thresh > 0.0:
+ keep_mask = data["stability_score"] >= self.stability_score_thresh
+ data.filter(keep_mask)
+
+ # Threshold masks and calculate boxes
+ data["masks"] = data["masks"] > self.predictor.model.mask_threshold
+ data["boxes"] = batched_mask_to_box(data["masks"])
+
+ # Filter boxes that touch crop boundaries
+ keep_mask = ~is_box_near_crop_edge(data["boxes"], crop_box, [0, 0, orig_w, orig_h])
+ if not torch.all(keep_mask):
+ data.filter(keep_mask)
+
+ # Compress to RLE
+ data["masks"] = uncrop_masks(data["masks"], crop_box, orig_h, orig_w)
+ data["rles"] = mask_to_rle_pytorch(data["masks"])
+ del data["masks"]
+
+ return data
+
+ @staticmethod
+ def postprocess_small_regions(
+ mask_data: MaskData, min_area: int, nms_thresh: float
+ ) -> MaskData:
+ """
+ Removes small disconnected regions and holes in masks, then reruns
+ box NMS to remove any new duplicates.
+
+ Edits mask_data in place.
+
+ Requires open-cv as a dependency.
+ """
+ if len(mask_data["rles"]) == 0:
+ return mask_data
+
+ # Filter small disconnected regions and holes
+ new_masks = []
+ scores = []
+ for rle in mask_data["rles"]:
+ mask = rle_to_mask(rle)
+
+ mask, changed = remove_small_regions(mask, min_area, mode="holes")
+ unchanged = not changed
+ mask, changed = remove_small_regions(mask, min_area, mode="islands")
+ unchanged = unchanged and not changed
+
+ new_masks.append(torch.as_tensor(mask).unsqueeze(0))
+ # Give score=0 to changed masks and score=1 to unchanged masks
+ # so NMS will prefer ones that didn't need postprocessing
+ scores.append(float(unchanged))
+
+ # Recalculate boxes and remove any new duplicates
+ masks = torch.cat(new_masks, dim=0)
+ boxes = batched_mask_to_box(masks)
+ keep_by_nms = batched_nms(
+ boxes.float(),
+ torch.as_tensor(scores),
+ torch.zeros_like(boxes[:, 0]), # categories
+ iou_threshold=nms_thresh,
+ )
+
+ # Only recalculate RLEs for masks that have changed
+ for i_mask in keep_by_nms:
+ if scores[i_mask] == 0.0:
+ mask_torch = masks[i_mask].unsqueeze(0)
+ mask_data["rles"][i_mask] = mask_to_rle_pytorch(mask_torch)[0]
+ mask_data["boxes"][i_mask] = boxes[i_mask] # update res directly
+ mask_data.filter(keep_by_nms)
+
+ return mask_data
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/build_sam.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/build_sam.py
new file mode 100644
index 0000000000000000000000000000000000000000..9a52c506b69d29ee2356cc0e62274fe6f6ee075b
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/build_sam.py
@@ -0,0 +1,159 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+# All rights reserved.
+
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+import torch
+
+from functools import partial
+
+from .modeling import ImageEncoderViT, MaskDecoder, PromptEncoder, Sam, TwoWayTransformer, TinyViT
+
+
+def build_sam_vit_h(checkpoint=None):
+ return _build_sam(
+ encoder_embed_dim=1280,
+ encoder_depth=32,
+ encoder_num_heads=16,
+ encoder_global_attn_indexes=[7, 15, 23, 31],
+ checkpoint=checkpoint,
+ )
+
+
+build_sam = build_sam_vit_h
+
+
+def build_sam_vit_l(checkpoint=None):
+ return _build_sam(
+ encoder_embed_dim=1024,
+ encoder_depth=24,
+ encoder_num_heads=16,
+ encoder_global_attn_indexes=[5, 11, 17, 23],
+ checkpoint=checkpoint,
+ )
+
+
+def build_sam_vit_b(checkpoint=None):
+ return _build_sam(
+ encoder_embed_dim=768,
+ encoder_depth=12,
+ encoder_num_heads=12,
+ encoder_global_attn_indexes=[2, 5, 8, 11],
+ checkpoint=checkpoint,
+ )
+
+
+def build_sam_vit_t(checkpoint=None):
+ prompt_embed_dim = 256
+ image_size = 1024
+ vit_patch_size = 16
+ image_embedding_size = image_size // vit_patch_size
+ mobile_sam = Sam(
+ image_encoder=TinyViT(img_size=1024, in_chans=3, num_classes=1000,
+ embed_dims=[64, 128, 160, 320],
+ depths=[2, 2, 6, 2],
+ num_heads=[2, 4, 5, 10],
+ window_sizes=[7, 7, 14, 7],
+ mlp_ratio=4.,
+ drop_rate=0.,
+ drop_path_rate=0.0,
+ use_checkpoint=False,
+ mbconv_expand_ratio=4.0,
+ local_conv_size=3,
+ layer_lr_decay=0.8
+ ),
+ prompt_encoder=PromptEncoder(
+ embed_dim=prompt_embed_dim,
+ image_embedding_size=(image_embedding_size, image_embedding_size),
+ input_image_size=(image_size, image_size),
+ mask_in_chans=16,
+ ),
+ mask_decoder=MaskDecoder(
+ num_multimask_outputs=3,
+ transformer=TwoWayTransformer(
+ depth=2,
+ embedding_dim=prompt_embed_dim,
+ mlp_dim=2048,
+ num_heads=8,
+ ),
+ transformer_dim=prompt_embed_dim,
+ iou_head_depth=3,
+ iou_head_hidden_dim=256,
+ ),
+ pixel_mean=[123.675, 116.28, 103.53],
+ pixel_std=[58.395, 57.12, 57.375],
+ )
+
+ mobile_sam.eval()
+ if checkpoint is not None:
+ with open(checkpoint, "rb") as f:
+ state_dict = torch.load(f)
+ mobile_sam.load_state_dict(state_dict)
+ return mobile_sam
+
+
+sam_model_registry = {
+ "default": build_sam_vit_h,
+ "vit_h": build_sam_vit_h,
+ "vit_l": build_sam_vit_l,
+ "vit_b": build_sam_vit_b,
+ "vit_t": build_sam_vit_t,
+}
+
+
+def _build_sam(
+ encoder_embed_dim,
+ encoder_depth,
+ encoder_num_heads,
+ encoder_global_attn_indexes,
+ checkpoint=None,
+):
+ prompt_embed_dim = 256
+ image_size = 1024
+ vit_patch_size = 16
+ image_embedding_size = image_size // vit_patch_size
+ sam = Sam(
+ image_encoder=ImageEncoderViT(
+ depth=encoder_depth,
+ embed_dim=encoder_embed_dim,
+ img_size=image_size,
+ mlp_ratio=4,
+ norm_layer=partial(torch.nn.LayerNorm, eps=1e-6),
+ num_heads=encoder_num_heads,
+ patch_size=vit_patch_size,
+ qkv_bias=True,
+ use_rel_pos=True,
+ global_attn_indexes=encoder_global_attn_indexes,
+ window_size=14,
+ out_chans=prompt_embed_dim,
+ ),
+ prompt_encoder=PromptEncoder(
+ embed_dim=prompt_embed_dim,
+ image_embedding_size=(image_embedding_size, image_embedding_size),
+ input_image_size=(image_size, image_size),
+ mask_in_chans=16,
+ ),
+ mask_decoder=MaskDecoder(
+ num_multimask_outputs=3,
+ transformer=TwoWayTransformer(
+ depth=2,
+ embedding_dim=prompt_embed_dim,
+ mlp_dim=2048,
+ num_heads=8,
+ ),
+ transformer_dim=prompt_embed_dim,
+ iou_head_depth=3,
+ iou_head_hidden_dim=256,
+ ),
+ pixel_mean=[123.675, 116.28, 103.53],
+ pixel_std=[58.395, 57.12, 57.375],
+ )
+ sam.eval()
+ if checkpoint is not None:
+ with open(checkpoint, "rb") as f:
+ state_dict = torch.load(f)
+ sam.load_state_dict(state_dict)
+ return sam
+
+
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/modeling/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/modeling/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..7aa261b8356b8c1174139c19782657abca0cfec2
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/modeling/__init__.py
@@ -0,0 +1,12 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+# All rights reserved.
+
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+from .sam import Sam
+from .image_encoder import ImageEncoderViT
+from .mask_decoder import MaskDecoder
+from .prompt_encoder import PromptEncoder
+from .transformer import TwoWayTransformer
+from .tiny_vit_sam import TinyViT
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/modeling/common.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/modeling/common.py
new file mode 100644
index 0000000000000000000000000000000000000000..2bf15236a3eb24d8526073bc4fa2b274cccb3f96
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/modeling/common.py
@@ -0,0 +1,43 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+# All rights reserved.
+
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+import torch
+import torch.nn as nn
+
+from typing import Type
+
+
+class MLPBlock(nn.Module):
+ def __init__(
+ self,
+ embedding_dim: int,
+ mlp_dim: int,
+ act: Type[nn.Module] = nn.GELU,
+ ) -> None:
+ super().__init__()
+ self.lin1 = nn.Linear(embedding_dim, mlp_dim)
+ self.lin2 = nn.Linear(mlp_dim, embedding_dim)
+ self.act = act()
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ return self.lin2(self.act(self.lin1(x)))
+
+
+# From https://github.com/facebookresearch/detectron2/blob/main/detectron2/layers/batch_norm.py # noqa
+# Itself from https://github.com/facebookresearch/ConvNeXt/blob/d1fa8f6fef0a165b27399986cc2bdacc92777e40/models/convnext.py#L119 # noqa
+class LayerNorm2d(nn.Module):
+ def __init__(self, num_channels: int, eps: float = 1e-6) -> None:
+ super().__init__()
+ self.weight = nn.Parameter(torch.ones(num_channels))
+ self.bias = nn.Parameter(torch.zeros(num_channels))
+ self.eps = eps
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ u = x.mean(1, keepdim=True)
+ s = (x - u).pow(2).mean(1, keepdim=True)
+ x = (x - u) / torch.sqrt(s + self.eps)
+ x = self.weight[:, None, None] * x + self.bias[:, None, None]
+ return x
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/modeling/image_encoder.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/modeling/image_encoder.py
new file mode 100644
index 0000000000000000000000000000000000000000..66351d9d7c589be693f4b3485901d3bdfed54d4a
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/modeling/image_encoder.py
@@ -0,0 +1,395 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+# All rights reserved.
+
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+
+from typing import Optional, Tuple, Type
+
+from .common import LayerNorm2d, MLPBlock
+
+
+# This class and its supporting functions below lightly adapted from the ViTDet backbone available at: https://github.com/facebookresearch/detectron2/blob/main/detectron2/modeling/backbone/vit.py # noqa
+class ImageEncoderViT(nn.Module):
+ def __init__(
+ self,
+ img_size: int = 1024,
+ patch_size: int = 16,
+ in_chans: int = 3,
+ embed_dim: int = 768,
+ depth: int = 12,
+ num_heads: int = 12,
+ mlp_ratio: float = 4.0,
+ out_chans: int = 256,
+ qkv_bias: bool = True,
+ norm_layer: Type[nn.Module] = nn.LayerNorm,
+ act_layer: Type[nn.Module] = nn.GELU,
+ use_abs_pos: bool = True,
+ use_rel_pos: bool = False,
+ rel_pos_zero_init: bool = True,
+ window_size: int = 0,
+ global_attn_indexes: Tuple[int, ...] = (),
+ ) -> None:
+ """
+ Args:
+ img_size (int): Input image size.
+ patch_size (int): Patch size.
+ in_chans (int): Number of input image channels.
+ embed_dim (int): Patch embedding dimension.
+ depth (int): Depth of ViT.
+ num_heads (int): Number of attention heads in each ViT block.
+ mlp_ratio (float): Ratio of mlp hidden dim to embedding dim.
+ qkv_bias (bool): If True, add a learnable bias to query, key, value.
+ norm_layer (nn.Module): Normalization layer.
+ act_layer (nn.Module): Activation layer.
+ use_abs_pos (bool): If True, use absolute positional embeddings.
+ use_rel_pos (bool): If True, add relative positional embeddings to the attention map.
+ rel_pos_zero_init (bool): If True, zero initialize relative positional parameters.
+ window_size (int): Window size for window attention blocks.
+ global_attn_indexes (list): Indexes for blocks using global attention.
+ """
+ super().__init__()
+ self.img_size = img_size
+
+ self.patch_embed = PatchEmbed(
+ kernel_size=(patch_size, patch_size),
+ stride=(patch_size, patch_size),
+ in_chans=in_chans,
+ embed_dim=embed_dim,
+ )
+
+ self.pos_embed: Optional[nn.Parameter] = None
+ if use_abs_pos:
+ # Initialize absolute positional embedding with pretrain image size.
+ self.pos_embed = nn.Parameter(
+ torch.zeros(1, img_size // patch_size, img_size // patch_size, embed_dim)
+ )
+
+ self.blocks = nn.ModuleList()
+ for i in range(depth):
+ block = Block(
+ dim=embed_dim,
+ num_heads=num_heads,
+ mlp_ratio=mlp_ratio,
+ qkv_bias=qkv_bias,
+ norm_layer=norm_layer,
+ act_layer=act_layer,
+ use_rel_pos=use_rel_pos,
+ rel_pos_zero_init=rel_pos_zero_init,
+ window_size=window_size if i not in global_attn_indexes else 0,
+ input_size=(img_size // patch_size, img_size // patch_size),
+ )
+ self.blocks.append(block)
+
+ self.neck = nn.Sequential(
+ nn.Conv2d(
+ embed_dim,
+ out_chans,
+ kernel_size=1,
+ bias=False,
+ ),
+ LayerNorm2d(out_chans),
+ nn.Conv2d(
+ out_chans,
+ out_chans,
+ kernel_size=3,
+ padding=1,
+ bias=False,
+ ),
+ LayerNorm2d(out_chans),
+ )
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ x = self.patch_embed(x)
+ if self.pos_embed is not None:
+ x = x + self.pos_embed
+
+ for blk in self.blocks:
+ x = blk(x)
+
+ x = self.neck(x.permute(0, 3, 1, 2))
+
+ return x
+
+
+class Block(nn.Module):
+ """Transformer blocks with support of window attention and residual propagation blocks"""
+
+ def __init__(
+ self,
+ dim: int,
+ num_heads: int,
+ mlp_ratio: float = 4.0,
+ qkv_bias: bool = True,
+ norm_layer: Type[nn.Module] = nn.LayerNorm,
+ act_layer: Type[nn.Module] = nn.GELU,
+ use_rel_pos: bool = False,
+ rel_pos_zero_init: bool = True,
+ window_size: int = 0,
+ input_size: Optional[Tuple[int, int]] = None,
+ ) -> None:
+ """
+ Args:
+ dim (int): Number of input channels.
+ num_heads (int): Number of attention heads in each ViT block.
+ mlp_ratio (float): Ratio of mlp hidden dim to embedding dim.
+ qkv_bias (bool): If True, add a learnable bias to query, key, value.
+ norm_layer (nn.Module): Normalization layer.
+ act_layer (nn.Module): Activation layer.
+ use_rel_pos (bool): If True, add relative positional embeddings to the attention map.
+ rel_pos_zero_init (bool): If True, zero initialize relative positional parameters.
+ window_size (int): Window size for window attention blocks. If it equals 0, then
+ use global attention.
+ input_size (tuple(int, int) or None): Input resolution for calculating the relative
+ positional parameter size.
+ """
+ super().__init__()
+ self.norm1 = norm_layer(dim)
+ self.attn = Attention(
+ dim,
+ num_heads=num_heads,
+ qkv_bias=qkv_bias,
+ use_rel_pos=use_rel_pos,
+ rel_pos_zero_init=rel_pos_zero_init,
+ input_size=input_size if window_size == 0 else (window_size, window_size),
+ )
+
+ self.norm2 = norm_layer(dim)
+ self.mlp = MLPBlock(embedding_dim=dim, mlp_dim=int(dim * mlp_ratio), act=act_layer)
+
+ self.window_size = window_size
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ shortcut = x
+ x = self.norm1(x)
+ # Window partition
+ if self.window_size > 0:
+ H, W = x.shape[1], x.shape[2]
+ x, pad_hw = window_partition(x, self.window_size)
+
+ x = self.attn(x)
+ # Reverse window partition
+ if self.window_size > 0:
+ x = window_unpartition(x, self.window_size, pad_hw, (H, W))
+
+ x = shortcut + x
+ x = x + self.mlp(self.norm2(x))
+
+ return x
+
+
+class Attention(nn.Module):
+ """Multi-head Attention block with relative position embeddings."""
+
+ def __init__(
+ self,
+ dim: int,
+ num_heads: int = 8,
+ qkv_bias: bool = True,
+ use_rel_pos: bool = False,
+ rel_pos_zero_init: bool = True,
+ input_size: Optional[Tuple[int, int]] = None,
+ ) -> None:
+ """
+ Args:
+ dim (int): Number of input channels.
+ num_heads (int): Number of attention heads.
+ qkv_bias (bool): If True, add a learnable bias to query, key, value.
+ rel_pos (bool): If True, add relative positional embeddings to the attention map.
+ rel_pos_zero_init (bool): If True, zero initialize relative positional parameters.
+ input_size (tuple(int, int) or None): Input resolution for calculating the relative
+ positional parameter size.
+ """
+ super().__init__()
+ self.num_heads = num_heads
+ head_dim = dim // num_heads
+ self.scale = head_dim**-0.5
+
+ self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)
+ self.proj = nn.Linear(dim, dim)
+
+ self.use_rel_pos = use_rel_pos
+ if self.use_rel_pos:
+ assert (
+ input_size is not None
+ ), "Input size must be provided if using relative positional encoding."
+ # initialize relative positional embeddings
+ self.rel_pos_h = nn.Parameter(torch.zeros(2 * input_size[0] - 1, head_dim))
+ self.rel_pos_w = nn.Parameter(torch.zeros(2 * input_size[1] - 1, head_dim))
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ B, H, W, _ = x.shape
+ # qkv with shape (3, B, nHead, H * W, C)
+ qkv = self.qkv(x).reshape(B, H * W, 3, self.num_heads, -1).permute(2, 0, 3, 1, 4)
+ # q, k, v with shape (B * nHead, H * W, C)
+ q, k, v = qkv.reshape(3, B * self.num_heads, H * W, -1).unbind(0)
+
+ attn = (q * self.scale) @ k.transpose(-2, -1)
+
+ if self.use_rel_pos:
+ attn = add_decomposed_rel_pos(attn, q, self.rel_pos_h, self.rel_pos_w, (H, W), (H, W))
+
+ attn = attn.softmax(dim=-1)
+ x = (attn @ v).view(B, self.num_heads, H, W, -1).permute(0, 2, 3, 1, 4).reshape(B, H, W, -1)
+ x = self.proj(x)
+
+ return x
+
+
+def window_partition(x: torch.Tensor, window_size: int) -> Tuple[torch.Tensor, Tuple[int, int]]:
+ """
+ Partition into non-overlapping windows with padding if needed.
+ Args:
+ x (tensor): input tokens with [B, H, W, C].
+ window_size (int): window size.
+
+ Returns:
+ windows: windows after partition with [B * num_windows, window_size, window_size, C].
+ (Hp, Wp): padded height and width before partition
+ """
+ B, H, W, C = x.shape
+
+ pad_h = (window_size - H % window_size) % window_size
+ pad_w = (window_size - W % window_size) % window_size
+ if pad_h > 0 or pad_w > 0:
+ x = F.pad(x, (0, 0, 0, pad_w, 0, pad_h))
+ Hp, Wp = H + pad_h, W + pad_w
+
+ x = x.view(B, Hp // window_size, window_size, Wp // window_size, window_size, C)
+ windows = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(-1, window_size, window_size, C)
+ return windows, (Hp, Wp)
+
+
+def window_unpartition(
+ windows: torch.Tensor, window_size: int, pad_hw: Tuple[int, int], hw: Tuple[int, int]
+) -> torch.Tensor:
+ """
+ Window unpartition into original sequences and removing padding.
+ Args:
+ windows (tensor): input tokens with [B * num_windows, window_size, window_size, C].
+ window_size (int): window size.
+ pad_hw (Tuple): padded height and width (Hp, Wp).
+ hw (Tuple): original height and width (H, W) before padding.
+
+ Returns:
+ x: unpartitioned sequences with [B, H, W, C].
+ """
+ Hp, Wp = pad_hw
+ H, W = hw
+ B = windows.shape[0] // (Hp * Wp // window_size // window_size)
+ x = windows.view(B, Hp // window_size, Wp // window_size, window_size, window_size, -1)
+ x = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(B, Hp, Wp, -1)
+
+ if Hp > H or Wp > W:
+ x = x[:, :H, :W, :].contiguous()
+ return x
+
+
+def get_rel_pos(q_size: int, k_size: int, rel_pos: torch.Tensor) -> torch.Tensor:
+ """
+ Get relative positional embeddings according to the relative positions of
+ query and key sizes.
+ Args:
+ q_size (int): size of query q.
+ k_size (int): size of key k.
+ rel_pos (Tensor): relative position embeddings (L, C).
+
+ Returns:
+ Extracted positional embeddings according to relative positions.
+ """
+ max_rel_dist = int(2 * max(q_size, k_size) - 1)
+ # Interpolate rel pos if needed.
+ if rel_pos.shape[0] != max_rel_dist:
+ # Interpolate rel pos.
+ rel_pos_resized = F.interpolate(
+ rel_pos.reshape(1, rel_pos.shape[0], -1).permute(0, 2, 1),
+ size=max_rel_dist,
+ mode="linear",
+ )
+ rel_pos_resized = rel_pos_resized.reshape(-1, max_rel_dist).permute(1, 0)
+ else:
+ rel_pos_resized = rel_pos
+
+ # Scale the coords with short length if shapes for q and k are different.
+ q_coords = torch.arange(q_size)[:, None] * max(k_size / q_size, 1.0)
+ k_coords = torch.arange(k_size)[None, :] * max(q_size / k_size, 1.0)
+ relative_coords = (q_coords - k_coords) + (k_size - 1) * max(q_size / k_size, 1.0)
+
+ return rel_pos_resized[relative_coords.long()]
+
+
+def add_decomposed_rel_pos(
+ attn: torch.Tensor,
+ q: torch.Tensor,
+ rel_pos_h: torch.Tensor,
+ rel_pos_w: torch.Tensor,
+ q_size: Tuple[int, int],
+ k_size: Tuple[int, int],
+) -> torch.Tensor:
+ """
+ Calculate decomposed Relative Positional Embeddings from :paper:`mvitv2`.
+ https://github.com/facebookresearch/mvit/blob/19786631e330df9f3622e5402b4a419a263a2c80/mvit/models/attention.py # noqa B950
+ Args:
+ attn (Tensor): attention map.
+ q (Tensor): query q in the attention layer with shape (B, q_h * q_w, C).
+ rel_pos_h (Tensor): relative position embeddings (Lh, C) for height axis.
+ rel_pos_w (Tensor): relative position embeddings (Lw, C) for width axis.
+ q_size (Tuple): spatial sequence size of query q with (q_h, q_w).
+ k_size (Tuple): spatial sequence size of key k with (k_h, k_w).
+
+ Returns:
+ attn (Tensor): attention map with added relative positional embeddings.
+ """
+ q_h, q_w = q_size
+ k_h, k_w = k_size
+ Rh = get_rel_pos(q_h, k_h, rel_pos_h)
+ Rw = get_rel_pos(q_w, k_w, rel_pos_w)
+
+ B, _, dim = q.shape
+ r_q = q.reshape(B, q_h, q_w, dim)
+ rel_h = torch.einsum("bhwc,hkc->bhwk", r_q, Rh)
+ rel_w = torch.einsum("bhwc,wkc->bhwk", r_q, Rw)
+
+ attn = (
+ attn.view(B, q_h, q_w, k_h, k_w) + rel_h[:, :, :, :, None] + rel_w[:, :, :, None, :]
+ ).view(B, q_h * q_w, k_h * k_w)
+
+ return attn
+
+
+class PatchEmbed(nn.Module):
+ """
+ Image to Patch Embedding.
+ """
+
+ def __init__(
+ self,
+ kernel_size: Tuple[int, int] = (16, 16),
+ stride: Tuple[int, int] = (16, 16),
+ padding: Tuple[int, int] = (0, 0),
+ in_chans: int = 3,
+ embed_dim: int = 768,
+ ) -> None:
+ """
+ Args:
+ kernel_size (Tuple): kernel size of the projection layer.
+ stride (Tuple): stride of the projection layer.
+ padding (Tuple): padding size of the projection layer.
+ in_chans (int): Number of input image channels.
+ embed_dim (int): Patch embedding dimension.
+ """
+ super().__init__()
+
+ self.proj = nn.Conv2d(
+ in_chans, embed_dim, kernel_size=kernel_size, stride=stride, padding=padding
+ )
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ x = self.proj(x)
+ # B C H W -> B H W C
+ x = x.permute(0, 2, 3, 1)
+ return x
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/modeling/mask_decoder.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/modeling/mask_decoder.py
new file mode 100644
index 0000000000000000000000000000000000000000..5d2fdb03d535a91fa725d1ec4e92a7a1f217dfe0
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/modeling/mask_decoder.py
@@ -0,0 +1,176 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+# All rights reserved.
+
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+import torch
+from torch import nn
+from torch.nn import functional as F
+
+from typing import List, Tuple, Type
+
+from .common import LayerNorm2d
+
+
+class MaskDecoder(nn.Module):
+ def __init__(
+ self,
+ *,
+ transformer_dim: int,
+ transformer: nn.Module,
+ num_multimask_outputs: int = 3,
+ activation: Type[nn.Module] = nn.GELU,
+ iou_head_depth: int = 3,
+ iou_head_hidden_dim: int = 256,
+ ) -> None:
+ """
+ Predicts masks given an image and prompt embeddings, using a
+ transformer architecture.
+
+ Arguments:
+ transformer_dim (int): the channel dimension of the transformer
+ transformer (nn.Module): the transformer used to predict masks
+ num_multimask_outputs (int): the number of masks to predict
+ when disambiguating masks
+ activation (nn.Module): the type of activation to use when
+ upscaling masks
+ iou_head_depth (int): the depth of the MLP used to predict
+ mask quality
+ iou_head_hidden_dim (int): the hidden dimension of the MLP
+ used to predict mask quality
+ """
+ super().__init__()
+ self.transformer_dim = transformer_dim
+ self.transformer = transformer
+
+ self.num_multimask_outputs = num_multimask_outputs
+
+ self.iou_token = nn.Embedding(1, transformer_dim)
+ self.num_mask_tokens = num_multimask_outputs + 1
+ self.mask_tokens = nn.Embedding(self.num_mask_tokens, transformer_dim)
+
+ self.output_upscaling = nn.Sequential(
+ nn.ConvTranspose2d(transformer_dim, transformer_dim // 4, kernel_size=2, stride=2),
+ LayerNorm2d(transformer_dim // 4),
+ activation(),
+ nn.ConvTranspose2d(transformer_dim // 4, transformer_dim // 8, kernel_size=2, stride=2),
+ activation(),
+ )
+ self.output_hypernetworks_mlps = nn.ModuleList(
+ [
+ MLP(transformer_dim, transformer_dim, transformer_dim // 8, 3)
+ for i in range(self.num_mask_tokens)
+ ]
+ )
+
+ self.iou_prediction_head = MLP(
+ transformer_dim, iou_head_hidden_dim, self.num_mask_tokens, iou_head_depth
+ )
+
+ def forward(
+ self,
+ image_embeddings: torch.Tensor,
+ image_pe: torch.Tensor,
+ sparse_prompt_embeddings: torch.Tensor,
+ dense_prompt_embeddings: torch.Tensor,
+ multimask_output: bool,
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
+ """
+ Predict masks given image and prompt embeddings.
+
+ Arguments:
+ image_embeddings (torch.Tensor): the embeddings from the image encoder
+ image_pe (torch.Tensor): positional encoding with the shape of image_embeddings
+ sparse_prompt_embeddings (torch.Tensor): the embeddings of the points and boxes
+ dense_prompt_embeddings (torch.Tensor): the embeddings of the mask inputs
+ multimask_output (bool): Whether to return multiple masks or a single
+ mask.
+
+ Returns:
+ torch.Tensor: batched predicted masks
+ torch.Tensor: batched predictions of mask quality
+ """
+ masks, iou_pred = self.predict_masks(
+ image_embeddings=image_embeddings,
+ image_pe=image_pe,
+ sparse_prompt_embeddings=sparse_prompt_embeddings,
+ dense_prompt_embeddings=dense_prompt_embeddings,
+ )
+
+ # Select the correct mask or masks for output
+ if multimask_output:
+ mask_slice = slice(1, None)
+ else:
+ mask_slice = slice(0, 1)
+ masks = masks[:, mask_slice, :, :]
+ iou_pred = iou_pred[:, mask_slice]
+
+ # Prepare output
+ return masks, iou_pred
+
+ def predict_masks(
+ self,
+ image_embeddings: torch.Tensor,
+ image_pe: torch.Tensor,
+ sparse_prompt_embeddings: torch.Tensor,
+ dense_prompt_embeddings: torch.Tensor,
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
+ """Predicts masks. See 'forward' for more details."""
+ # Concatenate output tokens
+ output_tokens = torch.cat([self.iou_token.weight, self.mask_tokens.weight], dim=0)
+ output_tokens = output_tokens.unsqueeze(0).expand(sparse_prompt_embeddings.size(0), -1, -1)
+ tokens = torch.cat((output_tokens, sparse_prompt_embeddings), dim=1)
+
+ # Expand per-image data in batch direction to be per-mask
+ src = torch.repeat_interleave(image_embeddings, tokens.shape[0], dim=0)
+ src = src + dense_prompt_embeddings
+ pos_src = torch.repeat_interleave(image_pe, tokens.shape[0], dim=0)
+ b, c, h, w = src.shape
+
+ # Run the transformer
+ hs, src = self.transformer(src, pos_src, tokens)
+ iou_token_out = hs[:, 0, :]
+ mask_tokens_out = hs[:, 1 : (1 + self.num_mask_tokens), :]
+
+ # Upscale mask embeddings and predict masks using the mask tokens
+ src = src.transpose(1, 2).view(b, c, h, w)
+ upscaled_embedding = self.output_upscaling(src)
+ hyper_in_list: List[torch.Tensor] = []
+ for i in range(self.num_mask_tokens):
+ hyper_in_list.append(self.output_hypernetworks_mlps[i](mask_tokens_out[:, i, :]))
+ hyper_in = torch.stack(hyper_in_list, dim=1)
+ b, c, h, w = upscaled_embedding.shape
+ masks = (hyper_in @ upscaled_embedding.view(b, c, h * w)).view(b, -1, h, w)
+
+ # Generate mask quality predictions
+ iou_pred = self.iou_prediction_head(iou_token_out)
+
+ return masks, iou_pred
+
+
+# Lightly adapted from
+# https://github.com/facebookresearch/MaskFormer/blob/main/mask_former/modeling/transformer/transformer_predictor.py # noqa
+class MLP(nn.Module):
+ def __init__(
+ self,
+ input_dim: int,
+ hidden_dim: int,
+ output_dim: int,
+ num_layers: int,
+ sigmoid_output: bool = False,
+ ) -> None:
+ super().__init__()
+ self.num_layers = num_layers
+ h = [hidden_dim] * (num_layers - 1)
+ self.layers = nn.ModuleList(
+ nn.Linear(n, k) for n, k in zip([input_dim] + h, h + [output_dim])
+ )
+ self.sigmoid_output = sigmoid_output
+
+ def forward(self, x):
+ for i, layer in enumerate(self.layers):
+ x = F.relu(layer(x)) if i < self.num_layers - 1 else layer(x)
+ if self.sigmoid_output:
+ x = F.sigmoid(x)
+ return x
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/modeling/prompt_encoder.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/modeling/prompt_encoder.py
new file mode 100644
index 0000000000000000000000000000000000000000..c3143f4f8e02ddd7ca8587b40ff5d47c3a6b7ef3
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/modeling/prompt_encoder.py
@@ -0,0 +1,214 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+# All rights reserved.
+
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+import numpy as np
+import torch
+from torch import nn
+
+from typing import Any, Optional, Tuple, Type
+
+from .common import LayerNorm2d
+
+
+class PromptEncoder(nn.Module):
+ def __init__(
+ self,
+ embed_dim: int,
+ image_embedding_size: Tuple[int, int],
+ input_image_size: Tuple[int, int],
+ mask_in_chans: int,
+ activation: Type[nn.Module] = nn.GELU,
+ ) -> None:
+ """
+ Encodes prompts for input to SAM's mask decoder.
+
+ Arguments:
+ embed_dim (int): The prompts' embedding dimension
+ image_embedding_size (tuple(int, int)): The spatial size of the
+ image embedding, as (H, W).
+ input_image_size (int): The padded size of the image as input
+ to the image encoder, as (H, W).
+ mask_in_chans (int): The number of hidden channels used for
+ encoding input masks.
+ activation (nn.Module): The activation to use when encoding
+ input masks.
+ """
+ super().__init__()
+ self.embed_dim = embed_dim
+ self.input_image_size = input_image_size
+ self.image_embedding_size = image_embedding_size
+ self.pe_layer = PositionEmbeddingRandom(embed_dim // 2)
+
+ self.num_point_embeddings: int = 4 # pos/neg point + 2 box corners
+ point_embeddings = [nn.Embedding(1, embed_dim) for i in range(self.num_point_embeddings)]
+ self.point_embeddings = nn.ModuleList(point_embeddings)
+ self.not_a_point_embed = nn.Embedding(1, embed_dim)
+
+ self.mask_input_size = (4 * image_embedding_size[0], 4 * image_embedding_size[1])
+ self.mask_downscaling = nn.Sequential(
+ nn.Conv2d(1, mask_in_chans // 4, kernel_size=2, stride=2),
+ LayerNorm2d(mask_in_chans // 4),
+ activation(),
+ nn.Conv2d(mask_in_chans // 4, mask_in_chans, kernel_size=2, stride=2),
+ LayerNorm2d(mask_in_chans),
+ activation(),
+ nn.Conv2d(mask_in_chans, embed_dim, kernel_size=1),
+ )
+ self.no_mask_embed = nn.Embedding(1, embed_dim)
+
+ def get_dense_pe(self) -> torch.Tensor:
+ """
+ Returns the positional encoding used to encode point prompts,
+ applied to a dense set of points the shape of the image encoding.
+
+ Returns:
+ torch.Tensor: Positional encoding with shape
+ 1x(embed_dim)x(embedding_h)x(embedding_w)
+ """
+ return self.pe_layer(self.image_embedding_size).unsqueeze(0)
+
+ def _embed_points(
+ self,
+ points: torch.Tensor,
+ labels: torch.Tensor,
+ pad: bool,
+ ) -> torch.Tensor:
+ """Embeds point prompts."""
+ points = points + 0.5 # Shift to center of pixel
+ if pad:
+ padding_point = torch.zeros((points.shape[0], 1, 2), device=points.device)
+ padding_label = -torch.ones((labels.shape[0], 1), device=labels.device)
+ points = torch.cat([points, padding_point], dim=1)
+ labels = torch.cat([labels, padding_label], dim=1)
+ point_embedding = self.pe_layer.forward_with_coords(points, self.input_image_size)
+ point_embedding[labels == -1] = 0.0
+ point_embedding[labels == -1] += self.not_a_point_embed.weight
+ point_embedding[labels == 0] += self.point_embeddings[0].weight
+ point_embedding[labels == 1] += self.point_embeddings[1].weight
+ return point_embedding
+
+ def _embed_boxes(self, boxes: torch.Tensor) -> torch.Tensor:
+ """Embeds box prompts."""
+ boxes = boxes + 0.5 # Shift to center of pixel
+ coords = boxes.reshape(-1, 2, 2)
+ corner_embedding = self.pe_layer.forward_with_coords(coords, self.input_image_size)
+ corner_embedding[:, 0, :] += self.point_embeddings[2].weight
+ corner_embedding[:, 1, :] += self.point_embeddings[3].weight
+ return corner_embedding
+
+ def _embed_masks(self, masks: torch.Tensor) -> torch.Tensor:
+ """Embeds mask inputs."""
+ mask_embedding = self.mask_downscaling(masks)
+ return mask_embedding
+
+ def _get_batch_size(
+ self,
+ points: Optional[Tuple[torch.Tensor, torch.Tensor]],
+ boxes: Optional[torch.Tensor],
+ masks: Optional[torch.Tensor],
+ ) -> int:
+ """
+ Gets the batch size of the output given the batch size of the input prompts.
+ """
+ if points is not None:
+ return points[0].shape[0]
+ elif boxes is not None:
+ return boxes.shape[0]
+ elif masks is not None:
+ return masks.shape[0]
+ else:
+ return 1
+
+ def _get_device(self) -> torch.device:
+ return self.point_embeddings[0].weight.device
+
+ def forward(
+ self,
+ points: Optional[Tuple[torch.Tensor, torch.Tensor]],
+ boxes: Optional[torch.Tensor],
+ masks: Optional[torch.Tensor],
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
+ """
+ Embeds different types of prompts, returning both sparse and dense
+ embeddings.
+
+ Arguments:
+ points (tuple(torch.Tensor, torch.Tensor) or none): point coordinates
+ and labels to embed.
+ boxes (torch.Tensor or none): boxes to embed
+ masks (torch.Tensor or none): masks to embed
+
+ Returns:
+ torch.Tensor: sparse embeddings for the points and boxes, with shape
+ BxNx(embed_dim), where N is determined by the number of input points
+ and boxes.
+ torch.Tensor: dense embeddings for the masks, in the shape
+ Bx(embed_dim)x(embed_H)x(embed_W)
+ """
+ bs = self._get_batch_size(points, boxes, masks)
+ sparse_embeddings = torch.empty((bs, 0, self.embed_dim), device=self._get_device())
+ if points is not None:
+ coords, labels = points
+ point_embeddings = self._embed_points(coords, labels, pad=(boxes is None))
+ sparse_embeddings = torch.cat([sparse_embeddings, point_embeddings], dim=1)
+ if boxes is not None:
+ box_embeddings = self._embed_boxes(boxes)
+ sparse_embeddings = torch.cat([sparse_embeddings, box_embeddings], dim=1)
+
+ if masks is not None:
+ dense_embeddings = self._embed_masks(masks)
+ else:
+ dense_embeddings = self.no_mask_embed.weight.reshape(1, -1, 1, 1).expand(
+ bs, -1, self.image_embedding_size[0], self.image_embedding_size[1]
+ )
+
+ return sparse_embeddings, dense_embeddings
+
+
+class PositionEmbeddingRandom(nn.Module):
+ """
+ Positional encoding using random spatial frequencies.
+ """
+
+ def __init__(self, num_pos_feats: int = 64, scale: Optional[float] = None) -> None:
+ super().__init__()
+ if scale is None or scale <= 0.0:
+ scale = 1.0
+ self.register_buffer(
+ "positional_encoding_gaussian_matrix",
+ scale * torch.randn((2, num_pos_feats)),
+ )
+
+ def _pe_encoding(self, coords: torch.Tensor) -> torch.Tensor:
+ """Positionally encode points that are normalized to [0,1]."""
+ # assuming coords are in [0, 1]^2 square and have d_1 x ... x d_n x 2 shape
+ coords = 2 * coords - 1
+ coords = coords @ self.positional_encoding_gaussian_matrix
+ coords = 2 * np.pi * coords
+ # outputs d_1 x ... x d_n x C shape
+ return torch.cat([torch.sin(coords), torch.cos(coords)], dim=-1)
+
+ def forward(self, size: Tuple[int, int]) -> torch.Tensor:
+ """Generate positional encoding for a grid of the specified size."""
+ h, w = size
+ device: Any = self.positional_encoding_gaussian_matrix.device
+ grid = torch.ones((h, w), device=device, dtype=torch.float32)
+ y_embed = grid.cumsum(dim=0) - 0.5
+ x_embed = grid.cumsum(dim=1) - 0.5
+ y_embed = y_embed / h
+ x_embed = x_embed / w
+
+ pe = self._pe_encoding(torch.stack([x_embed, y_embed], dim=-1))
+ return pe.permute(2, 0, 1) # C x H x W
+
+ def forward_with_coords(
+ self, coords_input: torch.Tensor, image_size: Tuple[int, int]
+ ) -> torch.Tensor:
+ """Positionally encode points that are not normalized to [0,1]."""
+ coords = coords_input.clone()
+ coords[:, :, 0] = coords[:, :, 0] / image_size[1]
+ coords[:, :, 1] = coords[:, :, 1] / image_size[0]
+ return self._pe_encoding(coords.to(torch.float)) # B x N x C
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/modeling/sam.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/modeling/sam.py
new file mode 100644
index 0000000000000000000000000000000000000000..45b9e7c56d10cc47e7ed0739e35d850bfccbb257
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/modeling/sam.py
@@ -0,0 +1,175 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+# All rights reserved.
+
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+import torch
+from torch import nn
+from torch.nn import functional as F
+
+from typing import Any, Dict, List, Tuple, Union
+
+from .tiny_vit_sam import TinyViT
+from .image_encoder import ImageEncoderViT
+from .mask_decoder import MaskDecoder
+from .prompt_encoder import PromptEncoder
+
+
+class Sam(nn.Module):
+ mask_threshold: float = 0.0
+ image_format: str = "RGB"
+
+ def __init__(
+ self,
+ image_encoder: Union[ImageEncoderViT, TinyViT],
+ prompt_encoder: PromptEncoder,
+ mask_decoder: MaskDecoder,
+ pixel_mean: List[float] = [123.675, 116.28, 103.53],
+ pixel_std: List[float] = [58.395, 57.12, 57.375],
+ ) -> None:
+ """
+ SAM predicts object masks from an image and input prompts.
+
+ Arguments:
+ image_encoder (ImageEncoderViT): The backbone used to encode the
+ image into image embeddings that allow for efficient mask prediction.
+ prompt_encoder (PromptEncoder): Encodes various types of input prompts.
+ mask_decoder (MaskDecoder): Predicts masks from the image embeddings
+ and encoded prompts.
+ pixel_mean (list(float)): Mean values for normalizing pixels in the input image.
+ pixel_std (list(float)): Std values for normalizing pixels in the input image.
+ """
+ super().__init__()
+ self.image_encoder = image_encoder
+ self.prompt_encoder = prompt_encoder
+ self.mask_decoder = mask_decoder
+ self.register_buffer("pixel_mean", torch.Tensor(pixel_mean).view(-1, 1, 1), False)
+ self.register_buffer("pixel_std", torch.Tensor(pixel_std).view(-1, 1, 1), False)
+
+ @property
+ def device(self) -> Any:
+ return self.pixel_mean.device
+
+ @torch.no_grad()
+ def forward(
+ self,
+ batched_input: List[Dict[str, Any]],
+ multimask_output: bool,
+ ) -> List[Dict[str, torch.Tensor]]:
+ """
+ Predicts masks end-to-end from provided images and prompts.
+ If prompts are not known in advance, using SamPredictor is
+ recommended over calling the model directly.
+
+ Arguments:
+ batched_input (list(dict)): A list over input images, each a
+ dictionary with the following keys. A prompt key can be
+ excluded if it is not present.
+ 'image': The image as a torch tensor in 3xHxW format,
+ already transformed for input to the model.
+ 'original_size': (tuple(int, int)) The original size of
+ the image before transformation, as (H, W).
+ 'point_coords': (torch.Tensor) Batched point prompts for
+ this image, with shape BxNx2. Already transformed to the
+ input frame of the model.
+ 'point_labels': (torch.Tensor) Batched labels for point prompts,
+ with shape BxN.
+ 'boxes': (torch.Tensor) Batched box inputs, with shape Bx4.
+ Already transformed to the input frame of the model.
+ 'mask_inputs': (torch.Tensor) Batched mask inputs to the model,
+ in the form Bx1xHxW.
+ multimask_output (bool): Whether the model should predict multiple
+ disambiguating masks, or return a single mask.
+
+ Returns:
+ (list(dict)): A list over input images, where each element is
+ as dictionary with the following keys.
+ 'masks': (torch.Tensor) Batched binary mask predictions,
+ with shape BxCxHxW, where B is the number of input prompts,
+ C is determined by multimask_output, and (H, W) is the
+ original size of the image.
+ 'iou_predictions': (torch.Tensor) The model's predictions
+ of mask quality, in shape BxC.
+ 'low_res_logits': (torch.Tensor) Low resolution logits with
+ shape BxCxHxW, where H=W=256. Can be passed as mask input
+ to subsequent iterations of prediction.
+ """
+ input_images = torch.stack([self.preprocess(x["image"]) for x in batched_input], dim=0)
+ image_embeddings = self.image_encoder(input_images)
+
+ outputs = []
+ for image_record, curr_embedding in zip(batched_input, image_embeddings):
+ if "point_coords" in image_record:
+ points = (image_record["point_coords"], image_record["point_labels"])
+ else:
+ points = None
+ sparse_embeddings, dense_embeddings = self.prompt_encoder(
+ points=points,
+ boxes=image_record.get("boxes", None),
+ masks=image_record.get("mask_inputs", None),
+ )
+ low_res_masks, iou_predictions = self.mask_decoder(
+ image_embeddings=curr_embedding.unsqueeze(0),
+ image_pe=self.prompt_encoder.get_dense_pe(),
+ sparse_prompt_embeddings=sparse_embeddings,
+ dense_prompt_embeddings=dense_embeddings,
+ multimask_output=multimask_output,
+ )
+ masks = self.postprocess_masks(
+ low_res_masks,
+ input_size=image_record["image"].shape[-2:],
+ original_size=image_record["original_size"],
+ )
+ masks = masks > self.mask_threshold
+ outputs.append(
+ {
+ "masks": masks,
+ "iou_predictions": iou_predictions,
+ "low_res_logits": low_res_masks,
+ }
+ )
+ return outputs
+
+ def postprocess_masks(
+ self,
+ masks: torch.Tensor,
+ input_size: Tuple[int, ...],
+ original_size: Tuple[int, ...],
+ ) -> torch.Tensor:
+ """
+ Remove padding and upscale masks to the original image size.
+
+ Arguments:
+ masks (torch.Tensor): Batched masks from the mask_decoder,
+ in BxCxHxW format.
+ input_size (tuple(int, int)): The size of the image input to the
+ model, in (H, W) format. Used to remove padding.
+ original_size (tuple(int, int)): The original size of the image
+ before resizing for input to the model, in (H, W) format.
+
+ Returns:
+ (torch.Tensor): Batched masks in BxCxHxW format, where (H, W)
+ is given by original_size.
+ """
+ masks = F.interpolate(
+ masks,
+ (self.image_encoder.img_size, self.image_encoder.img_size),
+ mode="bilinear",
+ align_corners=False,
+ )
+ masks = masks[..., : input_size[0], : input_size[1]]
+ masks = F.interpolate(masks, original_size, mode="bilinear", align_corners=False)
+ return masks
+
+ def preprocess(self, x: torch.Tensor) -> torch.Tensor:
+ """Normalize pixel values and pad to a square input."""
+ # Normalize colors
+ x = (x - self.pixel_mean) / self.pixel_std
+
+ # Pad
+ h, w = x.shape[-2:]
+ padh = self.image_encoder.img_size - h
+ padw = self.image_encoder.img_size - w
+ x = F.pad(x, (0, padw, 0, padh))
+ return x
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/modeling/tiny_vit_sam.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/modeling/tiny_vit_sam.py
new file mode 100644
index 0000000000000000000000000000000000000000..ce1aad263f07652385010194738f8f69254df644
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/modeling/tiny_vit_sam.py
@@ -0,0 +1,716 @@
+# --------------------------------------------------------
+# TinyViT Model Architecture
+# Copyright (c) 2022 Microsoft
+# Adapted from LeViT and Swin Transformer
+# LeViT: (https://github.com/facebookresearch/levit)
+# Swin: (https://github.com/microsoft/swin-transformer)
+# Build the TinyViT Model
+# --------------------------------------------------------
+
+import itertools
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+import torch.utils.checkpoint as checkpoint
+from custom_timm.models.layers import DropPath as TimmDropPath,\
+ to_2tuple, trunc_normal_
+from custom_timm.models.registry import register_model
+from typing import Tuple
+
+
+class Conv2d_BN(torch.nn.Sequential):
+ def __init__(self, a, b, ks=1, stride=1, pad=0, dilation=1,
+ groups=1, bn_weight_init=1):
+ super().__init__()
+ self.add_module('c', torch.nn.Conv2d(
+ a, b, ks, stride, pad, dilation, groups, bias=False))
+ bn = torch.nn.BatchNorm2d(b)
+ torch.nn.init.constant_(bn.weight, bn_weight_init)
+ torch.nn.init.constant_(bn.bias, 0)
+ self.add_module('bn', bn)
+
+ @torch.no_grad()
+ def fuse(self):
+ c, bn = self._modules.values()
+ w = bn.weight / (bn.running_var + bn.eps)**0.5
+ w = c.weight * w[:, None, None, None]
+ b = bn.bias - bn.running_mean * bn.weight / \
+ (bn.running_var + bn.eps)**0.5
+ m = torch.nn.Conv2d(w.size(1) * self.c.groups, w.size(
+ 0), w.shape[2:], stride=self.c.stride, padding=self.c.padding, dilation=self.c.dilation, groups=self.c.groups)
+ m.weight.data.copy_(w)
+ m.bias.data.copy_(b)
+ return m
+
+
+class DropPath(TimmDropPath):
+ def __init__(self, drop_prob=None):
+ super().__init__(drop_prob=drop_prob)
+ self.drop_prob = drop_prob
+
+ def __repr__(self):
+ msg = super().__repr__()
+ msg += f'(drop_prob={self.drop_prob})'
+ return msg
+
+
+class PatchEmbed(nn.Module):
+ def __init__(self, in_chans, embed_dim, resolution, activation):
+ super().__init__()
+ img_size: Tuple[int, int] = to_2tuple(resolution)
+ self.patches_resolution = (img_size[0] // 4, img_size[1] // 4)
+ self.num_patches = self.patches_resolution[0] * \
+ self.patches_resolution[1]
+ self.in_chans = in_chans
+ self.embed_dim = embed_dim
+ n = embed_dim
+ self.seq = nn.Sequential(
+ Conv2d_BN(in_chans, n // 2, 3, 2, 1),
+ activation(),
+ Conv2d_BN(n // 2, n, 3, 2, 1),
+ )
+
+ def forward(self, x):
+ return self.seq(x)
+
+
+class MBConv(nn.Module):
+ def __init__(self, in_chans, out_chans, expand_ratio,
+ activation, drop_path):
+ super().__init__()
+ self.in_chans = in_chans
+ self.hidden_chans = int(in_chans * expand_ratio)
+ self.out_chans = out_chans
+
+ self.conv1 = Conv2d_BN(in_chans, self.hidden_chans, ks=1)
+ self.act1 = activation()
+
+ self.conv2 = Conv2d_BN(self.hidden_chans, self.hidden_chans,
+ ks=3, stride=1, pad=1, groups=self.hidden_chans)
+ self.act2 = activation()
+
+ self.conv3 = Conv2d_BN(
+ self.hidden_chans, out_chans, ks=1, bn_weight_init=0.0)
+ self.act3 = activation()
+
+ self.drop_path = DropPath(
+ drop_path) if drop_path > 0. else nn.Identity()
+
+ def forward(self, x):
+ shortcut = x
+
+ x = self.conv1(x)
+ x = self.act1(x)
+
+ x = self.conv2(x)
+ x = self.act2(x)
+
+ x = self.conv3(x)
+
+ x = self.drop_path(x)
+
+ x += shortcut
+ x = self.act3(x)
+
+ return x
+
+
+class PatchMerging(nn.Module):
+ def __init__(self, input_resolution, dim, out_dim, activation):
+ super().__init__()
+
+ self.input_resolution = input_resolution
+ self.dim = dim
+ self.out_dim = out_dim
+ self.act = activation()
+ self.conv1 = Conv2d_BN(dim, out_dim, 1, 1, 0)
+ stride_c=2
+ if(out_dim==320 or out_dim==448 or out_dim==576):
+ stride_c=1
+ self.conv2 = Conv2d_BN(out_dim, out_dim, 3, stride_c, 1, groups=out_dim)
+ self.conv3 = Conv2d_BN(out_dim, out_dim, 1, 1, 0)
+
+ def forward(self, x):
+ if x.ndim == 3:
+ H, W = self.input_resolution
+ B = len(x)
+ # (B, C, H, W)
+ x = x.view(B, H, W, -1).permute(0, 3, 1, 2)
+
+ x = self.conv1(x)
+ x = self.act(x)
+
+ x = self.conv2(x)
+ x = self.act(x)
+ x = self.conv3(x)
+ x = x.flatten(2).transpose(1, 2)
+ return x
+
+
+class ConvLayer(nn.Module):
+ def __init__(self, dim, input_resolution, depth,
+ activation,
+ drop_path=0., downsample=None, use_checkpoint=False,
+ out_dim=None,
+ conv_expand_ratio=4.,
+ ):
+
+ super().__init__()
+ self.dim = dim
+ self.input_resolution = input_resolution
+ self.depth = depth
+ self.use_checkpoint = use_checkpoint
+
+ # build blocks
+ self.blocks = nn.ModuleList([
+ MBConv(dim, dim, conv_expand_ratio, activation,
+ drop_path[i] if isinstance(drop_path, list) else drop_path,
+ )
+ for i in range(depth)])
+
+ # patch merging layer
+ if downsample is not None:
+ self.downsample = downsample(
+ input_resolution, dim=dim, out_dim=out_dim, activation=activation)
+ else:
+ self.downsample = None
+
+ def forward(self, x):
+ for blk in self.blocks:
+ if self.use_checkpoint:
+ x = checkpoint.checkpoint(blk, x)
+ else:
+ x = blk(x)
+ if self.downsample is not None:
+ x = self.downsample(x)
+ return x
+
+
+class Mlp(nn.Module):
+ def __init__(self, in_features, hidden_features=None,
+ out_features=None, act_layer=nn.GELU, drop=0.):
+ super().__init__()
+ out_features = out_features or in_features
+ hidden_features = hidden_features or in_features
+ self.norm = nn.LayerNorm(in_features)
+ self.fc1 = nn.Linear(in_features, hidden_features)
+ self.fc2 = nn.Linear(hidden_features, out_features)
+ self.act = act_layer()
+ self.drop = nn.Dropout(drop)
+
+ def forward(self, x):
+ x = self.norm(x)
+
+ x = self.fc1(x)
+ x = self.act(x)
+ x = self.drop(x)
+ x = self.fc2(x)
+ x = self.drop(x)
+ return x
+
+
+class Attention(torch.nn.Module):
+ def __init__(self, dim, key_dim, num_heads=8,
+ attn_ratio=4,
+ resolution=(14, 14),
+ ):
+ super().__init__()
+ # (h, w)
+ assert isinstance(resolution, tuple) and len(resolution) == 2
+ self.num_heads = num_heads
+ self.scale = key_dim ** -0.5
+ self.key_dim = key_dim
+ self.nh_kd = nh_kd = key_dim * num_heads
+ self.d = int(attn_ratio * key_dim)
+ self.dh = int(attn_ratio * key_dim) * num_heads
+ self.attn_ratio = attn_ratio
+ h = self.dh + nh_kd * 2
+
+ self.norm = nn.LayerNorm(dim)
+ self.qkv = nn.Linear(dim, h)
+ self.proj = nn.Linear(self.dh, dim)
+
+ points = list(itertools.product(
+ range(resolution[0]), range(resolution[1])))
+ N = len(points)
+ attention_offsets = {}
+ idxs = []
+ for p1 in points:
+ for p2 in points:
+ offset = (abs(p1[0] - p2[0]), abs(p1[1] - p2[1]))
+ if offset not in attention_offsets:
+ attention_offsets[offset] = len(attention_offsets)
+ idxs.append(attention_offsets[offset])
+ self.attention_biases = torch.nn.Parameter(
+ torch.zeros(num_heads, len(attention_offsets)))
+ self.register_buffer('attention_bias_idxs',
+ torch.LongTensor(idxs).view(N, N),
+ persistent=False)
+
+ @torch.no_grad()
+ def train(self, mode=True):
+ super().train(mode)
+ if mode and hasattr(self, 'ab'):
+ del self.ab
+ else:
+ self.ab = self.attention_biases[:, self.attention_bias_idxs]
+
+ def forward(self, x): # x (B,N,C)
+ B, N, _ = x.shape
+
+ # Normalization
+ x = self.norm(x)
+
+ qkv = self.qkv(x)
+ # (B, N, num_heads, d)
+ q, k, v = qkv.view(B, N, self.num_heads, -
+ 1).split([self.key_dim, self.key_dim, self.d], dim=3)
+ # (B, num_heads, N, d)
+ q = q.permute(0, 2, 1, 3)
+ k = k.permute(0, 2, 1, 3)
+ v = v.permute(0, 2, 1, 3)
+
+ attn = (
+ (q @ k.transpose(-2, -1)) * self.scale
+ +
+ (self.attention_biases[:, self.attention_bias_idxs]
+ if self.training else self.ab)
+ )
+ attn = attn.softmax(dim=-1)
+ x = (attn @ v).transpose(1, 2).reshape(B, N, self.dh)
+ x = self.proj(x)
+ return x
+
+
+class TinyViTBlock(nn.Module):
+ r""" TinyViT Block.
+
+ Args:
+ dim (int): Number of input channels.
+ input_resolution (tuple[int, int]): Input resulotion.
+ num_heads (int): Number of attention heads.
+ window_size (int): Window size.
+ mlp_ratio (float): Ratio of mlp hidden dim to embedding dim.
+ drop (float, optional): Dropout rate. Default: 0.0
+ drop_path (float, optional): Stochastic depth rate. Default: 0.0
+ local_conv_size (int): the kernel size of the convolution between
+ Attention and MLP. Default: 3
+ activation: the activation function. Default: nn.GELU
+ """
+
+ def __init__(self, dim, input_resolution, num_heads, window_size=7,
+ mlp_ratio=4., drop=0., drop_path=0.,
+ local_conv_size=3,
+ activation=nn.GELU,
+ ):
+ super().__init__()
+ self.dim = dim
+ self.input_resolution = input_resolution
+ self.num_heads = num_heads
+ assert window_size > 0, 'window_size must be greater than 0'
+ self.window_size = window_size
+ self.mlp_ratio = mlp_ratio
+
+ self.drop_path = DropPath(
+ drop_path) if drop_path > 0. else nn.Identity()
+
+ assert dim % num_heads == 0, 'dim must be divisible by num_heads'
+ head_dim = dim // num_heads
+
+ window_resolution = (window_size, window_size)
+ self.attn = Attention(dim, head_dim, num_heads,
+ attn_ratio=1, resolution=window_resolution)
+
+ mlp_hidden_dim = int(dim * mlp_ratio)
+ mlp_activation = activation
+ self.mlp = Mlp(in_features=dim, hidden_features=mlp_hidden_dim,
+ act_layer=mlp_activation, drop=drop)
+
+ pad = local_conv_size // 2
+ self.local_conv = Conv2d_BN(
+ dim, dim, ks=local_conv_size, stride=1, pad=pad, groups=dim)
+
+ def forward(self, x):
+ H, W = self.input_resolution
+ B, L, C = x.shape
+ assert L == H * W, "input feature has wrong size"
+ res_x = x
+ if H == self.window_size and W == self.window_size:
+ x = self.attn(x)
+ else:
+ x = x.view(B, H, W, C)
+ pad_b = (self.window_size - H %
+ self.window_size) % self.window_size
+ pad_r = (self.window_size - W %
+ self.window_size) % self.window_size
+ padding = pad_b > 0 or pad_r > 0
+
+ if padding:
+ x = F.pad(x, (0, 0, 0, pad_r, 0, pad_b))
+
+ pH, pW = H + pad_b, W + pad_r
+ nH = pH // self.window_size
+ nW = pW // self.window_size
+ # window partition
+ x = x.view(B, nH, self.window_size, nW, self.window_size, C).transpose(2, 3).reshape(
+ B * nH * nW, self.window_size * self.window_size, C)
+ x = self.attn(x)
+ # window reverse
+ x = x.view(B, nH, nW, self.window_size, self.window_size,
+ C).transpose(2, 3).reshape(B, pH, pW, C)
+
+ if padding:
+ x = x[:, :H, :W].contiguous()
+
+ x = x.view(B, L, C)
+
+ x = res_x + self.drop_path(x)
+
+ x = x.transpose(1, 2).reshape(B, C, H, W)
+ x = self.local_conv(x)
+ x = x.view(B, C, L).transpose(1, 2)
+
+ x = x + self.drop_path(self.mlp(x))
+ return x
+
+ def extra_repr(self) -> str:
+ return f"dim={self.dim}, input_resolution={self.input_resolution}, num_heads={self.num_heads}, " \
+ f"window_size={self.window_size}, mlp_ratio={self.mlp_ratio}"
+
+
+class BasicLayer(nn.Module):
+ """ A basic TinyViT layer for one stage.
+
+ Args:
+ dim (int): Number of input channels.
+ input_resolution (tuple[int]): Input resolution.
+ depth (int): Number of blocks.
+ num_heads (int): Number of attention heads.
+ window_size (int): Local window size.
+ mlp_ratio (float): Ratio of mlp hidden dim to embedding dim.
+ drop (float, optional): Dropout rate. Default: 0.0
+ drop_path (float | tuple[float], optional): Stochastic depth rate. Default: 0.0
+ downsample (nn.Module | None, optional): Downsample layer at the end of the layer. Default: None
+ use_checkpoint (bool): Whether to use checkpointing to save memory. Default: False.
+ local_conv_size: the kernel size of the depthwise convolution between attention and MLP. Default: 3
+ activation: the activation function. Default: nn.GELU
+ out_dim: the output dimension of the layer. Default: dim
+ """
+
+ def __init__(self, dim, input_resolution, depth, num_heads, window_size,
+ mlp_ratio=4., drop=0.,
+ drop_path=0., downsample=None, use_checkpoint=False,
+ local_conv_size=3,
+ activation=nn.GELU,
+ out_dim=None,
+ ):
+
+ super().__init__()
+ self.dim = dim
+ self.input_resolution = input_resolution
+ self.depth = depth
+ self.use_checkpoint = use_checkpoint
+
+ # build blocks
+ self.blocks = nn.ModuleList([
+ TinyViTBlock(dim=dim, input_resolution=input_resolution,
+ num_heads=num_heads, window_size=window_size,
+ mlp_ratio=mlp_ratio,
+ drop=drop,
+ drop_path=drop_path[i] if isinstance(
+ drop_path, list) else drop_path,
+ local_conv_size=local_conv_size,
+ activation=activation,
+ )
+ for i in range(depth)])
+
+ # patch merging layer
+ if downsample is not None:
+ self.downsample = downsample(
+ input_resolution, dim=dim, out_dim=out_dim, activation=activation)
+ else:
+ self.downsample = None
+
+ def forward(self, x):
+ for blk in self.blocks:
+ if self.use_checkpoint:
+ x = checkpoint.checkpoint(blk, x)
+ else:
+ x = blk(x)
+ if self.downsample is not None:
+ x = self.downsample(x)
+ return x
+
+ def extra_repr(self) -> str:
+ return f"dim={self.dim}, input_resolution={self.input_resolution}, depth={self.depth}"
+
+class LayerNorm2d(nn.Module):
+ def __init__(self, num_channels: int, eps: float = 1e-6) -> None:
+ super().__init__()
+ self.weight = nn.Parameter(torch.ones(num_channels))
+ self.bias = nn.Parameter(torch.zeros(num_channels))
+ self.eps = eps
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ u = x.mean(1, keepdim=True)
+ s = (x - u).pow(2).mean(1, keepdim=True)
+ x = (x - u) / torch.sqrt(s + self.eps)
+ x = self.weight[:, None, None] * x + self.bias[:, None, None]
+ return x
+class TinyViT(nn.Module):
+ def __init__(self, img_size=224, in_chans=3, num_classes=1000,
+ embed_dims=[96, 192, 384, 768], depths=[2, 2, 6, 2],
+ num_heads=[3, 6, 12, 24],
+ window_sizes=[7, 7, 14, 7],
+ mlp_ratio=4.,
+ drop_rate=0.,
+ drop_path_rate=0.1,
+ use_checkpoint=False,
+ mbconv_expand_ratio=4.0,
+ local_conv_size=3,
+ layer_lr_decay=1.0,
+ ):
+ super().__init__()
+ self.img_size=img_size
+ self.num_classes = num_classes
+ self.depths = depths
+ self.num_layers = len(depths)
+ self.mlp_ratio = mlp_ratio
+
+ activation = nn.GELU
+
+ self.patch_embed = PatchEmbed(in_chans=in_chans,
+ embed_dim=embed_dims[0],
+ resolution=img_size,
+ activation=activation)
+
+ patches_resolution = self.patch_embed.patches_resolution
+ self.patches_resolution = patches_resolution
+
+ # stochastic depth
+ dpr = [x.item() for x in torch.linspace(0, drop_path_rate,
+ sum(depths))] # stochastic depth decay rule
+
+ # build layers
+ self.layers = nn.ModuleList()
+ for i_layer in range(self.num_layers):
+ kwargs = dict(dim=embed_dims[i_layer],
+ input_resolution=(patches_resolution[0] // (2 ** (i_layer-1 if i_layer == 3 else i_layer)),
+ patches_resolution[1] // (2 ** (i_layer-1 if i_layer == 3 else i_layer))),
+ # input_resolution=(patches_resolution[0] // (2 ** i_layer),
+ # patches_resolution[1] // (2 ** i_layer)),
+ depth=depths[i_layer],
+ drop_path=dpr[sum(depths[:i_layer]):sum(depths[:i_layer + 1])],
+ downsample=PatchMerging if (
+ i_layer < self.num_layers - 1) else None,
+ use_checkpoint=use_checkpoint,
+ out_dim=embed_dims[min(
+ i_layer + 1, len(embed_dims) - 1)],
+ activation=activation,
+ )
+ if i_layer == 0:
+ layer = ConvLayer(
+ conv_expand_ratio=mbconv_expand_ratio,
+ **kwargs,
+ )
+ else:
+ layer = BasicLayer(
+ num_heads=num_heads[i_layer],
+ window_size=window_sizes[i_layer],
+ mlp_ratio=self.mlp_ratio,
+ drop=drop_rate,
+ local_conv_size=local_conv_size,
+ **kwargs)
+ self.layers.append(layer)
+
+ # Classifier head
+ self.norm_head = nn.LayerNorm(embed_dims[-1])
+ self.head = nn.Linear(
+ embed_dims[-1], num_classes) if num_classes > 0 else torch.nn.Identity()
+
+ # init weights
+ self.apply(self._init_weights)
+ self.set_layer_lr_decay(layer_lr_decay)
+ self.neck = nn.Sequential(
+ nn.Conv2d(
+ embed_dims[-1],
+ 256,
+ kernel_size=1,
+ bias=False,
+ ),
+ LayerNorm2d(256),
+ nn.Conv2d(
+ 256,
+ 256,
+ kernel_size=3,
+ padding=1,
+ bias=False,
+ ),
+ LayerNorm2d(256),
+ )
+ def set_layer_lr_decay(self, layer_lr_decay):
+ decay_rate = layer_lr_decay
+
+ # layers -> blocks (depth)
+ depth = sum(self.depths)
+ lr_scales = [decay_rate ** (depth - i - 1) for i in range(depth)]
+ #print("LR SCALES:", lr_scales)
+
+ def _set_lr_scale(m, scale):
+ for p in m.parameters():
+ p.lr_scale = scale
+
+ self.patch_embed.apply(lambda x: _set_lr_scale(x, lr_scales[0]))
+ i = 0
+ for layer in self.layers:
+ for block in layer.blocks:
+ block.apply(lambda x: _set_lr_scale(x, lr_scales[i]))
+ i += 1
+ if layer.downsample is not None:
+ layer.downsample.apply(
+ lambda x: _set_lr_scale(x, lr_scales[i - 1]))
+ assert i == depth
+ for m in [self.norm_head, self.head]:
+ m.apply(lambda x: _set_lr_scale(x, lr_scales[-1]))
+
+ for k, p in self.named_parameters():
+ p.param_name = k
+
+ def _check_lr_scale(m):
+ for p in m.parameters():
+ assert hasattr(p, 'lr_scale'), p.param_name
+
+ self.apply(_check_lr_scale)
+
+ def _init_weights(self, m):
+ if isinstance(m, nn.Linear):
+ trunc_normal_(m.weight, std=.02)
+ if isinstance(m, nn.Linear) and m.bias is not None:
+ nn.init.constant_(m.bias, 0)
+ elif isinstance(m, nn.LayerNorm):
+ nn.init.constant_(m.bias, 0)
+ nn.init.constant_(m.weight, 1.0)
+
+ @torch.jit.ignore
+ def no_weight_decay_keywords(self):
+ return {'attention_biases'}
+
+ def forward_features(self, x):
+ # x: (N, C, H, W)
+ x = self.patch_embed(x)
+
+ x = self.layers[0](x)
+ start_i = 1
+
+ for i in range(start_i, len(self.layers)):
+ layer = self.layers[i]
+ x = layer(x)
+ B,_,C=x.size()
+ x = x.view(B, 64, 64, C)
+ x=x.permute(0, 3, 1, 2)
+ x=self.neck(x)
+ return x
+
+ def forward(self, x):
+ x = self.forward_features(x)
+ #x = self.norm_head(x)
+ #x = self.head(x)
+ return x
+
+
+_checkpoint_url_format = \
+ 'https://github.com/wkcn/TinyViT-model-zoo/releases/download/checkpoints/{}.pth'
+_provided_checkpoints = {
+ 'tiny_vit_5m_224': 'tiny_vit_5m_22kto1k_distill',
+ 'tiny_vit_11m_224': 'tiny_vit_11m_22kto1k_distill',
+ 'tiny_vit_21m_224': 'tiny_vit_21m_22kto1k_distill',
+ 'tiny_vit_21m_384': 'tiny_vit_21m_22kto1k_384_distill',
+ 'tiny_vit_21m_512': 'tiny_vit_21m_22kto1k_512_distill',
+}
+
+
+def register_tiny_vit_model(fn):
+ '''Register a TinyViT model
+ It is a wrapper of `register_model` with loading the pretrained checkpoint.
+ '''
+ def fn_wrapper(pretrained=False, **kwargs):
+ model = fn()
+ if pretrained:
+ model_name = fn.__name__
+ assert model_name in _provided_checkpoints, \
+ f'Sorry that the checkpoint `{model_name}` is not provided yet.'
+ url = _checkpoint_url_format.format(
+ _provided_checkpoints[model_name])
+ checkpoint = torch.hub.load_state_dict_from_url(
+ url=url,
+ map_location='cpu', check_hash=False,
+ )
+ model.load_state_dict(checkpoint['model'])
+
+ return model
+
+ # rename the name of fn_wrapper
+ fn_wrapper.__name__ = fn.__name__
+ return register_model(fn_wrapper)
+
+
+@register_tiny_vit_model
+def tiny_vit_5m_224(pretrained=False, num_classes=1000, drop_path_rate=0.0):
+ return TinyViT(
+ num_classes=num_classes,
+ embed_dims=[64, 128, 160, 320],
+ depths=[2, 2, 6, 2],
+ num_heads=[2, 4, 5, 10],
+ window_sizes=[7, 7, 14, 7],
+ drop_path_rate=drop_path_rate,
+ )
+
+
+@register_tiny_vit_model
+def tiny_vit_11m_224(pretrained=False, num_classes=1000, drop_path_rate=0.1):
+ return TinyViT(
+ num_classes=num_classes,
+ embed_dims=[64, 128, 256, 448],
+ depths=[2, 2, 6, 2],
+ num_heads=[2, 4, 8, 14],
+ window_sizes=[7, 7, 14, 7],
+ drop_path_rate=drop_path_rate,
+ )
+
+
+@register_tiny_vit_model
+def tiny_vit_21m_224(pretrained=False, num_classes=1000, drop_path_rate=0.2):
+ return TinyViT(
+ num_classes=num_classes,
+ embed_dims=[96, 192, 384, 576],
+ depths=[2, 2, 6, 2],
+ num_heads=[3, 6, 12, 18],
+ window_sizes=[7, 7, 14, 7],
+ drop_path_rate=drop_path_rate,
+ )
+
+
+@register_tiny_vit_model
+def tiny_vit_21m_384(pretrained=False, num_classes=1000, drop_path_rate=0.1):
+ return TinyViT(
+ img_size=384,
+ num_classes=num_classes,
+ embed_dims=[96, 192, 384, 576],
+ depths=[2, 2, 6, 2],
+ num_heads=[3, 6, 12, 18],
+ window_sizes=[12, 12, 24, 12],
+ drop_path_rate=drop_path_rate,
+ )
+
+
+@register_tiny_vit_model
+def tiny_vit_21m_512(pretrained=False, num_classes=1000, drop_path_rate=0.1):
+ return TinyViT(
+ img_size=512,
+ num_classes=num_classes,
+ embed_dims=[96, 192, 384, 576],
+ depths=[2, 2, 6, 2],
+ num_heads=[3, 6, 12, 18],
+ window_sizes=[16, 16, 32, 16],
+ drop_path_rate=drop_path_rate,
+ )
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/modeling/transformer.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/modeling/transformer.py
new file mode 100644
index 0000000000000000000000000000000000000000..28fafea52288603fea275f3a100790471825c34a
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/modeling/transformer.py
@@ -0,0 +1,240 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+# All rights reserved.
+
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+import torch
+from torch import Tensor, nn
+
+import math
+from typing import Tuple, Type
+
+from .common import MLPBlock
+
+
+class TwoWayTransformer(nn.Module):
+ def __init__(
+ self,
+ depth: int,
+ embedding_dim: int,
+ num_heads: int,
+ mlp_dim: int,
+ activation: Type[nn.Module] = nn.ReLU,
+ attention_downsample_rate: int = 2,
+ ) -> None:
+ """
+ A transformer decoder that attends to an input image using
+ queries whose positional embedding is supplied.
+
+ Args:
+ depth (int): number of layers in the transformer
+ embedding_dim (int): the channel dimension for the input embeddings
+ num_heads (int): the number of heads for multihead attention. Must
+ divide embedding_dim
+ mlp_dim (int): the channel dimension internal to the MLP block
+ activation (nn.Module): the activation to use in the MLP block
+ """
+ super().__init__()
+ self.depth = depth
+ self.embedding_dim = embedding_dim
+ self.num_heads = num_heads
+ self.mlp_dim = mlp_dim
+ self.layers = nn.ModuleList()
+
+ for i in range(depth):
+ self.layers.append(
+ TwoWayAttentionBlock(
+ embedding_dim=embedding_dim,
+ num_heads=num_heads,
+ mlp_dim=mlp_dim,
+ activation=activation,
+ attention_downsample_rate=attention_downsample_rate,
+ skip_first_layer_pe=(i == 0),
+ )
+ )
+
+ self.final_attn_token_to_image = Attention(
+ embedding_dim, num_heads, downsample_rate=attention_downsample_rate
+ )
+ self.norm_final_attn = nn.LayerNorm(embedding_dim)
+
+ def forward(
+ self,
+ image_embedding: Tensor,
+ image_pe: Tensor,
+ point_embedding: Tensor,
+ ) -> Tuple[Tensor, Tensor]:
+ """
+ Args:
+ image_embedding (torch.Tensor): image to attend to. Should be shape
+ B x embedding_dim x h x w for any h and w.
+ image_pe (torch.Tensor): the positional encoding to add to the image. Must
+ have the same shape as image_embedding.
+ point_embedding (torch.Tensor): the embedding to add to the query points.
+ Must have shape B x N_points x embedding_dim for any N_points.
+
+ Returns:
+ torch.Tensor: the processed point_embedding
+ torch.Tensor: the processed image_embedding
+ """
+ # BxCxHxW -> BxHWxC == B x N_image_tokens x C
+ bs, c, h, w = image_embedding.shape
+ image_embedding = image_embedding.flatten(2).permute(0, 2, 1)
+ image_pe = image_pe.flatten(2).permute(0, 2, 1)
+
+ # Prepare queries
+ queries = point_embedding
+ keys = image_embedding
+
+ # Apply transformer blocks and final layernorm
+ for layer in self.layers:
+ queries, keys = layer(
+ queries=queries,
+ keys=keys,
+ query_pe=point_embedding,
+ key_pe=image_pe,
+ )
+
+ # Apply the final attention layer from the points to the image
+ q = queries + point_embedding
+ k = keys + image_pe
+ attn_out = self.final_attn_token_to_image(q=q, k=k, v=keys)
+ queries = queries + attn_out
+ queries = self.norm_final_attn(queries)
+
+ return queries, keys
+
+
+class TwoWayAttentionBlock(nn.Module):
+ def __init__(
+ self,
+ embedding_dim: int,
+ num_heads: int,
+ mlp_dim: int = 2048,
+ activation: Type[nn.Module] = nn.ReLU,
+ attention_downsample_rate: int = 2,
+ skip_first_layer_pe: bool = False,
+ ) -> None:
+ """
+ A transformer block with four layers: (1) self-attention of sparse
+ inputs, (2) cross attention of sparse inputs to dense inputs, (3) mlp
+ block on sparse inputs, and (4) cross attention of dense inputs to sparse
+ inputs.
+
+ Arguments:
+ embedding_dim (int): the channel dimension of the embeddings
+ num_heads (int): the number of heads in the attention layers
+ mlp_dim (int): the hidden dimension of the mlp block
+ activation (nn.Module): the activation of the mlp block
+ skip_first_layer_pe (bool): skip the PE on the first layer
+ """
+ super().__init__()
+ self.self_attn = Attention(embedding_dim, num_heads)
+ self.norm1 = nn.LayerNorm(embedding_dim)
+
+ self.cross_attn_token_to_image = Attention(
+ embedding_dim, num_heads, downsample_rate=attention_downsample_rate
+ )
+ self.norm2 = nn.LayerNorm(embedding_dim)
+
+ self.mlp = MLPBlock(embedding_dim, mlp_dim, activation)
+ self.norm3 = nn.LayerNorm(embedding_dim)
+
+ self.norm4 = nn.LayerNorm(embedding_dim)
+ self.cross_attn_image_to_token = Attention(
+ embedding_dim, num_heads, downsample_rate=attention_downsample_rate
+ )
+
+ self.skip_first_layer_pe = skip_first_layer_pe
+
+ def forward(
+ self, queries: Tensor, keys: Tensor, query_pe: Tensor, key_pe: Tensor
+ ) -> Tuple[Tensor, Tensor]:
+ # Self attention block
+ if self.skip_first_layer_pe:
+ queries = self.self_attn(q=queries, k=queries, v=queries)
+ else:
+ q = queries + query_pe
+ attn_out = self.self_attn(q=q, k=q, v=queries)
+ queries = queries + attn_out
+ queries = self.norm1(queries)
+
+ # Cross attention block, tokens attending to image embedding
+ q = queries + query_pe
+ k = keys + key_pe
+ attn_out = self.cross_attn_token_to_image(q=q, k=k, v=keys)
+ queries = queries + attn_out
+ queries = self.norm2(queries)
+
+ # MLP block
+ mlp_out = self.mlp(queries)
+ queries = queries + mlp_out
+ queries = self.norm3(queries)
+
+ # Cross attention block, image embedding attending to tokens
+ q = queries + query_pe
+ k = keys + key_pe
+ attn_out = self.cross_attn_image_to_token(q=k, k=q, v=queries)
+ keys = keys + attn_out
+ keys = self.norm4(keys)
+
+ return queries, keys
+
+
+class Attention(nn.Module):
+ """
+ An attention layer that allows for downscaling the size of the embedding
+ after projection to queries, keys, and values.
+ """
+
+ def __init__(
+ self,
+ embedding_dim: int,
+ num_heads: int,
+ downsample_rate: int = 1,
+ ) -> None:
+ super().__init__()
+ self.embedding_dim = embedding_dim
+ self.internal_dim = embedding_dim // downsample_rate
+ self.num_heads = num_heads
+ assert self.internal_dim % num_heads == 0, "num_heads must divide embedding_dim."
+
+ self.q_proj = nn.Linear(embedding_dim, self.internal_dim)
+ self.k_proj = nn.Linear(embedding_dim, self.internal_dim)
+ self.v_proj = nn.Linear(embedding_dim, self.internal_dim)
+ self.out_proj = nn.Linear(self.internal_dim, embedding_dim)
+
+ def _separate_heads(self, x: Tensor, num_heads: int) -> Tensor:
+ b, n, c = x.shape
+ x = x.reshape(b, n, num_heads, c // num_heads)
+ return x.transpose(1, 2) # B x N_heads x N_tokens x C_per_head
+
+ def _recombine_heads(self, x: Tensor) -> Tensor:
+ b, n_heads, n_tokens, c_per_head = x.shape
+ x = x.transpose(1, 2)
+ return x.reshape(b, n_tokens, n_heads * c_per_head) # B x N_tokens x C
+
+ def forward(self, q: Tensor, k: Tensor, v: Tensor) -> Tensor:
+ # Input projections
+ q = self.q_proj(q)
+ k = self.k_proj(k)
+ v = self.v_proj(v)
+
+ # Separate into heads
+ q = self._separate_heads(q, self.num_heads)
+ k = self._separate_heads(k, self.num_heads)
+ v = self._separate_heads(v, self.num_heads)
+
+ # Attention
+ _, _, _, c_per_head = q.shape
+ attn = q @ k.permute(0, 1, 3, 2) # B x N_heads x N_tokens x N_tokens
+ attn = attn / math.sqrt(c_per_head)
+ attn = torch.softmax(attn, dim=-1)
+
+ # Get output
+ out = attn @ v
+ out = self._recombine_heads(out)
+ out = self.out_proj(out)
+
+ return out
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/predictor.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/predictor.py
new file mode 100644
index 0000000000000000000000000000000000000000..a3820fb7de8647e5d6adf229debc498b33caad62
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/predictor.py
@@ -0,0 +1,269 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+# All rights reserved.
+
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+import numpy as np
+import torch
+
+from .modeling import Sam
+
+from typing import Optional, Tuple
+
+from .utils.transforms import ResizeLongestSide
+
+
+class SamPredictor:
+ def __init__(
+ self,
+ sam_model: Sam,
+ ) -> None:
+ """
+ Uses SAM to calculate the image embedding for an image, and then
+ allow repeated, efficient mask prediction given prompts.
+
+ Arguments:
+ sam_model (Sam): The model to use for mask prediction.
+ """
+ super().__init__()
+ self.model = sam_model
+ self.transform = ResizeLongestSide(sam_model.image_encoder.img_size)
+ self.reset_image()
+
+ def set_image(
+ self,
+ image: np.ndarray,
+ image_format: str = "RGB",
+ ) -> None:
+ """
+ Calculates the image embeddings for the provided image, allowing
+ masks to be predicted with the 'predict' method.
+
+ Arguments:
+ image (np.ndarray): The image for calculating masks. Expects an
+ image in HWC uint8 format, with pixel values in [0, 255].
+ image_format (str): The color format of the image, in ['RGB', 'BGR'].
+ """
+ assert image_format in [
+ "RGB",
+ "BGR",
+ ], f"image_format must be in ['RGB', 'BGR'], is {image_format}."
+ if image_format != self.model.image_format:
+ image = image[..., ::-1]
+
+ # Transform the image to the form expected by the model
+ input_image = self.transform.apply_image(image)
+ input_image_torch = torch.as_tensor(input_image, device=self.device)
+ input_image_torch = input_image_torch.permute(2, 0, 1).contiguous()[None, :, :, :]
+
+ self.set_torch_image(input_image_torch, image.shape[:2])
+
+ @torch.no_grad()
+ def set_torch_image(
+ self,
+ transformed_image: torch.Tensor,
+ original_image_size: Tuple[int, ...],
+ ) -> None:
+ """
+ Calculates the image embeddings for the provided image, allowing
+ masks to be predicted with the 'predict' method. Expects the input
+ image to be already transformed to the format expected by the model.
+
+ Arguments:
+ transformed_image (torch.Tensor): The input image, with shape
+ 1x3xHxW, which has been transformed with ResizeLongestSide.
+ original_image_size (tuple(int, int)): The size of the image
+ before transformation, in (H, W) format.
+ """
+ assert (
+ len(transformed_image.shape) == 4
+ and transformed_image.shape[1] == 3
+ and max(*transformed_image.shape[2:]) == self.model.image_encoder.img_size
+ ), f"set_torch_image input must be BCHW with long side {self.model.image_encoder.img_size}."
+ self.reset_image()
+
+ self.original_size = original_image_size
+ self.input_size = tuple(transformed_image.shape[-2:])
+ input_image = self.model.preprocess(transformed_image)
+ self.features = self.model.image_encoder(input_image)
+ self.is_image_set = True
+
+ def predict(
+ self,
+ point_coords: Optional[np.ndarray] = None,
+ point_labels: Optional[np.ndarray] = None,
+ box: Optional[np.ndarray] = None,
+ mask_input: Optional[np.ndarray] = None,
+ multimask_output: bool = True,
+ return_logits: bool = False,
+ ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
+ """
+ Predict masks for the given input prompts, using the currently set image.
+
+ Arguments:
+ point_coords (np.ndarray or None): A Nx2 array of point prompts to the
+ model. Each point is in (X,Y) in pixels.
+ point_labels (np.ndarray or None): A length N array of labels for the
+ point prompts. 1 indicates a foreground point and 0 indicates a
+ background point.
+ box (np.ndarray or None): A length 4 array given a box prompt to the
+ model, in XYXY format.
+ mask_input (np.ndarray): A low resolution mask input to the model, typically
+ coming from a previous prediction iteration. Has form 1xHxW, where
+ for SAM, H=W=256.
+ multimask_output (bool): If true, the model will return three masks.
+ For ambiguous input prompts (such as a single click), this will often
+ produce better masks than a single prediction. If only a single
+ mask is needed, the model's predicted quality score can be used
+ to select the best mask. For non-ambiguous prompts, such as multiple
+ input prompts, multimask_output=False can give better results.
+ return_logits (bool): If true, returns un-thresholded masks logits
+ instead of a binary mask.
+
+ Returns:
+ (np.ndarray): The output masks in CxHxW format, where C is the
+ number of masks, and (H, W) is the original image size.
+ (np.ndarray): An array of length C containing the model's
+ predictions for the quality of each mask.
+ (np.ndarray): An array of shape CxHxW, where C is the number
+ of masks and H=W=256. These low resolution logits can be passed to
+ a subsequent iteration as mask input.
+ """
+ if not self.is_image_set:
+ raise RuntimeError("An image must be set with .set_image(...) before mask prediction.")
+
+ # Transform input prompts
+ coords_torch, labels_torch, box_torch, mask_input_torch = None, None, None, None
+ if point_coords is not None:
+ assert (
+ point_labels is not None
+ ), "point_labels must be supplied if point_coords is supplied."
+ point_coords = self.transform.apply_coords(point_coords, self.original_size)
+ coords_torch = torch.as_tensor(point_coords, dtype=torch.float, device=self.device)
+ labels_torch = torch.as_tensor(point_labels, dtype=torch.int, device=self.device)
+ coords_torch, labels_torch = coords_torch[None, :, :], labels_torch[None, :]
+ if box is not None:
+ box = self.transform.apply_boxes(box, self.original_size)
+ box_torch = torch.as_tensor(box, dtype=torch.float, device=self.device)
+ box_torch = box_torch[None, :]
+ if mask_input is not None:
+ mask_input_torch = torch.as_tensor(mask_input, dtype=torch.float, device=self.device)
+ mask_input_torch = mask_input_torch[None, :, :, :]
+
+ masks, iou_predictions, low_res_masks = self.predict_torch(
+ coords_torch,
+ labels_torch,
+ box_torch,
+ mask_input_torch,
+ multimask_output,
+ return_logits=return_logits,
+ )
+
+ masks_np = masks[0].detach().cpu().numpy()
+ iou_predictions_np = iou_predictions[0].detach().cpu().numpy()
+ low_res_masks_np = low_res_masks[0].detach().cpu().numpy()
+ return masks_np, iou_predictions_np, low_res_masks_np
+
+ @torch.no_grad()
+ def predict_torch(
+ self,
+ point_coords: Optional[torch.Tensor],
+ point_labels: Optional[torch.Tensor],
+ boxes: Optional[torch.Tensor] = None,
+ mask_input: Optional[torch.Tensor] = None,
+ multimask_output: bool = True,
+ return_logits: bool = False,
+ ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
+ """
+ Predict masks for the given input prompts, using the currently set image.
+ Input prompts are batched torch tensors and are expected to already be
+ transformed to the input frame using ResizeLongestSide.
+
+ Arguments:
+ point_coords (torch.Tensor or None): A BxNx2 array of point prompts to the
+ model. Each point is in (X,Y) in pixels.
+ point_labels (torch.Tensor or None): A BxN array of labels for the
+ point prompts. 1 indicates a foreground point and 0 indicates a
+ background point.
+ boxes (np.ndarray or None): A Bx4 array given a box prompt to the
+ model, in XYXY format.
+ mask_input (np.ndarray): A low resolution mask input to the model, typically
+ coming from a previous prediction iteration. Has form Bx1xHxW, where
+ for SAM, H=W=256. Masks returned by a previous iteration of the
+ predict method do not need further transformation.
+ multimask_output (bool): If true, the model will return three masks.
+ For ambiguous input prompts (such as a single click), this will often
+ produce better masks than a single prediction. If only a single
+ mask is needed, the model's predicted quality score can be used
+ to select the best mask. For non-ambiguous prompts, such as multiple
+ input prompts, multimask_output=False can give better results.
+ return_logits (bool): If true, returns un-thresholded masks logits
+ instead of a binary mask.
+
+ Returns:
+ (torch.Tensor): The output masks in BxCxHxW format, where C is the
+ number of masks, and (H, W) is the original image size.
+ (torch.Tensor): An array of shape BxC containing the model's
+ predictions for the quality of each mask.
+ (torch.Tensor): An array of shape BxCxHxW, where C is the number
+ of masks and H=W=256. These low res logits can be passed to
+ a subsequent iteration as mask input.
+ """
+ if not self.is_image_set:
+ raise RuntimeError("An image must be set with .set_image(...) before mask prediction.")
+
+ if point_coords is not None:
+ points = (point_coords, point_labels)
+ else:
+ points = None
+
+ # Embed prompts
+ sparse_embeddings, dense_embeddings = self.model.prompt_encoder(
+ points=points,
+ boxes=boxes,
+ masks=mask_input,
+ )
+
+ # Predict masks
+ low_res_masks, iou_predictions = self.model.mask_decoder(
+ image_embeddings=self.features,
+ image_pe=self.model.prompt_encoder.get_dense_pe(),
+ sparse_prompt_embeddings=sparse_embeddings,
+ dense_prompt_embeddings=dense_embeddings,
+ multimask_output=multimask_output,
+ )
+
+ # Upscale the masks to the original image resolution
+ masks = self.model.postprocess_masks(low_res_masks, self.input_size, self.original_size)
+
+ if not return_logits:
+ masks = masks > self.model.mask_threshold
+
+ return masks, iou_predictions, low_res_masks
+
+ def get_image_embedding(self) -> torch.Tensor:
+ """
+ Returns the image embeddings for the currently set image, with
+ shape 1xCxHxW, where C is the embedding dimension and (H,W) are
+ the embedding spatial dimension of SAM (typically C=256, H=W=64).
+ """
+ if not self.is_image_set:
+ raise RuntimeError(
+ "An image must be set with .set_image(...) to generate an embedding."
+ )
+ assert self.features is not None, "Features must exist if an image has been set."
+ return self.features
+
+ @property
+ def device(self) -> torch.device:
+ return self.model.device
+
+ def reset_image(self) -> None:
+ """Resets the currently set image."""
+ self.is_image_set = False
+ self.features = None
+ self.orig_h = None
+ self.orig_w = None
+ self.input_h = None
+ self.input_w = None
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/utils/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/utils/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..5277f46157403e47fd830fc519144b97ef69d4ae
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/utils/__init__.py
@@ -0,0 +1,5 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+# All rights reserved.
+
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/utils/amg.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/utils/amg.py
new file mode 100644
index 0000000000000000000000000000000000000000..de480030b10c60fa398fbd6019b7ca32b6ea970f
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/utils/amg.py
@@ -0,0 +1,346 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+# All rights reserved.
+
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+import numpy as np
+import torch
+
+import math
+from copy import deepcopy
+from itertools import product
+from typing import Any, Dict, Generator, ItemsView, List, Tuple
+
+
+class MaskData:
+ """
+ A structure for storing masks and their related data in batched format.
+ Implements basic filtering and concatenation.
+ """
+
+ def __init__(self, **kwargs) -> None:
+ for v in kwargs.values():
+ assert isinstance(
+ v, (list, np.ndarray, torch.Tensor)
+ ), "MaskData only supports list, numpy arrays, and torch tensors."
+ self._stats = dict(**kwargs)
+
+ def __setitem__(self, key: str, item: Any) -> None:
+ assert isinstance(
+ item, (list, np.ndarray, torch.Tensor)
+ ), "MaskData only supports list, numpy arrays, and torch tensors."
+ self._stats[key] = item
+
+ def __delitem__(self, key: str) -> None:
+ del self._stats[key]
+
+ def __getitem__(self, key: str) -> Any:
+ return self._stats[key]
+
+ def items(self) -> ItemsView[str, Any]:
+ return self._stats.items()
+
+ def filter(self, keep: torch.Tensor) -> None:
+ for k, v in self._stats.items():
+ if v is None:
+ self._stats[k] = None
+ elif isinstance(v, torch.Tensor):
+ self._stats[k] = v[torch.as_tensor(keep, device=v.device)]
+ elif isinstance(v, np.ndarray):
+ self._stats[k] = v[keep.detach().cpu().numpy()]
+ elif isinstance(v, list) and keep.dtype == torch.bool:
+ self._stats[k] = [a for i, a in enumerate(v) if keep[i]]
+ elif isinstance(v, list):
+ self._stats[k] = [v[i] for i in keep]
+ else:
+ raise TypeError(f"MaskData key {k} has an unsupported type {type(v)}.")
+
+ def cat(self, new_stats: "MaskData") -> None:
+ for k, v in new_stats.items():
+ if k not in self._stats or self._stats[k] is None:
+ self._stats[k] = deepcopy(v)
+ elif isinstance(v, torch.Tensor):
+ self._stats[k] = torch.cat([self._stats[k], v], dim=0)
+ elif isinstance(v, np.ndarray):
+ self._stats[k] = np.concatenate([self._stats[k], v], axis=0)
+ elif isinstance(v, list):
+ self._stats[k] = self._stats[k] + deepcopy(v)
+ else:
+ raise TypeError(f"MaskData key {k} has an unsupported type {type(v)}.")
+
+ def to_numpy(self) -> None:
+ for k, v in self._stats.items():
+ if isinstance(v, torch.Tensor):
+ self._stats[k] = v.detach().cpu().numpy()
+
+
+def is_box_near_crop_edge(
+ boxes: torch.Tensor, crop_box: List[int], orig_box: List[int], atol: float = 20.0
+) -> torch.Tensor:
+ """Filter masks at the edge of a crop, but not at the edge of the original image."""
+ crop_box_torch = torch.as_tensor(crop_box, dtype=torch.float, device=boxes.device)
+ orig_box_torch = torch.as_tensor(orig_box, dtype=torch.float, device=boxes.device)
+ boxes = uncrop_boxes_xyxy(boxes, crop_box).float()
+ near_crop_edge = torch.isclose(boxes, crop_box_torch[None, :], atol=atol, rtol=0)
+ near_image_edge = torch.isclose(boxes, orig_box_torch[None, :], atol=atol, rtol=0)
+ near_crop_edge = torch.logical_and(near_crop_edge, ~near_image_edge)
+ return torch.any(near_crop_edge, dim=1)
+
+
+def box_xyxy_to_xywh(box_xyxy: torch.Tensor) -> torch.Tensor:
+ box_xywh = deepcopy(box_xyxy)
+ box_xywh[2] = box_xywh[2] - box_xywh[0]
+ box_xywh[3] = box_xywh[3] - box_xywh[1]
+ return box_xywh
+
+
+def batch_iterator(batch_size: int, *args) -> Generator[List[Any], None, None]:
+ assert len(args) > 0 and all(
+ len(a) == len(args[0]) for a in args
+ ), "Batched iteration must have inputs of all the same size."
+ n_batches = len(args[0]) // batch_size + int(len(args[0]) % batch_size != 0)
+ for b in range(n_batches):
+ yield [arg[b * batch_size : (b + 1) * batch_size] for arg in args]
+
+
+def mask_to_rle_pytorch(tensor: torch.Tensor) -> List[Dict[str, Any]]:
+ """
+ Encodes masks to an uncompressed RLE, in the format expected by
+ pycoco tools.
+ """
+ # Put in fortran order and flatten h,w
+ b, h, w = tensor.shape
+ tensor = tensor.permute(0, 2, 1).flatten(1)
+
+ # Compute change indices
+ diff = tensor[:, 1:] ^ tensor[:, :-1]
+ change_indices = diff.nonzero()
+
+ # Encode run length
+ out = []
+ for i in range(b):
+ cur_idxs = change_indices[change_indices[:, 0] == i, 1]
+ cur_idxs = torch.cat(
+ [
+ torch.tensor([0], dtype=cur_idxs.dtype, device=cur_idxs.device),
+ cur_idxs + 1,
+ torch.tensor([h * w], dtype=cur_idxs.dtype, device=cur_idxs.device),
+ ]
+ )
+ btw_idxs = cur_idxs[1:] - cur_idxs[:-1]
+ counts = [] if tensor[i, 0] == 0 else [0]
+ counts.extend(btw_idxs.detach().cpu().tolist())
+ out.append({"size": [h, w], "counts": counts})
+ return out
+
+
+def rle_to_mask(rle: Dict[str, Any]) -> np.ndarray:
+ """Compute a binary mask from an uncompressed RLE."""
+ h, w = rle["size"]
+ mask = np.empty(h * w, dtype=bool)
+ idx = 0
+ parity = False
+ for count in rle["counts"]:
+ mask[idx : idx + count] = parity
+ idx += count
+ parity ^= True
+ mask = mask.reshape(w, h)
+ return mask.transpose() # Put in C order
+
+
+def area_from_rle(rle: Dict[str, Any]) -> int:
+ return sum(rle["counts"][1::2])
+
+
+def calculate_stability_score(
+ masks: torch.Tensor, mask_threshold: float, threshold_offset: float
+) -> torch.Tensor:
+ """
+ Computes the stability score for a batch of masks. The stability
+ score is the IoU between the binary masks obtained by thresholding
+ the predicted mask logits at high and low values.
+ """
+ # One mask is always contained inside the other.
+ # Save memory by preventing unnecessary cast to torch.int64
+ intersections = (
+ (masks > (mask_threshold + threshold_offset))
+ .sum(-1, dtype=torch.int16)
+ .sum(-1, dtype=torch.int32)
+ )
+ unions = (
+ (masks > (mask_threshold - threshold_offset))
+ .sum(-1, dtype=torch.int16)
+ .sum(-1, dtype=torch.int32)
+ )
+ return intersections / unions
+
+
+def build_point_grid(n_per_side: int) -> np.ndarray:
+ """Generates a 2D grid of points evenly spaced in [0,1]x[0,1]."""
+ offset = 1 / (2 * n_per_side)
+ points_one_side = np.linspace(offset, 1 - offset, n_per_side)
+ points_x = np.tile(points_one_side[None, :], (n_per_side, 1))
+ points_y = np.tile(points_one_side[:, None], (1, n_per_side))
+ points = np.stack([points_x, points_y], axis=-1).reshape(-1, 2)
+ return points
+
+
+def build_all_layer_point_grids(
+ n_per_side: int, n_layers: int, scale_per_layer: int
+) -> List[np.ndarray]:
+ """Generates point grids for all crop layers."""
+ points_by_layer = []
+ for i in range(n_layers + 1):
+ n_points = int(n_per_side / (scale_per_layer**i))
+ points_by_layer.append(build_point_grid(n_points))
+ return points_by_layer
+
+
+def generate_crop_boxes(
+ im_size: Tuple[int, ...], n_layers: int, overlap_ratio: float
+) -> Tuple[List[List[int]], List[int]]:
+ """
+ Generates a list of crop boxes of different sizes. Each layer
+ has (2**i)**2 boxes for the ith layer.
+ """
+ crop_boxes, layer_idxs = [], []
+ im_h, im_w = im_size
+ short_side = min(im_h, im_w)
+
+ # Original image
+ crop_boxes.append([0, 0, im_w, im_h])
+ layer_idxs.append(0)
+
+ def crop_len(orig_len, n_crops, overlap):
+ return int(math.ceil((overlap * (n_crops - 1) + orig_len) / n_crops))
+
+ for i_layer in range(n_layers):
+ n_crops_per_side = 2 ** (i_layer + 1)
+ overlap = int(overlap_ratio * short_side * (2 / n_crops_per_side))
+
+ crop_w = crop_len(im_w, n_crops_per_side, overlap)
+ crop_h = crop_len(im_h, n_crops_per_side, overlap)
+
+ crop_box_x0 = [int((crop_w - overlap) * i) for i in range(n_crops_per_side)]
+ crop_box_y0 = [int((crop_h - overlap) * i) for i in range(n_crops_per_side)]
+
+ # Crops in XYWH format
+ for x0, y0 in product(crop_box_x0, crop_box_y0):
+ box = [x0, y0, min(x0 + crop_w, im_w), min(y0 + crop_h, im_h)]
+ crop_boxes.append(box)
+ layer_idxs.append(i_layer + 1)
+
+ return crop_boxes, layer_idxs
+
+
+def uncrop_boxes_xyxy(boxes: torch.Tensor, crop_box: List[int]) -> torch.Tensor:
+ x0, y0, _, _ = crop_box
+ offset = torch.tensor([[x0, y0, x0, y0]], device=boxes.device)
+ # Check if boxes has a channel dimension
+ if len(boxes.shape) == 3:
+ offset = offset.unsqueeze(1)
+ return boxes + offset
+
+
+def uncrop_points(points: torch.Tensor, crop_box: List[int]) -> torch.Tensor:
+ x0, y0, _, _ = crop_box
+ offset = torch.tensor([[x0, y0]], device=points.device)
+ # Check if points has a channel dimension
+ if len(points.shape) == 3:
+ offset = offset.unsqueeze(1)
+ return points + offset
+
+
+def uncrop_masks(
+ masks: torch.Tensor, crop_box: List[int], orig_h: int, orig_w: int
+) -> torch.Tensor:
+ x0, y0, x1, y1 = crop_box
+ if x0 == 0 and y0 == 0 and x1 == orig_w and y1 == orig_h:
+ return masks
+ # Coordinate transform masks
+ pad_x, pad_y = orig_w - (x1 - x0), orig_h - (y1 - y0)
+ pad = (x0, pad_x - x0, y0, pad_y - y0)
+ return torch.nn.functional.pad(masks, pad, value=0)
+
+
+def remove_small_regions(
+ mask: np.ndarray, area_thresh: float, mode: str
+) -> Tuple[np.ndarray, bool]:
+ """
+ Removes small disconnected regions and holes in a mask. Returns the
+ mask and an indicator of if the mask has been modified.
+ """
+ import cv2 # type: ignore
+
+ assert mode in ["holes", "islands"]
+ correct_holes = mode == "holes"
+ working_mask = (correct_holes ^ mask).astype(np.uint8)
+ n_labels, regions, stats, _ = cv2.connectedComponentsWithStats(working_mask, 8)
+ sizes = stats[:, -1][1:] # Row 0 is background label
+ small_regions = [i + 1 for i, s in enumerate(sizes) if s < area_thresh]
+ if len(small_regions) == 0:
+ return mask, False
+ fill_labels = [0] + small_regions
+ if not correct_holes:
+ fill_labels = [i for i in range(n_labels) if i not in fill_labels]
+ # If every region is below threshold, keep largest
+ if len(fill_labels) == 0:
+ fill_labels = [int(np.argmax(sizes)) + 1]
+ mask = np.isin(regions, fill_labels)
+ return mask, True
+
+
+def coco_encode_rle(uncompressed_rle: Dict[str, Any]) -> Dict[str, Any]:
+ from custom_pycocotools import mask as mask_utils # type: ignore
+
+ h, w = uncompressed_rle["size"]
+ rle = mask_utils.frPyObjects(uncompressed_rle, h, w)
+ rle["counts"] = rle["counts"].decode("utf-8") # Necessary to serialize with json
+ return rle
+
+
+def batched_mask_to_box(masks: torch.Tensor) -> torch.Tensor:
+ """
+ Calculates boxes in XYXY format around masks. Return [0,0,0,0] for
+ an empty mask. For input shape C1xC2x...xHxW, the output shape is C1xC2x...x4.
+ """
+ # torch.max below raises an error on empty inputs, just skip in this case
+ if torch.numel(masks) == 0:
+ return torch.zeros(*masks.shape[:-2], 4, device=masks.device)
+
+ # Normalize shape to CxHxW
+ shape = masks.shape
+ h, w = shape[-2:]
+ if len(shape) > 2:
+ masks = masks.flatten(0, -3)
+ else:
+ masks = masks.unsqueeze(0)
+
+ # Get top and bottom edges
+ in_height, _ = torch.max(masks, dim=-1)
+ in_height_coords = in_height * torch.arange(h, device=in_height.device)[None, :]
+ bottom_edges, _ = torch.max(in_height_coords, dim=-1)
+ in_height_coords = in_height_coords + h * (~in_height)
+ top_edges, _ = torch.min(in_height_coords, dim=-1)
+
+ # Get left and right edges
+ in_width, _ = torch.max(masks, dim=-2)
+ in_width_coords = in_width * torch.arange(w, device=in_width.device)[None, :]
+ right_edges, _ = torch.max(in_width_coords, dim=-1)
+ in_width_coords = in_width_coords + w * (~in_width)
+ left_edges, _ = torch.min(in_width_coords, dim=-1)
+
+ # If the mask is empty the right edge will be to the left of the left edge.
+ # Replace these boxes with [0, 0, 0, 0]
+ empty_filter = (right_edges < left_edges) | (bottom_edges < top_edges)
+ out = torch.stack([left_edges, top_edges, right_edges, bottom_edges], dim=-1)
+ out = out * (~empty_filter).unsqueeze(-1)
+
+ # Return to original shape
+ if len(shape) > 2:
+ out = out.reshape(*shape[:-2], 4)
+ else:
+ out = out[0]
+
+ return out
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/utils/onnx.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/utils/onnx.py
new file mode 100644
index 0000000000000000000000000000000000000000..3196bdf4b782e6eeb3da4ad66ef3c7b1741535fe
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/utils/onnx.py
@@ -0,0 +1,144 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+# All rights reserved.
+
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+import torch
+import torch.nn as nn
+from torch.nn import functional as F
+
+from typing import Tuple
+
+from ..modeling import Sam
+from .amg import calculate_stability_score
+
+
+class SamOnnxModel(nn.Module):
+ """
+ This model should not be called directly, but is used in ONNX export.
+ It combines the prompt encoder, mask decoder, and mask postprocessing of Sam,
+ with some functions modified to enable model tracing. Also supports extra
+ options controlling what information. See the ONNX export script for details.
+ """
+
+ def __init__(
+ self,
+ model: Sam,
+ return_single_mask: bool,
+ use_stability_score: bool = False,
+ return_extra_metrics: bool = False,
+ ) -> None:
+ super().__init__()
+ self.mask_decoder = model.mask_decoder
+ self.model = model
+ self.img_size = model.image_encoder.img_size
+ self.return_single_mask = return_single_mask
+ self.use_stability_score = use_stability_score
+ self.stability_score_offset = 1.0
+ self.return_extra_metrics = return_extra_metrics
+
+ @staticmethod
+ def resize_longest_image_size(
+ input_image_size: torch.Tensor, longest_side: int
+ ) -> torch.Tensor:
+ input_image_size = input_image_size.to(torch.float32)
+ scale = longest_side / torch.max(input_image_size)
+ transformed_size = scale * input_image_size
+ transformed_size = torch.floor(transformed_size + 0.5).to(torch.int64)
+ return transformed_size
+
+ def _embed_points(self, point_coords: torch.Tensor, point_labels: torch.Tensor) -> torch.Tensor:
+ point_coords = point_coords + 0.5
+ point_coords = point_coords / self.img_size
+ point_embedding = self.model.prompt_encoder.pe_layer._pe_encoding(point_coords)
+ point_labels = point_labels.unsqueeze(-1).expand_as(point_embedding)
+
+ point_embedding = point_embedding * (point_labels != -1)
+ point_embedding = point_embedding + self.model.prompt_encoder.not_a_point_embed.weight * (
+ point_labels == -1
+ )
+
+ for i in range(self.model.prompt_encoder.num_point_embeddings):
+ point_embedding = point_embedding + self.model.prompt_encoder.point_embeddings[
+ i
+ ].weight * (point_labels == i)
+
+ return point_embedding
+
+ def _embed_masks(self, input_mask: torch.Tensor, has_mask_input: torch.Tensor) -> torch.Tensor:
+ mask_embedding = has_mask_input * self.model.prompt_encoder.mask_downscaling(input_mask)
+ mask_embedding = mask_embedding + (
+ 1 - has_mask_input
+ ) * self.model.prompt_encoder.no_mask_embed.weight.reshape(1, -1, 1, 1)
+ return mask_embedding
+
+ def mask_postprocessing(self, masks: torch.Tensor, orig_im_size: torch.Tensor) -> torch.Tensor:
+ masks = F.interpolate(
+ masks,
+ size=(self.img_size, self.img_size),
+ mode="bilinear",
+ align_corners=False,
+ )
+
+ prepadded_size = self.resize_longest_image_size(orig_im_size, self.img_size).to(torch.int64)
+ masks = masks[..., : prepadded_size[0], : prepadded_size[1]] # type: ignore
+
+ orig_im_size = orig_im_size.to(torch.int64)
+ h, w = orig_im_size[0], orig_im_size[1]
+ masks = F.interpolate(masks, size=(h, w), mode="bilinear", align_corners=False)
+ return masks
+
+ def select_masks(
+ self, masks: torch.Tensor, iou_preds: torch.Tensor, num_points: int
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
+ # Determine if we should return the multiclick mask or not from the number of points.
+ # The reweighting is used to avoid control flow.
+ score_reweight = torch.tensor(
+ [[1000] + [0] * (self.model.mask_decoder.num_mask_tokens - 1)]
+ ).to(iou_preds.device)
+ score = iou_preds + (num_points - 2.5) * score_reweight
+ best_idx = torch.argmax(score, dim=1)
+ masks = masks[torch.arange(masks.shape[0]), best_idx, :, :].unsqueeze(1)
+ iou_preds = iou_preds[torch.arange(masks.shape[0]), best_idx].unsqueeze(1)
+
+ return masks, iou_preds
+
+ @torch.no_grad()
+ def forward(
+ self,
+ image_embeddings: torch.Tensor,
+ point_coords: torch.Tensor,
+ point_labels: torch.Tensor,
+ mask_input: torch.Tensor,
+ has_mask_input: torch.Tensor,
+ orig_im_size: torch.Tensor,
+ ):
+ sparse_embedding = self._embed_points(point_coords, point_labels)
+ dense_embedding = self._embed_masks(mask_input, has_mask_input)
+
+ masks, scores = self.model.mask_decoder.predict_masks(
+ image_embeddings=image_embeddings,
+ image_pe=self.model.prompt_encoder.get_dense_pe(),
+ sparse_prompt_embeddings=sparse_embedding,
+ dense_prompt_embeddings=dense_embedding,
+ )
+
+ if self.use_stability_score:
+ scores = calculate_stability_score(
+ masks, self.model.mask_threshold, self.stability_score_offset
+ )
+
+ if self.return_single_mask:
+ masks, scores = self.select_masks(masks, scores, point_coords.shape[1])
+
+ upscaled_masks = self.mask_postprocessing(masks, orig_im_size)
+
+ if self.return_extra_metrics:
+ stability_scores = calculate_stability_score(
+ upscaled_masks, self.model.mask_threshold, self.stability_score_offset
+ )
+ areas = (upscaled_masks > self.model.mask_threshold).sum(-1).sum(-1)
+ return upscaled_masks, scores, stability_scores, areas, masks
+
+ return upscaled_masks, scores, masks
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/utils/transforms.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/utils/transforms.py
new file mode 100644
index 0000000000000000000000000000000000000000..c08ba1e3db751f3a5483a003be38c69c2cf2df85
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/sam/utils/transforms.py
@@ -0,0 +1,102 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+# All rights reserved.
+
+# This source code is licensed under the license found in the
+# LICENSE file in the root directory of this source tree.
+
+import numpy as np
+import torch
+from torch.nn import functional as F
+from torchvision.transforms.functional import resize, to_pil_image # type: ignore
+
+from copy import deepcopy
+from typing import Tuple
+
+
+class ResizeLongestSide:
+ """
+ Resizes images to the longest side 'target_length', as well as provides
+ methods for resizing coordinates and boxes. Provides methods for
+ transforming both numpy array and batched torch tensors.
+ """
+
+ def __init__(self, target_length: int) -> None:
+ self.target_length = target_length
+
+ def apply_image(self, image: np.ndarray) -> np.ndarray:
+ """
+ Expects a numpy array with shape HxWxC in uint8 format.
+ """
+ target_size = self.get_preprocess_shape(image.shape[0], image.shape[1], self.target_length)
+ return np.array(resize(to_pil_image(image), target_size))
+
+ def apply_coords(self, coords: np.ndarray, original_size: Tuple[int, ...]) -> np.ndarray:
+ """
+ Expects a numpy array of length 2 in the final dimension. Requires the
+ original image size in (H, W) format.
+ """
+ old_h, old_w = original_size
+ new_h, new_w = self.get_preprocess_shape(
+ original_size[0], original_size[1], self.target_length
+ )
+ coords = deepcopy(coords).astype(float)
+ coords[..., 0] = coords[..., 0] * (new_w / old_w)
+ coords[..., 1] = coords[..., 1] * (new_h / old_h)
+ return coords
+
+ def apply_boxes(self, boxes: np.ndarray, original_size: Tuple[int, ...]) -> np.ndarray:
+ """
+ Expects a numpy array shape Bx4. Requires the original image size
+ in (H, W) format.
+ """
+ boxes = self.apply_coords(boxes.reshape(-1, 2, 2), original_size)
+ return boxes.reshape(-1, 4)
+
+ def apply_image_torch(self, image: torch.Tensor) -> torch.Tensor:
+ """
+ Expects batched images with shape BxCxHxW and float format. This
+ transformation may not exactly match apply_image. apply_image is
+ the transformation expected by the model.
+ """
+ # Expects an image in BCHW format. May not exactly match apply_image.
+ target_size = self.get_preprocess_shape(image.shape[2], image.shape[3], self.target_length)
+ return F.interpolate(
+ image, target_size, mode="bilinear", align_corners=False, antialias=True
+ )
+
+ def apply_coords_torch(
+ self, coords: torch.Tensor, original_size: Tuple[int, ...]
+ ) -> torch.Tensor:
+ """
+ Expects a torch tensor with length 2 in the last dimension. Requires the
+ original image size in (H, W) format.
+ """
+ old_h, old_w = original_size
+ new_h, new_w = self.get_preprocess_shape(
+ original_size[0], original_size[1], self.target_length
+ )
+ coords = deepcopy(coords).to(torch.float)
+ coords[..., 0] = coords[..., 0] * (new_w / old_w)
+ coords[..., 1] = coords[..., 1] * (new_h / old_h)
+ return coords
+
+ def apply_boxes_torch(
+ self, boxes: torch.Tensor, original_size: Tuple[int, ...]
+ ) -> torch.Tensor:
+ """
+ Expects a torch tensor with shape Bx4. Requires the original image
+ size in (H, W) format.
+ """
+ boxes = self.apply_coords_torch(boxes.reshape(-1, 2, 2), original_size)
+ return boxes.reshape(-1, 4)
+
+ @staticmethod
+ def get_preprocess_shape(oldh: int, oldw: int, long_side_length: int) -> Tuple[int, int]:
+ """
+ Compute the output size given input size and target long side length.
+ """
+ scale = long_side_length * 1.0 / max(oldh, oldw)
+ newh, neww = oldh * scale, oldw * scale
+ neww = int(neww + 0.5)
+ newh = int(newh + 0.5)
+ return (newh, neww)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/scribble/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/scribble/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..3ce37647520f6e1975641b3da34cab7f18d9fd40
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/scribble/__init__.py
@@ -0,0 +1,41 @@
+import warnings
+import cv2
+import numpy as np
+from PIL import Image
+from controlnet_aux.util import HWC3, resize_image_with_pad, common_input_validate, HWC3
+
+#Not to be confused with "scribble" from HED. That is "fake scribble" which is more accurate and less picky than this.
+class ScribbleDetector:
+ def __call__(self, input_image=None, detect_resolution=512, output_type=None, upscale_method="INTER_AREA", **kwargs):
+ input_image, output_type = common_input_validate(input_image, output_type, **kwargs)
+ input_image, remove_pad = resize_image_with_pad(input_image, detect_resolution, upscale_method)
+
+ detected_map = np.zeros_like(input_image, dtype=np.uint8)
+ detected_map[np.min(input_image, axis=2) < 127] = 255
+ detected_map = 255 - detected_map
+
+ detected_map = remove_pad(detected_map)
+
+ if output_type == "pil":
+ detected_map = Image.fromarray(detected_map)
+
+ return detected_map
+
+class ScribbleXDog_Detector:
+ def __call__(self, input_image=None, detect_resolution=512, thr_a=32, output_type=None, upscale_method="INTER_CUBIC", **kwargs):
+ input_image, output_type = common_input_validate(input_image, output_type, **kwargs)
+ input_image, remove_pad = resize_image_with_pad(input_image, detect_resolution, upscale_method)
+
+ g1 = cv2.GaussianBlur(input_image.astype(np.float32), (0, 0), 0.5)
+ g2 = cv2.GaussianBlur(input_image.astype(np.float32), (0, 0), 5.0)
+ dog = (255 - np.min(g2 - g1, axis=2)).clip(0, 255).astype(np.uint8)
+ result = np.zeros_like(input_image, dtype=np.uint8)
+ result[2 * (255 - dog) > thr_a] = 255
+ #result = 255 - result
+
+ detected_map = HWC3(remove_pad(result))
+
+ if output_type == "pil":
+ detected_map = Image.fromarray(detected_map)
+
+ return detected_map
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/scribble/__pycache__/__init__.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/scribble/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2b3c0dc34600bc76807246d69713c8d525bdf435
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/scribble/__pycache__/__init__.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/shuffle/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/shuffle/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..71f131a6cd5437edbd22f92448323a0bd6522a48
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/shuffle/__init__.py
@@ -0,0 +1,86 @@
+import warnings
+
+import cv2
+import numpy as np
+from PIL import Image
+import random
+
+from controlnet_aux.util import HWC3, common_input_validate, img2mask, make_noise_disk, resize_image_with_pad
+
+
+class ContentShuffleDetector:
+ def __call__(self, input_image, h=None, w=None, f=None, detect_resolution=512, output_type="pil", upscale_method="INTER_CUBIC", **kwargs):
+ input_image, output_type = common_input_validate(input_image, output_type, **kwargs)
+ input_image, remove_pad = resize_image_with_pad(input_image, detect_resolution, upscale_method)
+
+ H, W, C = input_image.shape
+ if h is None:
+ h = H
+ if w is None:
+ w = W
+ if f is None:
+ f = 256
+ x = make_noise_disk(h, w, 1, f) * float(W - 1)
+ y = make_noise_disk(h, w, 1, f) * float(H - 1)
+ flow = np.concatenate([x, y], axis=2).astype(np.float32)
+ detected_map = cv2.remap(input_image, flow, None, cv2.INTER_LINEAR)
+ detected_map = remove_pad(detected_map)
+
+ if output_type == "pil":
+ detected_map = Image.fromarray(detected_map)
+
+ return detected_map
+
+
+class ColorShuffleDetector:
+ def __call__(self, img):
+ H, W, C = img.shape
+ F = np.random.randint(64, 384)
+ A = make_noise_disk(H, W, 3, F)
+ B = make_noise_disk(H, W, 3, F)
+ C = (A + B) / 2.0
+ A = (C + (A - C) * 3.0).clip(0, 1)
+ B = (C + (B - C) * 3.0).clip(0, 1)
+ L = img.astype(np.float32) / 255.0
+ Y = A * L + B * (1 - L)
+ Y -= np.min(Y, axis=(0, 1), keepdims=True)
+ Y /= np.maximum(np.max(Y, axis=(0, 1), keepdims=True), 1e-5)
+ Y *= 255.0
+ return Y.clip(0, 255).astype(np.uint8)
+
+
+class GrayDetector:
+ def __call__(self, img):
+ eps = 1e-5
+ X = img.astype(np.float32)
+ r, g, b = X[:, :, 0], X[:, :, 1], X[:, :, 2]
+ kr, kg, kb = [random.random() + eps for _ in range(3)]
+ ks = kr + kg + kb
+ kr /= ks
+ kg /= ks
+ kb /= ks
+ Y = r * kr + g * kg + b * kb
+ Y = np.stack([Y] * 3, axis=2)
+ return Y.clip(0, 255).astype(np.uint8)
+
+
+class DownSampleDetector:
+ def __call__(self, img, level=3, k=16.0):
+ h = img.astype(np.float32)
+ for _ in range(level):
+ h += np.random.normal(loc=0.0, scale=k, size=h.shape)
+ h = cv2.pyrDown(h)
+ for _ in range(level):
+ h = cv2.pyrUp(h)
+ h += np.random.normal(loc=0.0, scale=k, size=h.shape)
+ return h.clip(0, 255).astype(np.uint8)
+
+
+class Image2MaskShuffleDetector:
+ def __init__(self, resolution=(640, 512)):
+ self.H, self.W = resolution
+
+ def __call__(self, img):
+ m = img2mask(img, self.H, self.W)
+ m *= 255.0
+ return m.clip(0, 255).astype(np.uint8)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/shuffle/__pycache__/__init__.cpython-311.pyc b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/shuffle/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..63d0a1efa366af29d752258fb6b55816a6f80572
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/shuffle/__pycache__/__init__.cpython-311.pyc differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/tests/requirements.txt b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/tests/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/tests/test_image.png b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/tests/test_image.png
new file mode 100644
index 0000000000000000000000000000000000000000..c4a751e31da45af83c8a3d5ec02cf8c22c7bb8e9
Binary files /dev/null and b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/tests/test_image.png differ
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/tests/test_processor.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/tests/test_processor.py
new file mode 100644
index 0000000000000000000000000000000000000000..dee2a0b7e86815bf6c3fa533ed34d5fcb5b4c4aa
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/tests/test_processor.py
@@ -0,0 +1,95 @@
+"""Test the Processor class."""
+import unittest
+from PIL import Image
+
+from controlnet_aux.processor import Processor
+
+
+class TestProcessor(unittest.TestCase):
+ def test_hed(self):
+ processor = Processor('hed')
+ image = Image.open('test_image.png')
+ processed_image = processor(image)
+ self.assertIsInstance(processed_image, bytes)
+
+ def test_midas(self):
+ processor = Processor('midas')
+ image = Image.open('test_image.png')
+ processed_image = processor(image)
+ self.assertIsInstance(processed_image, bytes)
+
+ def test_mlsd(self):
+ processor = Processor('mlsd')
+ image = Image.open('test_image.png')
+ processed_image = processor(image)
+ self.assertIsInstance(processed_image, bytes)
+
+ def test_openpose(self):
+ processor = Processor('openpose')
+ image = Image.open('test_image.png')
+ processed_image = processor(image)
+ self.assertIsInstance(processed_image, bytes)
+
+ def test_pidinet(self):
+ processor = Processor('pidinet')
+ image = Image.open('test_image.png')
+ processed_image = processor(image)
+ self.assertIsInstance(processed_image, bytes)
+
+ def test_normalbae(self):
+ processor = Processor('normalbae')
+ image = Image.open('test_image.png')
+ processed_image = processor(image)
+ self.assertIsInstance(processed_image, bytes)
+
+ def test_lineart(self):
+ processor = Processor('lineart')
+ image = Image.open('test_image.png')
+ processed_image = processor(image)
+ self.assertIsInstance(processed_image, bytes)
+
+ def test_lineart_coarse(self):
+ processor = Processor('lineart_coarse')
+ image = Image.open('test_image.png')
+ processed_image = processor(image)
+ self.assertIsInstance(processed_image, bytes)
+
+ def test_lineart_anime(self):
+ processor = Processor('lineart_anime')
+ image = Image.open('test_image.png')
+ processed_image = processor(image)
+ self.assertIsInstance(processed_image, bytes)
+
+ def test_canny(self):
+ processor = Processor('canny')
+ image = Image.open('test_image.png')
+ processed_image = processor(image)
+ self.assertIsInstance(processed_image, bytes)
+
+ def test_content_shuffle(self):
+ processor = Processor('content_shuffle')
+ image = Image.open('test_image.png')
+ processed_image = processor(image)
+ self.assertIsInstance(processed_image, bytes)
+
+ def test_zoe(self):
+ processor = Processor('zoe')
+ image = Image.open('test_image.png')
+ processed_image = processor(image)
+ self.assertIsInstance(processed_image, bytes)
+
+ def test_mediapipe_face(self):
+ processor = Processor('mediapipe_face')
+ image = Image.open('test_image.png')
+ processed_image = processor(image)
+ self.assertIsInstance(processed_image, bytes)
+
+ def test_tile(self):
+ processor = Processor('tile')
+ image = Image.open('test_image.png')
+ processed_image = processor(image)
+ self.assertIsInstance(processed_image, bytes)
+
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/tests/test_processor_pytest.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/tests/test_processor_pytest.py
new file mode 100644
index 0000000000000000000000000000000000000000..065acc392f60652ee6a8994b21b5869b7885f40e
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/tests/test_processor_pytest.py
@@ -0,0 +1,78 @@
+import io
+
+import numpy as np
+import pytest
+from PIL import Image
+
+from controlnet_aux.processor import MODELS, Processor
+
+
+@pytest.fixture(params=[
+ 'scribble_hed',
+ 'softedge_hed',
+ 'scribble_hedsafe',
+ 'softedge_hedsafe',
+ 'depth_midas',
+ 'mlsd',
+ 'openpose',
+ 'openpose_hand',
+ 'openpose_face',
+ 'openpose_faceonly',
+ 'openpose_full',
+ 'scribble_pidinet',
+ 'softedge_pidinet',
+ 'scribble_pidsafe',
+ 'softedge_pidsafe',
+ 'normal_bae',
+ 'lineart_coarse',
+ 'lineart_realistic',
+ 'lineart_anime',
+ 'canny',
+ 'shuffle',
+ 'depth_zoe',
+ 'depth_leres',
+ 'depth_leres++',
+ 'mediapipe_face',
+ 'tile'
+])
+def processor(request):
+ return Processor(request.param)
+
+
+def test_processor_init(processor):
+ assert isinstance(processor.processor, MODELS[processor.processor_id]['class'])
+ assert isinstance(processor.params, dict)
+
+
+def test_processor_call(processor):
+ # Load test image
+ with open('test_image.png', 'rb') as f:
+ image_bytes = f.read()
+ image = Image.open(io.BytesIO(image_bytes))
+
+ # Output size
+ resolution = 512
+ W, H = image.size
+ H = float(H)
+ W = float(W)
+ k = float(resolution) / min(H, W)
+ H *= k
+ W *= k
+ H = int(np.round(H / 64.0)) * 64
+ W = int(np.round(W / 64.0)) * 64
+
+ # Test processing
+ processed_image = processor(image)
+ assert isinstance(processed_image, Image.Image)
+ assert processed_image.size == (W, H)
+
+
+def test_processor_call_bytes(processor):
+ # Load test image
+ with open('test_image.png', 'rb') as f:
+ image_bytes = f.read()
+
+ # Test processing
+ processed_image_bytes = processor(image_bytes, to_pil=False)
+ assert isinstance(processed_image_bytes, bytes)
+ assert len(processed_image_bytes) > 0
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/tile/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/tile/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..81041c819d524badb5bd951b085d4578f8afa504
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/tile/__init__.py
@@ -0,0 +1,24 @@
+import warnings
+import cv2
+import numpy as np
+from PIL import Image
+from controlnet_aux.util import get_upscale_method, common_input_validate, HWC3
+
+
+class TileDetector:
+ def __call__(self, input_image=None, pyrUp_iters=3, output_type=None, upscale_method="INTER_AREA", **kwargs):
+ input_image, output_type = common_input_validate(input_image, output_type, **kwargs)
+ H, W, _ = input_image.shape
+ H = int(np.round(H / 64.0)) * 64
+ W = int(np.round(W / 64.0)) * 64
+ detected_map = cv2.resize(input_image, (W // (2 ** pyrUp_iters), H // (2 ** pyrUp_iters)),
+ interpolation=get_upscale_method(upscale_method))
+ detected_map = HWC3(detected_map)
+
+ for _ in range(pyrUp_iters):
+ detected_map = cv2.pyrUp(detected_map)
+
+ if output_type == "pil":
+ detected_map = Image.fromarray(detected_map)
+
+ return detected_map
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..a6776499e5f4b06551deaf97ca3ffdc03b96b45d
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/__init__.py
@@ -0,0 +1,69 @@
+import os
+from .inference import init_segmentor, inference_segmentor, show_result_pyplot
+import warnings
+import cv2
+import numpy as np
+from PIL import Image
+from controlnet_aux.util import HWC3, common_input_validate, resize_image_with_pad, annotator_ckpts_path, custom_hf_download
+import torch
+
+from custom_mmpkg.custom_mmseg.core.evaluation import get_palette
+
+config_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "upernet_global_small.py")
+
+
+
+class UniformerSegmentor:
+ def __init__(self, netNetwork):
+ self.model = netNetwork
+
+ @classmethod
+ def from_pretrained(cls, pretrained_model_or_path, filename=None, cache_dir=annotator_ckpts_path):
+ filename = filename or "upernet_global_small.pth"
+ model_path = custom_hf_download(pretrained_model_or_path, filename, cache_dir=cache_dir)
+
+ netNetwork = init_segmentor(config_file, model_path, device="cpu")
+ netNetwork.load_state_dict({k.replace('module.', ''): v for k, v in torch.load(model_path)['state_dict'].items()})
+ netNetwork.eval()
+
+ return cls(netNetwork)
+
+ def to(self, device):
+ self.model.to(device)
+ return self
+
+ def _inference(self, img):
+ if next(self.model.parameters()).device.type == 'mps':
+ # adaptive_avg_pool2d can fail on MPS, workaround with CPU
+ import torch.nn.functional
+
+ orig_adaptive_avg_pool2d = torch.nn.functional.adaptive_avg_pool2d
+ def cpu_if_exception(input, *args, **kwargs):
+ try:
+ return orig_adaptive_avg_pool2d(input, *args, **kwargs)
+ except:
+ return orig_adaptive_avg_pool2d(input.cpu(), *args, **kwargs).to(input.device)
+
+ try:
+ torch.nn.functional.adaptive_avg_pool2d = cpu_if_exception
+ result = inference_segmentor(self.model, img)
+ finally:
+ torch.nn.functional.adaptive_avg_pool2d = orig_adaptive_avg_pool2d
+ else:
+ result = inference_segmentor(self.model, img)
+
+ res_img = show_result_pyplot(self.model, img, result, get_palette('ade'), opacity=1)
+ return res_img
+
+ def __call__(self, input_image=None, detect_resolution=512, output_type=None, upscale_method="INTER_CUBIC", **kwargs):
+ input_image, output_type = common_input_validate(input_image, output_type, **kwargs)
+ input_image, remove_pad = resize_image_with_pad(input_image, detect_resolution, upscale_method)
+
+ detected_map = self._inference(input_image)
+ detected_map = remove_pad(HWC3(detected_map))
+
+ if output_type == "pil":
+ detected_map = Image.fromarray(detected_map)
+
+ return detected_map
+
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/ade20k.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/ade20k.py
new file mode 100644
index 0000000000000000000000000000000000000000..efc8b4bb20c981f3db6df7eb52b3dc0744c94cc0
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/ade20k.py
@@ -0,0 +1,54 @@
+# dataset settings
+dataset_type = 'ADE20KDataset'
+data_root = 'data/ade/ADEChallengeData2016'
+img_norm_cfg = dict(
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
+crop_size = (512, 512)
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='LoadAnnotations', reduce_zero_label=True),
+ dict(type='Resize', img_scale=(2048, 512), ratio_range=(0.5, 2.0)),
+ dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75),
+ dict(type='RandomFlip', prob=0.5),
+ dict(type='PhotoMetricDistortion'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='Pad', size=crop_size, pad_val=0, seg_pad_val=255),
+ dict(type='DefaultFormatBundle'),
+ dict(type='Collect', keys=['img', 'gt_semantic_seg']),
+]
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='MultiScaleFlipAug',
+ img_scale=(2048, 512),
+ # img_ratios=[0.5, 0.75, 1.0, 1.25, 1.5, 1.75],
+ flip=False,
+ transforms=[
+ dict(type='Resize', keep_ratio=True),
+ dict(type='RandomFlip'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img']),
+ ])
+]
+data = dict(
+ samples_per_gpu=4,
+ workers_per_gpu=4,
+ train=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='images/training',
+ ann_dir='annotations/training',
+ pipeline=train_pipeline),
+ val=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='images/validation',
+ ann_dir='annotations/validation',
+ pipeline=test_pipeline),
+ test=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='images/validation',
+ ann_dir='annotations/validation',
+ pipeline=test_pipeline))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/chase_db1.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/chase_db1.py
new file mode 100644
index 0000000000000000000000000000000000000000..298594ea925f87f22b37094a2ec50e370aec96a0
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/chase_db1.py
@@ -0,0 +1,59 @@
+# dataset settings
+dataset_type = 'ChaseDB1Dataset'
+data_root = 'data/CHASE_DB1'
+img_norm_cfg = dict(
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
+img_scale = (960, 999)
+crop_size = (128, 128)
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='LoadAnnotations'),
+ dict(type='Resize', img_scale=img_scale, ratio_range=(0.5, 2.0)),
+ dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75),
+ dict(type='RandomFlip', prob=0.5),
+ dict(type='PhotoMetricDistortion'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='Pad', size=crop_size, pad_val=0, seg_pad_val=255),
+ dict(type='DefaultFormatBundle'),
+ dict(type='Collect', keys=['img', 'gt_semantic_seg'])
+]
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='MultiScaleFlipAug',
+ img_scale=img_scale,
+ # img_ratios=[0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0],
+ flip=False,
+ transforms=[
+ dict(type='Resize', keep_ratio=True),
+ dict(type='RandomFlip'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img'])
+ ])
+]
+
+data = dict(
+ samples_per_gpu=4,
+ workers_per_gpu=4,
+ train=dict(
+ type='RepeatDataset',
+ times=40000,
+ dataset=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='images/training',
+ ann_dir='annotations/training',
+ pipeline=train_pipeline)),
+ val=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='images/validation',
+ ann_dir='annotations/validation',
+ pipeline=test_pipeline),
+ test=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='images/validation',
+ ann_dir='annotations/validation',
+ pipeline=test_pipeline))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/cityscapes.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/cityscapes.py
new file mode 100644
index 0000000000000000000000000000000000000000..f21867c63e1835f6fceb61f066e802fd8fd2a735
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/cityscapes.py
@@ -0,0 +1,54 @@
+# dataset settings
+dataset_type = 'CityscapesDataset'
+data_root = 'data/cityscapes/'
+img_norm_cfg = dict(
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
+crop_size = (512, 1024)
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='LoadAnnotations'),
+ dict(type='Resize', img_scale=(2048, 1024), ratio_range=(0.5, 2.0)),
+ dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75),
+ dict(type='RandomFlip', prob=0.5),
+ dict(type='PhotoMetricDistortion'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='Pad', size=crop_size, pad_val=0, seg_pad_val=255),
+ dict(type='DefaultFormatBundle'),
+ dict(type='Collect', keys=['img', 'gt_semantic_seg']),
+]
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='MultiScaleFlipAug',
+ img_scale=(2048, 1024),
+ # img_ratios=[0.5, 0.75, 1.0, 1.25, 1.5, 1.75],
+ flip=False,
+ transforms=[
+ dict(type='Resize', keep_ratio=True),
+ dict(type='RandomFlip'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img']),
+ ])
+]
+data = dict(
+ samples_per_gpu=2,
+ workers_per_gpu=2,
+ train=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='leftImg8bit/train',
+ ann_dir='gtFine/train',
+ pipeline=train_pipeline),
+ val=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='leftImg8bit/val',
+ ann_dir='gtFine/val',
+ pipeline=test_pipeline),
+ test=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='leftImg8bit/val',
+ ann_dir='gtFine/val',
+ pipeline=test_pipeline))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/cityscapes_769x769.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/cityscapes_769x769.py
new file mode 100644
index 0000000000000000000000000000000000000000..336c7b254fe392b4703039fec86a83acdbd2e1a5
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/cityscapes_769x769.py
@@ -0,0 +1,35 @@
+_base_ = './cityscapes.py'
+img_norm_cfg = dict(
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
+crop_size = (769, 769)
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='LoadAnnotations'),
+ dict(type='Resize', img_scale=(2049, 1025), ratio_range=(0.5, 2.0)),
+ dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75),
+ dict(type='RandomFlip', prob=0.5),
+ dict(type='PhotoMetricDistortion'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='Pad', size=crop_size, pad_val=0, seg_pad_val=255),
+ dict(type='DefaultFormatBundle'),
+ dict(type='Collect', keys=['img', 'gt_semantic_seg']),
+]
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='MultiScaleFlipAug',
+ img_scale=(2049, 1025),
+ # img_ratios=[0.5, 0.75, 1.0, 1.25, 1.5, 1.75],
+ flip=False,
+ transforms=[
+ dict(type='Resize', keep_ratio=True),
+ dict(type='RandomFlip'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img']),
+ ])
+]
+data = dict(
+ train=dict(pipeline=train_pipeline),
+ val=dict(pipeline=test_pipeline),
+ test=dict(pipeline=test_pipeline))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/drive.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/drive.py
new file mode 100644
index 0000000000000000000000000000000000000000..06e8ff606e0d2a4514ec8b7d2c6c436a32efcbf4
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/drive.py
@@ -0,0 +1,59 @@
+# dataset settings
+dataset_type = 'DRIVEDataset'
+data_root = 'data/DRIVE'
+img_norm_cfg = dict(
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
+img_scale = (584, 565)
+crop_size = (64, 64)
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='LoadAnnotations'),
+ dict(type='Resize', img_scale=img_scale, ratio_range=(0.5, 2.0)),
+ dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75),
+ dict(type='RandomFlip', prob=0.5),
+ dict(type='PhotoMetricDistortion'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='Pad', size=crop_size, pad_val=0, seg_pad_val=255),
+ dict(type='DefaultFormatBundle'),
+ dict(type='Collect', keys=['img', 'gt_semantic_seg'])
+]
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='MultiScaleFlipAug',
+ img_scale=img_scale,
+ # img_ratios=[0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0],
+ flip=False,
+ transforms=[
+ dict(type='Resize', keep_ratio=True),
+ dict(type='RandomFlip'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img'])
+ ])
+]
+
+data = dict(
+ samples_per_gpu=4,
+ workers_per_gpu=4,
+ train=dict(
+ type='RepeatDataset',
+ times=40000,
+ dataset=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='images/training',
+ ann_dir='annotations/training',
+ pipeline=train_pipeline)),
+ val=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='images/validation',
+ ann_dir='annotations/validation',
+ pipeline=test_pipeline),
+ test=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='images/validation',
+ ann_dir='annotations/validation',
+ pipeline=test_pipeline))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/hrf.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/hrf.py
new file mode 100644
index 0000000000000000000000000000000000000000..242d790eb1b83e75cf6b7eaa7a35c674099311ad
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/hrf.py
@@ -0,0 +1,59 @@
+# dataset settings
+dataset_type = 'HRFDataset'
+data_root = 'data/HRF'
+img_norm_cfg = dict(
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
+img_scale = (2336, 3504)
+crop_size = (256, 256)
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='LoadAnnotations'),
+ dict(type='Resize', img_scale=img_scale, ratio_range=(0.5, 2.0)),
+ dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75),
+ dict(type='RandomFlip', prob=0.5),
+ dict(type='PhotoMetricDistortion'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='Pad', size=crop_size, pad_val=0, seg_pad_val=255),
+ dict(type='DefaultFormatBundle'),
+ dict(type='Collect', keys=['img', 'gt_semantic_seg'])
+]
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='MultiScaleFlipAug',
+ img_scale=img_scale,
+ # img_ratios=[0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0],
+ flip=False,
+ transforms=[
+ dict(type='Resize', keep_ratio=True),
+ dict(type='RandomFlip'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img'])
+ ])
+]
+
+data = dict(
+ samples_per_gpu=4,
+ workers_per_gpu=4,
+ train=dict(
+ type='RepeatDataset',
+ times=40000,
+ dataset=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='images/training',
+ ann_dir='annotations/training',
+ pipeline=train_pipeline)),
+ val=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='images/validation',
+ ann_dir='annotations/validation',
+ pipeline=test_pipeline),
+ test=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='images/validation',
+ ann_dir='annotations/validation',
+ pipeline=test_pipeline))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/pascal_context.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/pascal_context.py
new file mode 100644
index 0000000000000000000000000000000000000000..ff65bad1b86d7e3a5980bb5b9fc55798dc8df5f4
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/pascal_context.py
@@ -0,0 +1,60 @@
+# dataset settings
+dataset_type = 'PascalContextDataset'
+data_root = 'data/VOCdevkit/VOC2010/'
+img_norm_cfg = dict(
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
+
+img_scale = (520, 520)
+crop_size = (480, 480)
+
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='LoadAnnotations'),
+ dict(type='Resize', img_scale=img_scale, ratio_range=(0.5, 2.0)),
+ dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75),
+ dict(type='RandomFlip', prob=0.5),
+ dict(type='PhotoMetricDistortion'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='Pad', size=crop_size, pad_val=0, seg_pad_val=255),
+ dict(type='DefaultFormatBundle'),
+ dict(type='Collect', keys=['img', 'gt_semantic_seg']),
+]
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='MultiScaleFlipAug',
+ img_scale=img_scale,
+ # img_ratios=[0.5, 0.75, 1.0, 1.25, 1.5, 1.75],
+ flip=False,
+ transforms=[
+ dict(type='Resize', keep_ratio=True),
+ dict(type='RandomFlip'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img']),
+ ])
+]
+data = dict(
+ samples_per_gpu=4,
+ workers_per_gpu=4,
+ train=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='JPEGImages',
+ ann_dir='SegmentationClassContext',
+ split='ImageSets/SegmentationContext/train.txt',
+ pipeline=train_pipeline),
+ val=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='JPEGImages',
+ ann_dir='SegmentationClassContext',
+ split='ImageSets/SegmentationContext/val.txt',
+ pipeline=test_pipeline),
+ test=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='JPEGImages',
+ ann_dir='SegmentationClassContext',
+ split='ImageSets/SegmentationContext/val.txt',
+ pipeline=test_pipeline))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/pascal_context_59.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/pascal_context_59.py
new file mode 100644
index 0000000000000000000000000000000000000000..37585abab89834b95cd5bdd993b994fca1db65f6
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/pascal_context_59.py
@@ -0,0 +1,60 @@
+# dataset settings
+dataset_type = 'PascalContextDataset59'
+data_root = 'data/VOCdevkit/VOC2010/'
+img_norm_cfg = dict(
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
+
+img_scale = (520, 520)
+crop_size = (480, 480)
+
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='LoadAnnotations', reduce_zero_label=True),
+ dict(type='Resize', img_scale=img_scale, ratio_range=(0.5, 2.0)),
+ dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75),
+ dict(type='RandomFlip', prob=0.5),
+ dict(type='PhotoMetricDistortion'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='Pad', size=crop_size, pad_val=0, seg_pad_val=255),
+ dict(type='DefaultFormatBundle'),
+ dict(type='Collect', keys=['img', 'gt_semantic_seg']),
+]
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='MultiScaleFlipAug',
+ img_scale=img_scale,
+ # img_ratios=[0.5, 0.75, 1.0, 1.25, 1.5, 1.75],
+ flip=False,
+ transforms=[
+ dict(type='Resize', keep_ratio=True),
+ dict(type='RandomFlip'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img']),
+ ])
+]
+data = dict(
+ samples_per_gpu=4,
+ workers_per_gpu=4,
+ train=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='JPEGImages',
+ ann_dir='SegmentationClassContext',
+ split='ImageSets/SegmentationContext/train.txt',
+ pipeline=train_pipeline),
+ val=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='JPEGImages',
+ ann_dir='SegmentationClassContext',
+ split='ImageSets/SegmentationContext/val.txt',
+ pipeline=test_pipeline),
+ test=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='JPEGImages',
+ ann_dir='SegmentationClassContext',
+ split='ImageSets/SegmentationContext/val.txt',
+ pipeline=test_pipeline))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/pascal_voc12.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/pascal_voc12.py
new file mode 100644
index 0000000000000000000000000000000000000000..ba1d42d0c5781f56dc177d860d856bb34adce555
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/pascal_voc12.py
@@ -0,0 +1,57 @@
+# dataset settings
+dataset_type = 'PascalVOCDataset'
+data_root = 'data/VOCdevkit/VOC2012'
+img_norm_cfg = dict(
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
+crop_size = (512, 512)
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='LoadAnnotations'),
+ dict(type='Resize', img_scale=(2048, 512), ratio_range=(0.5, 2.0)),
+ dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75),
+ dict(type='RandomFlip', prob=0.5),
+ dict(type='PhotoMetricDistortion'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='Pad', size=crop_size, pad_val=0, seg_pad_val=255),
+ dict(type='DefaultFormatBundle'),
+ dict(type='Collect', keys=['img', 'gt_semantic_seg']),
+]
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='MultiScaleFlipAug',
+ img_scale=(2048, 512),
+ # img_ratios=[0.5, 0.75, 1.0, 1.25, 1.5, 1.75],
+ flip=False,
+ transforms=[
+ dict(type='Resize', keep_ratio=True),
+ dict(type='RandomFlip'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img']),
+ ])
+]
+data = dict(
+ samples_per_gpu=4,
+ workers_per_gpu=4,
+ train=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='JPEGImages',
+ ann_dir='SegmentationClass',
+ split='ImageSets/Segmentation/train.txt',
+ pipeline=train_pipeline),
+ val=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='JPEGImages',
+ ann_dir='SegmentationClass',
+ split='ImageSets/Segmentation/val.txt',
+ pipeline=test_pipeline),
+ test=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='JPEGImages',
+ ann_dir='SegmentationClass',
+ split='ImageSets/Segmentation/val.txt',
+ pipeline=test_pipeline))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/pascal_voc12_aug.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/pascal_voc12_aug.py
new file mode 100644
index 0000000000000000000000000000000000000000..3f23b6717d53ad29f02dd15046802a2631a5076b
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/pascal_voc12_aug.py
@@ -0,0 +1,9 @@
+_base_ = './pascal_voc12.py'
+# dataset settings
+data = dict(
+ train=dict(
+ ann_dir=['SegmentationClass', 'SegmentationClassAug'],
+ split=[
+ 'ImageSets/Segmentation/train.txt',
+ 'ImageSets/Segmentation/aug.txt'
+ ]))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/stare.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/stare.py
new file mode 100644
index 0000000000000000000000000000000000000000..3f71b25488cc11a6b4d582ac52b5a24e1ad1cf8e
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/datasets/stare.py
@@ -0,0 +1,59 @@
+# dataset settings
+dataset_type = 'STAREDataset'
+data_root = 'data/STARE'
+img_norm_cfg = dict(
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
+img_scale = (605, 700)
+crop_size = (128, 128)
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='LoadAnnotations'),
+ dict(type='Resize', img_scale=img_scale, ratio_range=(0.5, 2.0)),
+ dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75),
+ dict(type='RandomFlip', prob=0.5),
+ dict(type='PhotoMetricDistortion'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='Pad', size=crop_size, pad_val=0, seg_pad_val=255),
+ dict(type='DefaultFormatBundle'),
+ dict(type='Collect', keys=['img', 'gt_semantic_seg'])
+]
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='MultiScaleFlipAug',
+ img_scale=img_scale,
+ # img_ratios=[0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0],
+ flip=False,
+ transforms=[
+ dict(type='Resize', keep_ratio=True),
+ dict(type='RandomFlip'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img'])
+ ])
+]
+
+data = dict(
+ samples_per_gpu=4,
+ workers_per_gpu=4,
+ train=dict(
+ type='RepeatDataset',
+ times=40000,
+ dataset=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='images/training',
+ ann_dir='annotations/training',
+ pipeline=train_pipeline)),
+ val=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='images/validation',
+ ann_dir='annotations/validation',
+ pipeline=test_pipeline),
+ test=dict(
+ type=dataset_type,
+ data_root=data_root,
+ img_dir='images/validation',
+ ann_dir='annotations/validation',
+ pipeline=test_pipeline))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/default_runtime.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/default_runtime.py
new file mode 100644
index 0000000000000000000000000000000000000000..b564cc4e7e7d9a67dacaaddecb100e4d8f5c005b
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/default_runtime.py
@@ -0,0 +1,14 @@
+# yapf:disable
+log_config = dict(
+ interval=50,
+ hooks=[
+ dict(type='TextLoggerHook', by_epoch=False),
+ # dict(type='TensorboardLoggerHook')
+ ])
+# yapf:enable
+dist_params = dict(backend='nccl')
+log_level = 'INFO'
+load_from = None
+resume_from = None
+workflow = [('train', 1)]
+cudnn_benchmark = True
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/ann_r50-d8.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/ann_r50-d8.py
new file mode 100644
index 0000000000000000000000000000000000000000..a2cb653827e44e6015b3b83bc578003e614a6aa1
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/ann_r50-d8.py
@@ -0,0 +1,46 @@
+# model settings
+norm_cfg = dict(type='SyncBN', requires_grad=True)
+model = dict(
+ type='EncoderDecoder',
+ pretrained='open-mmlab://resnet50_v1c',
+ backbone=dict(
+ type='ResNetV1c',
+ depth=50,
+ num_stages=4,
+ out_indices=(0, 1, 2, 3),
+ dilations=(1, 1, 2, 4),
+ strides=(1, 2, 1, 1),
+ norm_cfg=norm_cfg,
+ norm_eval=False,
+ style='pytorch',
+ contract_dilation=True),
+ decode_head=dict(
+ type='ANNHead',
+ in_channels=[1024, 2048],
+ in_index=[2, 3],
+ channels=512,
+ project_channels=256,
+ query_scales=(1, ),
+ key_pool_scales=(1, 3, 6, 8),
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)),
+ auxiliary_head=dict(
+ type='FCNHead',
+ in_channels=1024,
+ in_index=2,
+ channels=256,
+ num_convs=1,
+ concat_input=False,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)),
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='whole'))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/apcnet_r50-d8.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/apcnet_r50-d8.py
new file mode 100644
index 0000000000000000000000000000000000000000..c8f5316cbcf3896ba9de7ca2c801eba512f01d5e
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/apcnet_r50-d8.py
@@ -0,0 +1,44 @@
+# model settings
+norm_cfg = dict(type='SyncBN', requires_grad=True)
+model = dict(
+ type='EncoderDecoder',
+ pretrained='open-mmlab://resnet50_v1c',
+ backbone=dict(
+ type='ResNetV1c',
+ depth=50,
+ num_stages=4,
+ out_indices=(0, 1, 2, 3),
+ dilations=(1, 1, 2, 4),
+ strides=(1, 2, 1, 1),
+ norm_cfg=norm_cfg,
+ norm_eval=False,
+ style='pytorch',
+ contract_dilation=True),
+ decode_head=dict(
+ type='APCHead',
+ in_channels=2048,
+ in_index=3,
+ channels=512,
+ pool_scales=(1, 2, 3, 6),
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=dict(type='SyncBN', requires_grad=True),
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)),
+ auxiliary_head=dict(
+ type='FCNHead',
+ in_channels=1024,
+ in_index=2,
+ channels=256,
+ num_convs=1,
+ concat_input=False,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)),
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='whole'))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/ccnet_r50-d8.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/ccnet_r50-d8.py
new file mode 100644
index 0000000000000000000000000000000000000000..794148f576b9e215c3c6963e73dffe98204b7717
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/ccnet_r50-d8.py
@@ -0,0 +1,44 @@
+# model settings
+norm_cfg = dict(type='SyncBN', requires_grad=True)
+model = dict(
+ type='EncoderDecoder',
+ pretrained='open-mmlab://resnet50_v1c',
+ backbone=dict(
+ type='ResNetV1c',
+ depth=50,
+ num_stages=4,
+ out_indices=(0, 1, 2, 3),
+ dilations=(1, 1, 2, 4),
+ strides=(1, 2, 1, 1),
+ norm_cfg=norm_cfg,
+ norm_eval=False,
+ style='pytorch',
+ contract_dilation=True),
+ decode_head=dict(
+ type='CCHead',
+ in_channels=2048,
+ in_index=3,
+ channels=512,
+ recurrence=2,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)),
+ auxiliary_head=dict(
+ type='FCNHead',
+ in_channels=1024,
+ in_index=2,
+ channels=256,
+ num_convs=1,
+ concat_input=False,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)),
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='whole'))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/cgnet.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/cgnet.py
new file mode 100644
index 0000000000000000000000000000000000000000..eff8d9458c877c5db894957e0b1b4597e40da6ab
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/cgnet.py
@@ -0,0 +1,35 @@
+# model settings
+norm_cfg = dict(type='SyncBN', eps=1e-03, requires_grad=True)
+model = dict(
+ type='EncoderDecoder',
+ backbone=dict(
+ type='CGNet',
+ norm_cfg=norm_cfg,
+ in_channels=3,
+ num_channels=(32, 64, 128),
+ num_blocks=(3, 21),
+ dilations=(2, 4),
+ reductions=(8, 16)),
+ decode_head=dict(
+ type='FCNHead',
+ in_channels=256,
+ in_index=2,
+ channels=256,
+ num_convs=0,
+ concat_input=False,
+ dropout_ratio=0,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ loss_decode=dict(
+ type='CrossEntropyLoss',
+ use_sigmoid=False,
+ loss_weight=1.0,
+ class_weight=[
+ 2.5959933, 6.7415504, 3.5354059, 9.8663225, 9.690899, 9.369352,
+ 10.289121, 9.953208, 4.3097677, 9.490387, 7.674431, 9.396905,
+ 10.347791, 6.3927646, 10.226669, 10.241062, 10.280587,
+ 10.396974, 10.055647
+ ])),
+ # model training and testing settings
+ train_cfg=dict(sampler=None),
+ test_cfg=dict(mode='whole'))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/danet_r50-d8.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/danet_r50-d8.py
new file mode 100644
index 0000000000000000000000000000000000000000..2c934939fac48525f22ad86f489a041dd7db7d09
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/danet_r50-d8.py
@@ -0,0 +1,44 @@
+# model settings
+norm_cfg = dict(type='SyncBN', requires_grad=True)
+model = dict(
+ type='EncoderDecoder',
+ pretrained='open-mmlab://resnet50_v1c',
+ backbone=dict(
+ type='ResNetV1c',
+ depth=50,
+ num_stages=4,
+ out_indices=(0, 1, 2, 3),
+ dilations=(1, 1, 2, 4),
+ strides=(1, 2, 1, 1),
+ norm_cfg=norm_cfg,
+ norm_eval=False,
+ style='pytorch',
+ contract_dilation=True),
+ decode_head=dict(
+ type='DAHead',
+ in_channels=2048,
+ in_index=3,
+ channels=512,
+ pam_channels=64,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)),
+ auxiliary_head=dict(
+ type='FCNHead',
+ in_channels=1024,
+ in_index=2,
+ channels=256,
+ num_convs=1,
+ concat_input=False,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)),
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='whole'))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/deeplabv3_r50-d8.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/deeplabv3_r50-d8.py
new file mode 100644
index 0000000000000000000000000000000000000000..d7a43bee01422ad4795dd27874e0cd4bb6cbfecf
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/deeplabv3_r50-d8.py
@@ -0,0 +1,44 @@
+# model settings
+norm_cfg = dict(type='SyncBN', requires_grad=True)
+model = dict(
+ type='EncoderDecoder',
+ pretrained='open-mmlab://resnet50_v1c',
+ backbone=dict(
+ type='ResNetV1c',
+ depth=50,
+ num_stages=4,
+ out_indices=(0, 1, 2, 3),
+ dilations=(1, 1, 2, 4),
+ strides=(1, 2, 1, 1),
+ norm_cfg=norm_cfg,
+ norm_eval=False,
+ style='pytorch',
+ contract_dilation=True),
+ decode_head=dict(
+ type='ASPPHead',
+ in_channels=2048,
+ in_index=3,
+ channels=512,
+ dilations=(1, 12, 24, 36),
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)),
+ auxiliary_head=dict(
+ type='FCNHead',
+ in_channels=1024,
+ in_index=2,
+ channels=256,
+ num_convs=1,
+ concat_input=False,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)),
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='whole'))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/deeplabv3_unet_s5-d16.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/deeplabv3_unet_s5-d16.py
new file mode 100644
index 0000000000000000000000000000000000000000..0cd262999d8b2cb8e14a5c32190ae73f479d8e81
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/deeplabv3_unet_s5-d16.py
@@ -0,0 +1,50 @@
+# model settings
+norm_cfg = dict(type='SyncBN', requires_grad=True)
+model = dict(
+ type='EncoderDecoder',
+ pretrained=None,
+ backbone=dict(
+ type='UNet',
+ in_channels=3,
+ base_channels=64,
+ num_stages=5,
+ strides=(1, 1, 1, 1, 1),
+ enc_num_convs=(2, 2, 2, 2, 2),
+ dec_num_convs=(2, 2, 2, 2),
+ downsamples=(True, True, True, True),
+ enc_dilations=(1, 1, 1, 1, 1),
+ dec_dilations=(1, 1, 1, 1),
+ with_cp=False,
+ conv_cfg=None,
+ norm_cfg=norm_cfg,
+ act_cfg=dict(type='ReLU'),
+ upsample_cfg=dict(type='InterpConv'),
+ norm_eval=False),
+ decode_head=dict(
+ type='ASPPHead',
+ in_channels=64,
+ in_index=4,
+ channels=16,
+ dilations=(1, 12, 24, 36),
+ dropout_ratio=0.1,
+ num_classes=2,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)),
+ auxiliary_head=dict(
+ type='FCNHead',
+ in_channels=128,
+ in_index=3,
+ channels=64,
+ num_convs=1,
+ concat_input=False,
+ dropout_ratio=0.1,
+ num_classes=2,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)),
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='slide', crop_size=256, stride=170))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/deeplabv3plus_r50-d8.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/deeplabv3plus_r50-d8.py
new file mode 100644
index 0000000000000000000000000000000000000000..050e39e091d816df9028d23aa3ecf9db74e441e1
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/deeplabv3plus_r50-d8.py
@@ -0,0 +1,46 @@
+# model settings
+norm_cfg = dict(type='SyncBN', requires_grad=True)
+model = dict(
+ type='EncoderDecoder',
+ pretrained='open-mmlab://resnet50_v1c',
+ backbone=dict(
+ type='ResNetV1c',
+ depth=50,
+ num_stages=4,
+ out_indices=(0, 1, 2, 3),
+ dilations=(1, 1, 2, 4),
+ strides=(1, 2, 1, 1),
+ norm_cfg=norm_cfg,
+ norm_eval=False,
+ style='pytorch',
+ contract_dilation=True),
+ decode_head=dict(
+ type='DepthwiseSeparableASPPHead',
+ in_channels=2048,
+ in_index=3,
+ channels=512,
+ dilations=(1, 12, 24, 36),
+ c1_in_channels=256,
+ c1_channels=48,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)),
+ auxiliary_head=dict(
+ type='FCNHead',
+ in_channels=1024,
+ in_index=2,
+ channels=256,
+ num_convs=1,
+ concat_input=False,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)),
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='whole'))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/dmnet_r50-d8.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/dmnet_r50-d8.py
new file mode 100644
index 0000000000000000000000000000000000000000..d22ba52640bebd805b3b8d07025e276dfb023759
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/dmnet_r50-d8.py
@@ -0,0 +1,44 @@
+# model settings
+norm_cfg = dict(type='SyncBN', requires_grad=True)
+model = dict(
+ type='EncoderDecoder',
+ pretrained='open-mmlab://resnet50_v1c',
+ backbone=dict(
+ type='ResNetV1c',
+ depth=50,
+ num_stages=4,
+ out_indices=(0, 1, 2, 3),
+ dilations=(1, 1, 2, 4),
+ strides=(1, 2, 1, 1),
+ norm_cfg=norm_cfg,
+ norm_eval=False,
+ style='pytorch',
+ contract_dilation=True),
+ decode_head=dict(
+ type='DMHead',
+ in_channels=2048,
+ in_index=3,
+ channels=512,
+ filter_sizes=(1, 3, 5, 7),
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=dict(type='SyncBN', requires_grad=True),
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)),
+ auxiliary_head=dict(
+ type='FCNHead',
+ in_channels=1024,
+ in_index=2,
+ channels=256,
+ num_convs=1,
+ concat_input=False,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)),
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='whole'))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/dnl_r50-d8.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/dnl_r50-d8.py
new file mode 100644
index 0000000000000000000000000000000000000000..edb4c174c51e34c103737ba39bfc48bf831e561d
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/dnl_r50-d8.py
@@ -0,0 +1,46 @@
+# model settings
+norm_cfg = dict(type='SyncBN', requires_grad=True)
+model = dict(
+ type='EncoderDecoder',
+ pretrained='open-mmlab://resnet50_v1c',
+ backbone=dict(
+ type='ResNetV1c',
+ depth=50,
+ num_stages=4,
+ out_indices=(0, 1, 2, 3),
+ dilations=(1, 1, 2, 4),
+ strides=(1, 2, 1, 1),
+ norm_cfg=norm_cfg,
+ norm_eval=False,
+ style='pytorch',
+ contract_dilation=True),
+ decode_head=dict(
+ type='DNLHead',
+ in_channels=2048,
+ in_index=3,
+ channels=512,
+ dropout_ratio=0.1,
+ reduction=2,
+ use_scale=True,
+ mode='embedded_gaussian',
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)),
+ auxiliary_head=dict(
+ type='FCNHead',
+ in_channels=1024,
+ in_index=2,
+ channels=256,
+ num_convs=1,
+ concat_input=False,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)),
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='whole'))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/emanet_r50-d8.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/emanet_r50-d8.py
new file mode 100644
index 0000000000000000000000000000000000000000..26adcd430926de0862204a71d345f2543167f27b
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/emanet_r50-d8.py
@@ -0,0 +1,47 @@
+# model settings
+norm_cfg = dict(type='SyncBN', requires_grad=True)
+model = dict(
+ type='EncoderDecoder',
+ pretrained='open-mmlab://resnet50_v1c',
+ backbone=dict(
+ type='ResNetV1c',
+ depth=50,
+ num_stages=4,
+ out_indices=(0, 1, 2, 3),
+ dilations=(1, 1, 2, 4),
+ strides=(1, 2, 1, 1),
+ norm_cfg=norm_cfg,
+ norm_eval=False,
+ style='pytorch',
+ contract_dilation=True),
+ decode_head=dict(
+ type='EMAHead',
+ in_channels=2048,
+ in_index=3,
+ channels=256,
+ ema_channels=512,
+ num_bases=64,
+ num_stages=3,
+ momentum=0.1,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)),
+ auxiliary_head=dict(
+ type='FCNHead',
+ in_channels=1024,
+ in_index=2,
+ channels=256,
+ num_convs=1,
+ concat_input=False,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)),
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='whole'))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/encnet_r50-d8.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/encnet_r50-d8.py
new file mode 100644
index 0000000000000000000000000000000000000000..be777123a886503172a95fe0719e956a147bbd68
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/encnet_r50-d8.py
@@ -0,0 +1,48 @@
+# model settings
+norm_cfg = dict(type='SyncBN', requires_grad=True)
+model = dict(
+ type='EncoderDecoder',
+ pretrained='open-mmlab://resnet50_v1c',
+ backbone=dict(
+ type='ResNetV1c',
+ depth=50,
+ num_stages=4,
+ out_indices=(0, 1, 2, 3),
+ dilations=(1, 1, 2, 4),
+ strides=(1, 2, 1, 1),
+ norm_cfg=norm_cfg,
+ norm_eval=False,
+ style='pytorch',
+ contract_dilation=True),
+ decode_head=dict(
+ type='EncHead',
+ in_channels=[512, 1024, 2048],
+ in_index=(1, 2, 3),
+ channels=512,
+ num_codes=32,
+ use_se_loss=True,
+ add_lateral=False,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0),
+ loss_se_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=True, loss_weight=0.2)),
+ auxiliary_head=dict(
+ type='FCNHead',
+ in_channels=1024,
+ in_index=2,
+ channels=256,
+ num_convs=1,
+ concat_input=False,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)),
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='whole'))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/fast_scnn.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/fast_scnn.py
new file mode 100644
index 0000000000000000000000000000000000000000..32fdeb659355a5ce5ef2cc7c2f30742703811cdf
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/fast_scnn.py
@@ -0,0 +1,57 @@
+# model settings
+norm_cfg = dict(type='SyncBN', requires_grad=True, momentum=0.01)
+model = dict(
+ type='EncoderDecoder',
+ backbone=dict(
+ type='FastSCNN',
+ downsample_dw_channels=(32, 48),
+ global_in_channels=64,
+ global_block_channels=(64, 96, 128),
+ global_block_strides=(2, 2, 1),
+ global_out_channels=128,
+ higher_in_channels=64,
+ lower_in_channels=128,
+ fusion_out_channels=128,
+ out_indices=(0, 1, 2),
+ norm_cfg=norm_cfg,
+ align_corners=False),
+ decode_head=dict(
+ type='DepthwiseSeparableFCNHead',
+ in_channels=128,
+ channels=128,
+ concat_input=False,
+ num_classes=19,
+ in_index=-1,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=True, loss_weight=0.4)),
+ auxiliary_head=[
+ dict(
+ type='FCNHead',
+ in_channels=128,
+ channels=32,
+ num_convs=1,
+ num_classes=19,
+ in_index=-2,
+ norm_cfg=norm_cfg,
+ concat_input=False,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=True, loss_weight=0.4)),
+ dict(
+ type='FCNHead',
+ in_channels=64,
+ channels=32,
+ num_convs=1,
+ num_classes=19,
+ in_index=-3,
+ norm_cfg=norm_cfg,
+ concat_input=False,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=True, loss_weight=0.4)),
+ ],
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='whole'))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/fcn_hr18.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/fcn_hr18.py
new file mode 100644
index 0000000000000000000000000000000000000000..c3e299bc89ada56ca14bbffcbdb08a586b8ed9e9
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/fcn_hr18.py
@@ -0,0 +1,52 @@
+# model settings
+norm_cfg = dict(type='SyncBN', requires_grad=True)
+model = dict(
+ type='EncoderDecoder',
+ pretrained='open-mmlab://msra/hrnetv2_w18',
+ backbone=dict(
+ type='HRNet',
+ norm_cfg=norm_cfg,
+ norm_eval=False,
+ extra=dict(
+ stage1=dict(
+ num_modules=1,
+ num_branches=1,
+ block='BOTTLENECK',
+ num_blocks=(4, ),
+ num_channels=(64, )),
+ stage2=dict(
+ num_modules=1,
+ num_branches=2,
+ block='BASIC',
+ num_blocks=(4, 4),
+ num_channels=(18, 36)),
+ stage3=dict(
+ num_modules=4,
+ num_branches=3,
+ block='BASIC',
+ num_blocks=(4, 4, 4),
+ num_channels=(18, 36, 72)),
+ stage4=dict(
+ num_modules=3,
+ num_branches=4,
+ block='BASIC',
+ num_blocks=(4, 4, 4, 4),
+ num_channels=(18, 36, 72, 144)))),
+ decode_head=dict(
+ type='FCNHead',
+ in_channels=[18, 36, 72, 144],
+ in_index=(0, 1, 2, 3),
+ channels=sum([18, 36, 72, 144]),
+ input_transform='resize_concat',
+ kernel_size=1,
+ num_convs=1,
+ concat_input=False,
+ dropout_ratio=-1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)),
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='whole'))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/fcn_r50-d8.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/fcn_r50-d8.py
new file mode 100644
index 0000000000000000000000000000000000000000..5e98f6cc918b6146fc6d613c6918e825ef1355c3
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/fcn_r50-d8.py
@@ -0,0 +1,45 @@
+# model settings
+norm_cfg = dict(type='SyncBN', requires_grad=True)
+model = dict(
+ type='EncoderDecoder',
+ pretrained='open-mmlab://resnet50_v1c',
+ backbone=dict(
+ type='ResNetV1c',
+ depth=50,
+ num_stages=4,
+ out_indices=(0, 1, 2, 3),
+ dilations=(1, 1, 2, 4),
+ strides=(1, 2, 1, 1),
+ norm_cfg=norm_cfg,
+ norm_eval=False,
+ style='pytorch',
+ contract_dilation=True),
+ decode_head=dict(
+ type='FCNHead',
+ in_channels=2048,
+ in_index=3,
+ channels=512,
+ num_convs=2,
+ concat_input=True,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)),
+ auxiliary_head=dict(
+ type='FCNHead',
+ in_channels=1024,
+ in_index=2,
+ channels=256,
+ num_convs=1,
+ concat_input=False,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)),
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='whole'))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/fcn_unet_s5-d16.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/fcn_unet_s5-d16.py
new file mode 100644
index 0000000000000000000000000000000000000000..a33e7972877f902d0e7d18401ca675e3e4e60a18
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/fcn_unet_s5-d16.py
@@ -0,0 +1,51 @@
+# model settings
+norm_cfg = dict(type='SyncBN', requires_grad=True)
+model = dict(
+ type='EncoderDecoder',
+ pretrained=None,
+ backbone=dict(
+ type='UNet',
+ in_channels=3,
+ base_channels=64,
+ num_stages=5,
+ strides=(1, 1, 1, 1, 1),
+ enc_num_convs=(2, 2, 2, 2, 2),
+ dec_num_convs=(2, 2, 2, 2),
+ downsamples=(True, True, True, True),
+ enc_dilations=(1, 1, 1, 1, 1),
+ dec_dilations=(1, 1, 1, 1),
+ with_cp=False,
+ conv_cfg=None,
+ norm_cfg=norm_cfg,
+ act_cfg=dict(type='ReLU'),
+ upsample_cfg=dict(type='InterpConv'),
+ norm_eval=False),
+ decode_head=dict(
+ type='FCNHead',
+ in_channels=64,
+ in_index=4,
+ channels=64,
+ num_convs=1,
+ concat_input=False,
+ dropout_ratio=0.1,
+ num_classes=2,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)),
+ auxiliary_head=dict(
+ type='FCNHead',
+ in_channels=128,
+ in_index=3,
+ channels=64,
+ num_convs=1,
+ concat_input=False,
+ dropout_ratio=0.1,
+ num_classes=2,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)),
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='slide', crop_size=256, stride=170))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/fpn_r50.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/fpn_r50.py
new file mode 100644
index 0000000000000000000000000000000000000000..86ab327db92e44c14822d65f1c9277cb007f17c1
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/fpn_r50.py
@@ -0,0 +1,36 @@
+# model settings
+norm_cfg = dict(type='SyncBN', requires_grad=True)
+model = dict(
+ type='EncoderDecoder',
+ pretrained='open-mmlab://resnet50_v1c',
+ backbone=dict(
+ type='ResNetV1c',
+ depth=50,
+ num_stages=4,
+ out_indices=(0, 1, 2, 3),
+ dilations=(1, 1, 1, 1),
+ strides=(1, 2, 2, 2),
+ norm_cfg=norm_cfg,
+ norm_eval=False,
+ style='pytorch',
+ contract_dilation=True),
+ neck=dict(
+ type='FPN',
+ in_channels=[256, 512, 1024, 2048],
+ out_channels=256,
+ num_outs=4),
+ decode_head=dict(
+ type='FPNHead',
+ in_channels=[256, 256, 256, 256],
+ in_index=[0, 1, 2, 3],
+ feature_strides=[4, 8, 16, 32],
+ channels=128,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)),
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='whole'))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/fpn_uniformer.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/fpn_uniformer.py
new file mode 100644
index 0000000000000000000000000000000000000000..8aae98c5991055bfcc08e82ccdc09f8b1d9f8a8d
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/fpn_uniformer.py
@@ -0,0 +1,35 @@
+# model settings
+norm_cfg = dict(type='SyncBN', requires_grad=True)
+model = dict(
+ type='EncoderDecoder',
+ backbone=dict(
+ type='UniFormer',
+ embed_dim=[64, 128, 320, 512],
+ layers=[3, 4, 8, 3],
+ head_dim=64,
+ mlp_ratio=4.,
+ qkv_bias=True,
+ drop_rate=0.,
+ attn_drop_rate=0.,
+ drop_path_rate=0.1),
+ neck=dict(
+ type='FPN',
+ in_channels=[64, 128, 320, 512],
+ out_channels=256,
+ num_outs=4),
+ decode_head=dict(
+ type='FPNHead',
+ in_channels=[256, 256, 256, 256],
+ in_index=[0, 1, 2, 3],
+ feature_strides=[4, 8, 16, 32],
+ channels=128,
+ dropout_ratio=0.1,
+ num_classes=150,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)),
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='whole')
+)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/gcnet_r50-d8.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/gcnet_r50-d8.py
new file mode 100644
index 0000000000000000000000000000000000000000..3d2ad69f5c22adfe79d5fdabf920217628987166
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/gcnet_r50-d8.py
@@ -0,0 +1,46 @@
+# model settings
+norm_cfg = dict(type='SyncBN', requires_grad=True)
+model = dict(
+ type='EncoderDecoder',
+ pretrained='open-mmlab://resnet50_v1c',
+ backbone=dict(
+ type='ResNetV1c',
+ depth=50,
+ num_stages=4,
+ out_indices=(0, 1, 2, 3),
+ dilations=(1, 1, 2, 4),
+ strides=(1, 2, 1, 1),
+ norm_cfg=norm_cfg,
+ norm_eval=False,
+ style='pytorch',
+ contract_dilation=True),
+ decode_head=dict(
+ type='GCHead',
+ in_channels=2048,
+ in_index=3,
+ channels=512,
+ ratio=1 / 4.,
+ pooling_type='att',
+ fusion_types=('channel_add', ),
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)),
+ auxiliary_head=dict(
+ type='FCNHead',
+ in_channels=1024,
+ in_index=2,
+ channels=256,
+ num_convs=1,
+ concat_input=False,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)),
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='whole'))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/lraspp_m-v3-d8.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/lraspp_m-v3-d8.py
new file mode 100644
index 0000000000000000000000000000000000000000..93258242a90695cc94a7c6bd41562d6a75988771
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/lraspp_m-v3-d8.py
@@ -0,0 +1,25 @@
+# model settings
+norm_cfg = dict(type='SyncBN', eps=0.001, requires_grad=True)
+model = dict(
+ type='EncoderDecoder',
+ backbone=dict(
+ type='MobileNetV3',
+ arch='large',
+ out_indices=(1, 3, 16),
+ norm_cfg=norm_cfg),
+ decode_head=dict(
+ type='LRASPPHead',
+ in_channels=(16, 24, 960),
+ in_index=(0, 1, 2),
+ channels=128,
+ input_transform='multiple_select',
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ act_cfg=dict(type='ReLU'),
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)),
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='whole'))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/nonlocal_r50-d8.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/nonlocal_r50-d8.py
new file mode 100644
index 0000000000000000000000000000000000000000..5674a39854cafd1f2e363bac99c58ccae62f24da
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/nonlocal_r50-d8.py
@@ -0,0 +1,46 @@
+# model settings
+norm_cfg = dict(type='SyncBN', requires_grad=True)
+model = dict(
+ type='EncoderDecoder',
+ pretrained='open-mmlab://resnet50_v1c',
+ backbone=dict(
+ type='ResNetV1c',
+ depth=50,
+ num_stages=4,
+ out_indices=(0, 1, 2, 3),
+ dilations=(1, 1, 2, 4),
+ strides=(1, 2, 1, 1),
+ norm_cfg=norm_cfg,
+ norm_eval=False,
+ style='pytorch',
+ contract_dilation=True),
+ decode_head=dict(
+ type='NLHead',
+ in_channels=2048,
+ in_index=3,
+ channels=512,
+ dropout_ratio=0.1,
+ reduction=2,
+ use_scale=True,
+ mode='embedded_gaussian',
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)),
+ auxiliary_head=dict(
+ type='FCNHead',
+ in_channels=1024,
+ in_index=2,
+ channels=256,
+ num_convs=1,
+ concat_input=False,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)),
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='whole'))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/ocrnet_hr18.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/ocrnet_hr18.py
new file mode 100644
index 0000000000000000000000000000000000000000..c60f62a7cdf3f5c5096a7a7e725e8268fddcb057
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/ocrnet_hr18.py
@@ -0,0 +1,68 @@
+# model settings
+norm_cfg = dict(type='SyncBN', requires_grad=True)
+model = dict(
+ type='CascadeEncoderDecoder',
+ num_stages=2,
+ pretrained='open-mmlab://msra/hrnetv2_w18',
+ backbone=dict(
+ type='HRNet',
+ norm_cfg=norm_cfg,
+ norm_eval=False,
+ extra=dict(
+ stage1=dict(
+ num_modules=1,
+ num_branches=1,
+ block='BOTTLENECK',
+ num_blocks=(4, ),
+ num_channels=(64, )),
+ stage2=dict(
+ num_modules=1,
+ num_branches=2,
+ block='BASIC',
+ num_blocks=(4, 4),
+ num_channels=(18, 36)),
+ stage3=dict(
+ num_modules=4,
+ num_branches=3,
+ block='BASIC',
+ num_blocks=(4, 4, 4),
+ num_channels=(18, 36, 72)),
+ stage4=dict(
+ num_modules=3,
+ num_branches=4,
+ block='BASIC',
+ num_blocks=(4, 4, 4, 4),
+ num_channels=(18, 36, 72, 144)))),
+ decode_head=[
+ dict(
+ type='FCNHead',
+ in_channels=[18, 36, 72, 144],
+ channels=sum([18, 36, 72, 144]),
+ in_index=(0, 1, 2, 3),
+ input_transform='resize_concat',
+ kernel_size=1,
+ num_convs=1,
+ concat_input=False,
+ dropout_ratio=-1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)),
+ dict(
+ type='OCRHead',
+ in_channels=[18, 36, 72, 144],
+ in_index=(0, 1, 2, 3),
+ input_transform='resize_concat',
+ channels=512,
+ ocr_channels=256,
+ dropout_ratio=-1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)),
+ ],
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='whole'))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/ocrnet_r50-d8.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/ocrnet_r50-d8.py
new file mode 100644
index 0000000000000000000000000000000000000000..615aa3ff703942b6c22b2d6e9642504dd3e41ebd
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/ocrnet_r50-d8.py
@@ -0,0 +1,47 @@
+# model settings
+norm_cfg = dict(type='SyncBN', requires_grad=True)
+model = dict(
+ type='CascadeEncoderDecoder',
+ num_stages=2,
+ pretrained='open-mmlab://resnet50_v1c',
+ backbone=dict(
+ type='ResNetV1c',
+ depth=50,
+ num_stages=4,
+ out_indices=(0, 1, 2, 3),
+ dilations=(1, 1, 2, 4),
+ strides=(1, 2, 1, 1),
+ norm_cfg=norm_cfg,
+ norm_eval=False,
+ style='pytorch',
+ contract_dilation=True),
+ decode_head=[
+ dict(
+ type='FCNHead',
+ in_channels=1024,
+ in_index=2,
+ channels=256,
+ num_convs=1,
+ concat_input=False,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)),
+ dict(
+ type='OCRHead',
+ in_channels=2048,
+ in_index=3,
+ channels=512,
+ ocr_channels=256,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0))
+ ],
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='whole'))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/pointrend_r50.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/pointrend_r50.py
new file mode 100644
index 0000000000000000000000000000000000000000..9d323dbf9466d41e0800aa57ef84045f3d874bdf
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/pointrend_r50.py
@@ -0,0 +1,56 @@
+# model settings
+norm_cfg = dict(type='SyncBN', requires_grad=True)
+model = dict(
+ type='CascadeEncoderDecoder',
+ num_stages=2,
+ pretrained='open-mmlab://resnet50_v1c',
+ backbone=dict(
+ type='ResNetV1c',
+ depth=50,
+ num_stages=4,
+ out_indices=(0, 1, 2, 3),
+ dilations=(1, 1, 1, 1),
+ strides=(1, 2, 2, 2),
+ norm_cfg=norm_cfg,
+ norm_eval=False,
+ style='pytorch',
+ contract_dilation=True),
+ neck=dict(
+ type='FPN',
+ in_channels=[256, 512, 1024, 2048],
+ out_channels=256,
+ num_outs=4),
+ decode_head=[
+ dict(
+ type='FPNHead',
+ in_channels=[256, 256, 256, 256],
+ in_index=[0, 1, 2, 3],
+ feature_strides=[4, 8, 16, 32],
+ channels=128,
+ dropout_ratio=-1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)),
+ dict(
+ type='PointHead',
+ in_channels=[256],
+ in_index=[0],
+ channels=256,
+ num_fcs=3,
+ coarse_pred_each_layer=True,
+ dropout_ratio=-1,
+ num_classes=19,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0))
+ ],
+ # model training and testing settings
+ train_cfg=dict(
+ num_points=2048, oversample_ratio=3, importance_sample_ratio=0.75),
+ test_cfg=dict(
+ mode='whole',
+ subdivision_steps=2,
+ subdivision_num_points=8196,
+ scale_factor=2))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/psanet_r50-d8.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/psanet_r50-d8.py
new file mode 100644
index 0000000000000000000000000000000000000000..689513fa9d2a40f14bf0ae4ae61f38f0dcc1b3da
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/psanet_r50-d8.py
@@ -0,0 +1,49 @@
+# model settings
+norm_cfg = dict(type='SyncBN', requires_grad=True)
+model = dict(
+ type='EncoderDecoder',
+ pretrained='open-mmlab://resnet50_v1c',
+ backbone=dict(
+ type='ResNetV1c',
+ depth=50,
+ num_stages=4,
+ out_indices=(0, 1, 2, 3),
+ dilations=(1, 1, 2, 4),
+ strides=(1, 2, 1, 1),
+ norm_cfg=norm_cfg,
+ norm_eval=False,
+ style='pytorch',
+ contract_dilation=True),
+ decode_head=dict(
+ type='PSAHead',
+ in_channels=2048,
+ in_index=3,
+ channels=512,
+ mask_size=(97, 97),
+ psa_type='bi-direction',
+ compact=False,
+ shrink_factor=2,
+ normalization_factor=1.0,
+ psa_softmax=True,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)),
+ auxiliary_head=dict(
+ type='FCNHead',
+ in_channels=1024,
+ in_index=2,
+ channels=256,
+ num_convs=1,
+ concat_input=False,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)),
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='whole'))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/pspnet_r50-d8.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/pspnet_r50-d8.py
new file mode 100644
index 0000000000000000000000000000000000000000..f451e08ad2eb0732dcb806b1851eb978d4acf136
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/pspnet_r50-d8.py
@@ -0,0 +1,44 @@
+# model settings
+norm_cfg = dict(type='SyncBN', requires_grad=True)
+model = dict(
+ type='EncoderDecoder',
+ pretrained='open-mmlab://resnet50_v1c',
+ backbone=dict(
+ type='ResNetV1c',
+ depth=50,
+ num_stages=4,
+ out_indices=(0, 1, 2, 3),
+ dilations=(1, 1, 2, 4),
+ strides=(1, 2, 1, 1),
+ norm_cfg=norm_cfg,
+ norm_eval=False,
+ style='pytorch',
+ contract_dilation=True),
+ decode_head=dict(
+ type='PSPHead',
+ in_channels=2048,
+ in_index=3,
+ channels=512,
+ pool_scales=(1, 2, 3, 6),
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)),
+ auxiliary_head=dict(
+ type='FCNHead',
+ in_channels=1024,
+ in_index=2,
+ channels=256,
+ num_convs=1,
+ concat_input=False,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)),
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='whole'))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/pspnet_unet_s5-d16.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/pspnet_unet_s5-d16.py
new file mode 100644
index 0000000000000000000000000000000000000000..fcff9ec4f41fad158344ecd77313dc14564f3682
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/pspnet_unet_s5-d16.py
@@ -0,0 +1,50 @@
+# model settings
+norm_cfg = dict(type='SyncBN', requires_grad=True)
+model = dict(
+ type='EncoderDecoder',
+ pretrained=None,
+ backbone=dict(
+ type='UNet',
+ in_channels=3,
+ base_channels=64,
+ num_stages=5,
+ strides=(1, 1, 1, 1, 1),
+ enc_num_convs=(2, 2, 2, 2, 2),
+ dec_num_convs=(2, 2, 2, 2),
+ downsamples=(True, True, True, True),
+ enc_dilations=(1, 1, 1, 1, 1),
+ dec_dilations=(1, 1, 1, 1),
+ with_cp=False,
+ conv_cfg=None,
+ norm_cfg=norm_cfg,
+ act_cfg=dict(type='ReLU'),
+ upsample_cfg=dict(type='InterpConv'),
+ norm_eval=False),
+ decode_head=dict(
+ type='PSPHead',
+ in_channels=64,
+ in_index=4,
+ channels=16,
+ pool_scales=(1, 2, 3, 6),
+ dropout_ratio=0.1,
+ num_classes=2,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)),
+ auxiliary_head=dict(
+ type='FCNHead',
+ in_channels=128,
+ in_index=3,
+ channels=64,
+ num_convs=1,
+ concat_input=False,
+ dropout_ratio=0.1,
+ num_classes=2,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)),
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='slide', crop_size=256, stride=170))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/upernet_r50.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/upernet_r50.py
new file mode 100644
index 0000000000000000000000000000000000000000..10974962fdd7136031fd06de1700f497d355ceaa
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/upernet_r50.py
@@ -0,0 +1,44 @@
+# model settings
+norm_cfg = dict(type='SyncBN', requires_grad=True)
+model = dict(
+ type='EncoderDecoder',
+ pretrained='open-mmlab://resnet50_v1c',
+ backbone=dict(
+ type='ResNetV1c',
+ depth=50,
+ num_stages=4,
+ out_indices=(0, 1, 2, 3),
+ dilations=(1, 1, 1, 1),
+ strides=(1, 2, 2, 2),
+ norm_cfg=norm_cfg,
+ norm_eval=False,
+ style='pytorch',
+ contract_dilation=True),
+ decode_head=dict(
+ type='UPerHead',
+ in_channels=[256, 512, 1024, 2048],
+ in_index=[0, 1, 2, 3],
+ pool_scales=(1, 2, 3, 6),
+ channels=512,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)),
+ auxiliary_head=dict(
+ type='FCNHead',
+ in_channels=1024,
+ in_index=2,
+ channels=256,
+ num_convs=1,
+ concat_input=False,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)),
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='whole'))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/upernet_uniformer.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/upernet_uniformer.py
new file mode 100644
index 0000000000000000000000000000000000000000..41aa4db809dc6e2c508e98051f61807d07477903
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/models/upernet_uniformer.py
@@ -0,0 +1,43 @@
+# model settings
+norm_cfg = dict(type='BN', requires_grad=True)
+model = dict(
+ type='EncoderDecoder',
+ pretrained=None,
+ backbone=dict(
+ type='UniFormer',
+ embed_dim=[64, 128, 320, 512],
+ layers=[3, 4, 8, 3],
+ head_dim=64,
+ mlp_ratio=4.,
+ qkv_bias=True,
+ drop_rate=0.,
+ attn_drop_rate=0.,
+ drop_path_rate=0.1),
+ decode_head=dict(
+ type='UPerHead',
+ in_channels=[64, 128, 320, 512],
+ in_index=[0, 1, 2, 3],
+ pool_scales=(1, 2, 3, 6),
+ channels=512,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)),
+ auxiliary_head=dict(
+ type='FCNHead',
+ in_channels=320,
+ in_index=2,
+ channels=256,
+ num_convs=1,
+ concat_input=False,
+ dropout_ratio=0.1,
+ num_classes=19,
+ norm_cfg=norm_cfg,
+ align_corners=False,
+ loss_decode=dict(
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)),
+ # model training and testing settings
+ train_cfg=dict(),
+ test_cfg=dict(mode='whole'))
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/schedules/schedule_160k.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/schedules/schedule_160k.py
new file mode 100644
index 0000000000000000000000000000000000000000..52603890b10f25faf8eec9f9e5a4468fae09b811
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/schedules/schedule_160k.py
@@ -0,0 +1,9 @@
+# optimizer
+optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005)
+optimizer_config = dict()
+# learning policy
+lr_config = dict(policy='poly', power=0.9, min_lr=1e-4, by_epoch=False)
+# runtime settings
+runner = dict(type='IterBasedRunner', max_iters=160000)
+checkpoint_config = dict(by_epoch=False, interval=16000)
+evaluation = dict(interval=16000, metric='mIoU')
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/schedules/schedule_20k.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/schedules/schedule_20k.py
new file mode 100644
index 0000000000000000000000000000000000000000..bf780a1b6f6521833c6a5859675147824efa599d
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/schedules/schedule_20k.py
@@ -0,0 +1,9 @@
+# optimizer
+optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005)
+optimizer_config = dict()
+# learning policy
+lr_config = dict(policy='poly', power=0.9, min_lr=1e-4, by_epoch=False)
+# runtime settings
+runner = dict(type='IterBasedRunner', max_iters=20000)
+checkpoint_config = dict(by_epoch=False, interval=2000)
+evaluation = dict(interval=2000, metric='mIoU')
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/schedules/schedule_40k.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/schedules/schedule_40k.py
new file mode 100644
index 0000000000000000000000000000000000000000..cdbf841abcb26eed87bf76ab816aff4bae0630ee
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/schedules/schedule_40k.py
@@ -0,0 +1,9 @@
+# optimizer
+optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005)
+optimizer_config = dict()
+# learning policy
+lr_config = dict(policy='poly', power=0.9, min_lr=1e-4, by_epoch=False)
+# runtime settings
+runner = dict(type='IterBasedRunner', max_iters=40000)
+checkpoint_config = dict(by_epoch=False, interval=4000)
+evaluation = dict(interval=4000, metric='mIoU')
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/schedules/schedule_80k.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/schedules/schedule_80k.py
new file mode 100644
index 0000000000000000000000000000000000000000..c190cee6bdc7922b688ea75dc8f152fa15c24617
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/configs/_base_/schedules/schedule_80k.py
@@ -0,0 +1,9 @@
+# optimizer
+optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005)
+optimizer_config = dict()
+# learning policy
+lr_config = dict(policy='poly', power=0.9, min_lr=1e-4, by_epoch=False)
+# runtime settings
+runner = dict(type='IterBasedRunner', max_iters=80000)
+checkpoint_config = dict(by_epoch=False, interval=8000)
+evaluation = dict(interval=8000, metric='mIoU')
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/inference.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/inference.py
new file mode 100644
index 0000000000000000000000000000000000000000..7efc93e16f51e70d80340f76d74c6f3db5a26443
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/inference.py
@@ -0,0 +1,137 @@
+
+import torch
+
+import custom_mmpkg.custom_mmcv as mmcv
+from custom_mmpkg.custom_mmcv.parallel import collate, scatter
+from custom_mmpkg.custom_mmcv.runner import load_checkpoint
+from custom_mmpkg.custom_mmseg.datasets.pipelines import Compose
+from custom_mmpkg.custom_mmseg.models import build_segmentor
+
+def init_segmentor(config, checkpoint=None, device='cuda:0'):
+ """Initialize a segmentor from config file.
+
+ Args:
+ config (str or :obj:`mmcv.Config`): Config file path or the config
+ object.
+ checkpoint (str, optional): Checkpoint path. If left as None, the model
+ will not load any weights.
+ device (str, optional) CPU/CUDA device option. Default 'cuda:0'.
+ Use 'cpu' for loading model on CPU.
+ Returns:
+ nn.Module: The constructed segmentor.
+ """
+ if isinstance(config, str):
+ config = mmcv.Config.fromfile(config)
+ elif not isinstance(config, mmcv.Config):
+ raise TypeError('config must be a filename or Config object, '
+ 'but got {}'.format(type(config)))
+ config.model.pretrained = None
+ config.model.train_cfg = None
+ model = build_segmentor(config.model, test_cfg=config.get('test_cfg'))
+ if checkpoint is not None:
+ checkpoint = load_checkpoint(model, checkpoint, map_location='cpu')
+ model.CLASSES = checkpoint['meta']['CLASSES']
+ model.PALETTE = checkpoint['meta']['PALETTE']
+ model.cfg = config # save the config in the model for convenience
+ model.to(device)
+ model.eval()
+ return model
+
+
+class LoadImage:
+ """A simple pipeline to load image."""
+
+ def __call__(self, results):
+ """Call function to load images into results.
+
+ Args:
+ results (dict): A result dict contains the file name
+ of the image to be read.
+
+ Returns:
+ dict: ``results`` will be returned containing loaded image.
+ """
+
+ if isinstance(results['img'], str):
+ results['filename'] = results['img']
+ results['ori_filename'] = results['img']
+ else:
+ results['filename'] = None
+ results['ori_filename'] = None
+ img = mmcv.imread(results['img'])
+ results['img'] = img
+ results['img_shape'] = img.shape
+ results['ori_shape'] = img.shape
+ return results
+
+
+def inference_segmentor(model, img):
+ """Inference image(s) with the segmentor.
+
+ Args:
+ model (nn.Module): The loaded segmentor.
+ imgs (str/ndarray or list[str/ndarray]): Either image files or loaded
+ images.
+
+ Returns:
+ (list[Tensor]): The segmentation result.
+ """
+ cfg = model.cfg
+ device = next(model.parameters()).device # model device
+ # build the data pipeline
+ test_pipeline = [LoadImage()] + cfg.data.test.pipeline[1:]
+ test_pipeline = Compose(test_pipeline)
+ # prepare data
+ data = dict(img=img)
+ data = test_pipeline(data)
+ data = collate([data], samples_per_gpu=1)
+ if next(model.parameters()).is_cuda:
+ # scatter to specified GPU
+ data = scatter(data, [device])[0]
+ else:
+ data['img_metas'] = [i.data[0] for i in data['img_metas']]
+
+ data['img'] = [x.to(device) for x in data['img']]
+
+ # forward the model
+ with torch.no_grad():
+ result = model(return_loss=False, rescale=True, **data)
+ return result
+
+
+def show_result_pyplot(model,
+ img,
+ result,
+ palette=None,
+ fig_size=(15, 10),
+ opacity=0.5,
+ title='',
+ block=True):
+ """Visualize the segmentation results on the image.
+
+ Args:
+ model (nn.Module): The loaded segmentor.
+ img (str or np.ndarray): Image filename or loaded image.
+ result (list): The segmentation result.
+ palette (list[list[int]]] | None): The palette of segmentation
+ map. If None is given, random palette will be generated.
+ Default: None
+ fig_size (tuple): Figure size of the pyplot figure.
+ opacity(float): Opacity of painted segmentation map.
+ Default 0.5.
+ Must be in (0, 1] range.
+ title (str): The title of pyplot figure.
+ Default is ''.
+ block (bool): Whether to block the pyplot figure.
+ Default is True.
+ """
+ if hasattr(model, 'module'):
+ model = model.module
+ img = model.show_result(
+ img, result, palette=palette, show=False, opacity=opacity)
+ # plt.figure(figsize=fig_size)
+ # plt.imshow(mmcv.bgr2rgb(img))
+ # plt.title(title)
+ # plt.tight_layout()
+ # plt.show(block=block)
+ return mmcv.bgr2rgb(img)
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/mmcv_custom/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/mmcv_custom/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..4b958738b9fd93bfcec239c550df1d9a44b8c536
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/mmcv_custom/__init__.py
@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+
+from .checkpoint import load_checkpoint
+
+__all__ = ['load_checkpoint']
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/mmcv_custom/checkpoint.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/mmcv_custom/checkpoint.py
new file mode 100644
index 0000000000000000000000000000000000000000..8453fedcd47fafbedd40ca7ed485dce2e23434e0
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/mmcv_custom/checkpoint.py
@@ -0,0 +1,500 @@
+# Copyright (c) Open-MMLab. All rights reserved.
+import io
+import os
+import os.path as osp
+import pkgutil
+import time
+import warnings
+from collections import OrderedDict
+from importlib import import_module
+from tempfile import TemporaryDirectory
+
+import torch
+import torchvision
+from torch.optim import Optimizer
+from torch.utils import model_zoo
+from torch.nn import functional as F
+
+import custom_mmpkg.custom_mmcv as mmcv
+from custom_mmpkg.custom_mmcv.fileio import FileClient
+from custom_mmpkg.custom_mmcv.fileio import load as load_file
+from custom_mmpkg.custom_mmcv.parallel import is_module_wrapper
+from custom_mmpkg.custom_mmcv.utils import mkdir_or_exist
+from custom_mmpkg.custom_mmcv.runner import get_dist_info
+
+ENV_MMCV_HOME = 'MMCV_HOME'
+ENV_XDG_CACHE_HOME = 'XDG_CACHE_HOME'
+DEFAULT_CACHE_DIR = '~/.cache'
+
+
+def _get_mmcv_home():
+ mmcv_home = os.path.expanduser(
+ os.getenv(
+ ENV_MMCV_HOME,
+ os.path.join(
+ os.getenv(ENV_XDG_CACHE_HOME, DEFAULT_CACHE_DIR), 'mmcv')))
+
+ mkdir_or_exist(mmcv_home)
+ return mmcv_home
+
+
+def load_state_dict(module, state_dict, strict=False, logger=None):
+ """Load state_dict to a module.
+
+ This method is modified from :meth:`torch.nn.Module.load_state_dict`.
+ Default value for ``strict`` is set to ``False`` and the message for
+ param mismatch will be shown even if strict is False.
+
+ Args:
+ module (Module): Module that receives the state_dict.
+ state_dict (OrderedDict): Weights.
+ strict (bool): whether to strictly enforce that the keys
+ in :attr:`state_dict` match the keys returned by this module's
+ :meth:`~torch.nn.Module.state_dict` function. Default: ``False``.
+ logger (:obj:`logging.Logger`, optional): Logger to log the error
+ message. If not specified, print function will be used.
+ """
+ unexpected_keys = []
+ all_missing_keys = []
+ err_msg = []
+
+ metadata = getattr(state_dict, '_metadata', None)
+ state_dict = state_dict.copy()
+ if metadata is not None:
+ state_dict._metadata = metadata
+
+ # use _load_from_state_dict to enable checkpoint version control
+ def load(module, prefix=''):
+ # recursively check parallel module in case that the model has a
+ # complicated structure, e.g., nn.Module(nn.Module(DDP))
+ if is_module_wrapper(module):
+ module = module.module
+ local_metadata = {} if metadata is None else metadata.get(
+ prefix[:-1], {})
+ module._load_from_state_dict(state_dict, prefix, local_metadata, True,
+ all_missing_keys, unexpected_keys,
+ err_msg)
+ for name, child in module._modules.items():
+ if child is not None:
+ load(child, prefix + name + '.')
+
+ load(module)
+ load = None # break load->load reference cycle
+
+ # ignore "num_batches_tracked" of BN layers
+ missing_keys = [
+ key for key in all_missing_keys if 'num_batches_tracked' not in key
+ ]
+
+ if unexpected_keys:
+ err_msg.append('unexpected key in source '
+ f'state_dict: {", ".join(unexpected_keys)}\n')
+ if missing_keys:
+ err_msg.append(
+ f'missing keys in source state_dict: {", ".join(missing_keys)}\n')
+
+ rank, _ = get_dist_info()
+ if len(err_msg) > 0 and rank == 0:
+ err_msg.insert(
+ 0, 'The model and loaded state dict do not match exactly\n')
+ err_msg = '\n'.join(err_msg)
+ if strict:
+ raise RuntimeError(err_msg)
+ elif logger is not None:
+ logger.warning(err_msg)
+ else:
+ print(err_msg)
+
+
+def load_url_dist(url, model_dir=None):
+ """In distributed setting, this function only download checkpoint at local
+ rank 0."""
+ rank, world_size = get_dist_info()
+ rank = int(os.environ.get('LOCAL_RANK', rank))
+ if rank == 0:
+ checkpoint = model_zoo.load_url(url, model_dir=model_dir)
+ if world_size > 1:
+ torch.distributed.barrier()
+ if rank > 0:
+ checkpoint = model_zoo.load_url(url, model_dir=model_dir)
+ return checkpoint
+
+
+def load_pavimodel_dist(model_path, map_location=None):
+ """In distributed setting, this function only download checkpoint at local
+ rank 0."""
+ try:
+ from pavi import modelcloud
+ except ImportError:
+ raise ImportError(
+ 'Please install pavi to load checkpoint from modelcloud.')
+ rank, world_size = get_dist_info()
+ rank = int(os.environ.get('LOCAL_RANK', rank))
+ if rank == 0:
+ model = modelcloud.get(model_path)
+ with TemporaryDirectory() as tmp_dir:
+ downloaded_file = osp.join(tmp_dir, model.name)
+ model.download(downloaded_file)
+ checkpoint = torch.load(downloaded_file, map_location=map_location)
+ if world_size > 1:
+ torch.distributed.barrier()
+ if rank > 0:
+ model = modelcloud.get(model_path)
+ with TemporaryDirectory() as tmp_dir:
+ downloaded_file = osp.join(tmp_dir, model.name)
+ model.download(downloaded_file)
+ checkpoint = torch.load(
+ downloaded_file, map_location=map_location)
+ return checkpoint
+
+
+def load_fileclient_dist(filename, backend, map_location):
+ """In distributed setting, this function only download checkpoint at local
+ rank 0."""
+ rank, world_size = get_dist_info()
+ rank = int(os.environ.get('LOCAL_RANK', rank))
+ allowed_backends = ['ceph']
+ if backend not in allowed_backends:
+ raise ValueError(f'Load from Backend {backend} is not supported.')
+ if rank == 0:
+ fileclient = FileClient(backend=backend)
+ buffer = io.BytesIO(fileclient.get(filename))
+ checkpoint = torch.load(buffer, map_location=map_location)
+ if world_size > 1:
+ torch.distributed.barrier()
+ if rank > 0:
+ fileclient = FileClient(backend=backend)
+ buffer = io.BytesIO(fileclient.get(filename))
+ checkpoint = torch.load(buffer, map_location=map_location)
+ return checkpoint
+
+
+def get_torchvision_models():
+ model_urls = dict()
+ for _, name, ispkg in pkgutil.walk_packages(torchvision.models.__path__):
+ if ispkg:
+ continue
+ _zoo = import_module(f'torchvision.models.{name}')
+ if hasattr(_zoo, 'model_urls'):
+ _urls = getattr(_zoo, 'model_urls')
+ model_urls.update(_urls)
+ return model_urls
+
+
+def get_external_models():
+ mmcv_home = _get_mmcv_home()
+ default_json_path = osp.join(mmcv.__path__[0], 'model_zoo/open_mmlab.json')
+ default_urls = load_file(default_json_path)
+ assert isinstance(default_urls, dict)
+ external_json_path = osp.join(mmcv_home, 'open_mmlab.json')
+ if osp.exists(external_json_path):
+ external_urls = load_file(external_json_path)
+ assert isinstance(external_urls, dict)
+ default_urls.update(external_urls)
+
+ return default_urls
+
+
+def get_mmcls_models():
+ mmcls_json_path = osp.join(mmcv.__path__[0], 'model_zoo/mmcls.json')
+ mmcls_urls = load_file(mmcls_json_path)
+
+ return mmcls_urls
+
+
+def get_deprecated_model_names():
+ deprecate_json_path = osp.join(mmcv.__path__[0],
+ 'model_zoo/deprecated.json')
+ deprecate_urls = load_file(deprecate_json_path)
+ assert isinstance(deprecate_urls, dict)
+
+ return deprecate_urls
+
+
+def _process_mmcls_checkpoint(checkpoint):
+ state_dict = checkpoint['state_dict']
+ new_state_dict = OrderedDict()
+ for k, v in state_dict.items():
+ if k.startswith('backbone.'):
+ new_state_dict[k[9:]] = v
+ new_checkpoint = dict(state_dict=new_state_dict)
+
+ return new_checkpoint
+
+
+def _load_checkpoint(filename, map_location=None):
+ """Load checkpoint from somewhere (modelzoo, file, url).
+
+ Args:
+ filename (str): Accept local filepath, URL, ``torchvision://xxx``,
+ ``open-mmlab://xxx``. Please refer to ``docs/model_zoo.md`` for
+ details.
+ map_location (str | None): Same as :func:`torch.load`. Default: None.
+
+ Returns:
+ dict | OrderedDict: The loaded checkpoint. It can be either an
+ OrderedDict storing model weights or a dict containing other
+ information, which depends on the checkpoint.
+ """
+ if filename.startswith('modelzoo://'):
+ warnings.warn('The URL scheme of "modelzoo://" is deprecated, please '
+ 'use "torchvision://" instead')
+ model_urls = get_torchvision_models()
+ model_name = filename[11:]
+ checkpoint = load_url_dist(model_urls[model_name])
+ elif filename.startswith('torchvision://'):
+ model_urls = get_torchvision_models()
+ model_name = filename[14:]
+ checkpoint = load_url_dist(model_urls[model_name])
+ elif filename.startswith('open-mmlab://'):
+ model_urls = get_external_models()
+ model_name = filename[13:]
+ deprecated_urls = get_deprecated_model_names()
+ if model_name in deprecated_urls:
+ warnings.warn(f'open-mmlab://{model_name} is deprecated in favor '
+ f'of open-mmlab://{deprecated_urls[model_name]}')
+ model_name = deprecated_urls[model_name]
+ model_url = model_urls[model_name]
+ # check if is url
+ if model_url.startswith(('http://', 'https://')):
+ checkpoint = load_url_dist(model_url)
+ else:
+ filename = osp.join(_get_mmcv_home(), model_url)
+ if not osp.isfile(filename):
+ raise IOError(f'{filename} is not a checkpoint file')
+ checkpoint = torch.load(filename, map_location=map_location)
+ elif filename.startswith('mmcls://'):
+ model_urls = get_mmcls_models()
+ model_name = filename[8:]
+ checkpoint = load_url_dist(model_urls[model_name])
+ checkpoint = _process_mmcls_checkpoint(checkpoint)
+ elif filename.startswith(('http://', 'https://')):
+ checkpoint = load_url_dist(filename)
+ elif filename.startswith('pavi://'):
+ model_path = filename[7:]
+ checkpoint = load_pavimodel_dist(model_path, map_location=map_location)
+ elif filename.startswith('s3://'):
+ checkpoint = load_fileclient_dist(
+ filename, backend='ceph', map_location=map_location)
+ else:
+ if not osp.isfile(filename):
+ raise IOError(f'{filename} is not a checkpoint file')
+ checkpoint = torch.load(filename, map_location=map_location)
+ return checkpoint
+
+
+def load_checkpoint(model,
+ filename,
+ map_location='cpu',
+ strict=False,
+ logger=None):
+ """Load checkpoint from a file or URI.
+
+ Args:
+ model (Module): Module to load checkpoint.
+ filename (str): Accept local filepath, URL, ``torchvision://xxx``,
+ ``open-mmlab://xxx``. Please refer to ``docs/model_zoo.md`` for
+ details.
+ map_location (str): Same as :func:`torch.load`.
+ strict (bool): Whether to allow different params for the model and
+ checkpoint.
+ logger (:mod:`logging.Logger` or None): The logger for error message.
+
+ Returns:
+ dict or OrderedDict: The loaded checkpoint.
+ """
+ checkpoint = _load_checkpoint(filename, map_location)
+ # OrderedDict is a subclass of dict
+ if not isinstance(checkpoint, dict):
+ raise RuntimeError(
+ f'No state_dict found in checkpoint file {filename}')
+ # get state_dict from checkpoint
+ if 'state_dict' in checkpoint:
+ state_dict = checkpoint['state_dict']
+ elif 'model' in checkpoint:
+ state_dict = checkpoint['model']
+ else:
+ state_dict = checkpoint
+ # strip prefix of state_dict
+ if list(state_dict.keys())[0].startswith('module.'):
+ state_dict = {k[7:]: v for k, v in state_dict.items()}
+
+ # for MoBY, load model of online branch
+ if sorted(list(state_dict.keys()))[0].startswith('encoder'):
+ state_dict = {k.replace('encoder.', ''): v for k, v in state_dict.items() if k.startswith('encoder.')}
+
+ # reshape absolute position embedding
+ if state_dict.get('absolute_pos_embed') is not None:
+ absolute_pos_embed = state_dict['absolute_pos_embed']
+ N1, L, C1 = absolute_pos_embed.size()
+ N2, C2, H, W = model.absolute_pos_embed.size()
+ if N1 != N2 or C1 != C2 or L != H*W:
+ logger.warning("Error in loading absolute_pos_embed, pass")
+ else:
+ state_dict['absolute_pos_embed'] = absolute_pos_embed.view(N2, H, W, C2).permute(0, 3, 1, 2)
+
+ # interpolate position bias table if needed
+ relative_position_bias_table_keys = [k for k in state_dict.keys() if "relative_position_bias_table" in k]
+ for table_key in relative_position_bias_table_keys:
+ table_pretrained = state_dict[table_key]
+ table_current = model.state_dict()[table_key]
+ L1, nH1 = table_pretrained.size()
+ L2, nH2 = table_current.size()
+ if nH1 != nH2:
+ logger.warning(f"Error in loading {table_key}, pass")
+ else:
+ if L1 != L2:
+ S1 = int(L1 ** 0.5)
+ S2 = int(L2 ** 0.5)
+ table_pretrained_resized = F.interpolate(
+ table_pretrained.permute(1, 0).view(1, nH1, S1, S1),
+ size=(S2, S2), mode='bicubic')
+ state_dict[table_key] = table_pretrained_resized.view(nH2, L2).permute(1, 0)
+
+ # load state_dict
+ load_state_dict(model, state_dict, strict, logger)
+ return checkpoint
+
+
+def weights_to_cpu(state_dict):
+ """Copy a model state_dict to cpu.
+
+ Args:
+ state_dict (OrderedDict): Model weights on GPU.
+
+ Returns:
+ OrderedDict: Model weights on GPU.
+ """
+ state_dict_cpu = OrderedDict()
+ for key, val in state_dict.items():
+ state_dict_cpu[key] = val.cpu()
+ return state_dict_cpu
+
+
+def _save_to_state_dict(module, destination, prefix, keep_vars):
+ """Saves module state to `destination` dictionary.
+
+ This method is modified from :meth:`torch.nn.Module._save_to_state_dict`.
+
+ Args:
+ module (nn.Module): The module to generate state_dict.
+ destination (dict): A dict where state will be stored.
+ prefix (str): The prefix for parameters and buffers used in this
+ module.
+ """
+ for name, param in module._parameters.items():
+ if param is not None:
+ destination[prefix + name] = param if keep_vars else param.detach()
+ for name, buf in module._buffers.items():
+ # remove check of _non_persistent_buffers_set to allow nn.BatchNorm2d
+ if buf is not None:
+ destination[prefix + name] = buf if keep_vars else buf.detach()
+
+
+def get_state_dict(module, destination=None, prefix='', keep_vars=False):
+ """Returns a dictionary containing a whole state of the module.
+
+ Both parameters and persistent buffers (e.g. running averages) are
+ included. Keys are corresponding parameter and buffer names.
+
+ This method is modified from :meth:`torch.nn.Module.state_dict` to
+ recursively check parallel module in case that the model has a complicated
+ structure, e.g., nn.Module(nn.Module(DDP)).
+
+ Args:
+ module (nn.Module): The module to generate state_dict.
+ destination (OrderedDict): Returned dict for the state of the
+ module.
+ prefix (str): Prefix of the key.
+ keep_vars (bool): Whether to keep the variable property of the
+ parameters. Default: False.
+
+ Returns:
+ dict: A dictionary containing a whole state of the module.
+ """
+ # recursively check parallel module in case that the model has a
+ # complicated structure, e.g., nn.Module(nn.Module(DDP))
+ if is_module_wrapper(module):
+ module = module.module
+
+ # below is the same as torch.nn.Module.state_dict()
+ if destination is None:
+ destination = OrderedDict()
+ destination._metadata = OrderedDict()
+ destination._metadata[prefix[:-1]] = local_metadata = dict(
+ version=module._version)
+ _save_to_state_dict(module, destination, prefix, keep_vars)
+ for name, child in module._modules.items():
+ if child is not None:
+ get_state_dict(
+ child, destination, prefix + name + '.', keep_vars=keep_vars)
+ for hook in module._state_dict_hooks.values():
+ hook_result = hook(module, destination, prefix, local_metadata)
+ if hook_result is not None:
+ destination = hook_result
+ return destination
+
+
+def save_checkpoint(model, filename, optimizer=None, meta=None):
+ """Save checkpoint to file.
+
+ The checkpoint will have 3 fields: ``meta``, ``state_dict`` and
+ ``optimizer``. By default ``meta`` will contain version and time info.
+
+ Args:
+ model (Module): Module whose params are to be saved.
+ filename (str): Checkpoint filename.
+ optimizer (:obj:`Optimizer`, optional): Optimizer to be saved.
+ meta (dict, optional): Metadata to be saved in checkpoint.
+ """
+ if meta is None:
+ meta = {}
+ elif not isinstance(meta, dict):
+ raise TypeError(f'meta must be a dict or None, but got {type(meta)}')
+ meta.update(mmcv_version=mmcv.__version__, time=time.asctime())
+
+ if is_module_wrapper(model):
+ model = model.module
+
+ if hasattr(model, 'CLASSES') and model.CLASSES is not None:
+ # save class name to the meta
+ meta.update(CLASSES=model.CLASSES)
+
+ checkpoint = {
+ 'meta': meta,
+ 'state_dict': weights_to_cpu(get_state_dict(model))
+ }
+ # save optimizer state dict in the checkpoint
+ if isinstance(optimizer, Optimizer):
+ checkpoint['optimizer'] = optimizer.state_dict()
+ elif isinstance(optimizer, dict):
+ checkpoint['optimizer'] = {}
+ for name, optim in optimizer.items():
+ checkpoint['optimizer'][name] = optim.state_dict()
+
+ if filename.startswith('pavi://'):
+ try:
+ from pavi import modelcloud
+ from pavi.exception import NodeNotFoundError
+ except ImportError:
+ raise ImportError(
+ 'Please install pavi to load checkpoint from modelcloud.')
+ model_path = filename[7:]
+ root = modelcloud.Folder()
+ model_dir, model_name = osp.split(model_path)
+ try:
+ model = modelcloud.get(model_dir)
+ except NodeNotFoundError:
+ model = root.create_training_model(model_dir)
+ with TemporaryDirectory() as tmp_dir:
+ checkpoint_file = osp.join(tmp_dir, model_name)
+ with open(checkpoint_file, 'wb') as f:
+ torch.save(checkpoint, f)
+ f.flush()
+ model.create_file(checkpoint_file, name=model_name)
+ else:
+ mmcv.mkdir_or_exist(osp.dirname(filename))
+ # immediately flush buffer
+ with open(filename, 'wb') as f:
+ torch.save(checkpoint, f)
+ f.flush()
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/uniformer.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/uniformer.py
new file mode 100644
index 0000000000000000000000000000000000000000..30039e2eb45b01c336493ad7a86a5a4e33aa9c1b
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/uniformer.py
@@ -0,0 +1,421 @@
+# --------------------------------------------------------
+# UniFormer
+# Copyright (c) 2022 SenseTime X-Lab
+# Licensed under The MIT License [see LICENSE for details]
+# Written by Kunchang Li
+# --------------------------------------------------------
+
+
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+import torch.utils.checkpoint as checkpoint
+
+from functools import partial
+from collections import OrderedDict
+from custom_timm.models.layers import DropPath, to_2tuple, trunc_normal_
+from custom_mmpkg.custom_mmseg.utils import get_root_logger
+from custom_mmpkg.custom_mmseg.models.builder import BACKBONES
+
+from .mmcv_custom import load_checkpoint
+
+
+class Mlp(nn.Module):
+ def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.):
+ super().__init__()
+ out_features = out_features or in_features
+ hidden_features = hidden_features or in_features
+ self.fc1 = nn.Linear(in_features, hidden_features)
+ self.act = act_layer()
+ self.fc2 = nn.Linear(hidden_features, out_features)
+ self.drop = nn.Dropout(drop)
+
+ def forward(self, x):
+ x = self.fc1(x)
+ x = self.act(x)
+ x = self.drop(x)
+ x = self.fc2(x)
+ x = self.drop(x)
+ return x
+
+
+class CMlp(nn.Module):
+ def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.):
+ super().__init__()
+ out_features = out_features or in_features
+ hidden_features = hidden_features or in_features
+ self.fc1 = nn.Conv2d(in_features, hidden_features, 1)
+ self.act = act_layer()
+ self.fc2 = nn.Conv2d(hidden_features, out_features, 1)
+ self.drop = nn.Dropout(drop)
+
+ def forward(self, x):
+ x = self.fc1(x)
+ x = self.act(x)
+ x = self.drop(x)
+ x = self.fc2(x)
+ x = self.drop(x)
+ return x
+
+
+class CBlock(nn.Module):
+ def __init__(self, dim, num_heads, mlp_ratio=4., qkv_bias=False, qk_scale=None, drop=0., attn_drop=0.,
+ drop_path=0., act_layer=nn.GELU, norm_layer=nn.LayerNorm):
+ super().__init__()
+ self.pos_embed = nn.Conv2d(dim, dim, 3, padding=1, groups=dim)
+ self.norm1 = nn.BatchNorm2d(dim)
+ self.conv1 = nn.Conv2d(dim, dim, 1)
+ self.conv2 = nn.Conv2d(dim, dim, 1)
+ self.attn = nn.Conv2d(dim, dim, 5, padding=2, groups=dim)
+ # NOTE: drop path for stochastic depth, we shall see if this is better than dropout here
+ self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()
+ self.norm2 = nn.BatchNorm2d(dim)
+ mlp_hidden_dim = int(dim * mlp_ratio)
+ self.mlp = CMlp(in_features=dim, hidden_features=mlp_hidden_dim, act_layer=act_layer, drop=drop)
+
+ def forward(self, x):
+ x = x + self.pos_embed(x)
+ x = x + self.drop_path(self.conv2(self.attn(self.conv1(self.norm1(x)))))
+ x = x + self.drop_path(self.mlp(self.norm2(x)))
+ return x
+
+
+class Attention(nn.Module):
+ def __init__(self, dim, num_heads=8, qkv_bias=False, qk_scale=None, attn_drop=0., proj_drop=0.):
+ super().__init__()
+ self.num_heads = num_heads
+ head_dim = dim // num_heads
+ # NOTE scale factor was wrong in my original version, can set manually to be compat with prev weights
+ self.scale = qk_scale or head_dim ** -0.5
+
+ self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)
+ self.attn_drop = nn.Dropout(attn_drop)
+ self.proj = nn.Linear(dim, dim)
+ self.proj_drop = nn.Dropout(proj_drop)
+
+ def forward(self, x):
+ B, N, C = x.shape
+ qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)
+ q, k, v = qkv[0], qkv[1], qkv[2] # make torchscript happy (cannot use tensor as tuple)
+
+ attn = (q @ k.transpose(-2, -1)) * self.scale
+ attn = attn.softmax(dim=-1)
+ attn = self.attn_drop(attn)
+
+ x = (attn @ v).transpose(1, 2).reshape(B, N, C)
+ x = self.proj(x)
+ x = self.proj_drop(x)
+ return x
+
+
+class SABlock(nn.Module):
+ def __init__(self, dim, num_heads, mlp_ratio=4., qkv_bias=False, qk_scale=None, drop=0., attn_drop=0.,
+ drop_path=0., act_layer=nn.GELU, norm_layer=nn.LayerNorm):
+ super().__init__()
+ self.pos_embed = nn.Conv2d(dim, dim, 3, padding=1, groups=dim)
+ self.norm1 = norm_layer(dim)
+ self.attn = Attention(
+ dim,
+ num_heads=num_heads, qkv_bias=qkv_bias, qk_scale=qk_scale,
+ attn_drop=attn_drop, proj_drop=drop)
+ # NOTE: drop path for stochastic depth, we shall see if this is better than dropout here
+ self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()
+ self.norm2 = norm_layer(dim)
+ mlp_hidden_dim = int(dim * mlp_ratio)
+ self.mlp = Mlp(in_features=dim, hidden_features=mlp_hidden_dim, act_layer=act_layer, drop=drop)
+
+ def forward(self, x):
+ x = x + self.pos_embed(x)
+ B, N, H, W = x.shape
+ x = x.flatten(2).transpose(1, 2)
+ x = x + self.drop_path(self.attn(self.norm1(x)))
+ x = x + self.drop_path(self.mlp(self.norm2(x)))
+ x = x.transpose(1, 2).reshape(B, N, H, W)
+ return x
+
+
+def window_partition(x, window_size):
+ """
+ Args:
+ x: (B, H, W, C)
+ window_size (int): window size
+ Returns:
+ windows: (num_windows*B, window_size, window_size, C)
+ """
+ B, H, W, C = x.shape
+ x = x.view(B, H // window_size, window_size, W // window_size, window_size, C)
+ windows = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(-1, window_size, window_size, C)
+ return windows
+
+
+def window_reverse(windows, window_size, H, W):
+ """
+ Args:
+ windows: (num_windows*B, window_size, window_size, C)
+ window_size (int): Window size
+ H (int): Height of image
+ W (int): Width of image
+ Returns:
+ x: (B, H, W, C)
+ """
+ B = int(windows.shape[0] / (H * W / window_size / window_size))
+ x = windows.view(B, H // window_size, W // window_size, window_size, window_size, -1)
+ x = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(B, H, W, -1)
+ return x
+
+
+class SABlock_Windows(nn.Module):
+ def __init__(self, dim, num_heads, window_size=14, mlp_ratio=4., qkv_bias=False, qk_scale=None, drop=0., attn_drop=0.,
+ drop_path=0., act_layer=nn.GELU, norm_layer=nn.LayerNorm):
+ super().__init__()
+ self.window_size=window_size
+ self.pos_embed = nn.Conv2d(dim, dim, 3, padding=1, groups=dim)
+ self.norm1 = norm_layer(dim)
+ self.attn = Attention(
+ dim,
+ num_heads=num_heads, qkv_bias=qkv_bias, qk_scale=qk_scale,
+ attn_drop=attn_drop, proj_drop=drop)
+ # NOTE: drop path for stochastic depth, we shall see if this is better than dropout here
+ self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()
+ self.norm2 = norm_layer(dim)
+ mlp_hidden_dim = int(dim * mlp_ratio)
+ self.mlp = Mlp(in_features=dim, hidden_features=mlp_hidden_dim, act_layer=act_layer, drop=drop)
+
+ def forward(self, x):
+ x = x + self.pos_embed(x)
+ x = x.permute(0, 2, 3, 1)
+ B, H, W, C = x.shape
+ shortcut = x
+ x = self.norm1(x)
+
+ pad_l = pad_t = 0
+ pad_r = (self.window_size - W % self.window_size) % self.window_size
+ pad_b = (self.window_size - H % self.window_size) % self.window_size
+ x = F.pad(x, (0, 0, pad_l, pad_r, pad_t, pad_b))
+ _, Hp, Wp, _ = x.shape
+
+ x_windows = window_partition(x, self.window_size) # nW*B, window_size, window_size, C
+ x_windows = x_windows.view(-1, self.window_size * self.window_size, C) # nW*B, window_size*window_size, C
+
+ # W-MSA/SW-MSA
+ attn_windows = self.attn(x_windows) # nW*B, window_size*window_size, C
+
+ # merge windows
+ attn_windows = attn_windows.view(-1, self.window_size, self.window_size, C)
+ x = window_reverse(attn_windows, self.window_size, Hp, Wp) # B H' W' C
+
+ # reverse cyclic shift
+ if pad_r > 0 or pad_b > 0:
+ x = x[:, :H, :W, :].contiguous()
+
+ x = shortcut + self.drop_path(x)
+ x = x + self.drop_path(self.mlp(self.norm2(x)))
+ x = x.permute(0, 3, 1, 2).reshape(B, C, H, W)
+ return x
+
+
+class PatchEmbed(nn.Module):
+ """ Image to Patch Embedding
+ """
+ def __init__(self, img_size=224, patch_size=16, in_chans=3, embed_dim=768):
+ super().__init__()
+ img_size = to_2tuple(img_size)
+ patch_size = to_2tuple(patch_size)
+ num_patches = (img_size[1] // patch_size[1]) * (img_size[0] // patch_size[0])
+ self.img_size = img_size
+ self.patch_size = patch_size
+ self.num_patches = num_patches
+ self.norm = nn.LayerNorm(embed_dim)
+ self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_size)
+
+ def forward(self, x):
+ B, _, H, W = x.shape
+ x = self.proj(x)
+ B, _, H, W = x.shape
+ x = x.flatten(2).transpose(1, 2)
+ x = self.norm(x)
+ x = x.reshape(B, H, W, -1).permute(0, 3, 1, 2).contiguous()
+ return x
+
+
+@BACKBONES.register_module()
+class UniFormer(nn.Module):
+ """ Vision Transformer
+ A PyTorch impl of : `An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale` -
+ https://arxiv.org/abs/2010.11929
+ """
+ def __init__(self, layers=[3, 4, 8, 3], img_size=224, in_chans=3, num_classes=80, embed_dim=[64, 128, 320, 512],
+ head_dim=64, mlp_ratio=4., qkv_bias=True, qk_scale=None, representation_size=None,
+ drop_rate=0., attn_drop_rate=0., drop_path_rate=0., norm_layer=partial(nn.LayerNorm, eps=1e-6),
+ pretrained_path=None, use_checkpoint=False, checkpoint_num=[0, 0, 0, 0],
+ windows=False, hybrid=False, window_size=14):
+ """
+ Args:
+ layer (list): number of block in each layer
+ img_size (int, tuple): input image size
+ in_chans (int): number of input channels
+ num_classes (int): number of classes for classification head
+ embed_dim (int): embedding dimension
+ head_dim (int): dimension of attention heads
+ mlp_ratio (int): ratio of mlp hidden dim to embedding dim
+ qkv_bias (bool): enable bias for qkv if True
+ qk_scale (float): override default qk scale of head_dim ** -0.5 if set
+ representation_size (Optional[int]): enable and set representation layer (pre-logits) to this value if set
+ drop_rate (float): dropout rate
+ attn_drop_rate (float): attention dropout rate
+ drop_path_rate (float): stochastic depth rate
+ norm_layer (nn.Module): normalization layer
+ pretrained_path (str): path of pretrained model
+ use_checkpoint (bool): whether use checkpoint
+ checkpoint_num (list): index for using checkpoint in every stage
+ windows (bool): whether use window MHRA
+ hybrid (bool): whether use hybrid MHRA
+ window_size (int): size of window (>14)
+ """
+ super().__init__()
+ self.num_classes = num_classes
+ self.use_checkpoint = use_checkpoint
+ self.checkpoint_num = checkpoint_num
+ self.windows = windows
+ print(f'Use Checkpoint: {self.use_checkpoint}')
+ print(f'Checkpoint Number: {self.checkpoint_num}')
+ self.num_features = self.embed_dim = embed_dim # num_features for consistency with other models
+ norm_layer = norm_layer or partial(nn.LayerNorm, eps=1e-6)
+
+ self.patch_embed1 = PatchEmbed(
+ img_size=img_size, patch_size=4, in_chans=in_chans, embed_dim=embed_dim[0])
+ self.patch_embed2 = PatchEmbed(
+ img_size=img_size // 4, patch_size=2, in_chans=embed_dim[0], embed_dim=embed_dim[1])
+ self.patch_embed3 = PatchEmbed(
+ img_size=img_size // 8, patch_size=2, in_chans=embed_dim[1], embed_dim=embed_dim[2])
+ self.patch_embed4 = PatchEmbed(
+ img_size=img_size // 16, patch_size=2, in_chans=embed_dim[2], embed_dim=embed_dim[3])
+
+ self.pos_drop = nn.Dropout(p=drop_rate)
+ dpr = [x.item() for x in torch.linspace(0, drop_path_rate, sum(layers))] # stochastic depth decay rule
+ num_heads = [dim // head_dim for dim in embed_dim]
+ self.blocks1 = nn.ModuleList([
+ CBlock(
+ dim=embed_dim[0], num_heads=num_heads[0], mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, qk_scale=qk_scale,
+ drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[i], norm_layer=norm_layer)
+ for i in range(layers[0])])
+ self.norm1=norm_layer(embed_dim[0])
+ self.blocks2 = nn.ModuleList([
+ CBlock(
+ dim=embed_dim[1], num_heads=num_heads[1], mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, qk_scale=qk_scale,
+ drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[i+layers[0]], norm_layer=norm_layer)
+ for i in range(layers[1])])
+ self.norm2 = norm_layer(embed_dim[1])
+ if self.windows:
+ print('Use local window for all blocks in stage3')
+ self.blocks3 = nn.ModuleList([
+ SABlock_Windows(
+ dim=embed_dim[2], num_heads=num_heads[2], window_size=window_size, mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, qk_scale=qk_scale,
+ drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[i+layers[0]+layers[1]], norm_layer=norm_layer)
+ for i in range(layers[2])])
+ elif hybrid:
+ print('Use hybrid window for blocks in stage3')
+ block3 = []
+ for i in range(layers[2]):
+ if (i + 1) % 4 == 0:
+ block3.append(SABlock(
+ dim=embed_dim[2], num_heads=num_heads[2], mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, qk_scale=qk_scale,
+ drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[i+layers[0]+layers[1]], norm_layer=norm_layer))
+ else:
+ block3.append(SABlock_Windows(
+ dim=embed_dim[2], num_heads=num_heads[2], window_size=window_size, mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, qk_scale=qk_scale,
+ drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[i+layers[0]+layers[1]], norm_layer=norm_layer))
+ self.blocks3 = nn.ModuleList(block3)
+ else:
+ print('Use global window for all blocks in stage3')
+ self.blocks3 = nn.ModuleList([
+ SABlock(
+ dim=embed_dim[2], num_heads=num_heads[2], mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, qk_scale=qk_scale,
+ drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[i+layers[0]+layers[1]], norm_layer=norm_layer)
+ for i in range(layers[2])])
+ self.norm3 = norm_layer(embed_dim[2])
+ self.blocks4 = nn.ModuleList([
+ SABlock(
+ dim=embed_dim[3], num_heads=num_heads[3], mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, qk_scale=qk_scale,
+ drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[i+layers[0]+layers[1]+layers[2]], norm_layer=norm_layer)
+ for i in range(layers[3])])
+ self.norm4 = norm_layer(embed_dim[3])
+
+ # Representation layer
+ if representation_size:
+ self.num_features = representation_size
+ self.pre_logits = nn.Sequential(OrderedDict([
+ ('fc', nn.Linear(embed_dim, representation_size)),
+ ('act', nn.Tanh())
+ ]))
+ else:
+ self.pre_logits = nn.Identity()
+
+ self.apply(self._init_weights)
+ self.init_weights(pretrained=pretrained_path)
+
+ def init_weights(self, pretrained):
+ if isinstance(pretrained, str):
+ logger = get_root_logger()
+ load_checkpoint(self, pretrained, map_location='cpu', strict=False, logger=logger)
+ print(f'Load pretrained model from {pretrained}')
+ def _init_weights(self, m):
+ if isinstance(m, nn.Linear):
+ trunc_normal_(m.weight, std=.02)
+ if isinstance(m, nn.Linear) and m.bias is not None:
+ nn.init.constant_(m.bias, 0)
+ elif isinstance(m, nn.LayerNorm):
+ nn.init.constant_(m.bias, 0)
+ nn.init.constant_(m.weight, 1.0)
+
+ @torch.jit.ignore
+ def no_weight_decay(self):
+ return {'pos_embed', 'cls_token'}
+
+ def get_classifier(self):
+ return self.head
+
+ def reset_classifier(self, num_classes, global_pool=''):
+ self.num_classes = num_classes
+ self.head = nn.Linear(self.embed_dim, num_classes) if num_classes > 0 else nn.Identity()
+
+ def forward_features(self, x):
+ out = []
+ x = self.patch_embed1(x)
+ x = self.pos_drop(x)
+ for i, blk in enumerate(self.blocks1):
+ if self.use_checkpoint and i < self.checkpoint_num[0]:
+ x = checkpoint.checkpoint(blk, x)
+ else:
+ x = blk(x)
+ x_out = self.norm1(x.permute(0, 2, 3, 1))
+ out.append(x_out.permute(0, 3, 1, 2).contiguous())
+ x = self.patch_embed2(x)
+ for i, blk in enumerate(self.blocks2):
+ if self.use_checkpoint and i < self.checkpoint_num[1]:
+ x = checkpoint.checkpoint(blk, x)
+ else:
+ x = blk(x)
+ x_out = self.norm2(x.permute(0, 2, 3, 1))
+ out.append(x_out.permute(0, 3, 1, 2).contiguous())
+ x = self.patch_embed3(x)
+ for i, blk in enumerate(self.blocks3):
+ if self.use_checkpoint and i < self.checkpoint_num[2]:
+ x = checkpoint.checkpoint(blk, x)
+ else:
+ x = blk(x)
+ x_out = self.norm3(x.permute(0, 2, 3, 1))
+ out.append(x_out.permute(0, 3, 1, 2).contiguous())
+ x = self.patch_embed4(x)
+ for i, blk in enumerate(self.blocks4):
+ if self.use_checkpoint and i < self.checkpoint_num[3]:
+ x = checkpoint.checkpoint(blk, x)
+ else:
+ x = blk(x)
+ x_out = self.norm4(x.permute(0, 2, 3, 1))
+ out.append(x_out.permute(0, 3, 1, 2).contiguous())
+ return tuple(out)
+
+ def forward(self, x):
+ x = self.forward_features(x)
+ return x
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/upernet_global_small.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/upernet_global_small.py
new file mode 100644
index 0000000000000000000000000000000000000000..2410de80ecccf39a0034509539248f8ffe037d85
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/uniformer/upernet_global_small.py
@@ -0,0 +1,44 @@
+_base_ = [
+ 'configs/_base_/models/upernet_uniformer.py',
+ 'configs/_base_/datasets/ade20k.py',
+ 'configs/_base_/default_runtime.py',
+ 'configs/_base_/schedules/schedule_160k.py'
+]
+
+custom_imports = dict(
+ imports=['controlnet_aux.uniformer.uniformer'],
+ allow_failed_imports=False
+)
+
+model = dict(
+ backbone=dict(
+ type='UniFormer',
+ embed_dim=[64, 128, 320, 512],
+ layers=[3, 4, 8, 3],
+ head_dim=64,
+ drop_path_rate=0.25,
+ windows=False,
+ hybrid=False
+ ),
+ decode_head=dict(
+ in_channels=[64, 128, 320, 512],
+ num_classes=150
+ ),
+ auxiliary_head=dict(
+ in_channels=320,
+ num_classes=150
+ ))
+
+# AdamW optimizer, no weight decay for position embedding & layer norm in backbone
+optimizer = dict(_delete_=True, type='AdamW', lr=0.00006, betas=(0.9, 0.999), weight_decay=0.01,
+ paramwise_cfg=dict(custom_keys={'absolute_pos_embed': dict(decay_mult=0.),
+ 'relative_position_bias_table': dict(decay_mult=0.),
+ 'norm': dict(decay_mult=0.)}))
+
+lr_config = dict(_delete_=True, policy='poly',
+ warmup='linear',
+ warmup_iters=1500,
+ warmup_ratio=1e-6,
+ power=1.0, min_lr=0.0, by_epoch=False)
+
+data=dict(samples_per_gpu=2)
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/util.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/util.py
new file mode 100644
index 0000000000000000000000000000000000000000..61d283ae20e995e382a4a15b6c4bcc3cba913f5f
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/util.py
@@ -0,0 +1,259 @@
+import os
+import random
+
+import cv2
+import numpy as np
+import torch
+from pathlib import Path
+import warnings
+from huggingface_hub import hf_hub_download
+
+annotator_ckpts_path = os.path.join(Path(__file__).parents[2], 'ckpts')
+USE_SYMLINKS = False
+
+try:
+ USE_SYMLINKS = eval(os.environ['AUX_USE_SYMLINKS'])
+except:
+ warnings.warn("USE_SYMLINKS not set successfully. Using default value: False to download models.")
+ pass
+
+# fix SSL: CERTIFICATE_VERIFY_FAILED issue with pytorch download https://github.com/pytorch/pytorch/issues/33288
+try:
+ from torch.hub import load_state_dict_from_url
+ test_url = "https://download.pytorch.org/models/mobilenet_v2-b0353104.pth"
+ load_state_dict_from_url(test_url, progress=False)
+except:
+ import ssl
+ ssl._create_default_https_context = ssl._create_unverified_context
+
+here = Path(__file__).parent.resolve()
+
+def HWC3(x):
+ assert x.dtype == np.uint8
+ if x.ndim == 2:
+ x = x[:, :, None]
+ assert x.ndim == 3
+ H, W, C = x.shape
+ assert C == 1 or C == 3 or C == 4
+ if C == 3:
+ return x
+ if C == 1:
+ return np.concatenate([x, x, x], axis=2)
+ if C == 4:
+ color = x[:, :, 0:3].astype(np.float32)
+ alpha = x[:, :, 3:4].astype(np.float32) / 255.0
+ y = color * alpha + 255.0 * (1.0 - alpha)
+ y = y.clip(0, 255).astype(np.uint8)
+ return y
+
+
+def make_noise_disk(H, W, C, F):
+ noise = np.random.uniform(low=0, high=1, size=((H // F) + 2, (W // F) + 2, C))
+ noise = cv2.resize(noise, (W + 2 * F, H + 2 * F), interpolation=cv2.INTER_CUBIC)
+ noise = noise[F: F + H, F: F + W]
+ noise -= np.min(noise)
+ noise /= np.max(noise)
+ if C == 1:
+ noise = noise[:, :, None]
+ return noise
+
+
+def nms(x, t, s):
+ x = cv2.GaussianBlur(x.astype(np.float32), (0, 0), s)
+
+ f1 = np.array([[0, 0, 0], [1, 1, 1], [0, 0, 0]], dtype=np.uint8)
+ f2 = np.array([[0, 1, 0], [0, 1, 0], [0, 1, 0]], dtype=np.uint8)
+ f3 = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype=np.uint8)
+ f4 = np.array([[0, 0, 1], [0, 1, 0], [1, 0, 0]], dtype=np.uint8)
+
+ y = np.zeros_like(x)
+
+ for f in [f1, f2, f3, f4]:
+ np.putmask(y, cv2.dilate(x, kernel=f) == x, x)
+
+ z = np.zeros_like(y, dtype=np.uint8)
+ z[y > t] = 255
+ return z
+
+def min_max_norm(x):
+ x -= np.min(x)
+ x /= np.maximum(np.max(x), 1e-5)
+ return x
+
+
+def safe_step(x, step=2):
+ y = x.astype(np.float32) * float(step + 1)
+ y = y.astype(np.int32).astype(np.float32) / float(step)
+ return y
+
+
+def img2mask(img, H, W, low=10, high=90):
+ assert img.ndim == 3 or img.ndim == 2
+ assert img.dtype == np.uint8
+
+ if img.ndim == 3:
+ y = img[:, :, random.randrange(0, img.shape[2])]
+ else:
+ y = img
+
+ y = cv2.resize(y, (W, H), interpolation=cv2.INTER_CUBIC)
+
+ if random.uniform(0, 1) < 0.5:
+ y = 255 - y
+
+ return y < np.percentile(y, random.randrange(low, high))
+
+def safer_memory(x):
+ # Fix many MAC/AMD problems
+ return np.ascontiguousarray(x.copy()).copy()
+
+UPSCALE_METHODS = ["INTER_NEAREST", "INTER_LINEAR", "INTER_AREA", "INTER_CUBIC", "INTER_LANCZOS4"]
+def get_upscale_method(method_str):
+ assert method_str in UPSCALE_METHODS, f"Method {method_str} not found in {UPSCALE_METHODS}"
+ return getattr(cv2, method_str)
+
+def pad64(x):
+ return int(np.ceil(float(x) / 64.0) * 64 - x)
+
+#https://github.com/Mikubill/sd-webui-controlnet/blob/main/scripts/processor.py#L17
+#Added upscale_method param
+def resize_image_with_pad(input_image, resolution, upscale_method = "", skip_hwc3=False):
+ if skip_hwc3:
+ img = input_image
+ else:
+ img = HWC3(input_image)
+ H_raw, W_raw, _ = img.shape
+ k = float(resolution) / float(min(H_raw, W_raw))
+ H_target = int(np.round(float(H_raw) * k))
+ W_target = int(np.round(float(W_raw) * k))
+ img = cv2.resize(img, (W_target, H_target), interpolation=get_upscale_method(upscale_method) if k > 1 else cv2.INTER_AREA)
+ H_pad, W_pad = pad64(H_target), pad64(W_target)
+ img_padded = np.pad(img, [[0, H_pad], [0, W_pad], [0, 0]], mode='edge')
+
+ def remove_pad(x):
+ return safer_memory(x[:H_target, :W_target, ...])
+
+ return safer_memory(img_padded), remove_pad
+
+def common_input_validate(input_image, output_type, **kwargs):
+ if "img" in kwargs:
+ warnings.warn("img is deprecated, please use `input_image=...` instead.", DeprecationWarning)
+ input_image = kwargs.pop("img")
+
+ if "return_pil" in kwargs:
+ warnings.warn("return_pil is deprecated. Use output_type instead.", DeprecationWarning)
+ output_type = "pil" if kwargs["return_pil"] else "np"
+
+ if type(output_type) is bool:
+ warnings.warn("Passing `True` or `False` to `output_type` is deprecated and will raise an error in future versions")
+ if output_type:
+ output_type = "pil"
+
+ if input_image is None:
+ raise ValueError("input_image must be defined.")
+
+ if not isinstance(input_image, np.ndarray):
+ input_image = np.array(input_image, dtype=np.uint8)
+ output_type = output_type or "pil"
+ else:
+ output_type = output_type or "np"
+
+ return (input_image, output_type)
+
+def torch_gc():
+ if torch.cuda.is_available():
+ torch.cuda.empty_cache()
+ torch.cuda.ipc_collect()
+
+
+def ade_palette():
+ """ADE20K palette that maps each class to RGB values."""
+ return [[120, 120, 120], [180, 120, 120], [6, 230, 230], [80, 50, 50],
+ [4, 200, 3], [120, 120, 80], [140, 140, 140], [204, 5, 255],
+ [230, 230, 230], [4, 250, 7], [224, 5, 255], [235, 255, 7],
+ [150, 5, 61], [120, 120, 70], [8, 255, 51], [255, 6, 82],
+ [143, 255, 140], [204, 255, 4], [255, 51, 7], [204, 70, 3],
+ [0, 102, 200], [61, 230, 250], [255, 6, 51], [11, 102, 255],
+ [255, 7, 71], [255, 9, 224], [9, 7, 230], [220, 220, 220],
+ [255, 9, 92], [112, 9, 255], [8, 255, 214], [7, 255, 224],
+ [255, 184, 6], [10, 255, 71], [255, 41, 10], [7, 255, 255],
+ [224, 255, 8], [102, 8, 255], [255, 61, 6], [255, 194, 7],
+ [255, 122, 8], [0, 255, 20], [255, 8, 41], [255, 5, 153],
+ [6, 51, 255], [235, 12, 255], [160, 150, 20], [0, 163, 255],
+ [140, 140, 140], [250, 10, 15], [20, 255, 0], [31, 255, 0],
+ [255, 31, 0], [255, 224, 0], [153, 255, 0], [0, 0, 255],
+ [255, 71, 0], [0, 235, 255], [0, 173, 255], [31, 0, 255],
+ [11, 200, 200], [255, 82, 0], [0, 255, 245], [0, 61, 255],
+ [0, 255, 112], [0, 255, 133], [255, 0, 0], [255, 163, 0],
+ [255, 102, 0], [194, 255, 0], [0, 143, 255], [51, 255, 0],
+ [0, 82, 255], [0, 255, 41], [0, 255, 173], [10, 0, 255],
+ [173, 255, 0], [0, 255, 153], [255, 92, 0], [255, 0, 255],
+ [255, 0, 245], [255, 0, 102], [255, 173, 0], [255, 0, 20],
+ [255, 184, 184], [0, 31, 255], [0, 255, 61], [0, 71, 255],
+ [255, 0, 204], [0, 255, 194], [0, 255, 82], [0, 10, 255],
+ [0, 112, 255], [51, 0, 255], [0, 194, 255], [0, 122, 255],
+ [0, 255, 163], [255, 153, 0], [0, 255, 10], [255, 112, 0],
+ [143, 255, 0], [82, 0, 255], [163, 255, 0], [255, 235, 0],
+ [8, 184, 170], [133, 0, 255], [0, 255, 92], [184, 0, 255],
+ [255, 0, 31], [0, 184, 255], [0, 214, 255], [255, 0, 112],
+ [92, 255, 0], [0, 224, 255], [112, 224, 255], [70, 184, 160],
+ [163, 0, 255], [153, 0, 255], [71, 255, 0], [255, 0, 163],
+ [255, 204, 0], [255, 0, 143], [0, 255, 235], [133, 255, 0],
+ [255, 0, 235], [245, 0, 255], [255, 0, 122], [255, 245, 0],
+ [10, 190, 212], [214, 255, 0], [0, 204, 255], [20, 0, 255],
+ [255, 255, 0], [0, 153, 255], [0, 41, 255], [0, 255, 204],
+ [41, 0, 255], [41, 255, 0], [173, 0, 255], [0, 245, 255],
+ [71, 0, 255], [122, 0, 255], [0, 255, 184], [0, 92, 255],
+ [184, 255, 0], [0, 133, 255], [255, 214, 0], [25, 194, 194],
+ [102, 255, 0], [92, 0, 255]]
+
+def custom_hf_download(pretrained_model_or_path, filename, cache_dir=annotator_ckpts_path, subfolder='', use_symlinks=USE_SYMLINKS):
+ local_dir = os.path.join(cache_dir, pretrained_model_or_path)
+ model_path = os.path.join(local_dir, *subfolder.split('/'), filename)
+
+ if not os.path.exists(model_path):
+ print(f"Failed to find {model_path}.\n Downloading from huggingface.co")
+ if use_symlinks:
+ cache_dir_d = os.getenv("HUGGINGFACE_HUB_CACHE")
+ if cache_dir_d is None:
+ import platform
+ if platform.system() == "Windows":
+ cache_dir_d = os.path.join(os.getenv("USERPROFILE"), ".cache", "huggingface", "hub")
+ else:
+ cache_dir_d = os.path.join(os.getenv("HOME"), ".cache", "huggingface", "hub")
+ try:
+ # test_link
+ if not os.path.exists(cache_dir_d):
+ os.makedirs(cache_dir_d)
+ open(os.path.join(cache_dir_d, f"linktest_{filename}.txt"), "w")
+ os.link(os.path.join(cache_dir_d, f"linktest_{filename}.txt"), os.path.join(cache_dir, f"linktest_{filename}.txt"))
+ os.remove(os.path.join(cache_dir, f"linktest_{filename}.txt"))
+ os.remove(os.path.join(cache_dir_d, f"linktest_{filename}.txt"))
+ print("Using symlinks to download models. \n",\
+ "Make sure you have enough space on your cache folder. \n",\
+ "And do not purge the cache folder after downloading.\n",\
+ "Otherwise, you will have to re-download the models every time you run the script.\n",\
+ "You can use USE_SYMLINKS: False in config.yaml to avoid this behavior.")
+ except:
+ print("Maybe not able to create symlink. Disable using symlinks.")
+ use_symlinks = False
+ cache_dir_d = os.path.join(cache_dir, pretrained_model_or_path, "cache")
+ else:
+ cache_dir_d = os.path.join(cache_dir, pretrained_model_or_path, "cache")
+
+ model_path = hf_hub_download(repo_id=pretrained_model_or_path,
+ cache_dir=cache_dir_d,
+ local_dir=local_dir,
+ subfolder=subfolder,
+ filename=filename,
+ local_dir_use_symlinks=use_symlinks,
+ resume_download=True,
+ etag_timeout=100
+ )
+ if not use_symlinks:
+ try:
+ import shutil
+ shutil.rmtree(cache_dir_d)
+ except Exception as e :
+ print(e)
+ return model_path
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/LICENSE b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..7a1e90d007836c327846ce8e5151013b115042ab
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Intelligent Systems Lab Org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d2442c2ec6618d9d2cfa9e478eade8f89c5a8922
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/__init__.py
@@ -0,0 +1,61 @@
+import os
+
+import cv2
+import numpy as np
+import torch
+from einops import rearrange
+from PIL import Image
+
+from controlnet_aux.util import HWC3, common_input_validate, resize_image_with_pad, annotator_ckpts_path, custom_hf_download
+from .zoedepth.models.zoedepth.zoedepth_v1 import ZoeDepth
+from .zoedepth.utils.config import get_config
+
+
+class ZoeDetector:
+ def __init__(self, model):
+ self.model = model
+
+ @classmethod
+ def from_pretrained(cls, pretrained_model_or_path, filename=None, cache_dir=annotator_ckpts_path):
+ filename = filename or "ZoeD_M12_N.pt"
+ model_path = custom_hf_download(pretrained_model_or_path, filename, cache_dir=cache_dir)
+
+ conf = get_config("zoedepth", "infer")
+ model = ZoeDepth.build_from_config(conf)
+ model.load_state_dict(torch.load(model_path, map_location=torch.device('cpu'))['model'])
+ model.eval()
+
+ return cls(model)
+
+ def to(self, device):
+ self.model.to(device)
+ return self
+
+ def __call__(self, input_image, detect_resolution=512, output_type=None, upscale_method="INTER_CUBIC", **kwargs):
+ device = next(iter(self.model.parameters())).device
+ input_image, output_type = common_input_validate(input_image, output_type, **kwargs)
+ input_image, remove_pad = resize_image_with_pad(input_image, detect_resolution, upscale_method)
+
+ image_depth = input_image
+ with torch.no_grad():
+ image_depth = torch.from_numpy(image_depth).float().to(device)
+ image_depth = image_depth / 255.0
+ image_depth = rearrange(image_depth, 'h w c -> 1 c h w')
+ depth = self.model.infer(image_depth)
+
+ depth = depth[0, 0].cpu().numpy()
+
+ vmin = np.percentile(depth, 2)
+ vmax = np.percentile(depth, 85)
+
+ depth -= vmin
+ depth /= vmax - vmin
+ depth = 1.0 - depth
+ depth_image = (depth * 255.0).clip(0, 255).astype(np.uint8)
+
+ detected_map = remove_pad(HWC3(depth_image))
+
+ if output_type == "pil":
+ detected_map = Image.fromarray(detected_map)
+
+ return detected_map
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..5f2668792389157609abb2a0846fb620e7d67eb9
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/__init__.py
@@ -0,0 +1,24 @@
+# MIT License
+
+# Copyright (c) 2022 Intelligent Systems Lab Org
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# File author: Shariq Farooq Bhat
+
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/base_models/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/base_models/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..5f2668792389157609abb2a0846fb620e7d67eb9
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/base_models/__init__.py
@@ -0,0 +1,24 @@
+# MIT License
+
+# Copyright (c) 2022 Intelligent Systems Lab Org
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# File author: Shariq Farooq Bhat
+
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/base_models/midas.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/base_models/midas.py
new file mode 100644
index 0000000000000000000000000000000000000000..e04f81f42e2179303be0e3ad3c354e70a993c349
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/base_models/midas.py
@@ -0,0 +1,383 @@
+# MIT License
+import os
+
+# Copyright (c) 2022 Intelligent Systems Lab Org
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# File author: Shariq Farooq Bhat
+
+import torch
+import torch.nn as nn
+import numpy as np
+from torchvision.transforms import Normalize
+import inspect
+from pathlib import Path
+
+
+def denormalize(x):
+ """Reverses the imagenet normalization applied to the input.
+
+ Args:
+ x (torch.Tensor - shape(N,3,H,W)): input tensor
+
+ Returns:
+ torch.Tensor - shape(N,3,H,W): Denormalized input
+ """
+ mean = torch.Tensor([0.485, 0.456, 0.406]).view(1, 3, 1, 1).to(x.device)
+ std = torch.Tensor([0.229, 0.224, 0.225]).view(1, 3, 1, 1).to(x.device)
+ return x * std + mean
+
+def get_activation(name, bank):
+ def hook(model, input, output):
+ bank[name] = output
+ return hook
+
+
+class Resize(object):
+ """Resize sample to given size (width, height).
+ """
+
+ def __init__(
+ self,
+ width,
+ height,
+ resize_target=True,
+ keep_aspect_ratio=False,
+ ensure_multiple_of=1,
+ resize_method="lower_bound",
+ ):
+ """Init.
+ Args:
+ width (int): desired output width
+ height (int): desired output height
+ resize_target (bool, optional):
+ True: Resize the full sample (image, mask, target).
+ False: Resize image only.
+ Defaults to True.
+ keep_aspect_ratio (bool, optional):
+ True: Keep the aspect ratio of the input sample.
+ Output sample might not have the given width and height, and
+ resize behaviour depends on the parameter 'resize_method'.
+ Defaults to False.
+ ensure_multiple_of (int, optional):
+ Output width and height is constrained to be multiple of this parameter.
+ Defaults to 1.
+ resize_method (str, optional):
+ "lower_bound": Output will be at least as large as the given size.
+ "upper_bound": Output will be at max as large as the given size. (Output size might be smaller than given size.)
+ "minimal": Scale as least as possible. (Output size might be smaller than given size.)
+ Defaults to "lower_bound".
+ """
+ # print("Params passed to Resize transform:")
+ # print("\twidth: ", width)
+ # print("\theight: ", height)
+ # print("\tresize_target: ", resize_target)
+ # print("\tkeep_aspect_ratio: ", keep_aspect_ratio)
+ # print("\tensure_multiple_of: ", ensure_multiple_of)
+ # print("\tresize_method: ", resize_method)
+
+ self.__width = width
+ self.__height = height
+
+ self.__keep_aspect_ratio = keep_aspect_ratio
+ self.__multiple_of = ensure_multiple_of
+ self.__resize_method = resize_method
+
+ def constrain_to_multiple_of(self, x, min_val=0, max_val=None):
+ y = (np.round(x / self.__multiple_of) * self.__multiple_of).astype(int)
+
+ if max_val is not None and y > max_val:
+ y = (np.floor(x / self.__multiple_of)
+ * self.__multiple_of).astype(int)
+
+ if y < min_val:
+ y = (np.ceil(x / self.__multiple_of)
+ * self.__multiple_of).astype(int)
+
+ return y
+
+ def get_size(self, width, height):
+ # determine new height and width
+ scale_height = self.__height / height
+ scale_width = self.__width / width
+
+ if self.__keep_aspect_ratio:
+ if self.__resize_method == "lower_bound":
+ # scale such that output size is lower bound
+ if scale_width > scale_height:
+ # fit width
+ scale_height = scale_width
+ else:
+ # fit height
+ scale_width = scale_height
+ elif self.__resize_method == "upper_bound":
+ # scale such that output size is upper bound
+ if scale_width < scale_height:
+ # fit width
+ scale_height = scale_width
+ else:
+ # fit height
+ scale_width = scale_height
+ elif self.__resize_method == "minimal":
+ # scale as least as possbile
+ if abs(1 - scale_width) < abs(1 - scale_height):
+ # fit width
+ scale_height = scale_width
+ else:
+ # fit height
+ scale_width = scale_height
+ else:
+ raise ValueError(
+ f"resize_method {self.__resize_method} not implemented"
+ )
+
+ if self.__resize_method == "lower_bound":
+ new_height = self.constrain_to_multiple_of(
+ scale_height * height, min_val=self.__height
+ )
+ new_width = self.constrain_to_multiple_of(
+ scale_width * width, min_val=self.__width
+ )
+ elif self.__resize_method == "upper_bound":
+ new_height = self.constrain_to_multiple_of(
+ scale_height * height, max_val=self.__height
+ )
+ new_width = self.constrain_to_multiple_of(
+ scale_width * width, max_val=self.__width
+ )
+ elif self.__resize_method == "minimal":
+ new_height = self.constrain_to_multiple_of(scale_height * height)
+ new_width = self.constrain_to_multiple_of(scale_width * width)
+ else:
+ raise ValueError(
+ f"resize_method {self.__resize_method} not implemented")
+
+ return (new_width, new_height)
+
+ def __call__(self, x):
+ width, height = self.get_size(*x.shape[-2:][::-1])
+ return nn.functional.interpolate(x, (int(height), int(width)), mode='bilinear', align_corners=True)
+
+class PrepForMidas(object):
+ def __init__(self, resize_mode="minimal", keep_aspect_ratio=True, img_size=384, do_resize=True):
+ if isinstance(img_size, int):
+ img_size = (img_size, img_size)
+ net_h, net_w = img_size
+ self.normalization = Normalize(
+ mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
+ self.resizer = Resize(net_w, net_h, keep_aspect_ratio=keep_aspect_ratio, ensure_multiple_of=32, resize_method=resize_mode) \
+ if do_resize else nn.Identity()
+
+ def __call__(self, x):
+ return self.normalization(self.resizer(x))
+
+
+class MidasCore(nn.Module):
+ def __init__(self, midas, trainable=False, fetch_features=True, layer_names=('out_conv', 'l4_rn', 'r4', 'r3', 'r2', 'r1'), freeze_bn=False, keep_aspect_ratio=True,
+ img_size=384, **kwargs):
+ """Midas Base model used for multi-scale feature extraction.
+
+ Args:
+ midas (torch.nn.Module): Midas model.
+ trainable (bool, optional): Train midas model. Defaults to False.
+ fetch_features (bool, optional): Extract multi-scale features. Defaults to True.
+ layer_names (tuple, optional): Layers used for feature extraction. Order = (head output features, last layer features, ...decoder features). Defaults to ('out_conv', 'l4_rn', 'r4', 'r3', 'r2', 'r1').
+ freeze_bn (bool, optional): Freeze BatchNorm. Generally results in better finetuning performance. Defaults to False.
+ keep_aspect_ratio (bool, optional): Keep the aspect ratio of input images while resizing. Defaults to True.
+ img_size (int, tuple, optional): Input resolution. Defaults to 384.
+ """
+ super().__init__()
+ self.core = midas
+ self.output_channels = None
+ self.core_out = {}
+ self.trainable = trainable
+ self.fetch_features = fetch_features
+ # midas.scratch.output_conv = nn.Identity()
+ self.handles = []
+ # self.layer_names = ['out_conv','l4_rn', 'r4', 'r3', 'r2', 'r1']
+ self.layer_names = layer_names
+
+ self.set_trainable(trainable)
+ self.set_fetch_features(fetch_features)
+
+ self.prep = PrepForMidas(keep_aspect_ratio=keep_aspect_ratio,
+ img_size=img_size, do_resize=kwargs.get('do_resize', True))
+
+ if freeze_bn:
+ self.freeze_bn()
+
+ def set_trainable(self, trainable):
+ self.trainable = trainable
+ if trainable:
+ self.unfreeze()
+ else:
+ self.freeze()
+ return self
+
+ def set_fetch_features(self, fetch_features):
+ self.fetch_features = fetch_features
+ if fetch_features:
+ if len(self.handles) == 0:
+ self.attach_hooks(self.core)
+ else:
+ self.remove_hooks()
+ return self
+
+ def freeze(self):
+ for p in self.parameters():
+ p.requires_grad = False
+ self.trainable = False
+ return self
+
+ def unfreeze(self):
+ for p in self.parameters():
+ p.requires_grad = True
+ self.trainable = True
+ return self
+
+ def freeze_bn(self):
+ for m in self.modules():
+ if isinstance(m, nn.BatchNorm2d):
+ m.eval()
+ return self
+
+ def forward(self, x, denorm=False, return_rel_depth=False):
+ with torch.no_grad():
+ if denorm:
+ x = denormalize(x)
+ x = self.prep(x)
+ # print("Shape after prep: ", x.shape)
+
+ with torch.set_grad_enabled(self.trainable):
+
+ # print("Input size to Midascore", x.shape)
+ rel_depth = self.core(x)
+ # print("Output from custom_midas_repo.midas shape", rel_depth.shape)
+ if not self.fetch_features:
+ return rel_depth
+ out = [self.core_out[k] for k in self.layer_names]
+
+ if return_rel_depth:
+ return rel_depth, out
+ return out
+
+ def get_rel_pos_params(self):
+ for name, p in self.core.pretrained.named_parameters():
+ if "relative_position" in name:
+ yield p
+
+ def get_enc_params_except_rel_pos(self):
+ for name, p in self.core.pretrained.named_parameters():
+ if "relative_position" not in name:
+ yield p
+
+ def freeze_encoder(self, freeze_rel_pos=False):
+ if freeze_rel_pos:
+ for p in self.core.pretrained.parameters():
+ p.requires_grad = False
+ else:
+ for p in self.get_enc_params_except_rel_pos():
+ p.requires_grad = False
+ return self
+
+ def attach_hooks(self, midas):
+ if len(self.handles) > 0:
+ self.remove_hooks()
+ if "out_conv" in self.layer_names:
+ self.handles.append(list(midas.scratch.output_conv.children())[
+ 3].register_forward_hook(get_activation("out_conv", self.core_out)))
+ if "r4" in self.layer_names:
+ self.handles.append(midas.scratch.refinenet4.register_forward_hook(
+ get_activation("r4", self.core_out)))
+ if "r3" in self.layer_names:
+ self.handles.append(midas.scratch.refinenet3.register_forward_hook(
+ get_activation("r3", self.core_out)))
+ if "r2" in self.layer_names:
+ self.handles.append(midas.scratch.refinenet2.register_forward_hook(
+ get_activation("r2", self.core_out)))
+ if "r1" in self.layer_names:
+ self.handles.append(midas.scratch.refinenet1.register_forward_hook(
+ get_activation("r1", self.core_out)))
+ if "l4_rn" in self.layer_names:
+ self.handles.append(midas.scratch.layer4_rn.register_forward_hook(
+ get_activation("l4_rn", self.core_out)))
+
+ return self
+
+ def remove_hooks(self):
+ for h in self.handles:
+ h.remove()
+ return self
+
+ def __del__(self):
+ self.remove_hooks()
+
+ def set_output_channels(self, model_type):
+ self.output_channels = MIDAS_SETTINGS[model_type]
+
+ @staticmethod
+ def build(midas_model_type="DPT_BEiT_L_384", train_midas=False, use_pretrained_midas=True, fetch_features=False, freeze_bn=True, force_keep_ar=False, force_reload=False, **kwargs):
+ if midas_model_type not in MIDAS_SETTINGS:
+ raise ValueError(
+ f"Invalid model type: {midas_model_type}. Must be one of {list(MIDAS_SETTINGS.keys())}")
+ if "img_size" in kwargs:
+ kwargs = MidasCore.parse_img_size(kwargs)
+ img_size = kwargs.pop("img_size", [384, 384])
+ # print("img_size", img_size)
+ import custom_midas_repo
+ midas_path = Path(inspect.getfile(custom_midas_repo)).parent.resolve()
+ del custom_midas_repo
+ midas = torch.hub.load(midas_path, midas_model_type,
+ pretrained=use_pretrained_midas, force_reload=force_reload, source='local')
+ kwargs.update({'keep_aspect_ratio': force_keep_ar})
+ midas_core = MidasCore(midas, trainable=train_midas, fetch_features=fetch_features,
+ freeze_bn=freeze_bn, img_size=img_size, **kwargs)
+ midas_core.set_output_channels(midas_model_type)
+ return midas_core
+
+ @staticmethod
+ def build_from_config(config):
+ return MidasCore.build(**config)
+
+ @staticmethod
+ def parse_img_size(config):
+ assert 'img_size' in config
+ if isinstance(config['img_size'], str):
+ assert "," in config['img_size'], "img_size should be a string with comma separated img_size=H,W"
+ config['img_size'] = list(map(int, config['img_size'].split(",")))
+ assert len(
+ config['img_size']) == 2, "img_size should be a string with comma separated img_size=H,W"
+ elif isinstance(config['img_size'], int):
+ config['img_size'] = [config['img_size'], config['img_size']]
+ else:
+ assert isinstance(config['img_size'], list) and len(
+ config['img_size']) == 2, "img_size should be a list of H,W"
+ return config
+
+
+nchannels2models = {
+ tuple([256]*5): ["DPT_BEiT_L_384", "DPT_BEiT_L_512", "DPT_BEiT_B_384", "DPT_SwinV2_L_384", "DPT_SwinV2_B_384", "DPT_SwinV2_T_256", "DPT_Large", "DPT_Hybrid"],
+ (512, 256, 128, 64, 64): ["MiDaS_small"]
+}
+
+# Model name to number of output channels
+MIDAS_SETTINGS = {m: k for k, v in nchannels2models.items()
+ for m in v
+ }
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/builder.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/builder.py
new file mode 100644
index 0000000000000000000000000000000000000000..0818311b642561712a03a66655c638ce09a04cca
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/builder.py
@@ -0,0 +1,51 @@
+# MIT License
+
+# Copyright (c) 2022 Intelligent Systems Lab Org
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# File author: Shariq Farooq Bhat
+
+from importlib import import_module
+from .depth_model import DepthModel
+
+def build_model(config) -> DepthModel:
+ """Builds a model from a config. The model is specified by the model name and version in the config. The model is then constructed using the build_from_config function of the model interface.
+ This function should be used to construct models for training and evaluation.
+
+ Args:
+ config (dict): Config dict. Config is constructed in utils/config.py. Each model has its own config file(s) saved in its root model folder.
+
+ Returns:
+ torch.nn.Module: Model corresponding to name and version as specified in config
+ """
+ module_name = f"zoedepth.models.{config.model}"
+ try:
+ module = import_module(module_name)
+ except ModuleNotFoundError as e:
+ # print the original error message
+ print(e)
+ raise ValueError(
+ f"Model {config.model} not found. Refer above error for details.") from e
+ try:
+ get_version = getattr(module, "get_version")
+ except AttributeError as e:
+ raise ValueError(
+ f"Model {config.model} has no get_version function.") from e
+ return get_version(config.version_name).build_from_config(config)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/depth_model.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/depth_model.py
new file mode 100644
index 0000000000000000000000000000000000000000..fc421c108ea3928c9add62b4c190500d9bd4eda1
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/depth_model.py
@@ -0,0 +1,152 @@
+# MIT License
+
+# Copyright (c) 2022 Intelligent Systems Lab Org
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# File author: Shariq Farooq Bhat
+
+import numpy as np
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+from torchvision import transforms
+import PIL.Image
+from PIL import Image
+from typing import Union
+
+
+class DepthModel(nn.Module):
+ def __init__(self):
+ super().__init__()
+ self.device = 'cpu'
+
+ def to(self, device) -> nn.Module:
+ self.device = device
+ return super().to(device)
+
+ def forward(self, x, *args, **kwargs):
+ raise NotImplementedError
+
+ def _infer(self, x: torch.Tensor):
+ """
+ Inference interface for the model
+ Args:
+ x (torch.Tensor): input tensor of shape (b, c, h, w)
+ Returns:
+ torch.Tensor: output tensor of shape (b, 1, h, w)
+ """
+ return self(x)['metric_depth']
+
+ def _infer_with_pad_aug(self, x: torch.Tensor, pad_input: bool=True, fh: float=3, fw: float=3, upsampling_mode: str='bicubic', padding_mode="reflect", **kwargs) -> torch.Tensor:
+ """
+ Inference interface for the model with padding augmentation
+ Padding augmentation fixes the boundary artifacts in the output depth map.
+ Boundary artifacts are sometimes caused by the fact that the model is trained on NYU raw dataset which has a black or white border around the image.
+ This augmentation pads the input image and crops the prediction back to the original size / view.
+
+ Note: This augmentation is not required for the models trained with 'avoid_boundary'=True.
+ Args:
+ x (torch.Tensor): input tensor of shape (b, c, h, w)
+ pad_input (bool, optional): whether to pad the input or not. Defaults to True.
+ fh (float, optional): height padding factor. The padding is calculated as sqrt(h/2) * fh. Defaults to 3.
+ fw (float, optional): width padding factor. The padding is calculated as sqrt(w/2) * fw. Defaults to 3.
+ upsampling_mode (str, optional): upsampling mode. Defaults to 'bicubic'.
+ padding_mode (str, optional): padding mode. Defaults to "reflect".
+ Returns:
+ torch.Tensor: output tensor of shape (b, 1, h, w)
+ """
+ # assert x is nchw and c = 3
+ assert x.dim() == 4, "x must be 4 dimensional, got {}".format(x.dim())
+ assert x.shape[1] == 3, "x must have 3 channels, got {}".format(x.shape[1])
+
+ if pad_input:
+ assert fh > 0 or fw > 0, "atlease one of fh and fw must be greater than 0"
+ pad_h = int(np.sqrt(x.shape[2]/2) * fh)
+ pad_w = int(np.sqrt(x.shape[3]/2) * fw)
+ padding = [pad_w, pad_w]
+ if pad_h > 0:
+ padding += [pad_h, pad_h]
+
+ x = F.pad(x, padding, mode=padding_mode, **kwargs)
+ out = self._infer(x)
+ if out.shape[-2:] != x.shape[-2:]:
+ out = F.interpolate(out, size=(x.shape[2], x.shape[3]), mode=upsampling_mode, align_corners=False)
+ if pad_input:
+ # crop to the original size, handling the case where pad_h and pad_w is 0
+ if pad_h > 0:
+ out = out[:, :, pad_h:-pad_h,:]
+ if pad_w > 0:
+ out = out[:, :, :, pad_w:-pad_w]
+ return out
+
+ def infer_with_flip_aug(self, x, pad_input: bool=True, **kwargs) -> torch.Tensor:
+ """
+ Inference interface for the model with horizontal flip augmentation
+ Horizontal flip augmentation improves the accuracy of the model by averaging the output of the model with and without horizontal flip.
+ Args:
+ x (torch.Tensor): input tensor of shape (b, c, h, w)
+ pad_input (bool, optional): whether to use padding augmentation. Defaults to True.
+ Returns:
+ torch.Tensor: output tensor of shape (b, 1, h, w)
+ """
+ # infer with horizontal flip and average
+ out = self._infer_with_pad_aug(x, pad_input=pad_input, **kwargs)
+ out_flip = self._infer_with_pad_aug(torch.flip(x, dims=[3]), pad_input=pad_input, **kwargs)
+ out = (out + torch.flip(out_flip, dims=[3])) / 2
+ return out
+
+ def infer(self, x, pad_input: bool=True, with_flip_aug: bool=True, **kwargs) -> torch.Tensor:
+ """
+ Inference interface for the model
+ Args:
+ x (torch.Tensor): input tensor of shape (b, c, h, w)
+ pad_input (bool, optional): whether to use padding augmentation. Defaults to True.
+ with_flip_aug (bool, optional): whether to use horizontal flip augmentation. Defaults to True.
+ Returns:
+ torch.Tensor: output tensor of shape (b, 1, h, w)
+ """
+ if with_flip_aug:
+ return self.infer_with_flip_aug(x, pad_input=pad_input, **kwargs)
+ else:
+ return self._infer_with_pad_aug(x, pad_input=pad_input, **kwargs)
+
+ @torch.no_grad()
+ def infer_pil(self, pil_img, pad_input: bool=True, with_flip_aug: bool=True, output_type: str="numpy", **kwargs) -> Union[np.ndarray, PIL.Image.Image, torch.Tensor]:
+ """
+ Inference interface for the model for PIL image
+ Args:
+ pil_img (PIL.Image.Image): input PIL image
+ pad_input (bool, optional): whether to use padding augmentation. Defaults to True.
+ with_flip_aug (bool, optional): whether to use horizontal flip augmentation. Defaults to True.
+ output_type (str, optional): output type. Supported values are 'numpy', 'pil' and 'tensor'. Defaults to "numpy".
+ """
+ x = transforms.ToTensor()(pil_img).unsqueeze(0).to(self.device)
+ out_tensor = self.infer(x, pad_input=pad_input, with_flip_aug=with_flip_aug, **kwargs)
+ if output_type == "numpy":
+ return out_tensor.squeeze().cpu().numpy()
+ elif output_type == "pil":
+ # uint16 is required for depth pil image
+ out_16bit_numpy = (out_tensor.squeeze().cpu().numpy()*256).astype(np.uint16)
+ return Image.fromarray(out_16bit_numpy)
+ elif output_type == "tensor":
+ return out_tensor.squeeze().cpu()
+ else:
+ raise ValueError(f"output_type {output_type} not supported. Supported values are 'numpy', 'pil' and 'tensor'")
+
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/layers/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/layers/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..c344f725c8a10dcaf29d4c308eb49d86ac51ff88
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/layers/__init__.py
@@ -0,0 +1,23 @@
+# MIT License
+
+# Copyright (c) 2022 Intelligent Systems Lab Org
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# File author: Shariq Farooq Bhat
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/layers/attractor.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/layers/attractor.py
new file mode 100644
index 0000000000000000000000000000000000000000..2a8efe645adea1d88a12e2ac5cc6bb2a251eef9d
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/layers/attractor.py
@@ -0,0 +1,208 @@
+# MIT License
+
+# Copyright (c) 2022 Intelligent Systems Lab Org
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# File author: Shariq Farooq Bhat
+
+import torch
+import torch.nn as nn
+
+
+@torch.jit.script
+def exp_attractor(dx, alpha: float = 300, gamma: int = 2):
+ """Exponential attractor: dc = exp(-alpha*|dx|^gamma) * dx , where dx = a - c, a = attractor point, c = bin center, dc = shift in bin centermmary for exp_attractor
+
+ Args:
+ dx (torch.Tensor): The difference tensor dx = Ai - Cj, where Ai is the attractor point and Cj is the bin center.
+ alpha (float, optional): Proportional Attractor strength. Determines the absolute strength. Lower alpha = greater attraction. Defaults to 300.
+ gamma (int, optional): Exponential Attractor strength. Determines the "region of influence" and indirectly number of bin centers affected. Lower gamma = farther reach. Defaults to 2.
+
+ Returns:
+ torch.Tensor : Delta shifts - dc; New bin centers = Old bin centers + dc
+ """
+ return torch.exp(-alpha*(torch.abs(dx)**gamma)) * (dx)
+
+
+@torch.jit.script
+def inv_attractor(dx, alpha: float = 300, gamma: int = 2):
+ """Inverse attractor: dc = dx / (1 + alpha*dx^gamma), where dx = a - c, a = attractor point, c = bin center, dc = shift in bin center
+ This is the default one according to the accompanying paper.
+
+ Args:
+ dx (torch.Tensor): The difference tensor dx = Ai - Cj, where Ai is the attractor point and Cj is the bin center.
+ alpha (float, optional): Proportional Attractor strength. Determines the absolute strength. Lower alpha = greater attraction. Defaults to 300.
+ gamma (int, optional): Exponential Attractor strength. Determines the "region of influence" and indirectly number of bin centers affected. Lower gamma = farther reach. Defaults to 2.
+
+ Returns:
+ torch.Tensor: Delta shifts - dc; New bin centers = Old bin centers + dc
+ """
+ return dx.div(1+alpha*dx.pow(gamma))
+
+
+class AttractorLayer(nn.Module):
+ def __init__(self, in_features, n_bins, n_attractors=16, mlp_dim=128, min_depth=1e-3, max_depth=10,
+ alpha=300, gamma=2, kind='sum', attractor_type='exp', memory_efficient=False):
+ """
+ Attractor layer for bin centers. Bin centers are bounded on the interval (min_depth, max_depth)
+ """
+ super().__init__()
+
+ self.n_attractors = n_attractors
+ self.n_bins = n_bins
+ self.min_depth = min_depth
+ self.max_depth = max_depth
+ self.alpha = alpha
+ self.gamma = gamma
+ self.kind = kind
+ self.attractor_type = attractor_type
+ self.memory_efficient = memory_efficient
+
+ self._net = nn.Sequential(
+ nn.Conv2d(in_features, mlp_dim, 1, 1, 0),
+ nn.ReLU(inplace=True),
+ nn.Conv2d(mlp_dim, n_attractors*2, 1, 1, 0), # x2 for linear norm
+ nn.ReLU(inplace=True)
+ )
+
+ def forward(self, x, b_prev, prev_b_embedding=None, interpolate=True, is_for_query=False):
+ """
+ Args:
+ x (torch.Tensor) : feature block; shape - n, c, h, w
+ b_prev (torch.Tensor) : previous bin centers normed; shape - n, prev_nbins, h, w
+
+ Returns:
+ tuple(torch.Tensor,torch.Tensor) : new bin centers normed and scaled; shape - n, nbins, h, w
+ """
+ if prev_b_embedding is not None:
+ if interpolate:
+ prev_b_embedding = nn.functional.interpolate(
+ prev_b_embedding, x.shape[-2:], mode='bilinear', align_corners=True)
+ x = x + prev_b_embedding
+
+ A = self._net(x)
+ eps = 1e-3
+ A = A + eps
+ n, c, h, w = A.shape
+ A = A.view(n, self.n_attractors, 2, h, w)
+ A_normed = A / A.sum(dim=2, keepdim=True) # n, a, 2, h, w
+ A_normed = A[:, :, 0, ...] # n, na, h, w
+
+ b_prev = nn.functional.interpolate(
+ b_prev, (h, w), mode='bilinear', align_corners=True)
+ b_centers = b_prev
+
+ if self.attractor_type == 'exp':
+ dist = exp_attractor
+ else:
+ dist = inv_attractor
+
+ if not self.memory_efficient:
+ func = {'mean': torch.mean, 'sum': torch.sum}[self.kind]
+ # .shape N, nbins, h, w
+ delta_c = func(dist(A_normed.unsqueeze(
+ 2) - b_centers.unsqueeze(1)), dim=1)
+ else:
+ delta_c = torch.zeros_like(b_centers, device=b_centers.device)
+ for i in range(self.n_attractors):
+ # .shape N, nbins, h, w
+ delta_c += dist(A_normed[:, i, ...].unsqueeze(1) - b_centers)
+
+ if self.kind == 'mean':
+ delta_c = delta_c / self.n_attractors
+
+ b_new_centers = b_centers + delta_c
+ B_centers = (self.max_depth - self.min_depth) * \
+ b_new_centers + self.min_depth
+ B_centers, _ = torch.sort(B_centers, dim=1)
+ B_centers = torch.clip(B_centers, self.min_depth, self.max_depth)
+ return b_new_centers, B_centers
+
+
+class AttractorLayerUnnormed(nn.Module):
+ def __init__(self, in_features, n_bins, n_attractors=16, mlp_dim=128, min_depth=1e-3, max_depth=10,
+ alpha=300, gamma=2, kind='sum', attractor_type='exp', memory_efficient=False):
+ """
+ Attractor layer for bin centers. Bin centers are unbounded
+ """
+ super().__init__()
+
+ self.n_attractors = n_attractors
+ self.n_bins = n_bins
+ self.min_depth = min_depth
+ self.max_depth = max_depth
+ self.alpha = alpha
+ self.gamma = gamma
+ self.kind = kind
+ self.attractor_type = attractor_type
+ self.memory_efficient = memory_efficient
+
+ self._net = nn.Sequential(
+ nn.Conv2d(in_features, mlp_dim, 1, 1, 0),
+ nn.ReLU(inplace=True),
+ nn.Conv2d(mlp_dim, n_attractors, 1, 1, 0),
+ nn.Softplus()
+ )
+
+ def forward(self, x, b_prev, prev_b_embedding=None, interpolate=True, is_for_query=False):
+ """
+ Args:
+ x (torch.Tensor) : feature block; shape - n, c, h, w
+ b_prev (torch.Tensor) : previous bin centers normed; shape - n, prev_nbins, h, w
+
+ Returns:
+ tuple(torch.Tensor,torch.Tensor) : new bin centers unbounded; shape - n, nbins, h, w. Two outputs just to keep the API consistent with the normed version
+ """
+ if prev_b_embedding is not None:
+ if interpolate:
+ prev_b_embedding = nn.functional.interpolate(
+ prev_b_embedding, x.shape[-2:], mode='bilinear', align_corners=True)
+ x = x + prev_b_embedding
+
+ A = self._net(x)
+ n, c, h, w = A.shape
+
+ b_prev = nn.functional.interpolate(
+ b_prev, (h, w), mode='bilinear', align_corners=True)
+ b_centers = b_prev
+
+ if self.attractor_type == 'exp':
+ dist = exp_attractor
+ else:
+ dist = inv_attractor
+
+ if not self.memory_efficient:
+ func = {'mean': torch.mean, 'sum': torch.sum}[self.kind]
+ # .shape N, nbins, h, w
+ delta_c = func(
+ dist(A.unsqueeze(2) - b_centers.unsqueeze(1)), dim=1)
+ else:
+ delta_c = torch.zeros_like(b_centers, device=b_centers.device)
+ for i in range(self.n_attractors):
+ delta_c += dist(A[:, i, ...].unsqueeze(1) -
+ b_centers) # .shape N, nbins, h, w
+
+ if self.kind == 'mean':
+ delta_c = delta_c / self.n_attractors
+
+ b_new_centers = b_centers + delta_c
+ B_centers = b_new_centers
+
+ return b_new_centers, B_centers
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/layers/dist_layers.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/layers/dist_layers.py
new file mode 100644
index 0000000000000000000000000000000000000000..3208405dfb78fdfc28d5765e5a6d5dbe31967a23
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/layers/dist_layers.py
@@ -0,0 +1,121 @@
+# MIT License
+
+# Copyright (c) 2022 Intelligent Systems Lab Org
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# File author: Shariq Farooq Bhat
+
+import torch
+import torch.nn as nn
+
+
+def log_binom(n, k, eps=1e-7):
+ """ log(nCk) using stirling approximation """
+ n = n + eps
+ k = k + eps
+ return n * torch.log(n) - k * torch.log(k) - (n-k) * torch.log(n-k+eps)
+
+
+class LogBinomial(nn.Module):
+ def __init__(self, n_classes=256, act=torch.softmax):
+ """Compute log binomial distribution for n_classes
+
+ Args:
+ n_classes (int, optional): number of output classes. Defaults to 256.
+ """
+ super().__init__()
+ self.K = n_classes
+ self.act = act
+ self.register_buffer('k_idx', torch.arange(
+ 0, n_classes).view(1, -1, 1, 1))
+ self.register_buffer('K_minus_1', torch.Tensor(
+ [self.K-1]).view(1, -1, 1, 1))
+
+ def forward(self, x, t=1., eps=1e-4):
+ """Compute log binomial distribution for x
+
+ Args:
+ x (torch.Tensor - NCHW): probabilities
+ t (float, torch.Tensor - NCHW, optional): Temperature of distribution. Defaults to 1..
+ eps (float, optional): Small number for numerical stability. Defaults to 1e-4.
+
+ Returns:
+ torch.Tensor -NCHW: log binomial distribution logbinomial(p;t)
+ """
+ if x.ndim == 3:
+ x = x.unsqueeze(1) # make it nchw
+
+ one_minus_x = torch.clamp(1 - x, eps, 1)
+ x = torch.clamp(x, eps, 1)
+ y = log_binom(self.K_minus_1, self.k_idx) + self.k_idx * \
+ torch.log(x) + (self.K - 1 - self.k_idx) * torch.log(one_minus_x)
+ return self.act(y/t, dim=1)
+
+
+class ConditionalLogBinomial(nn.Module):
+ def __init__(self, in_features, condition_dim, n_classes=256, bottleneck_factor=2, p_eps=1e-4, max_temp=50, min_temp=1e-7, act=torch.softmax):
+ """Conditional Log Binomial distribution
+
+ Args:
+ in_features (int): number of input channels in main feature
+ condition_dim (int): number of input channels in condition feature
+ n_classes (int, optional): Number of classes. Defaults to 256.
+ bottleneck_factor (int, optional): Hidden dim factor. Defaults to 2.
+ p_eps (float, optional): small eps value. Defaults to 1e-4.
+ max_temp (float, optional): Maximum temperature of output distribution. Defaults to 50.
+ min_temp (float, optional): Minimum temperature of output distribution. Defaults to 1e-7.
+ """
+ super().__init__()
+ self.p_eps = p_eps
+ self.max_temp = max_temp
+ self.min_temp = min_temp
+ self.log_binomial_transform = LogBinomial(n_classes, act=act)
+ bottleneck = (in_features + condition_dim) // bottleneck_factor
+ self.mlp = nn.Sequential(
+ nn.Conv2d(in_features + condition_dim, bottleneck,
+ kernel_size=1, stride=1, padding=0),
+ nn.GELU(),
+ # 2 for p linear norm, 2 for t linear norm
+ nn.Conv2d(bottleneck, 2+2, kernel_size=1, stride=1, padding=0),
+ nn.Softplus()
+ )
+
+ def forward(self, x, cond):
+ """Forward pass
+
+ Args:
+ x (torch.Tensor - NCHW): Main feature
+ cond (torch.Tensor - NCHW): condition feature
+
+ Returns:
+ torch.Tensor: Output log binomial distribution
+ """
+ pt = self.mlp(torch.concat((x, cond), dim=1))
+ p, t = pt[:, :2, ...], pt[:, 2:, ...]
+
+ p = p + self.p_eps
+ p = p[:, 0, ...] / (p[:, 0, ...] + p[:, 1, ...])
+
+ t = t + self.p_eps
+ t = t[:, 0, ...] / (t[:, 0, ...] + t[:, 1, ...])
+ t = t.unsqueeze(1)
+ t = (self.max_temp - self.min_temp) * t + self.min_temp
+
+ return self.log_binomial_transform(p, t)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/layers/localbins_layers.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/layers/localbins_layers.py
new file mode 100644
index 0000000000000000000000000000000000000000..f94481605c3e6958ce50e73b2eb31d9f0c07dc67
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/layers/localbins_layers.py
@@ -0,0 +1,169 @@
+# MIT License
+
+# Copyright (c) 2022 Intelligent Systems Lab Org
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# File author: Shariq Farooq Bhat
+
+import torch
+import torch.nn as nn
+
+
+class SeedBinRegressor(nn.Module):
+ def __init__(self, in_features, n_bins=16, mlp_dim=256, min_depth=1e-3, max_depth=10):
+ """Bin center regressor network. Bin centers are bounded on (min_depth, max_depth) interval.
+
+ Args:
+ in_features (int): input channels
+ n_bins (int, optional): Number of bin centers. Defaults to 16.
+ mlp_dim (int, optional): Hidden dimension. Defaults to 256.
+ min_depth (float, optional): Min depth value. Defaults to 1e-3.
+ max_depth (float, optional): Max depth value. Defaults to 10.
+ """
+ super().__init__()
+ self.version = "1_1"
+ self.min_depth = min_depth
+ self.max_depth = max_depth
+
+ self._net = nn.Sequential(
+ nn.Conv2d(in_features, mlp_dim, 1, 1, 0),
+ nn.ReLU(inplace=True),
+ nn.Conv2d(mlp_dim, n_bins, 1, 1, 0),
+ nn.ReLU(inplace=True)
+ )
+
+ def forward(self, x):
+ """
+ Returns tensor of bin_width vectors (centers). One vector b for every pixel
+ """
+ B = self._net(x)
+ eps = 1e-3
+ B = B + eps
+ B_widths_normed = B / B.sum(dim=1, keepdim=True)
+ B_widths = (self.max_depth - self.min_depth) * \
+ B_widths_normed # .shape NCHW
+ # pad has the form (left, right, top, bottom, front, back)
+ B_widths = nn.functional.pad(
+ B_widths, (0, 0, 0, 0, 1, 0), mode='constant', value=self.min_depth)
+ B_edges = torch.cumsum(B_widths, dim=1) # .shape NCHW
+
+ B_centers = 0.5 * (B_edges[:, :-1, ...] + B_edges[:, 1:, ...])
+ return B_widths_normed, B_centers
+
+
+class SeedBinRegressorUnnormed(nn.Module):
+ def __init__(self, in_features, n_bins=16, mlp_dim=256, min_depth=1e-3, max_depth=10):
+ """Bin center regressor network. Bin centers are unbounded
+
+ Args:
+ in_features (int): input channels
+ n_bins (int, optional): Number of bin centers. Defaults to 16.
+ mlp_dim (int, optional): Hidden dimension. Defaults to 256.
+ min_depth (float, optional): Not used. (for compatibility with SeedBinRegressor)
+ max_depth (float, optional): Not used. (for compatibility with SeedBinRegressor)
+ """
+ super().__init__()
+ self.version = "1_1"
+ self._net = nn.Sequential(
+ nn.Conv2d(in_features, mlp_dim, 1, 1, 0),
+ nn.ReLU(inplace=True),
+ nn.Conv2d(mlp_dim, n_bins, 1, 1, 0),
+ nn.Softplus()
+ )
+
+ def forward(self, x):
+ """
+ Returns tensor of bin_width vectors (centers). One vector b for every pixel
+ """
+ B_centers = self._net(x)
+ return B_centers, B_centers
+
+
+class Projector(nn.Module):
+ def __init__(self, in_features, out_features, mlp_dim=128):
+ """Projector MLP
+
+ Args:
+ in_features (int): input channels
+ out_features (int): output channels
+ mlp_dim (int, optional): hidden dimension. Defaults to 128.
+ """
+ super().__init__()
+
+ self._net = nn.Sequential(
+ nn.Conv2d(in_features, mlp_dim, 1, 1, 0),
+ nn.ReLU(inplace=True),
+ nn.Conv2d(mlp_dim, out_features, 1, 1, 0),
+ )
+
+ def forward(self, x):
+ return self._net(x)
+
+
+
+class LinearSplitter(nn.Module):
+ def __init__(self, in_features, prev_nbins, split_factor=2, mlp_dim=128, min_depth=1e-3, max_depth=10):
+ super().__init__()
+
+ self.prev_nbins = prev_nbins
+ self.split_factor = split_factor
+ self.min_depth = min_depth
+ self.max_depth = max_depth
+
+ self._net = nn.Sequential(
+ nn.Conv2d(in_features, mlp_dim, 1, 1, 0),
+ nn.GELU(),
+ nn.Conv2d(mlp_dim, prev_nbins * split_factor, 1, 1, 0),
+ nn.ReLU()
+ )
+
+ def forward(self, x, b_prev, prev_b_embedding=None, interpolate=True, is_for_query=False):
+ """
+ x : feature block; shape - n, c, h, w
+ b_prev : previous bin widths normed; shape - n, prev_nbins, h, w
+ """
+ if prev_b_embedding is not None:
+ if interpolate:
+ prev_b_embedding = nn.functional.interpolate(prev_b_embedding, x.shape[-2:], mode='bilinear', align_corners=True)
+ x = x + prev_b_embedding
+ S = self._net(x)
+ eps = 1e-3
+ S = S + eps
+ n, c, h, w = S.shape
+ S = S.view(n, self.prev_nbins, self.split_factor, h, w)
+ S_normed = S / S.sum(dim=2, keepdim=True) # fractional splits
+
+ b_prev = nn.functional.interpolate(b_prev, (h,w), mode='bilinear', align_corners=True)
+
+
+ b_prev = b_prev / b_prev.sum(dim=1, keepdim=True) # renormalize for gurantees
+ # print(b_prev.shape, S_normed.shape)
+ # if is_for_query:(1).expand(-1, b_prev.size(0)//n, -1, -1, -1, -1).flatten(0,1) # TODO ? can replace all this with a single torch.repeat?
+ b = b_prev.unsqueeze(2) * S_normed
+ b = b.flatten(1,2) # .shape n, prev_nbins * split_factor, h, w
+
+ # calculate bin centers for loss calculation
+ B_widths = (self.max_depth - self.min_depth) * b # .shape N, nprev * splitfactor, H, W
+ # pad has the form (left, right, top, bottom, front, back)
+ B_widths = nn.functional.pad(B_widths, (0,0,0,0,1,0), mode='constant', value=self.min_depth)
+ B_edges = torch.cumsum(B_widths, dim=1) # .shape NCHW
+
+ B_centers = 0.5 * (B_edges[:, :-1, ...] + B_edges[:,1:,...])
+ return b, B_centers
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/layers/patch_transformer.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/layers/patch_transformer.py
new file mode 100644
index 0000000000000000000000000000000000000000..99d9e51a06b981bae45ce7dd64eaef19a4121991
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/layers/patch_transformer.py
@@ -0,0 +1,91 @@
+# MIT License
+
+# Copyright (c) 2022 Intelligent Systems Lab Org
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# File author: Shariq Farooq Bhat
+
+import torch
+import torch.nn as nn
+
+
+class PatchTransformerEncoder(nn.Module):
+ def __init__(self, in_channels, patch_size=10, embedding_dim=128, num_heads=4, use_class_token=False):
+ """ViT-like transformer block
+
+ Args:
+ in_channels (int): Input channels
+ patch_size (int, optional): patch size. Defaults to 10.
+ embedding_dim (int, optional): Embedding dimension in transformer model. Defaults to 128.
+ num_heads (int, optional): number of attention heads. Defaults to 4.
+ use_class_token (bool, optional): Whether to use extra token at the start for global accumulation (called as "class token"). Defaults to False.
+ """
+ super(PatchTransformerEncoder, self).__init__()
+ self.use_class_token = use_class_token
+ encoder_layers = nn.TransformerEncoderLayer(
+ embedding_dim, num_heads, dim_feedforward=1024)
+ self.transformer_encoder = nn.TransformerEncoder(
+ encoder_layers, num_layers=4) # takes shape S,N,E
+
+ self.embedding_convPxP = nn.Conv2d(in_channels, embedding_dim,
+ kernel_size=patch_size, stride=patch_size, padding=0)
+
+ def positional_encoding_1d(self, sequence_length, batch_size, embedding_dim, device='cpu'):
+ """Generate positional encodings
+
+ Args:
+ sequence_length (int): Sequence length
+ embedding_dim (int): Embedding dimension
+
+ Returns:
+ torch.Tensor SBE: Positional encodings
+ """
+ position = torch.arange(
+ 0, sequence_length, dtype=torch.float32, device=device).unsqueeze(1)
+ index = torch.arange(
+ 0, embedding_dim, 2, dtype=torch.float32, device=device).unsqueeze(0)
+ div_term = torch.exp(index * (-torch.log(torch.tensor(10000.0, device=device)) / embedding_dim))
+ pos_encoding = position * div_term
+ pos_encoding = torch.cat([torch.sin(pos_encoding), torch.cos(pos_encoding)], dim=1)
+ pos_encoding = pos_encoding.unsqueeze(1).repeat(1, batch_size, 1)
+ return pos_encoding
+
+
+ def forward(self, x):
+ """Forward pass
+
+ Args:
+ x (torch.Tensor - NCHW): Input feature tensor
+
+ Returns:
+ torch.Tensor - SNE: Transformer output embeddings. S - sequence length (=HW/patch_size^2), N - batch size, E - embedding dim
+ """
+ embeddings = self.embedding_convPxP(x).flatten(
+ 2) # .shape = n,c,s = n, embedding_dim, s
+ if self.use_class_token:
+ # extra special token at start ?
+ embeddings = nn.functional.pad(embeddings, (1, 0))
+
+ # change to S,N,E format required by transformer
+ embeddings = embeddings.permute(2, 0, 1)
+ S, N, E = embeddings.shape
+ embeddings = embeddings + self.positional_encoding_1d(S, N, E, device=embeddings.device)
+ x = self.transformer_encoder(embeddings) # .shape = S, N, E
+ return x
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/model_io.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/model_io.py
new file mode 100644
index 0000000000000000000000000000000000000000..78b6579631dd847ac76651238cb5a948b5a66286
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/model_io.py
@@ -0,0 +1,92 @@
+# MIT License
+
+# Copyright (c) 2022 Intelligent Systems Lab Org
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# File author: Shariq Farooq Bhat
+
+import torch
+
+def load_state_dict(model, state_dict):
+ """Load state_dict into model, handling DataParallel and DistributedDataParallel. Also checks for "model" key in state_dict.
+
+ DataParallel prefixes state_dict keys with 'module.' when saving.
+ If the model is not a DataParallel model but the state_dict is, then prefixes are removed.
+ If the model is a DataParallel model but the state_dict is not, then prefixes are added.
+ """
+ state_dict = state_dict.get('model', state_dict)
+ # if model is a DataParallel model, then state_dict keys are prefixed with 'module.'
+
+ do_prefix = isinstance(
+ model, (torch.nn.DataParallel, torch.nn.parallel.DistributedDataParallel))
+ state = {}
+ for k, v in state_dict.items():
+ if k.startswith('module.') and not do_prefix:
+ k = k[7:]
+
+ if not k.startswith('module.') and do_prefix:
+ k = 'module.' + k
+
+ state[k] = v
+
+ model.load_state_dict(state)
+ print("Loaded successfully")
+ return model
+
+
+def load_wts(model, checkpoint_path):
+ ckpt = torch.load(checkpoint_path, map_location='cpu')
+ return load_state_dict(model, ckpt)
+
+
+def load_state_dict_from_url(model, url, **kwargs):
+ state_dict = torch.hub.load_state_dict_from_url(url, map_location='cpu', **kwargs)
+ return load_state_dict(model, state_dict)
+
+
+def load_state_from_resource(model, resource: str):
+ """Loads weights to the model from a given resource. A resource can be of following types:
+ 1. URL. Prefixed with "url::"
+ e.g. url::http(s)://url.resource.com/ckpt.pt
+
+ 2. Local path. Prefixed with "local::"
+ e.g. local::/path/to/ckpt.pt
+
+
+ Args:
+ model (torch.nn.Module): Model
+ resource (str): resource string
+
+ Returns:
+ torch.nn.Module: Model with loaded weights
+ """
+ print(f"Using pretrained resource {resource}")
+
+ if resource.startswith('url::'):
+ url = resource.split('url::')[1]
+ return load_state_dict_from_url(model, url, progress=True)
+
+ elif resource.startswith('local::'):
+ path = resource.split('local::')[1]
+ return load_wts(model, path)
+
+ else:
+ raise ValueError("Invalid resource type, only url:: and local:: are supported")
+
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/zoedepth/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/zoedepth/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..cc33f737d238766559f0e3a8def3c0b568f23b7f
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/zoedepth/__init__.py
@@ -0,0 +1,31 @@
+# MIT License
+
+# Copyright (c) 2022 Intelligent Systems Lab Org
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# File author: Shariq Farooq Bhat
+
+from .zoedepth_v1 import ZoeDepth
+
+all_versions = {
+ "v1": ZoeDepth,
+}
+
+get_version = lambda v : all_versions[v]
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/zoedepth/config_zoedepth.json b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/zoedepth/config_zoedepth.json
new file mode 100644
index 0000000000000000000000000000000000000000..3112ed78c89f00e1d13f5d6e5be87cd3216b6dc7
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/zoedepth/config_zoedepth.json
@@ -0,0 +1,58 @@
+{
+ "model": {
+ "name": "ZoeDepth",
+ "version_name": "v1",
+ "n_bins": 64,
+ "bin_embedding_dim": 128,
+ "bin_centers_type": "softplus",
+ "n_attractors":[16, 8, 4, 1],
+ "attractor_alpha": 1000,
+ "attractor_gamma": 2,
+ "attractor_kind" : "mean",
+ "attractor_type" : "inv",
+ "midas_model_type" : "DPT_BEiT_L_384",
+ "min_temp": 0.0212,
+ "max_temp": 50.0,
+ "output_distribution": "logbinomial",
+ "memory_efficient": true,
+ "inverse_midas": false,
+ "img_size": [384, 512]
+ },
+
+ "train": {
+ "train_midas": true,
+ "use_pretrained_midas": true,
+ "trainer": "zoedepth",
+ "epochs": 5,
+ "bs": 16,
+ "optim_kwargs": {"lr": 0.000161, "wd": 0.01},
+ "sched_kwargs": {"div_factor": 1, "final_div_factor": 10000, "pct_start": 0.7, "three_phase":false, "cycle_momentum": true},
+ "same_lr": false,
+ "w_si": 1,
+ "w_domain": 0.2,
+ "w_reg": 0,
+ "w_grad": 0,
+ "avoid_boundary": false,
+ "random_crop": false,
+ "input_width": 640,
+ "input_height": 480,
+ "midas_lr_factor": 1,
+ "encoder_lr_factor":10,
+ "pos_enc_lr_factor":10,
+ "freeze_midas_bn": true
+
+ },
+
+ "infer":{
+ "train_midas": false,
+ "use_pretrained_midas": false,
+ "pretrained_resource" : null,
+ "force_keep_ar": true
+ },
+
+ "eval":{
+ "train_midas": false,
+ "use_pretrained_midas": false,
+ "pretrained_resource" : null
+ }
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/zoedepth/config_zoedepth_kitti.json b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/zoedepth/config_zoedepth_kitti.json
new file mode 100644
index 0000000000000000000000000000000000000000..b51802aa44b91c39e15aacaac4b5ab6bec884414
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/zoedepth/config_zoedepth_kitti.json
@@ -0,0 +1,22 @@
+{
+ "model": {
+ "bin_centers_type": "normed",
+ "img_size": [384, 768]
+ },
+
+ "train": {
+ },
+
+ "infer":{
+ "train_midas": false,
+ "use_pretrained_midas": false,
+ "pretrained_resource" : "url::https://github.com/isl-org/ZoeDepth/releases/download/v1.0/ZoeD_M12_K.pt",
+ "force_keep_ar": true
+ },
+
+ "eval":{
+ "train_midas": false,
+ "use_pretrained_midas": false,
+ "pretrained_resource" : "url::https://github.com/isl-org/ZoeDepth/releases/download/v1.0/ZoeD_M12_K.pt"
+ }
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/zoedepth/zoedepth_v1.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/zoedepth/zoedepth_v1.py
new file mode 100644
index 0000000000000000000000000000000000000000..bc931b059d6165c84e8ff4f09d5c62d19930cee9
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/zoedepth/zoedepth_v1.py
@@ -0,0 +1,250 @@
+# MIT License
+
+# Copyright (c) 2022 Intelligent Systems Lab Org
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# File author: Shariq Farooq Bhat
+
+import itertools
+
+import torch
+import torch.nn as nn
+from ..depth_model import DepthModel
+from ..base_models.midas import MidasCore
+from ..layers.attractor import AttractorLayer, AttractorLayerUnnormed
+from ..layers.dist_layers import ConditionalLogBinomial
+from ..layers.localbins_layers import (Projector, SeedBinRegressor,
+ SeedBinRegressorUnnormed)
+from ..model_io import load_state_from_resource
+
+
+class ZoeDepth(DepthModel):
+ def __init__(self, core, n_bins=64, bin_centers_type="softplus", bin_embedding_dim=128, min_depth=1e-3, max_depth=10,
+ n_attractors=[16, 8, 4, 1], attractor_alpha=300, attractor_gamma=2, attractor_kind='sum', attractor_type='exp', min_temp=5, max_temp=50, train_midas=True,
+ midas_lr_factor=10, encoder_lr_factor=10, pos_enc_lr_factor=10, inverse_midas=False, **kwargs):
+ """ZoeDepth model. This is the version of ZoeDepth that has a single metric head
+
+ Args:
+ core (models.base_models.midas.MidasCore): The base midas model that is used for extraction of "relative" features
+ n_bins (int, optional): Number of bin centers. Defaults to 64.
+ bin_centers_type (str, optional): "normed" or "softplus". Activation type used for bin centers. For "normed" bin centers, linear normalization trick is applied. This results in bounded bin centers.
+ For "softplus", softplus activation is used and thus are unbounded. Defaults to "softplus".
+ bin_embedding_dim (int, optional): bin embedding dimension. Defaults to 128.
+ min_depth (float, optional): Lower bound for normed bin centers. Defaults to 1e-3.
+ max_depth (float, optional): Upper bound for normed bin centers. Defaults to 10.
+ n_attractors (List[int], optional): Number of bin attractors at decoder layers. Defaults to [16, 8, 4, 1].
+ attractor_alpha (int, optional): Proportional attractor strength. Refer to models.layers.attractor for more details. Defaults to 300.
+ attractor_gamma (int, optional): Exponential attractor strength. Refer to models.layers.attractor for more details. Defaults to 2.
+ attractor_kind (str, optional): Attraction aggregation "sum" or "mean". Defaults to 'sum'.
+ attractor_type (str, optional): Type of attractor to use; "inv" (Inverse attractor) or "exp" (Exponential attractor). Defaults to 'exp'.
+ min_temp (int, optional): Lower bound for temperature of output probability distribution. Defaults to 5.
+ max_temp (int, optional): Upper bound for temperature of output probability distribution. Defaults to 50.
+ train_midas (bool, optional): Whether to train "core", the base midas model. Defaults to True.
+ midas_lr_factor (int, optional): Learning rate reduction factor for base midas model except its encoder and positional encodings. Defaults to 10.
+ encoder_lr_factor (int, optional): Learning rate reduction factor for the encoder in midas model. Defaults to 10.
+ pos_enc_lr_factor (int, optional): Learning rate reduction factor for positional encodings in the base midas model. Defaults to 10.
+ """
+ super().__init__()
+
+ self.core = core
+ self.max_depth = max_depth
+ self.min_depth = min_depth
+ self.min_temp = min_temp
+ self.bin_centers_type = bin_centers_type
+
+ self.midas_lr_factor = midas_lr_factor
+ self.encoder_lr_factor = encoder_lr_factor
+ self.pos_enc_lr_factor = pos_enc_lr_factor
+ self.train_midas = train_midas
+ self.inverse_midas = inverse_midas
+
+ if self.encoder_lr_factor <= 0:
+ self.core.freeze_encoder(
+ freeze_rel_pos=self.pos_enc_lr_factor <= 0)
+
+ N_MIDAS_OUT = 32
+ btlnck_features = self.core.output_channels[0]
+ num_out_features = self.core.output_channels[1:]
+
+ self.conv2 = nn.Conv2d(btlnck_features, btlnck_features,
+ kernel_size=1, stride=1, padding=0) # btlnck conv
+
+ if bin_centers_type == "normed":
+ SeedBinRegressorLayer = SeedBinRegressor
+ Attractor = AttractorLayer
+ elif bin_centers_type == "softplus":
+ SeedBinRegressorLayer = SeedBinRegressorUnnormed
+ Attractor = AttractorLayerUnnormed
+ elif bin_centers_type == "hybrid1":
+ SeedBinRegressorLayer = SeedBinRegressor
+ Attractor = AttractorLayerUnnormed
+ elif bin_centers_type == "hybrid2":
+ SeedBinRegressorLayer = SeedBinRegressorUnnormed
+ Attractor = AttractorLayer
+ else:
+ raise ValueError(
+ "bin_centers_type should be one of 'normed', 'softplus', 'hybrid1', 'hybrid2'")
+
+ self.seed_bin_regressor = SeedBinRegressorLayer(
+ btlnck_features, n_bins=n_bins, min_depth=min_depth, max_depth=max_depth)
+ self.seed_projector = Projector(btlnck_features, bin_embedding_dim)
+ self.projectors = nn.ModuleList([
+ Projector(num_out, bin_embedding_dim)
+ for num_out in num_out_features
+ ])
+ self.attractors = nn.ModuleList([
+ Attractor(bin_embedding_dim, n_bins, n_attractors=n_attractors[i], min_depth=min_depth, max_depth=max_depth,
+ alpha=attractor_alpha, gamma=attractor_gamma, kind=attractor_kind, attractor_type=attractor_type)
+ for i in range(len(num_out_features))
+ ])
+
+ last_in = N_MIDAS_OUT + 1 # +1 for relative depth
+
+ # use log binomial instead of softmax
+ self.conditional_log_binomial = ConditionalLogBinomial(
+ last_in, bin_embedding_dim, n_classes=n_bins, min_temp=min_temp, max_temp=max_temp)
+
+ def forward(self, x, return_final_centers=False, denorm=False, return_probs=False, **kwargs):
+ """
+ Args:
+ x (torch.Tensor): Input image tensor of shape (B, C, H, W)
+ return_final_centers (bool, optional): Whether to return the final bin centers. Defaults to False.
+ denorm (bool, optional): Whether to denormalize the input image. This reverses ImageNet normalization as midas normalization is different. Defaults to False.
+ return_probs (bool, optional): Whether to return the output probability distribution. Defaults to False.
+
+ Returns:
+ dict: Dictionary containing the following keys:
+ - rel_depth (torch.Tensor): Relative depth map of shape (B, H, W)
+ - metric_depth (torch.Tensor): Metric depth map of shape (B, 1, H, W)
+ - bin_centers (torch.Tensor): Bin centers of shape (B, n_bins). Present only if return_final_centers is True
+ - probs (torch.Tensor): Output probability distribution of shape (B, n_bins, H, W). Present only if return_probs is True
+
+ """
+ b, c, h, w = x.shape
+ # print("input shape ", x.shape)
+ self.orig_input_width = w
+ self.orig_input_height = h
+ rel_depth, out = self.core(x, denorm=denorm, return_rel_depth=True)
+ # print("output shapes", rel_depth.shape, out.shape)
+
+ outconv_activation = out[0]
+ btlnck = out[1]
+ x_blocks = out[2:]
+
+ x_d0 = self.conv2(btlnck)
+ x = x_d0
+ _, seed_b_centers = self.seed_bin_regressor(x)
+
+ if self.bin_centers_type == 'normed' or self.bin_centers_type == 'hybrid2':
+ b_prev = (seed_b_centers - self.min_depth) / \
+ (self.max_depth - self.min_depth)
+ else:
+ b_prev = seed_b_centers
+
+ prev_b_embedding = self.seed_projector(x)
+
+ # unroll this loop for better performance
+ for projector, attractor, x in zip(self.projectors, self.attractors, x_blocks):
+ b_embedding = projector(x)
+ b, b_centers = attractor(
+ b_embedding, b_prev, prev_b_embedding, interpolate=True)
+ b_prev = b.clone()
+ prev_b_embedding = b_embedding.clone()
+
+ last = outconv_activation
+
+ if self.inverse_midas:
+ # invert depth followed by normalization
+ rel_depth = 1.0 / (rel_depth + 1e-6)
+ rel_depth = (rel_depth - rel_depth.min()) / \
+ (rel_depth.max() - rel_depth.min())
+ # concat rel depth with last. First interpolate rel depth to last size
+ rel_cond = rel_depth.unsqueeze(1)
+ rel_cond = nn.functional.interpolate(
+ rel_cond, size=last.shape[2:], mode='bilinear', align_corners=True)
+ last = torch.cat([last, rel_cond], dim=1)
+
+ b_embedding = nn.functional.interpolate(
+ b_embedding, last.shape[-2:], mode='bilinear', align_corners=True)
+ x = self.conditional_log_binomial(last, b_embedding)
+
+ # Now depth value is Sum px * cx , where cx are bin_centers from the last bin tensor
+ # print(x.shape, b_centers.shape)
+ b_centers = nn.functional.interpolate(
+ b_centers, x.shape[-2:], mode='bilinear', align_corners=True)
+ out = torch.sum(x * b_centers, dim=1, keepdim=True)
+
+ # Structure output dict
+ output = dict(metric_depth=out)
+ if return_final_centers or return_probs:
+ output['bin_centers'] = b_centers
+
+ if return_probs:
+ output['probs'] = x
+
+ return output
+
+ def get_lr_params(self, lr):
+ """
+ Learning rate configuration for different layers of the model
+ Args:
+ lr (float) : Base learning rate
+ Returns:
+ list : list of parameters to optimize and their learning rates, in the format required by torch optimizers.
+ """
+ param_conf = []
+ if self.train_midas:
+ if self.encoder_lr_factor > 0:
+ param_conf.append({'params': self.core.get_enc_params_except_rel_pos(
+ ), 'lr': lr / self.encoder_lr_factor})
+
+ if self.pos_enc_lr_factor > 0:
+ param_conf.append(
+ {'params': self.core.get_rel_pos_params(), 'lr': lr / self.pos_enc_lr_factor})
+
+ midas_params = self.core.core.scratch.parameters()
+ midas_lr_factor = self.midas_lr_factor
+ param_conf.append(
+ {'params': midas_params, 'lr': lr / midas_lr_factor})
+
+ remaining_modules = []
+ for name, child in self.named_children():
+ if name != 'core':
+ remaining_modules.append(child)
+ remaining_params = itertools.chain(
+ *[child.parameters() for child in remaining_modules])
+
+ param_conf.append({'params': remaining_params, 'lr': lr})
+
+ return param_conf
+
+ @staticmethod
+ def build(midas_model_type="DPT_BEiT_L_384", pretrained_resource=None, use_pretrained_midas=False, train_midas=False, freeze_midas_bn=True, **kwargs):
+ core = MidasCore.build(midas_model_type=midas_model_type, use_pretrained_midas=use_pretrained_midas,
+ train_midas=train_midas, fetch_features=True, freeze_bn=freeze_midas_bn, **kwargs)
+ model = ZoeDepth(core, **kwargs)
+ if pretrained_resource:
+ assert isinstance(pretrained_resource, str), "pretrained_resource must be a string"
+ model = load_state_from_resource(model, pretrained_resource)
+ return model
+
+ @staticmethod
+ def build_from_config(config):
+ return ZoeDepth.build(**config)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/zoedepth_nk/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/zoedepth_nk/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..513a278b939c10c010e3c0250ec73544d5663886
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/zoedepth_nk/__init__.py
@@ -0,0 +1,31 @@
+# MIT License
+
+# Copyright (c) 2022 Intelligent Systems Lab Org
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# File author: Shariq Farooq Bhat
+
+from .zoedepth_nk_v1 import ZoeDepthNK
+
+all_versions = {
+ "v1": ZoeDepthNK,
+}
+
+get_version = lambda v : all_versions[v]
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/zoedepth_nk/config_zoedepth_nk.json b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/zoedepth_nk/config_zoedepth_nk.json
new file mode 100644
index 0000000000000000000000000000000000000000..42bab2a3ad159a09599a5aba270c491021a3cf1a
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/zoedepth_nk/config_zoedepth_nk.json
@@ -0,0 +1,67 @@
+{
+ "model": {
+ "name": "ZoeDepthNK",
+ "version_name": "v1",
+ "bin_conf" : [
+ {
+ "name": "nyu",
+ "n_bins": 64,
+ "min_depth": 1e-3,
+ "max_depth": 10.0
+ },
+ {
+ "name": "kitti",
+ "n_bins": 64,
+ "min_depth": 1e-3,
+ "max_depth": 80.0
+ }
+ ],
+ "bin_embedding_dim": 128,
+ "bin_centers_type": "softplus",
+ "n_attractors":[16, 8, 4, 1],
+ "attractor_alpha": 1000,
+ "attractor_gamma": 2,
+ "attractor_kind" : "mean",
+ "attractor_type" : "inv",
+ "min_temp": 0.0212,
+ "max_temp": 50.0,
+ "memory_efficient": true,
+ "midas_model_type" : "DPT_BEiT_L_384",
+ "img_size": [384, 512]
+ },
+
+ "train": {
+ "train_midas": true,
+ "use_pretrained_midas": true,
+ "trainer": "zoedepth_nk",
+ "epochs": 5,
+ "bs": 16,
+ "optim_kwargs": {"lr": 0.0002512, "wd": 0.01},
+ "sched_kwargs": {"div_factor": 1, "final_div_factor": 10000, "pct_start": 0.7, "three_phase":false, "cycle_momentum": true},
+ "same_lr": false,
+ "w_si": 1,
+ "w_domain": 100,
+ "avoid_boundary": false,
+ "random_crop": false,
+ "input_width": 640,
+ "input_height": 480,
+ "w_grad": 0,
+ "w_reg": 0,
+ "midas_lr_factor": 10,
+ "encoder_lr_factor":10,
+ "pos_enc_lr_factor":10
+ },
+
+ "infer": {
+ "train_midas": false,
+ "pretrained_resource": "url::https://github.com/isl-org/ZoeDepth/releases/download/v1.0/ZoeD_M12_NK.pt",
+ "use_pretrained_midas": false,
+ "force_keep_ar": true
+ },
+
+ "eval": {
+ "train_midas": false,
+ "pretrained_resource": "url::https://github.com/isl-org/ZoeDepth/releases/download/v1.0/ZoeD_M12_NK.pt",
+ "use_pretrained_midas": false
+ }
+}
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/zoedepth_nk/zoedepth_nk_v1.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/zoedepth_nk/zoedepth_nk_v1.py
new file mode 100644
index 0000000000000000000000000000000000000000..7368ae8031188a9f946d9d3f29633c96e791e68e
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/models/zoedepth_nk/zoedepth_nk_v1.py
@@ -0,0 +1,333 @@
+# MIT License
+
+# Copyright (c) 2022 Intelligent Systems Lab Org
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# File author: Shariq Farooq Bhat
+
+import itertools
+
+import torch
+import torch.nn as nn
+
+from zoedepth.models.depth_model import DepthModel
+from zoedepth.models.base_models.midas import MidasCore
+from zoedepth.models.layers.attractor import AttractorLayer, AttractorLayerUnnormed
+from zoedepth.models.layers.dist_layers import ConditionalLogBinomial
+from zoedepth.models.layers.localbins_layers import (Projector, SeedBinRegressor,
+ SeedBinRegressorUnnormed)
+from zoedepth.models.layers.patch_transformer import PatchTransformerEncoder
+from zoedepth.models.model_io import load_state_from_resource
+
+
+class ZoeDepthNK(DepthModel):
+ def __init__(self, core, bin_conf, bin_centers_type="softplus", bin_embedding_dim=128,
+ n_attractors=[16, 8, 4, 1], attractor_alpha=300, attractor_gamma=2, attractor_kind='sum', attractor_type='exp',
+ min_temp=5, max_temp=50,
+ memory_efficient=False, train_midas=True,
+ is_midas_pretrained=True, midas_lr_factor=1, encoder_lr_factor=10, pos_enc_lr_factor=10, inverse_midas=False, **kwargs):
+ """ZoeDepthNK model. This is the version of ZoeDepth that has two metric heads and uses a learned router to route to experts.
+
+ Args:
+ core (models.base_models.midas.MidasCore): The base midas model that is used for extraction of "relative" features
+
+ bin_conf (List[dict]): A list of dictionaries that contain the bin configuration for each metric head. Each dictionary should contain the following keys:
+ "name" (str, typically same as the dataset name), "n_bins" (int), "min_depth" (float), "max_depth" (float)
+
+ The length of this list determines the number of metric heads.
+ bin_centers_type (str, optional): "normed" or "softplus". Activation type used for bin centers. For "normed" bin centers, linear normalization trick is applied. This results in bounded bin centers.
+ For "softplus", softplus activation is used and thus are unbounded. Defaults to "normed".
+ bin_embedding_dim (int, optional): bin embedding dimension. Defaults to 128.
+
+ n_attractors (List[int], optional): Number of bin attractors at decoder layers. Defaults to [16, 8, 4, 1].
+ attractor_alpha (int, optional): Proportional attractor strength. Refer to models.layers.attractor for more details. Defaults to 300.
+ attractor_gamma (int, optional): Exponential attractor strength. Refer to models.layers.attractor for more details. Defaults to 2.
+ attractor_kind (str, optional): Attraction aggregation "sum" or "mean". Defaults to 'sum'.
+ attractor_type (str, optional): Type of attractor to use; "inv" (Inverse attractor) or "exp" (Exponential attractor). Defaults to 'exp'.
+
+ min_temp (int, optional): Lower bound for temperature of output probability distribution. Defaults to 5.
+ max_temp (int, optional): Upper bound for temperature of output probability distribution. Defaults to 50.
+
+ memory_efficient (bool, optional): Whether to use memory efficient version of attractor layers. Memory efficient version is slower but is recommended incase of multiple metric heads in order save GPU memory. Defaults to False.
+
+ train_midas (bool, optional): Whether to train "core", the base midas model. Defaults to True.
+ is_midas_pretrained (bool, optional): Is "core" pretrained? Defaults to True.
+ midas_lr_factor (int, optional): Learning rate reduction factor for base midas model except its encoder and positional encodings. Defaults to 10.
+ encoder_lr_factor (int, optional): Learning rate reduction factor for the encoder in midas model. Defaults to 10.
+ pos_enc_lr_factor (int, optional): Learning rate reduction factor for positional encodings in the base midas model. Defaults to 10.
+
+ """
+
+ super().__init__()
+
+ self.core = core
+ self.bin_conf = bin_conf
+ self.min_temp = min_temp
+ self.max_temp = max_temp
+ self.memory_efficient = memory_efficient
+ self.train_midas = train_midas
+ self.is_midas_pretrained = is_midas_pretrained
+ self.midas_lr_factor = midas_lr_factor
+ self.encoder_lr_factor = encoder_lr_factor
+ self.pos_enc_lr_factor = pos_enc_lr_factor
+ self.inverse_midas = inverse_midas
+
+ N_MIDAS_OUT = 32
+ btlnck_features = self.core.output_channels[0]
+ num_out_features = self.core.output_channels[1:]
+ # self.scales = [16, 8, 4, 2] # spatial scale factors
+
+ self.conv2 = nn.Conv2d(
+ btlnck_features, btlnck_features, kernel_size=1, stride=1, padding=0)
+
+ # Transformer classifier on the bottleneck
+ self.patch_transformer = PatchTransformerEncoder(
+ btlnck_features, 1, 128, use_class_token=True)
+ self.mlp_classifier = nn.Sequential(
+ nn.Linear(128, 128),
+ nn.ReLU(),
+ nn.Linear(128, 2)
+ )
+
+ if bin_centers_type == "normed":
+ SeedBinRegressorLayer = SeedBinRegressor
+ Attractor = AttractorLayer
+ elif bin_centers_type == "softplus":
+ SeedBinRegressorLayer = SeedBinRegressorUnnormed
+ Attractor = AttractorLayerUnnormed
+ elif bin_centers_type == "hybrid1":
+ SeedBinRegressorLayer = SeedBinRegressor
+ Attractor = AttractorLayerUnnormed
+ elif bin_centers_type == "hybrid2":
+ SeedBinRegressorLayer = SeedBinRegressorUnnormed
+ Attractor = AttractorLayer
+ else:
+ raise ValueError(
+ "bin_centers_type should be one of 'normed', 'softplus', 'hybrid1', 'hybrid2'")
+ self.bin_centers_type = bin_centers_type
+ # We have bins for each bin conf.
+ # Create a map (ModuleDict) of 'name' -> seed_bin_regressor
+ self.seed_bin_regressors = nn.ModuleDict(
+ {conf['name']: SeedBinRegressorLayer(btlnck_features, conf["n_bins"], mlp_dim=bin_embedding_dim//2, min_depth=conf["min_depth"], max_depth=conf["max_depth"])
+ for conf in bin_conf}
+ )
+
+ self.seed_projector = Projector(
+ btlnck_features, bin_embedding_dim, mlp_dim=bin_embedding_dim//2)
+ self.projectors = nn.ModuleList([
+ Projector(num_out, bin_embedding_dim, mlp_dim=bin_embedding_dim//2)
+ for num_out in num_out_features
+ ])
+
+ # Create a map (ModuleDict) of 'name' -> attractors (ModuleList)
+ self.attractors = nn.ModuleDict(
+ {conf['name']: nn.ModuleList([
+ Attractor(bin_embedding_dim, n_attractors[i],
+ mlp_dim=bin_embedding_dim, alpha=attractor_alpha,
+ gamma=attractor_gamma, kind=attractor_kind,
+ attractor_type=attractor_type, memory_efficient=memory_efficient,
+ min_depth=conf["min_depth"], max_depth=conf["max_depth"])
+ for i in range(len(n_attractors))
+ ])
+ for conf in bin_conf}
+ )
+
+ last_in = N_MIDAS_OUT
+ # conditional log binomial for each bin conf
+ self.conditional_log_binomial = nn.ModuleDict(
+ {conf['name']: ConditionalLogBinomial(last_in, bin_embedding_dim, conf['n_bins'], bottleneck_factor=4, min_temp=self.min_temp, max_temp=self.max_temp)
+ for conf in bin_conf}
+ )
+
+ def forward(self, x, return_final_centers=False, denorm=False, return_probs=False, **kwargs):
+ """
+ Args:
+ x (torch.Tensor): Input image tensor of shape (B, C, H, W). Assumes all images are from the same domain.
+ return_final_centers (bool, optional): Whether to return the final centers of the attractors. Defaults to False.
+ denorm (bool, optional): Whether to denormalize the input image. Defaults to False.
+ return_probs (bool, optional): Whether to return the probabilities of the bins. Defaults to False.
+
+ Returns:
+ dict: Dictionary of outputs with keys:
+ - "rel_depth": Relative depth map of shape (B, 1, H, W)
+ - "metric_depth": Metric depth map of shape (B, 1, H, W)
+ - "domain_logits": Domain logits of shape (B, 2)
+ - "bin_centers": Bin centers of shape (B, N, H, W). Present only if return_final_centers is True
+ - "probs": Bin probabilities of shape (B, N, H, W). Present only if return_probs is True
+ """
+ b, c, h, w = x.shape
+ self.orig_input_width = w
+ self.orig_input_height = h
+ rel_depth, out = self.core(x, denorm=denorm, return_rel_depth=True)
+
+ outconv_activation = out[0]
+ btlnck = out[1]
+ x_blocks = out[2:]
+
+ x_d0 = self.conv2(btlnck)
+ x = x_d0
+
+ # Predict which path to take
+ embedding = self.patch_transformer(x)[0] # N, E
+ domain_logits = self.mlp_classifier(embedding) # N, 2
+ domain_vote = torch.softmax(domain_logits.sum(
+ dim=0, keepdim=True), dim=-1) # 1, 2
+
+ # Get the path
+ bin_conf_name = ["nyu", "kitti"][torch.argmax(
+ domain_vote, dim=-1).squeeze().item()]
+
+ try:
+ conf = [c for c in self.bin_conf if c.name == bin_conf_name][0]
+ except IndexError:
+ raise ValueError(
+ f"bin_conf_name {bin_conf_name} not found in bin_confs")
+
+ min_depth = conf['min_depth']
+ max_depth = conf['max_depth']
+
+ seed_bin_regressor = self.seed_bin_regressors[bin_conf_name]
+ _, seed_b_centers = seed_bin_regressor(x)
+ if self.bin_centers_type == 'normed' or self.bin_centers_type == 'hybrid2':
+ b_prev = (seed_b_centers - min_depth)/(max_depth - min_depth)
+ else:
+ b_prev = seed_b_centers
+ prev_b_embedding = self.seed_projector(x)
+
+ attractors = self.attractors[bin_conf_name]
+ for projector, attractor, x in zip(self.projectors, attractors, x_blocks):
+ b_embedding = projector(x)
+ b, b_centers = attractor(
+ b_embedding, b_prev, prev_b_embedding, interpolate=True)
+ b_prev = b
+ prev_b_embedding = b_embedding
+
+ last = outconv_activation
+
+ b_centers = nn.functional.interpolate(
+ b_centers, last.shape[-2:], mode='bilinear', align_corners=True)
+ b_embedding = nn.functional.interpolate(
+ b_embedding, last.shape[-2:], mode='bilinear', align_corners=True)
+
+ clb = self.conditional_log_binomial[bin_conf_name]
+ x = clb(last, b_embedding)
+
+ # Now depth value is Sum px * cx , where cx are bin_centers from the last bin tensor
+ # print(x.shape, b_centers.shape)
+ # b_centers = nn.functional.interpolate(b_centers, x.shape[-2:], mode='bilinear', align_corners=True)
+ out = torch.sum(x * b_centers, dim=1, keepdim=True)
+
+ output = dict(domain_logits=domain_logits, metric_depth=out)
+ if return_final_centers or return_probs:
+ output['bin_centers'] = b_centers
+
+ if return_probs:
+ output['probs'] = x
+ return output
+
+ def get_lr_params(self, lr):
+ """
+ Learning rate configuration for different layers of the model
+
+ Args:
+ lr (float) : Base learning rate
+ Returns:
+ list : list of parameters to optimize and their learning rates, in the format required by torch optimizers.
+ """
+ param_conf = []
+ if self.train_midas:
+ def get_rel_pos_params():
+ for name, p in self.core.core.pretrained.named_parameters():
+ if "relative_position" in name:
+ yield p
+
+ def get_enc_params_except_rel_pos():
+ for name, p in self.core.core.pretrained.named_parameters():
+ if "relative_position" not in name:
+ yield p
+
+ encoder_params = get_enc_params_except_rel_pos()
+ rel_pos_params = get_rel_pos_params()
+ midas_params = self.core.core.scratch.parameters()
+ midas_lr_factor = self.midas_lr_factor if self.is_midas_pretrained else 1.0
+ param_conf.extend([
+ {'params': encoder_params, 'lr': lr / self.encoder_lr_factor},
+ {'params': rel_pos_params, 'lr': lr / self.pos_enc_lr_factor},
+ {'params': midas_params, 'lr': lr / midas_lr_factor}
+ ])
+
+ remaining_modules = []
+ for name, child in self.named_children():
+ if name != 'core':
+ remaining_modules.append(child)
+ remaining_params = itertools.chain(
+ *[child.parameters() for child in remaining_modules])
+ param_conf.append({'params': remaining_params, 'lr': lr})
+ return param_conf
+
+ def get_conf_parameters(self, conf_name):
+ """
+ Returns parameters of all the ModuleDicts children that are exclusively used for the given bin configuration
+ """
+ params = []
+ for name, child in self.named_children():
+ if isinstance(child, nn.ModuleDict):
+ for bin_conf_name, module in child.items():
+ if bin_conf_name == conf_name:
+ params += list(module.parameters())
+ return params
+
+ def freeze_conf(self, conf_name):
+ """
+ Freezes all the parameters of all the ModuleDicts children that are exclusively used for the given bin configuration
+ """
+ for p in self.get_conf_parameters(conf_name):
+ p.requires_grad = False
+
+ def unfreeze_conf(self, conf_name):
+ """
+ Unfreezes all the parameters of all the ModuleDicts children that are exclusively used for the given bin configuration
+ """
+ for p in self.get_conf_parameters(conf_name):
+ p.requires_grad = True
+
+ def freeze_all_confs(self):
+ """
+ Freezes all the parameters of all the ModuleDicts children
+ """
+ for name, child in self.named_children():
+ if isinstance(child, nn.ModuleDict):
+ for bin_conf_name, module in child.items():
+ for p in module.parameters():
+ p.requires_grad = False
+
+ @staticmethod
+ def build(midas_model_type="DPT_BEiT_L_384", pretrained_resource=None, use_pretrained_midas=False, train_midas=False, freeze_midas_bn=True, **kwargs):
+ core = MidasCore.build(midas_model_type=midas_model_type, use_pretrained_midas=use_pretrained_midas,
+ train_midas=train_midas, fetch_features=True, freeze_bn=freeze_midas_bn, **kwargs)
+ model = ZoeDepthNK(core, **kwargs)
+ if pretrained_resource:
+ assert isinstance(pretrained_resource, str), "pretrained_resource must be a string"
+ model = load_state_from_resource(model, pretrained_resource)
+ return model
+
+ @staticmethod
+ def build_from_config(config):
+ return ZoeDepthNK.build(**config)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/utils/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/utils/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..5f2668792389157609abb2a0846fb620e7d67eb9
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/utils/__init__.py
@@ -0,0 +1,24 @@
+# MIT License
+
+# Copyright (c) 2022 Intelligent Systems Lab Org
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# File author: Shariq Farooq Bhat
+
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/utils/arg_utils.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/utils/arg_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..8a3004ec3679c0a40fd8961253733fb4343ad545
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/utils/arg_utils.py
@@ -0,0 +1,33 @@
+
+
+def infer_type(x): # hacky way to infer type from string args
+ if not isinstance(x, str):
+ return x
+
+ try:
+ x = int(x)
+ return x
+ except ValueError:
+ pass
+
+ try:
+ x = float(x)
+ return x
+ except ValueError:
+ pass
+
+ return x
+
+
+def parse_unknown(unknown_args):
+ clean = []
+ for a in unknown_args:
+ if "=" in a:
+ k, v = a.split("=")
+ clean.extend([k, v])
+ else:
+ clean.append(a)
+
+ keys = clean[::2]
+ values = clean[1::2]
+ return {k.replace("--", ""): infer_type(v) for k, v in zip(keys, values)}
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/utils/config.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/utils/config.py
new file mode 100644
index 0000000000000000000000000000000000000000..84996564663dadf0e720de2a68ef8c53106ed666
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/utils/config.py
@@ -0,0 +1,437 @@
+# MIT License
+
+# Copyright (c) 2022 Intelligent Systems Lab Org
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# File author: Shariq Farooq Bhat
+
+import json
+import os
+
+from .easydict import EasyDict as edict
+from .arg_utils import infer_type
+
+import pathlib
+import platform
+
+ROOT = pathlib.Path(__file__).parent.parent.resolve()
+
+HOME_DIR = os.path.expanduser("~")
+
+COMMON_CONFIG = {
+ "save_dir": os.path.expanduser("~/shortcuts/monodepth3_checkpoints"),
+ "project": "ZoeDepth",
+ "tags": '',
+ "notes": "",
+ "gpu": None,
+ "root": ".",
+ "uid": None,
+ "print_losses": False
+}
+
+DATASETS_CONFIG = {
+ "kitti": {
+ "dataset": "kitti",
+ "min_depth": 0.001,
+ "max_depth": 80,
+ "data_path": os.path.join(HOME_DIR, "shortcuts/datasets/kitti/raw"),
+ "gt_path": os.path.join(HOME_DIR, "shortcuts/datasets/kitti/gts"),
+ "filenames_file": "./train_test_inputs/kitti_eigen_train_files_with_gt.txt",
+ "input_height": 352,
+ "input_width": 1216, # 704
+ "data_path_eval": os.path.join(HOME_DIR, "shortcuts/datasets/kitti/raw"),
+ "gt_path_eval": os.path.join(HOME_DIR, "shortcuts/datasets/kitti/gts"),
+ "filenames_file_eval": "./train_test_inputs/kitti_eigen_test_files_with_gt.txt",
+
+ "min_depth_eval": 1e-3,
+ "max_depth_eval": 80,
+
+ "do_random_rotate": True,
+ "degree": 1.0,
+ "do_kb_crop": True,
+ "garg_crop": True,
+ "eigen_crop": False,
+ "use_right": False
+ },
+ "kitti_test": {
+ "dataset": "kitti",
+ "min_depth": 0.001,
+ "max_depth": 80,
+ "data_path": os.path.join(HOME_DIR, "shortcuts/datasets/kitti/raw"),
+ "gt_path": os.path.join(HOME_DIR, "shortcuts/datasets/kitti/gts"),
+ "filenames_file": "./train_test_inputs/kitti_eigen_train_files_with_gt.txt",
+ "input_height": 352,
+ "input_width": 1216,
+ "data_path_eval": os.path.join(HOME_DIR, "shortcuts/datasets/kitti/raw"),
+ "gt_path_eval": os.path.join(HOME_DIR, "shortcuts/datasets/kitti/gts"),
+ "filenames_file_eval": "./train_test_inputs/kitti_eigen_test_files_with_gt.txt",
+
+ "min_depth_eval": 1e-3,
+ "max_depth_eval": 80,
+
+ "do_random_rotate": False,
+ "degree": 1.0,
+ "do_kb_crop": True,
+ "garg_crop": True,
+ "eigen_crop": False,
+ "use_right": False
+ },
+ "nyu": {
+ "dataset": "nyu",
+ "avoid_boundary": False,
+ "min_depth": 1e-3, # originally 0.1
+ "max_depth": 10,
+ "data_path": os.path.join(HOME_DIR, "shortcuts/datasets/nyu_depth_v2/sync/"),
+ "gt_path": os.path.join(HOME_DIR, "shortcuts/datasets/nyu_depth_v2/sync/"),
+ "filenames_file": "./train_test_inputs/nyudepthv2_train_files_with_gt.txt",
+ "input_height": 480,
+ "input_width": 640,
+ "data_path_eval": os.path.join(HOME_DIR, "shortcuts/datasets/nyu_depth_v2/official_splits/test/"),
+ "gt_path_eval": os.path.join(HOME_DIR, "shortcuts/datasets/nyu_depth_v2/official_splits/test/"),
+ "filenames_file_eval": "./train_test_inputs/nyudepthv2_test_files_with_gt.txt",
+ "min_depth_eval": 1e-3,
+ "max_depth_eval": 10,
+ "min_depth_diff": -10,
+ "max_depth_diff": 10,
+
+ "do_random_rotate": True,
+ "degree": 1.0,
+ "do_kb_crop": False,
+ "garg_crop": False,
+ "eigen_crop": True
+ },
+ "ibims": {
+ "dataset": "ibims",
+ "ibims_root": os.path.join(HOME_DIR, "shortcuts/datasets/ibims/ibims1_core_raw/"),
+ "eigen_crop": True,
+ "garg_crop": False,
+ "do_kb_crop": False,
+ "min_depth_eval": 0,
+ "max_depth_eval": 10,
+ "min_depth": 1e-3,
+ "max_depth": 10
+ },
+ "sunrgbd": {
+ "dataset": "sunrgbd",
+ "sunrgbd_root": os.path.join(HOME_DIR, "shortcuts/datasets/SUNRGBD/test/"),
+ "eigen_crop": True,
+ "garg_crop": False,
+ "do_kb_crop": False,
+ "min_depth_eval": 0,
+ "max_depth_eval": 8,
+ "min_depth": 1e-3,
+ "max_depth": 10
+ },
+ "diml_indoor": {
+ "dataset": "diml_indoor",
+ "diml_indoor_root": os.path.join(HOME_DIR, "shortcuts/datasets/diml_indoor_test/"),
+ "eigen_crop": True,
+ "garg_crop": False,
+ "do_kb_crop": False,
+ "min_depth_eval": 0,
+ "max_depth_eval": 10,
+ "min_depth": 1e-3,
+ "max_depth": 10
+ },
+ "diml_outdoor": {
+ "dataset": "diml_outdoor",
+ "diml_outdoor_root": os.path.join(HOME_DIR, "shortcuts/datasets/diml_outdoor_test/"),
+ "eigen_crop": False,
+ "garg_crop": True,
+ "do_kb_crop": False,
+ "min_depth_eval": 2,
+ "max_depth_eval": 80,
+ "min_depth": 1e-3,
+ "max_depth": 80
+ },
+ "diode_indoor": {
+ "dataset": "diode_indoor",
+ "diode_indoor_root": os.path.join(HOME_DIR, "shortcuts/datasets/diode_indoor/"),
+ "eigen_crop": True,
+ "garg_crop": False,
+ "do_kb_crop": False,
+ "min_depth_eval": 1e-3,
+ "max_depth_eval": 10,
+ "min_depth": 1e-3,
+ "max_depth": 10
+ },
+ "diode_outdoor": {
+ "dataset": "diode_outdoor",
+ "diode_outdoor_root": os.path.join(HOME_DIR, "shortcuts/datasets/diode_outdoor/"),
+ "eigen_crop": False,
+ "garg_crop": True,
+ "do_kb_crop": False,
+ "min_depth_eval": 1e-3,
+ "max_depth_eval": 80,
+ "min_depth": 1e-3,
+ "max_depth": 80
+ },
+ "hypersim_test": {
+ "dataset": "hypersim_test",
+ "hypersim_test_root": os.path.join(HOME_DIR, "shortcuts/datasets/hypersim_test/"),
+ "eigen_crop": True,
+ "garg_crop": False,
+ "do_kb_crop": False,
+ "min_depth_eval": 1e-3,
+ "max_depth_eval": 80,
+ "min_depth": 1e-3,
+ "max_depth": 10
+ },
+ "vkitti": {
+ "dataset": "vkitti",
+ "vkitti_root": os.path.join(HOME_DIR, "shortcuts/datasets/vkitti_test/"),
+ "eigen_crop": False,
+ "garg_crop": True,
+ "do_kb_crop": True,
+ "min_depth_eval": 1e-3,
+ "max_depth_eval": 80,
+ "min_depth": 1e-3,
+ "max_depth": 80
+ },
+ "vkitti2": {
+ "dataset": "vkitti2",
+ "vkitti2_root": os.path.join(HOME_DIR, "shortcuts/datasets/vkitti2/"),
+ "eigen_crop": False,
+ "garg_crop": True,
+ "do_kb_crop": True,
+ "min_depth_eval": 1e-3,
+ "max_depth_eval": 80,
+ "min_depth": 1e-3,
+ "max_depth": 80,
+ },
+ "ddad": {
+ "dataset": "ddad",
+ "ddad_root": os.path.join(HOME_DIR, "shortcuts/datasets/ddad/ddad_val/"),
+ "eigen_crop": False,
+ "garg_crop": True,
+ "do_kb_crop": True,
+ "min_depth_eval": 1e-3,
+ "max_depth_eval": 80,
+ "min_depth": 1e-3,
+ "max_depth": 80,
+ },
+}
+
+ALL_INDOOR = ["nyu", "ibims", "sunrgbd", "diode_indoor", "hypersim_test"]
+ALL_OUTDOOR = ["kitti", "diml_outdoor", "diode_outdoor", "vkitti2", "ddad"]
+ALL_EVAL_DATASETS = ALL_INDOOR + ALL_OUTDOOR
+
+COMMON_TRAINING_CONFIG = {
+ "dataset": "nyu",
+ "distributed": True,
+ "workers": 16,
+ "clip_grad": 0.1,
+ "use_shared_dict": False,
+ "shared_dict": None,
+ "use_amp": False,
+
+ "aug": True,
+ "random_crop": False,
+ "random_translate": False,
+ "translate_prob": 0.2,
+ "max_translation": 100,
+
+ "validate_every": 0.25,
+ "log_images_every": 0.1,
+ "prefetch": False,
+}
+
+
+def flatten(config, except_keys=('bin_conf')):
+ def recurse(inp):
+ if isinstance(inp, dict):
+ for key, value in inp.items():
+ if key in except_keys:
+ yield (key, value)
+ if isinstance(value, dict):
+ yield from recurse(value)
+ else:
+ yield (key, value)
+
+ return dict(list(recurse(config)))
+
+
+def split_combined_args(kwargs):
+ """Splits the arguments that are combined with '__' into multiple arguments.
+ Combined arguments should have equal number of keys and values.
+ Keys are separated by '__' and Values are separated with ';'.
+ For example, '__n_bins__lr=256;0.001'
+
+ Args:
+ kwargs (dict): key-value pairs of arguments where key-value is optionally combined according to the above format.
+
+ Returns:
+ dict: Parsed dict with the combined arguments split into individual key-value pairs.
+ """
+ new_kwargs = dict(kwargs)
+ for key, value in kwargs.items():
+ if key.startswith("__"):
+ keys = key.split("__")[1:]
+ values = value.split(";")
+ assert len(keys) == len(
+ values), f"Combined arguments should have equal number of keys and values. Keys are separated by '__' and Values are separated with ';'. For example, '__n_bins__lr=256;0.001. Given (keys,values) is ({keys}, {values})"
+ for k, v in zip(keys, values):
+ new_kwargs[k] = v
+ return new_kwargs
+
+
+def parse_list(config, key, dtype=int):
+ """Parse a list of values for the key if the value is a string. The values are separated by a comma.
+ Modifies the config in place.
+ """
+ if key in config:
+ if isinstance(config[key], str):
+ config[key] = list(map(dtype, config[key].split(',')))
+ assert isinstance(config[key], list) and all([isinstance(e, dtype) for e in config[key]]
+ ), f"{key} should be a list of values dtype {dtype}. Given {config[key]} of type {type(config[key])} with values of type {[type(e) for e in config[key]]}."
+
+
+def get_model_config(model_name, model_version=None):
+ """Find and parse the .json config file for the model.
+
+ Args:
+ model_name (str): name of the model. The config file should be named config_{model_name}[_{model_version}].json under the models/{model_name} directory.
+ model_version (str, optional): Specific config version. If specified config_{model_name}_{model_version}.json is searched for and used. Otherwise config_{model_name}.json is used. Defaults to None.
+
+ Returns:
+ easydict: the config dictionary for the model.
+ """
+ config_fname = f"config_{model_name}_{model_version}.json" if model_version is not None else f"config_{model_name}.json"
+ config_file = os.path.join(ROOT, "models", model_name, config_fname)
+ if not os.path.exists(config_file):
+ return None
+
+ with open(config_file, "r") as f:
+ config = edict(json.load(f))
+
+ # handle dictionary inheritance
+ # only training config is supported for inheritance
+ if "inherit" in config.train and config.train.inherit is not None:
+ inherit_config = get_model_config(config.train["inherit"]).train
+ for key, value in inherit_config.items():
+ if key not in config.train:
+ config.train[key] = value
+ return edict(config)
+
+
+def update_model_config(config, mode, model_name, model_version=None, strict=False):
+ model_config = get_model_config(model_name, model_version)
+ if model_config is not None:
+ config = {**config, **
+ flatten({**model_config.model, **model_config[mode]})}
+ elif strict:
+ raise ValueError(f"Config file for model {model_name} not found.")
+ return config
+
+
+def check_choices(name, value, choices):
+ # return # No checks in dev branch
+ if value not in choices:
+ raise ValueError(f"{name} {value} not in supported choices {choices}")
+
+
+KEYS_TYPE_BOOL = ["use_amp", "distributed", "use_shared_dict", "same_lr", "aug", "three_phase",
+ "prefetch", "cycle_momentum"] # Casting is not necessary as their int casted values in config are 0 or 1
+
+
+def get_config(model_name, mode='train', dataset=None, **overwrite_kwargs):
+ """Main entry point to get the config for the model.
+
+ Args:
+ model_name (str): name of the desired model.
+ mode (str, optional): "train" or "infer". Defaults to 'train'.
+ dataset (str, optional): If specified, the corresponding dataset configuration is loaded as well. Defaults to None.
+
+ Keyword Args: key-value pairs of arguments to overwrite the default config.
+
+ The order of precedence for overwriting the config is (Higher precedence first):
+ # 1. overwrite_kwargs
+ # 2. "config_version": Config file version if specified in overwrite_kwargs. The corresponding config loaded is config_{model_name}_{config_version}.json
+ # 3. "version_name": Default Model version specific config specified in overwrite_kwargs. The corresponding config loaded is config_{model_name}_{version_name}.json
+ # 4. common_config: Default config for all models specified in COMMON_CONFIG
+
+ Returns:
+ easydict: The config dictionary for the model.
+ """
+
+
+ check_choices("Model", model_name, ["zoedepth", "zoedepth_nk"])
+ check_choices("Mode", mode, ["train", "infer", "eval"])
+ if mode == "train":
+ check_choices("Dataset", dataset, ["nyu", "kitti", "mix", None])
+
+ config = flatten({**COMMON_CONFIG, **COMMON_TRAINING_CONFIG})
+ config = update_model_config(config, mode, model_name)
+
+ # update with model version specific config
+ version_name = overwrite_kwargs.get("version_name", config["version_name"])
+ config = update_model_config(config, mode, model_name, version_name)
+
+ # update with config version if specified
+ config_version = overwrite_kwargs.get("config_version", None)
+ if config_version is not None:
+ print("Overwriting config with config_version", config_version)
+ config = update_model_config(config, mode, model_name, config_version)
+
+ # update with overwrite_kwargs
+ # Combined args are useful for hyperparameter search
+ overwrite_kwargs = split_combined_args(overwrite_kwargs)
+ config = {**config, **overwrite_kwargs}
+
+ # Casting to bool # TODO: Not necessary. Remove and test
+ for key in KEYS_TYPE_BOOL:
+ if key in config:
+ config[key] = bool(config[key])
+
+ # Model specific post processing of config
+ parse_list(config, "n_attractors")
+
+ # adjust n_bins for each bin configuration if bin_conf is given and n_bins is passed in overwrite_kwargs
+ if 'bin_conf' in config and 'n_bins' in overwrite_kwargs:
+ bin_conf = config['bin_conf'] # list of dicts
+ n_bins = overwrite_kwargs['n_bins']
+ new_bin_conf = []
+ for conf in bin_conf:
+ conf['n_bins'] = n_bins
+ new_bin_conf.append(conf)
+ config['bin_conf'] = new_bin_conf
+
+ if mode == "train":
+ orig_dataset = dataset
+ if dataset == "mix":
+ dataset = 'nyu' # Use nyu as default for mix. Dataset config is changed accordingly while loading the dataloader
+ if dataset is not None:
+ config['project'] = f"MonoDepth3-{orig_dataset}" # Set project for wandb
+
+ if dataset is not None:
+ config['dataset'] = dataset
+ config = {**DATASETS_CONFIG[dataset], **config}
+
+
+ config['model'] = model_name
+ typed_config = {k: infer_type(v) for k, v in config.items()}
+ # add hostname to config
+ config['hostname'] = platform.node()
+ return edict(typed_config)
+
+
+def change_dataset(config, new_dataset):
+ config.update(DATASETS_CONFIG[new_dataset])
+ return config
diff --git a/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/utils/easydict/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/utils/easydict/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..15928179b0182c6045d98bc0a7be1c6ca45f675e
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/controlnet_aux/zoe/zoedepth/utils/easydict/__init__.py
@@ -0,0 +1,158 @@
+"""
+EasyDict
+Copy/pasted from https://github.com/makinacorpus/easydict
+Original author: Mathieu Leplatre
+"""
+
+class EasyDict(dict):
+ """
+ Get attributes
+
+ >>> d = EasyDict({'foo':3})
+ >>> d['foo']
+ 3
+ >>> d.foo
+ 3
+ >>> d.bar
+ Traceback (most recent call last):
+ ...
+ AttributeError: 'EasyDict' object has no attribute 'bar'
+
+ Works recursively
+
+ >>> d = EasyDict({'foo':3, 'bar':{'x':1, 'y':2}})
+ >>> isinstance(d.bar, dict)
+ True
+ >>> d.bar.x
+ 1
+
+ Bullet-proof
+
+ >>> EasyDict({})
+ {}
+ >>> EasyDict(d={})
+ {}
+ >>> EasyDict(None)
+ {}
+ >>> d = {'a': 1}
+ >>> EasyDict(**d)
+ {'a': 1}
+ >>> EasyDict((('a', 1), ('b', 2)))
+ {'a': 1, 'b': 2}
+
+ Set attributes
+
+ >>> d = EasyDict()
+ >>> d.foo = 3
+ >>> d.foo
+ 3
+ >>> d.bar = {'prop': 'value'}
+ >>> d.bar.prop
+ 'value'
+ >>> d
+ {'foo': 3, 'bar': {'prop': 'value'}}
+ >>> d.bar.prop = 'newer'
+ >>> d.bar.prop
+ 'newer'
+
+
+ Values extraction
+
+ >>> d = EasyDict({'foo':0, 'bar':[{'x':1, 'y':2}, {'x':3, 'y':4}]})
+ >>> isinstance(d.bar, list)
+ True
+ >>> from operator import attrgetter
+ >>> list(map(attrgetter('x'), d.bar))
+ [1, 3]
+ >>> list(map(attrgetter('y'), d.bar))
+ [2, 4]
+ >>> d = EasyDict()
+ >>> list(d.keys())
+ []
+ >>> d = EasyDict(foo=3, bar=dict(x=1, y=2))
+ >>> d.foo
+ 3
+ >>> d.bar.x
+ 1
+
+ Still like a dict though
+
+ >>> o = EasyDict({'clean':True})
+ >>> list(o.items())
+ [('clean', True)]
+
+ And like a class
+
+ >>> class Flower(EasyDict):
+ ... power = 1
+ ...
+ >>> f = Flower()
+ >>> f.power
+ 1
+ >>> f = Flower({'height': 12})
+ >>> f.height
+ 12
+ >>> f['power']
+ 1
+ >>> sorted(f.keys())
+ ['height', 'power']
+
+ update and pop items
+ >>> d = EasyDict(a=1, b='2')
+ >>> e = EasyDict(c=3.0, a=9.0)
+ >>> d.update(e)
+ >>> d.c
+ 3.0
+ >>> d['c']
+ 3.0
+ >>> d.get('c')
+ 3.0
+ >>> d.update(a=4, b=4)
+ >>> d.b
+ 4
+ >>> d.pop('a')
+ 4
+ >>> d.a
+ Traceback (most recent call last):
+ ...
+ AttributeError: 'EasyDict' object has no attribute 'a'
+ """
+ def __init__(self, d=None, **kwargs):
+ if d is None:
+ d = {}
+ else:
+ d = dict(d)
+ if kwargs:
+ d.update(**kwargs)
+ for k, v in d.items():
+ setattr(self, k, v)
+ # Class attributes
+ for k in self.__class__.__dict__.keys():
+ if not (k.startswith('__') and k.endswith('__')) and not k in ('update', 'pop'):
+ setattr(self, k, getattr(self, k))
+
+ def __setattr__(self, name, value):
+ if isinstance(value, (list, tuple)):
+ value = [self.__class__(x)
+ if isinstance(x, dict) else x for x in value]
+ elif isinstance(value, dict) and not isinstance(value, self.__class__):
+ value = self.__class__(value)
+ super(EasyDict, self).__setattr__(name, value)
+ super(EasyDict, self).__setitem__(name, value)
+
+ __setitem__ = __setattr__
+
+ def update(self, e=None, **f):
+ d = e or dict()
+ d.update(f)
+ for k in d:
+ setattr(self, k, d[k])
+
+ def pop(self, k, d=None):
+ delattr(self, k)
+ return super(EasyDict, self).pop(k, d)
+
+
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod()
\ No newline at end of file
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..bdd994b49294485c27610772f97f177741f5518f
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/__init__.py
@@ -0,0 +1,10 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+from .utils.env import setup_environment
+
+setup_environment()
+
+
+# This line will be programatically read/write by setup.py.
+# Leave them at the bottom of this file and don't touch them.
+__version__ = "0.6"
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/checkpoint/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/checkpoint/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..99da0469ae7e169d8970e4b642fed3f870076860
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/checkpoint/__init__.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+# File:
+
+
+from . import catalog as _UNUSED # register the handler
+from .detection_checkpoint import DetectionCheckpointer
+from fvcore.common.checkpoint import Checkpointer, PeriodicCheckpointer
+
+__all__ = ["Checkpointer", "PeriodicCheckpointer", "DetectionCheckpointer"]
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/checkpoint/c2_model_loading.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/checkpoint/c2_model_loading.py
new file mode 100644
index 0000000000000000000000000000000000000000..c6de2a3c830089aa7a0d27df96bb4a45fc5a7b0d
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/checkpoint/c2_model_loading.py
@@ -0,0 +1,412 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import copy
+import logging
+import re
+from typing import Dict, List
+import torch
+from tabulate import tabulate
+
+
+def convert_basic_c2_names(original_keys):
+ """
+ Apply some basic name conversion to names in C2 weights.
+ It only deals with typical backbone models.
+
+ Args:
+ original_keys (list[str]):
+ Returns:
+ list[str]: The same number of strings matching those in original_keys.
+ """
+ layer_keys = copy.deepcopy(original_keys)
+ layer_keys = [
+ {"pred_b": "linear_b", "pred_w": "linear_w"}.get(k, k) for k in layer_keys
+ ] # some hard-coded mappings
+
+ layer_keys = [k.replace("_", ".") for k in layer_keys]
+ layer_keys = [re.sub("\\.b$", ".bias", k) for k in layer_keys]
+ layer_keys = [re.sub("\\.w$", ".weight", k) for k in layer_keys]
+ # Uniform both bn and gn names to "norm"
+ layer_keys = [re.sub("bn\\.s$", "norm.weight", k) for k in layer_keys]
+ layer_keys = [re.sub("bn\\.bias$", "norm.bias", k) for k in layer_keys]
+ layer_keys = [re.sub("bn\\.rm", "norm.running_mean", k) for k in layer_keys]
+ layer_keys = [re.sub("bn\\.running.mean$", "norm.running_mean", k) for k in layer_keys]
+ layer_keys = [re.sub("bn\\.riv$", "norm.running_var", k) for k in layer_keys]
+ layer_keys = [re.sub("bn\\.running.var$", "norm.running_var", k) for k in layer_keys]
+ layer_keys = [re.sub("bn\\.gamma$", "norm.weight", k) for k in layer_keys]
+ layer_keys = [re.sub("bn\\.beta$", "norm.bias", k) for k in layer_keys]
+ layer_keys = [re.sub("gn\\.s$", "norm.weight", k) for k in layer_keys]
+ layer_keys = [re.sub("gn\\.bias$", "norm.bias", k) for k in layer_keys]
+
+ # stem
+ layer_keys = [re.sub("^res\\.conv1\\.norm\\.", "conv1.norm.", k) for k in layer_keys]
+ # to avoid mis-matching with "conv1" in other components (e.g. detection head)
+ layer_keys = [re.sub("^conv1\\.", "stem.conv1.", k) for k in layer_keys]
+
+ # layer1-4 is used by torchvision, however we follow the C2 naming strategy (res2-5)
+ # layer_keys = [re.sub("^res2.", "layer1.", k) for k in layer_keys]
+ # layer_keys = [re.sub("^res3.", "layer2.", k) for k in layer_keys]
+ # layer_keys = [re.sub("^res4.", "layer3.", k) for k in layer_keys]
+ # layer_keys = [re.sub("^res5.", "layer4.", k) for k in layer_keys]
+
+ # blocks
+ layer_keys = [k.replace(".branch1.", ".shortcut.") for k in layer_keys]
+ layer_keys = [k.replace(".branch2a.", ".conv1.") for k in layer_keys]
+ layer_keys = [k.replace(".branch2b.", ".conv2.") for k in layer_keys]
+ layer_keys = [k.replace(".branch2c.", ".conv3.") for k in layer_keys]
+
+ # DensePose substitutions
+ layer_keys = [re.sub("^body.conv.fcn", "body_conv_fcn", k) for k in layer_keys]
+ layer_keys = [k.replace("AnnIndex.lowres", "ann_index_lowres") for k in layer_keys]
+ layer_keys = [k.replace("Index.UV.lowres", "index_uv_lowres") for k in layer_keys]
+ layer_keys = [k.replace("U.lowres", "u_lowres") for k in layer_keys]
+ layer_keys = [k.replace("V.lowres", "v_lowres") for k in layer_keys]
+ return layer_keys
+
+
+def convert_c2_detectron_names(weights):
+ """
+ Map Caffe2 Detectron weight names to Detectron2 names.
+
+ Args:
+ weights (dict): name -> tensor
+
+ Returns:
+ dict: detectron2 names -> tensor
+ dict: detectron2 names -> C2 names
+ """
+ logger = logging.getLogger(__name__)
+ logger.info("Renaming Caffe2 weights ......")
+ original_keys = sorted(weights.keys())
+ layer_keys = copy.deepcopy(original_keys)
+
+ layer_keys = convert_basic_c2_names(layer_keys)
+
+ # --------------------------------------------------------------------------
+ # RPN hidden representation conv
+ # --------------------------------------------------------------------------
+ # FPN case
+ # In the C2 model, the RPN hidden layer conv is defined for FPN level 2 and then
+ # shared for all other levels, hence the appearance of "fpn2"
+ layer_keys = [
+ k.replace("conv.rpn.fpn2", "proposal_generator.rpn_head.conv") for k in layer_keys
+ ]
+ # Non-FPN case
+ layer_keys = [k.replace("conv.rpn", "proposal_generator.rpn_head.conv") for k in layer_keys]
+
+ # --------------------------------------------------------------------------
+ # RPN box transformation conv
+ # --------------------------------------------------------------------------
+ # FPN case (see note above about "fpn2")
+ layer_keys = [
+ k.replace("rpn.bbox.pred.fpn2", "proposal_generator.rpn_head.anchor_deltas")
+ for k in layer_keys
+ ]
+ layer_keys = [
+ k.replace("rpn.cls.logits.fpn2", "proposal_generator.rpn_head.objectness_logits")
+ for k in layer_keys
+ ]
+ # Non-FPN case
+ layer_keys = [
+ k.replace("rpn.bbox.pred", "proposal_generator.rpn_head.anchor_deltas") for k in layer_keys
+ ]
+ layer_keys = [
+ k.replace("rpn.cls.logits", "proposal_generator.rpn_head.objectness_logits")
+ for k in layer_keys
+ ]
+
+ # --------------------------------------------------------------------------
+ # Fast R-CNN box head
+ # --------------------------------------------------------------------------
+ layer_keys = [re.sub("^bbox\\.pred", "bbox_pred", k) for k in layer_keys]
+ layer_keys = [re.sub("^cls\\.score", "cls_score", k) for k in layer_keys]
+ layer_keys = [re.sub("^fc6\\.", "box_head.fc1.", k) for k in layer_keys]
+ layer_keys = [re.sub("^fc7\\.", "box_head.fc2.", k) for k in layer_keys]
+ # 4conv1fc head tensor names: head_conv1_w, head_conv1_gn_s
+ layer_keys = [re.sub("^head\\.conv", "box_head.conv", k) for k in layer_keys]
+
+ # --------------------------------------------------------------------------
+ # FPN lateral and output convolutions
+ # --------------------------------------------------------------------------
+ def fpn_map(name):
+ """
+ Look for keys with the following patterns:
+ 1) Starts with "fpn.inner."
+ Example: "fpn.inner.res2.2.sum.lateral.weight"
+ Meaning: These are lateral pathway convolutions
+ 2) Starts with "fpn.res"
+ Example: "fpn.res2.2.sum.weight"
+ Meaning: These are FPN output convolutions
+ """
+ splits = name.split(".")
+ norm = ".norm" if "norm" in splits else ""
+ if name.startswith("fpn.inner."):
+ # splits example: ['fpn', 'inner', 'res2', '2', 'sum', 'lateral', 'weight']
+ stage = int(splits[2][len("res") :])
+ return "fpn_lateral{}{}.{}".format(stage, norm, splits[-1])
+ elif name.startswith("fpn.res"):
+ # splits example: ['fpn', 'res2', '2', 'sum', 'weight']
+ stage = int(splits[1][len("res") :])
+ return "fpn_output{}{}.{}".format(stage, norm, splits[-1])
+ return name
+
+ layer_keys = [fpn_map(k) for k in layer_keys]
+
+ # --------------------------------------------------------------------------
+ # Mask R-CNN mask head
+ # --------------------------------------------------------------------------
+ # roi_heads.StandardROIHeads case
+ layer_keys = [k.replace(".[mask].fcn", "mask_head.mask_fcn") for k in layer_keys]
+ layer_keys = [re.sub("^\\.mask\\.fcn", "mask_head.mask_fcn", k) for k in layer_keys]
+ layer_keys = [k.replace("mask.fcn.logits", "mask_head.predictor") for k in layer_keys]
+ # roi_heads.Res5ROIHeads case
+ layer_keys = [k.replace("conv5.mask", "mask_head.deconv") for k in layer_keys]
+
+ # --------------------------------------------------------------------------
+ # Keypoint R-CNN head
+ # --------------------------------------------------------------------------
+ # interestingly, the keypoint head convs have blob names that are simply "conv_fcnX"
+ layer_keys = [k.replace("conv.fcn", "roi_heads.keypoint_head.conv_fcn") for k in layer_keys]
+ layer_keys = [
+ k.replace("kps.score.lowres", "roi_heads.keypoint_head.score_lowres") for k in layer_keys
+ ]
+ layer_keys = [k.replace("kps.score.", "roi_heads.keypoint_head.score.") for k in layer_keys]
+
+ # --------------------------------------------------------------------------
+ # Done with replacements
+ # --------------------------------------------------------------------------
+ assert len(set(layer_keys)) == len(layer_keys)
+ assert len(original_keys) == len(layer_keys)
+
+ new_weights = {}
+ new_keys_to_original_keys = {}
+ for orig, renamed in zip(original_keys, layer_keys):
+ new_keys_to_original_keys[renamed] = orig
+ if renamed.startswith("bbox_pred.") or renamed.startswith("mask_head.predictor."):
+ # remove the meaningless prediction weight for background class
+ new_start_idx = 4 if renamed.startswith("bbox_pred.") else 1
+ new_weights[renamed] = weights[orig][new_start_idx:]
+ logger.info(
+ "Remove prediction weight for background class in {}. The shape changes from "
+ "{} to {}.".format(
+ renamed, tuple(weights[orig].shape), tuple(new_weights[renamed].shape)
+ )
+ )
+ elif renamed.startswith("cls_score."):
+ # move weights of bg class from original index 0 to last index
+ logger.info(
+ "Move classification weights for background class in {} from index 0 to "
+ "index {}.".format(renamed, weights[orig].shape[0] - 1)
+ )
+ new_weights[renamed] = torch.cat([weights[orig][1:], weights[orig][:1]])
+ else:
+ new_weights[renamed] = weights[orig]
+
+ return new_weights, new_keys_to_original_keys
+
+
+# Note the current matching is not symmetric.
+# it assumes model_state_dict will have longer names.
+def align_and_update_state_dicts(model_state_dict, ckpt_state_dict, c2_conversion=True):
+ """
+ Match names between the two state-dict, and returns a new chkpt_state_dict with names
+ converted to match model_state_dict with heuristics. The returned dict can be later
+ loaded with fvcore checkpointer.
+ If `c2_conversion==True`, `ckpt_state_dict` is assumed to be a Caffe2
+ model and will be renamed at first.
+
+ Strategy: suppose that the models that we will create will have prefixes appended
+ to each of its keys, for example due to an extra level of nesting that the original
+ pre-trained weights from ImageNet won't contain. For example, model.state_dict()
+ might return backbone[0].body.res2.conv1.weight, while the pre-trained model contains
+ res2.conv1.weight. We thus want to match both parameters together.
+ For that, we look for each model weight, look among all loaded keys if there is one
+ that is a suffix of the current weight name, and use it if that's the case.
+ If multiple matches exist, take the one with longest size
+ of the corresponding name. For example, for the same model as before, the pretrained
+ weight file can contain both res2.conv1.weight, as well as conv1.weight. In this case,
+ we want to match backbone[0].body.conv1.weight to conv1.weight, and
+ backbone[0].body.res2.conv1.weight to res2.conv1.weight.
+ """
+ model_keys = sorted(model_state_dict.keys())
+ if c2_conversion:
+ ckpt_state_dict, original_keys = convert_c2_detectron_names(ckpt_state_dict)
+ # original_keys: the name in the original dict (before renaming)
+ else:
+ original_keys = {x: x for x in ckpt_state_dict.keys()}
+ ckpt_keys = sorted(ckpt_state_dict.keys())
+
+ def match(a, b):
+ # Matched ckpt_key should be a complete (starts with '.') suffix.
+ # For example, roi_heads.mesh_head.whatever_conv1 does not match conv1,
+ # but matches whatever_conv1 or mesh_head.whatever_conv1.
+ return a == b or a.endswith("." + b)
+
+ # get a matrix of string matches, where each (i, j) entry correspond to the size of the
+ # ckpt_key string, if it matches
+ match_matrix = [len(j) if match(i, j) else 0 for i in model_keys for j in ckpt_keys]
+ match_matrix = torch.as_tensor(match_matrix).view(len(model_keys), len(ckpt_keys))
+ # use the matched one with longest size in case of multiple matches
+ max_match_size, idxs = match_matrix.max(1)
+ # remove indices that correspond to no-match
+ idxs[max_match_size == 0] = -1
+
+ logger = logging.getLogger(__name__)
+ # matched_pairs (matched checkpoint key --> matched model key)
+ matched_keys = {}
+ result_state_dict = {}
+ for idx_model, idx_ckpt in enumerate(idxs.tolist()):
+ if idx_ckpt == -1:
+ continue
+ key_model = model_keys[idx_model]
+ key_ckpt = ckpt_keys[idx_ckpt]
+ value_ckpt = ckpt_state_dict[key_ckpt]
+ shape_in_model = model_state_dict[key_model].shape
+
+ if shape_in_model != value_ckpt.shape:
+ logger.warning(
+ "Shape of {} in checkpoint is {}, while shape of {} in model is {}.".format(
+ key_ckpt, value_ckpt.shape, key_model, shape_in_model
+ )
+ )
+ logger.warning(
+ "{} will not be loaded. Please double check and see if this is desired.".format(
+ key_ckpt
+ )
+ )
+ continue
+
+ assert key_model not in result_state_dict
+ result_state_dict[key_model] = value_ckpt
+ if key_ckpt in matched_keys: # already added to matched_keys
+ logger.error(
+ "Ambiguity found for {} in checkpoint!"
+ "It matches at least two keys in the model ({} and {}).".format(
+ key_ckpt, key_model, matched_keys[key_ckpt]
+ )
+ )
+ raise ValueError("Cannot match one checkpoint key to multiple keys in the model.")
+
+ matched_keys[key_ckpt] = key_model
+
+ # logging:
+ matched_model_keys = sorted(matched_keys.values())
+ if len(matched_model_keys) == 0:
+ logger.warning("No weights in checkpoint matched with model.")
+ return ckpt_state_dict
+ common_prefix = _longest_common_prefix(matched_model_keys)
+ rev_matched_keys = {v: k for k, v in matched_keys.items()}
+ original_keys = {k: original_keys[rev_matched_keys[k]] for k in matched_model_keys}
+
+ model_key_groups = _group_keys_by_module(matched_model_keys, original_keys)
+ table = []
+ memo = set()
+ for key_model in matched_model_keys:
+ if key_model in memo:
+ continue
+ if key_model in model_key_groups:
+ group = model_key_groups[key_model]
+ memo |= set(group)
+ shapes = [tuple(model_state_dict[k].shape) for k in group]
+ table.append(
+ (
+ _longest_common_prefix([k[len(common_prefix) :] for k in group]) + "*",
+ _group_str([original_keys[k] for k in group]),
+ " ".join([str(x).replace(" ", "") for x in shapes]),
+ )
+ )
+ else:
+ key_checkpoint = original_keys[key_model]
+ shape = str(tuple(model_state_dict[key_model].shape))
+ table.append((key_model[len(common_prefix) :], key_checkpoint, shape))
+ table_str = tabulate(
+ table, tablefmt="pipe", headers=["Names in Model", "Names in Checkpoint", "Shapes"]
+ )
+ logger.info(
+ "Following weights matched with "
+ + (f"submodule {common_prefix[:-1]}" if common_prefix else "model")
+ + ":\n"
+ + table_str
+ )
+
+ unmatched_ckpt_keys = [k for k in ckpt_keys if k not in set(matched_keys.keys())]
+ for k in unmatched_ckpt_keys:
+ result_state_dict[k] = ckpt_state_dict[k]
+ return result_state_dict
+
+
+def _group_keys_by_module(keys: List[str], original_names: Dict[str, str]):
+ """
+ Params in the same submodule are grouped together.
+
+ Args:
+ keys: names of all parameters
+ original_names: mapping from parameter name to their name in the checkpoint
+
+ Returns:
+ dict[name -> all other names in the same group]
+ """
+
+ def _submodule_name(key):
+ pos = key.rfind(".")
+ if pos < 0:
+ return None
+ prefix = key[: pos + 1]
+ return prefix
+
+ all_submodules = [_submodule_name(k) for k in keys]
+ all_submodules = [x for x in all_submodules if x]
+ all_submodules = sorted(all_submodules, key=len)
+
+ ret = {}
+ for prefix in all_submodules:
+ group = [k for k in keys if k.startswith(prefix)]
+ if len(group) <= 1:
+ continue
+ original_name_lcp = _longest_common_prefix_str([original_names[k] for k in group])
+ if len(original_name_lcp) == 0:
+ # don't group weights if original names don't share prefix
+ continue
+
+ for k in group:
+ if k in ret:
+ continue
+ ret[k] = group
+ return ret
+
+
+def _longest_common_prefix(names: List[str]) -> str:
+ """
+ ["abc.zfg", "abc.zef"] -> "abc."
+ """
+ names = [n.split(".") for n in names]
+ m1, m2 = min(names), max(names)
+ ret = [a for a, b in zip(m1, m2) if a == b]
+ ret = ".".join(ret) + "." if len(ret) else ""
+ return ret
+
+
+def _longest_common_prefix_str(names: List[str]) -> str:
+ m1, m2 = min(names), max(names)
+ lcp = []
+ for a, b in zip(m1, m2):
+ if a == b:
+ lcp.append(a)
+ else:
+ break
+ lcp = "".join(lcp)
+ return lcp
+
+
+def _group_str(names: List[str]) -> str:
+ """
+ Turn "common1", "common2", "common3" into "common{1,2,3}"
+ """
+ lcp = _longest_common_prefix_str(names)
+ rest = [x[len(lcp) :] for x in names]
+ rest = "{" + ",".join(rest) + "}"
+ ret = lcp + rest
+
+ # add some simplification for BN specifically
+ ret = ret.replace("bn_{beta,running_mean,running_var,gamma}", "bn_*")
+ ret = ret.replace("bn_beta,bn_running_mean,bn_running_var,bn_gamma", "bn_*")
+ return ret
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/checkpoint/catalog.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/checkpoint/catalog.py
new file mode 100644
index 0000000000000000000000000000000000000000..2287e9b7fbf4d3ed7ec9bdb26d6d1d4d8ed91196
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/checkpoint/catalog.py
@@ -0,0 +1,114 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import logging
+
+from custom_detectron2.utils.file_io import PathHandler, PathManager
+
+class ModelCatalog(object):
+ """
+ Store mappings from names to third-party models.
+ """
+
+ S3_C2_DETECTRON_PREFIX = "https://dl.fbaipublicfiles.com/detectron"
+
+ # MSRA models have STRIDE_IN_1X1=True. False otherwise.
+ # NOTE: all BN models here have fused BN into an affine layer.
+ # As a result, you should only load them to a model with "FrozenBN".
+ # Loading them to a model with regular BN or SyncBN is wrong.
+ # Even when loaded to FrozenBN, it is still different from affine by an epsilon,
+ # which should be negligible for training.
+ # NOTE: all models here uses PIXEL_STD=[1,1,1]
+ # NOTE: Most of the BN models here are no longer used. We use the
+ # re-converted pre-trained models under detectron2 model zoo instead.
+ C2_IMAGENET_MODELS = {
+ "MSRA/R-50": "ImageNetPretrained/MSRA/R-50.pkl",
+ "MSRA/R-101": "ImageNetPretrained/MSRA/R-101.pkl",
+ "FAIR/R-50-GN": "ImageNetPretrained/47261647/R-50-GN.pkl",
+ "FAIR/R-101-GN": "ImageNetPretrained/47592356/R-101-GN.pkl",
+ "FAIR/X-101-32x8d": "ImageNetPretrained/20171220/X-101-32x8d.pkl",
+ "FAIR/X-101-64x4d": "ImageNetPretrained/FBResNeXt/X-101-64x4d.pkl",
+ "FAIR/X-152-32x8d-IN5k": "ImageNetPretrained/25093814/X-152-32x8d-IN5k.pkl",
+ }
+
+ C2_DETECTRON_PATH_FORMAT = (
+ "{prefix}/{url}/output/train/{dataset}/{type}/model_final.pkl" # noqa B950
+ )
+
+ C2_DATASET_COCO = "coco_2014_train%3Acoco_2014_valminusminival"
+ C2_DATASET_COCO_KEYPOINTS = "keypoints_coco_2014_train%3Akeypoints_coco_2014_valminusminival"
+
+ # format: {model_name} -> part of the url
+ C2_DETECTRON_MODELS = {
+ "35857197/e2e_faster_rcnn_R-50-C4_1x": "35857197/12_2017_baselines/e2e_faster_rcnn_R-50-C4_1x.yaml.01_33_49.iAX0mXvW", # noqa B950
+ "35857345/e2e_faster_rcnn_R-50-FPN_1x": "35857345/12_2017_baselines/e2e_faster_rcnn_R-50-FPN_1x.yaml.01_36_30.cUF7QR7I", # noqa B950
+ "35857890/e2e_faster_rcnn_R-101-FPN_1x": "35857890/12_2017_baselines/e2e_faster_rcnn_R-101-FPN_1x.yaml.01_38_50.sNxI7sX7", # noqa B950
+ "36761737/e2e_faster_rcnn_X-101-32x8d-FPN_1x": "36761737/12_2017_baselines/e2e_faster_rcnn_X-101-32x8d-FPN_1x.yaml.06_31_39.5MIHi1fZ", # noqa B950
+ "35858791/e2e_mask_rcnn_R-50-C4_1x": "35858791/12_2017_baselines/e2e_mask_rcnn_R-50-C4_1x.yaml.01_45_57.ZgkA7hPB", # noqa B950
+ "35858933/e2e_mask_rcnn_R-50-FPN_1x": "35858933/12_2017_baselines/e2e_mask_rcnn_R-50-FPN_1x.yaml.01_48_14.DzEQe4wC", # noqa B950
+ "35861795/e2e_mask_rcnn_R-101-FPN_1x": "35861795/12_2017_baselines/e2e_mask_rcnn_R-101-FPN_1x.yaml.02_31_37.KqyEK4tT", # noqa B950
+ "36761843/e2e_mask_rcnn_X-101-32x8d-FPN_1x": "36761843/12_2017_baselines/e2e_mask_rcnn_X-101-32x8d-FPN_1x.yaml.06_35_59.RZotkLKI", # noqa B950
+ "48616381/e2e_mask_rcnn_R-50-FPN_2x_gn": "GN/48616381/04_2018_gn_baselines/e2e_mask_rcnn_R-50-FPN_2x_gn_0416.13_23_38.bTlTI97Q", # noqa B950
+ "37697547/e2e_keypoint_rcnn_R-50-FPN_1x": "37697547/12_2017_baselines/e2e_keypoint_rcnn_R-50-FPN_1x.yaml.08_42_54.kdzV35ao", # noqa B950
+ "35998355/rpn_R-50-C4_1x": "35998355/12_2017_baselines/rpn_R-50-C4_1x.yaml.08_00_43.njH5oD9L", # noqa B950
+ "35998814/rpn_R-50-FPN_1x": "35998814/12_2017_baselines/rpn_R-50-FPN_1x.yaml.08_06_03.Axg0r179", # noqa B950
+ "36225147/fast_R-50-FPN_1x": "36225147/12_2017_baselines/fast_rcnn_R-50-FPN_1x.yaml.08_39_09.L3obSdQ2", # noqa B950
+ }
+
+ @staticmethod
+ def get(name):
+ if name.startswith("Caffe2Detectron/COCO"):
+ return ModelCatalog._get_c2_detectron_baseline(name)
+ if name.startswith("ImageNetPretrained/"):
+ return ModelCatalog._get_c2_imagenet_pretrained(name)
+ raise RuntimeError("model not present in the catalog: {}".format(name))
+
+ @staticmethod
+ def _get_c2_imagenet_pretrained(name):
+ prefix = ModelCatalog.S3_C2_DETECTRON_PREFIX
+ name = name[len("ImageNetPretrained/") :]
+ name = ModelCatalog.C2_IMAGENET_MODELS[name]
+ url = "/".join([prefix, name])
+ return url
+
+ @staticmethod
+ def _get_c2_detectron_baseline(name):
+ name = name[len("Caffe2Detectron/COCO/") :]
+ url = ModelCatalog.C2_DETECTRON_MODELS[name]
+ if "keypoint_rcnn" in name:
+ dataset = ModelCatalog.C2_DATASET_COCO_KEYPOINTS
+ else:
+ dataset = ModelCatalog.C2_DATASET_COCO
+
+ if "35998355/rpn_R-50-C4_1x" in name:
+ # this one model is somehow different from others ..
+ type = "rpn"
+ else:
+ type = "generalized_rcnn"
+
+ # Detectron C2 models are stored in the structure defined in `C2_DETECTRON_PATH_FORMAT`.
+ url = ModelCatalog.C2_DETECTRON_PATH_FORMAT.format(
+ prefix=ModelCatalog.S3_C2_DETECTRON_PREFIX, url=url, type=type, dataset=dataset
+ )
+ return url
+
+
+class ModelCatalogHandler(PathHandler):
+ """
+ Resolve URL like catalog://.
+ """
+
+ PREFIX = "catalog://"
+
+ def _get_supported_prefixes(self):
+ return [self.PREFIX]
+
+ def _get_local_path(self, path, **kwargs):
+ logger = logging.getLogger(__name__)
+ catalog_path = ModelCatalog.get(path[len(self.PREFIX) :])
+ logger.info("Catalog entry {} points to {}".format(path, catalog_path))
+ return PathManager.get_local_path(catalog_path, **kwargs)
+
+ def _open(self, path, mode="r", **kwargs):
+ return PathManager.open(self._get_local_path(path), mode, **kwargs)
+
+
+PathManager.register_handler(ModelCatalogHandler())
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/checkpoint/detection_checkpoint.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/checkpoint/detection_checkpoint.py
new file mode 100644
index 0000000000000000000000000000000000000000..2776783ac380d931ded4af69fc5e08bab5ab159a
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/checkpoint/detection_checkpoint.py
@@ -0,0 +1,145 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import logging
+import os
+import pickle
+from urllib.parse import parse_qs, urlparse
+import torch
+from fvcore.common.checkpoint import Checkpointer
+from torch.nn.parallel import DistributedDataParallel
+
+import custom_detectron2.utils.comm as comm
+from custom_detectron2.utils.file_io import PathManager
+
+from .c2_model_loading import align_and_update_state_dicts
+
+
+class DetectionCheckpointer(Checkpointer):
+ """
+ Same as :class:`Checkpointer`, but is able to:
+ 1. handle models in detectron & detectron2 model zoo, and apply conversions for legacy models.
+ 2. correctly load checkpoints that are only available on the master worker
+ """
+
+ def __init__(self, model, save_dir="", *, save_to_disk=None, **checkpointables):
+ is_main_process = comm.is_main_process()
+ super().__init__(
+ model,
+ save_dir,
+ save_to_disk=is_main_process if save_to_disk is None else save_to_disk,
+ **checkpointables,
+ )
+ self.path_manager = PathManager
+ self._parsed_url_during_load = None
+
+ def load(self, path, *args, **kwargs):
+ assert self._parsed_url_during_load is None
+ need_sync = False
+ logger = logging.getLogger(__name__)
+ logger.info("[DetectionCheckpointer] Loading from {} ...".format(path))
+
+ if path and isinstance(self.model, DistributedDataParallel):
+ path = self.path_manager.get_local_path(path)
+ has_file = os.path.isfile(path)
+ all_has_file = comm.all_gather(has_file)
+ if not all_has_file[0]:
+ raise OSError(f"File {path} not found on main worker.")
+ if not all(all_has_file):
+ logger.warning(
+ f"Not all workers can read checkpoint {path}. "
+ "Training may fail to fully resume."
+ )
+ # TODO: broadcast the checkpoint file contents from main
+ # worker, and load from it instead.
+ need_sync = True
+ if not has_file:
+ path = None # don't load if not readable
+
+ if path:
+ parsed_url = urlparse(path)
+ self._parsed_url_during_load = parsed_url
+ path = parsed_url._replace(query="").geturl() # remove query from filename
+ path = self.path_manager.get_local_path(path)
+
+ self.logger.setLevel('CRITICAL')
+ ret = super().load(path, *args, **kwargs)
+
+ if need_sync:
+ logger.info("Broadcasting model states from main worker ...")
+ self.model._sync_params_and_buffers()
+ self._parsed_url_during_load = None # reset to None
+ return ret
+
+ def _load_file(self, filename):
+ if filename.endswith(".pkl"):
+ with PathManager.open(filename, "rb") as f:
+ data = pickle.load(f, encoding="latin1")
+ if "model" in data and "__author__" in data:
+ # file is in Detectron2 model zoo format
+ self.logger.info("Reading a file from '{}'".format(data["__author__"]))
+ return data
+ else:
+ # assume file is from Caffe2 / Detectron1 model zoo
+ if "blobs" in data:
+ # Detection models have "blobs", but ImageNet models don't
+ data = data["blobs"]
+ data = {k: v for k, v in data.items() if not k.endswith("_momentum")}
+ return {"model": data, "__author__": "Caffe2", "matching_heuristics": True}
+ elif filename.endswith(".pyth"):
+ # assume file is from pycls; no one else seems to use the ".pyth" extension
+ with PathManager.open(filename, "rb") as f:
+ data = torch.load(f)
+ assert (
+ "model_state" in data
+ ), f"Cannot load .pyth file {filename}; pycls checkpoints must contain 'model_state'."
+ model_state = {
+ k: v
+ for k, v in data["model_state"].items()
+ if not k.endswith("num_batches_tracked")
+ }
+ return {"model": model_state, "__author__": "pycls", "matching_heuristics": True}
+
+ loaded = self._torch_load(filename)
+ if "model" not in loaded:
+ loaded = {"model": loaded}
+ assert self._parsed_url_during_load is not None, "`_load_file` must be called inside `load`"
+ parsed_url = self._parsed_url_during_load
+ queries = parse_qs(parsed_url.query)
+ if queries.pop("matching_heuristics", "False") == ["True"]:
+ loaded["matching_heuristics"] = True
+ if len(queries) > 0:
+ raise ValueError(
+ f"Unsupported query remaining: f{queries}, orginal filename: {parsed_url.geturl()}"
+ )
+ return loaded
+
+ def _torch_load(self, f):
+ return super()._load_file(f)
+
+ def _load_model(self, checkpoint):
+ if checkpoint.get("matching_heuristics", False):
+ self._convert_ndarray_to_tensor(checkpoint["model"])
+ # convert weights by name-matching heuristics
+ checkpoint["model"] = align_and_update_state_dicts(
+ self.model.state_dict(),
+ checkpoint["model"],
+ c2_conversion=checkpoint.get("__author__", None) == "Caffe2",
+ )
+ # for non-caffe2 models, use standard ways to load it
+ incompatible = super()._load_model(checkpoint)
+
+ model_buffers = dict(self.model.named_buffers(recurse=False))
+ for k in ["pixel_mean", "pixel_std"]:
+ # Ignore missing key message about pixel_mean/std.
+ # Though they may be missing in old checkpoints, they will be correctly
+ # initialized from config anyway.
+ if k in model_buffers:
+ try:
+ incompatible.missing_keys.remove(k)
+ except ValueError:
+ pass
+ for k in incompatible.unexpected_keys[:]:
+ # Ignore unexpected keys about cell anchors. They exist in old checkpoints
+ # but now they are non-persistent buffers and will not be in new checkpoints.
+ if "anchor_generator.cell_anchors" in k:
+ incompatible.unexpected_keys.remove(k)
+ return incompatible
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/config/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/config/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..b98b0872b423a665525d25ab8c203c217543e149
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/config/__init__.py
@@ -0,0 +1,24 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+from .compat import downgrade_config, upgrade_config
+from .config import CfgNode, get_cfg, global_cfg, set_global_cfg, configurable
+from .instantiate import instantiate
+from .lazy import LazyCall, LazyConfig
+
+__all__ = [
+ "CfgNode",
+ "get_cfg",
+ "global_cfg",
+ "set_global_cfg",
+ "downgrade_config",
+ "upgrade_config",
+ "configurable",
+ "instantiate",
+ "LazyCall",
+ "LazyConfig",
+]
+
+
+from custom_detectron2.utils.env import fixup_module_metadata
+
+fixup_module_metadata(__name__, globals(), __all__)
+del fixup_module_metadata
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/config/compat.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/config/compat.py
new file mode 100644
index 0000000000000000000000000000000000000000..11a08c439bf14defd880e37a938fab8a08e68eeb
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/config/compat.py
@@ -0,0 +1,229 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+"""
+Backward compatibility of configs.
+
+Instructions to bump version:
++ It's not needed to bump version if new keys are added.
+ It's only needed when backward-incompatible changes happen
+ (i.e., some existing keys disappear, or the meaning of a key changes)
++ To bump version, do the following:
+ 1. Increment _C.VERSION in defaults.py
+ 2. Add a converter in this file.
+
+ Each ConverterVX has a function "upgrade" which in-place upgrades config from X-1 to X,
+ and a function "downgrade" which in-place downgrades config from X to X-1
+
+ In each function, VERSION is left unchanged.
+
+ Each converter assumes that its input has the relevant keys
+ (i.e., the input is not a partial config).
+ 3. Run the tests (test_config.py) to make sure the upgrade & downgrade
+ functions are consistent.
+"""
+
+import logging
+from typing import List, Optional, Tuple
+
+from .config import CfgNode as CN
+from .defaults import _C
+
+__all__ = ["upgrade_config", "downgrade_config"]
+
+
+def upgrade_config(cfg: CN, to_version: Optional[int] = None) -> CN:
+ """
+ Upgrade a config from its current version to a newer version.
+
+ Args:
+ cfg (CfgNode):
+ to_version (int): defaults to the latest version.
+ """
+ cfg = cfg.clone()
+ if to_version is None:
+ to_version = _C.VERSION
+
+ assert cfg.VERSION <= to_version, "Cannot upgrade from v{} to v{}!".format(
+ cfg.VERSION, to_version
+ )
+ for k in range(cfg.VERSION, to_version):
+ converter = globals()["ConverterV" + str(k + 1)]
+ converter.upgrade(cfg)
+ cfg.VERSION = k + 1
+ return cfg
+
+
+def downgrade_config(cfg: CN, to_version: int) -> CN:
+ """
+ Downgrade a config from its current version to an older version.
+
+ Args:
+ cfg (CfgNode):
+ to_version (int):
+
+ Note:
+ A general downgrade of arbitrary configs is not always possible due to the
+ different functionalities in different versions.
+ The purpose of downgrade is only to recover the defaults in old versions,
+ allowing it to load an old partial yaml config.
+ Therefore, the implementation only needs to fill in the default values
+ in the old version when a general downgrade is not possible.
+ """
+ cfg = cfg.clone()
+ assert cfg.VERSION >= to_version, "Cannot downgrade from v{} to v{}!".format(
+ cfg.VERSION, to_version
+ )
+ for k in range(cfg.VERSION, to_version, -1):
+ converter = globals()["ConverterV" + str(k)]
+ converter.downgrade(cfg)
+ cfg.VERSION = k - 1
+ return cfg
+
+
+def guess_version(cfg: CN, filename: str) -> int:
+ """
+ Guess the version of a partial config where the VERSION field is not specified.
+ Returns the version, or the latest if cannot make a guess.
+
+ This makes it easier for users to migrate.
+ """
+ logger = logging.getLogger(__name__)
+
+ def _has(name: str) -> bool:
+ cur = cfg
+ for n in name.split("."):
+ if n not in cur:
+ return False
+ cur = cur[n]
+ return True
+
+ # Most users' partial configs have "MODEL.WEIGHT", so guess on it
+ ret = None
+ if _has("MODEL.WEIGHT") or _has("TEST.AUG_ON"):
+ ret = 1
+
+ if ret is not None:
+ logger.warning("Config '{}' has no VERSION. Assuming it to be v{}.".format(filename, ret))
+ else:
+ ret = _C.VERSION
+ logger.warning(
+ "Config '{}' has no VERSION. Assuming it to be compatible with latest v{}.".format(
+ filename, ret
+ )
+ )
+ return ret
+
+
+def _rename(cfg: CN, old: str, new: str) -> None:
+ old_keys = old.split(".")
+ new_keys = new.split(".")
+
+ def _set(key_seq: List[str], val: str) -> None:
+ cur = cfg
+ for k in key_seq[:-1]:
+ if k not in cur:
+ cur[k] = CN()
+ cur = cur[k]
+ cur[key_seq[-1]] = val
+
+ def _get(key_seq: List[str]) -> CN:
+ cur = cfg
+ for k in key_seq:
+ cur = cur[k]
+ return cur
+
+ def _del(key_seq: List[str]) -> None:
+ cur = cfg
+ for k in key_seq[:-1]:
+ cur = cur[k]
+ del cur[key_seq[-1]]
+ if len(cur) == 0 and len(key_seq) > 1:
+ _del(key_seq[:-1])
+
+ _set(new_keys, _get(old_keys))
+ _del(old_keys)
+
+
+class _RenameConverter:
+ """
+ A converter that handles simple rename.
+ """
+
+ RENAME: List[Tuple[str, str]] = [] # list of tuples of (old name, new name)
+
+ @classmethod
+ def upgrade(cls, cfg: CN) -> None:
+ for old, new in cls.RENAME:
+ _rename(cfg, old, new)
+
+ @classmethod
+ def downgrade(cls, cfg: CN) -> None:
+ for old, new in cls.RENAME[::-1]:
+ _rename(cfg, new, old)
+
+
+class ConverterV1(_RenameConverter):
+ RENAME = [("MODEL.RPN_HEAD.NAME", "MODEL.RPN.HEAD_NAME")]
+
+
+class ConverterV2(_RenameConverter):
+ """
+ A large bulk of rename, before public release.
+ """
+
+ RENAME = [
+ ("MODEL.WEIGHT", "MODEL.WEIGHTS"),
+ ("MODEL.PANOPTIC_FPN.SEMANTIC_LOSS_SCALE", "MODEL.SEM_SEG_HEAD.LOSS_WEIGHT"),
+ ("MODEL.PANOPTIC_FPN.RPN_LOSS_SCALE", "MODEL.RPN.LOSS_WEIGHT"),
+ ("MODEL.PANOPTIC_FPN.INSTANCE_LOSS_SCALE", "MODEL.PANOPTIC_FPN.INSTANCE_LOSS_WEIGHT"),
+ ("MODEL.PANOPTIC_FPN.COMBINE_ON", "MODEL.PANOPTIC_FPN.COMBINE.ENABLED"),
+ (
+ "MODEL.PANOPTIC_FPN.COMBINE_OVERLAP_THRESHOLD",
+ "MODEL.PANOPTIC_FPN.COMBINE.OVERLAP_THRESH",
+ ),
+ (
+ "MODEL.PANOPTIC_FPN.COMBINE_STUFF_AREA_LIMIT",
+ "MODEL.PANOPTIC_FPN.COMBINE.STUFF_AREA_LIMIT",
+ ),
+ (
+ "MODEL.PANOPTIC_FPN.COMBINE_INSTANCES_CONFIDENCE_THRESHOLD",
+ "MODEL.PANOPTIC_FPN.COMBINE.INSTANCES_CONFIDENCE_THRESH",
+ ),
+ ("MODEL.ROI_HEADS.SCORE_THRESH", "MODEL.ROI_HEADS.SCORE_THRESH_TEST"),
+ ("MODEL.ROI_HEADS.NMS", "MODEL.ROI_HEADS.NMS_THRESH_TEST"),
+ ("MODEL.RETINANET.INFERENCE_SCORE_THRESHOLD", "MODEL.RETINANET.SCORE_THRESH_TEST"),
+ ("MODEL.RETINANET.INFERENCE_TOPK_CANDIDATES", "MODEL.RETINANET.TOPK_CANDIDATES_TEST"),
+ ("MODEL.RETINANET.INFERENCE_NMS_THRESHOLD", "MODEL.RETINANET.NMS_THRESH_TEST"),
+ ("TEST.DETECTIONS_PER_IMG", "TEST.DETECTIONS_PER_IMAGE"),
+ ("TEST.AUG_ON", "TEST.AUG.ENABLED"),
+ ("TEST.AUG_MIN_SIZES", "TEST.AUG.MIN_SIZES"),
+ ("TEST.AUG_MAX_SIZE", "TEST.AUG.MAX_SIZE"),
+ ("TEST.AUG_FLIP", "TEST.AUG.FLIP"),
+ ]
+
+ @classmethod
+ def upgrade(cls, cfg: CN) -> None:
+ super().upgrade(cfg)
+
+ if cfg.MODEL.META_ARCHITECTURE == "RetinaNet":
+ _rename(
+ cfg, "MODEL.RETINANET.ANCHOR_ASPECT_RATIOS", "MODEL.ANCHOR_GENERATOR.ASPECT_RATIOS"
+ )
+ _rename(cfg, "MODEL.RETINANET.ANCHOR_SIZES", "MODEL.ANCHOR_GENERATOR.SIZES")
+ del cfg["MODEL"]["RPN"]["ANCHOR_SIZES"]
+ del cfg["MODEL"]["RPN"]["ANCHOR_ASPECT_RATIOS"]
+ else:
+ _rename(cfg, "MODEL.RPN.ANCHOR_ASPECT_RATIOS", "MODEL.ANCHOR_GENERATOR.ASPECT_RATIOS")
+ _rename(cfg, "MODEL.RPN.ANCHOR_SIZES", "MODEL.ANCHOR_GENERATOR.SIZES")
+ del cfg["MODEL"]["RETINANET"]["ANCHOR_SIZES"]
+ del cfg["MODEL"]["RETINANET"]["ANCHOR_ASPECT_RATIOS"]
+ del cfg["MODEL"]["RETINANET"]["ANCHOR_STRIDES"]
+
+ @classmethod
+ def downgrade(cls, cfg: CN) -> None:
+ super().downgrade(cfg)
+
+ _rename(cfg, "MODEL.ANCHOR_GENERATOR.ASPECT_RATIOS", "MODEL.RPN.ANCHOR_ASPECT_RATIOS")
+ _rename(cfg, "MODEL.ANCHOR_GENERATOR.SIZES", "MODEL.RPN.ANCHOR_SIZES")
+ cfg.MODEL.RETINANET.ANCHOR_ASPECT_RATIOS = cfg.MODEL.RPN.ANCHOR_ASPECT_RATIOS
+ cfg.MODEL.RETINANET.ANCHOR_SIZES = cfg.MODEL.RPN.ANCHOR_SIZES
+ cfg.MODEL.RETINANET.ANCHOR_STRIDES = [] # this is not used anywhere in any version
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/config/config.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/config/config.py
new file mode 100644
index 0000000000000000000000000000000000000000..e9ba1a5e4b7006bece7388efb59008dc902db0ba
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/config/config.py
@@ -0,0 +1,265 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import functools
+import inspect
+import logging
+from fvcore.common.config import CfgNode as _CfgNode
+
+from custom_detectron2.utils.file_io import PathManager
+
+
+class CfgNode(_CfgNode):
+ """
+ The same as `fvcore.common.config.CfgNode`, but different in:
+
+ 1. Use unsafe yaml loading by default.
+ Note that this may lead to arbitrary code execution: you must not
+ load a config file from untrusted sources before manually inspecting
+ the content of the file.
+ 2. Support config versioning.
+ When attempting to merge an old config, it will convert the old config automatically.
+
+ .. automethod:: clone
+ .. automethod:: freeze
+ .. automethod:: defrost
+ .. automethod:: is_frozen
+ .. automethod:: load_yaml_with_base
+ .. automethod:: merge_from_list
+ .. automethod:: merge_from_other_cfg
+ """
+
+ @classmethod
+ def _open_cfg(cls, filename):
+ return PathManager.open(filename, "r")
+
+ # Note that the default value of allow_unsafe is changed to True
+ def merge_from_file(self, cfg_filename: str, allow_unsafe: bool = True) -> None:
+ """
+ Load content from the given config file and merge it into self.
+
+ Args:
+ cfg_filename: config filename
+ allow_unsafe: allow unsafe yaml syntax
+ """
+ assert PathManager.isfile(cfg_filename), f"Config file '{cfg_filename}' does not exist!"
+ loaded_cfg = self.load_yaml_with_base(cfg_filename, allow_unsafe=allow_unsafe)
+ loaded_cfg = type(self)(loaded_cfg)
+
+ # defaults.py needs to import CfgNode
+ from .defaults import _C
+
+ latest_ver = _C.VERSION
+ assert (
+ latest_ver == self.VERSION
+ ), "CfgNode.merge_from_file is only allowed on a config object of latest version!"
+
+ logger = logging.getLogger(__name__)
+
+ loaded_ver = loaded_cfg.get("VERSION", None)
+ if loaded_ver is None:
+ from .compat import guess_version
+
+ loaded_ver = guess_version(loaded_cfg, cfg_filename)
+ assert loaded_ver <= self.VERSION, "Cannot merge a v{} config into a v{} config.".format(
+ loaded_ver, self.VERSION
+ )
+
+ if loaded_ver == self.VERSION:
+ self.merge_from_other_cfg(loaded_cfg)
+ else:
+ # compat.py needs to import CfgNode
+ from .compat import upgrade_config, downgrade_config
+
+ logger.warning(
+ "Loading an old v{} config file '{}' by automatically upgrading to v{}. "
+ "See docs/CHANGELOG.md for instructions to update your files.".format(
+ loaded_ver, cfg_filename, self.VERSION
+ )
+ )
+ # To convert, first obtain a full config at an old version
+ old_self = downgrade_config(self, to_version=loaded_ver)
+ old_self.merge_from_other_cfg(loaded_cfg)
+ new_config = upgrade_config(old_self)
+ self.clear()
+ self.update(new_config)
+
+ def dump(self, *args, **kwargs):
+ """
+ Returns:
+ str: a yaml string representation of the config
+ """
+ # to make it show up in docs
+ return super().dump(*args, **kwargs)
+
+
+global_cfg = CfgNode()
+
+
+def get_cfg() -> CfgNode:
+ """
+ Get a copy of the default config.
+
+ Returns:
+ a detectron2 CfgNode instance.
+ """
+ from .defaults import _C
+
+ return _C.clone()
+
+
+def set_global_cfg(cfg: CfgNode) -> None:
+ """
+ Let the global config point to the given cfg.
+
+ Assume that the given "cfg" has the key "KEY", after calling
+ `set_global_cfg(cfg)`, the key can be accessed by:
+ ::
+ from custom_detectron2.config import global_cfg
+ print(global_cfg.KEY)
+
+ By using a hacky global config, you can access these configs anywhere,
+ without having to pass the config object or the values deep into the code.
+ This is a hacky feature introduced for quick prototyping / research exploration.
+ """
+ global global_cfg
+ global_cfg.clear()
+ global_cfg.update(cfg)
+
+
+def configurable(init_func=None, *, from_config=None):
+ """
+ Decorate a function or a class's __init__ method so that it can be called
+ with a :class:`CfgNode` object using a :func:`from_config` function that translates
+ :class:`CfgNode` to arguments.
+
+ Examples:
+ ::
+ # Usage 1: Decorator on __init__:
+ class A:
+ @configurable
+ def __init__(self, a, b=2, c=3):
+ pass
+
+ @classmethod
+ def from_config(cls, cfg): # 'cfg' must be the first argument
+ # Returns kwargs to be passed to __init__
+ return {"a": cfg.A, "b": cfg.B}
+
+ a1 = A(a=1, b=2) # regular construction
+ a2 = A(cfg) # construct with a cfg
+ a3 = A(cfg, b=3, c=4) # construct with extra overwrite
+
+ # Usage 2: Decorator on any function. Needs an extra from_config argument:
+ @configurable(from_config=lambda cfg: {"a: cfg.A, "b": cfg.B})
+ def a_func(a, b=2, c=3):
+ pass
+
+ a1 = a_func(a=1, b=2) # regular call
+ a2 = a_func(cfg) # call with a cfg
+ a3 = a_func(cfg, b=3, c=4) # call with extra overwrite
+
+ Args:
+ init_func (callable): a class's ``__init__`` method in usage 1. The
+ class must have a ``from_config`` classmethod which takes `cfg` as
+ the first argument.
+ from_config (callable): the from_config function in usage 2. It must take `cfg`
+ as its first argument.
+ """
+
+ if init_func is not None:
+ assert (
+ inspect.isfunction(init_func)
+ and from_config is None
+ and init_func.__name__ == "__init__"
+ ), "Incorrect use of @configurable. Check API documentation for examples."
+
+ @functools.wraps(init_func)
+ def wrapped(self, *args, **kwargs):
+ try:
+ from_config_func = type(self).from_config
+ except AttributeError as e:
+ raise AttributeError(
+ "Class with @configurable must have a 'from_config' classmethod."
+ ) from e
+ if not inspect.ismethod(from_config_func):
+ raise TypeError("Class with @configurable must have a 'from_config' classmethod.")
+
+ if _called_with_cfg(*args, **kwargs):
+ explicit_args = _get_args_from_config(from_config_func, *args, **kwargs)
+ init_func(self, **explicit_args)
+ else:
+ init_func(self, *args, **kwargs)
+
+ return wrapped
+
+ else:
+ if from_config is None:
+ return configurable # @configurable() is made equivalent to @configurable
+ assert inspect.isfunction(
+ from_config
+ ), "from_config argument of configurable must be a function!"
+
+ def wrapper(orig_func):
+ @functools.wraps(orig_func)
+ def wrapped(*args, **kwargs):
+ if _called_with_cfg(*args, **kwargs):
+ explicit_args = _get_args_from_config(from_config, *args, **kwargs)
+ return orig_func(**explicit_args)
+ else:
+ return orig_func(*args, **kwargs)
+
+ wrapped.from_config = from_config
+ return wrapped
+
+ return wrapper
+
+
+def _get_args_from_config(from_config_func, *args, **kwargs):
+ """
+ Use `from_config` to obtain explicit arguments.
+
+ Returns:
+ dict: arguments to be used for cls.__init__
+ """
+ signature = inspect.signature(from_config_func)
+ if list(signature.parameters.keys())[0] != "cfg":
+ if inspect.isfunction(from_config_func):
+ name = from_config_func.__name__
+ else:
+ name = f"{from_config_func.__self__}.from_config"
+ raise TypeError(f"{name} must take 'cfg' as the first argument!")
+ support_var_arg = any(
+ param.kind in [param.VAR_POSITIONAL, param.VAR_KEYWORD]
+ for param in signature.parameters.values()
+ )
+ if support_var_arg: # forward all arguments to from_config, if from_config accepts them
+ ret = from_config_func(*args, **kwargs)
+ else:
+ # forward supported arguments to from_config
+ supported_arg_names = set(signature.parameters.keys())
+ extra_kwargs = {}
+ for name in list(kwargs.keys()):
+ if name not in supported_arg_names:
+ extra_kwargs[name] = kwargs.pop(name)
+ ret = from_config_func(*args, **kwargs)
+ # forward the other arguments to __init__
+ ret.update(extra_kwargs)
+ return ret
+
+
+def _called_with_cfg(*args, **kwargs):
+ """
+ Returns:
+ bool: whether the arguments contain CfgNode and should be considered
+ forwarded to from_config.
+ """
+ from omegaconf import DictConfig
+
+ if len(args) and isinstance(args[0], (_CfgNode, DictConfig)):
+ return True
+ if isinstance(kwargs.pop("cfg", None), (_CfgNode, DictConfig)):
+ return True
+ # `from_config`'s first argument is forced to be "cfg".
+ # So the above check covers all cases.
+ return False
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/config/defaults.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/config/defaults.py
new file mode 100644
index 0000000000000000000000000000000000000000..6ec2f00a69ac53d97dcf67623c7b262d911454b4
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/config/defaults.py
@@ -0,0 +1,650 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+from .config import CfgNode as CN
+
+# NOTE: given the new config system
+# (https://detectron2.readthedocs.io/en/latest/tutorials/lazyconfigs.html),
+# we will stop adding new functionalities to default CfgNode.
+
+# -----------------------------------------------------------------------------
+# Convention about Training / Test specific parameters
+# -----------------------------------------------------------------------------
+# Whenever an argument can be either used for training or for testing, the
+# corresponding name will be post-fixed by a _TRAIN for a training parameter,
+# or _TEST for a test-specific parameter.
+# For example, the number of images during training will be
+# IMAGES_PER_BATCH_TRAIN, while the number of images for testing will be
+# IMAGES_PER_BATCH_TEST
+
+# -----------------------------------------------------------------------------
+# Config definition
+# -----------------------------------------------------------------------------
+
+_C = CN()
+
+# The version number, to upgrade from old configs to new ones if any
+# changes happen. It's recommended to keep a VERSION in your config file.
+_C.VERSION = 2
+
+_C.MODEL = CN()
+_C.MODEL.LOAD_PROPOSALS = False
+_C.MODEL.MASK_ON = False
+_C.MODEL.KEYPOINT_ON = False
+_C.MODEL.DEVICE = "cuda"
+_C.MODEL.META_ARCHITECTURE = "GeneralizedRCNN"
+
+# Path (a file path, or URL like detectron2://.., https://..) to a checkpoint file
+# to be loaded to the model. You can find available models in the model zoo.
+_C.MODEL.WEIGHTS = ""
+
+# Values to be used for image normalization (BGR order, since INPUT.FORMAT defaults to BGR).
+# To train on images of different number of channels, just set different mean & std.
+# Default values are the mean pixel value from ImageNet: [103.53, 116.28, 123.675]
+_C.MODEL.PIXEL_MEAN = [103.530, 116.280, 123.675]
+# When using pre-trained models in Detectron1 or any MSRA models,
+# std has been absorbed into its conv1 weights, so the std needs to be set 1.
+# Otherwise, you can use [57.375, 57.120, 58.395] (ImageNet std)
+_C.MODEL.PIXEL_STD = [1.0, 1.0, 1.0]
+
+
+# -----------------------------------------------------------------------------
+# INPUT
+# -----------------------------------------------------------------------------
+_C.INPUT = CN()
+# By default, {MIN,MAX}_SIZE options are used in transforms.ResizeShortestEdge.
+# Please refer to ResizeShortestEdge for detailed definition.
+# Size of the smallest side of the image during training
+_C.INPUT.MIN_SIZE_TRAIN = (800,)
+# Sample size of smallest side by choice or random selection from range give by
+# INPUT.MIN_SIZE_TRAIN
+_C.INPUT.MIN_SIZE_TRAIN_SAMPLING = "choice"
+# Maximum size of the side of the image during training
+_C.INPUT.MAX_SIZE_TRAIN = 1333
+# Size of the smallest side of the image during testing. Set to zero to disable resize in testing.
+_C.INPUT.MIN_SIZE_TEST = 800
+# Maximum size of the side of the image during testing
+_C.INPUT.MAX_SIZE_TEST = 1333
+# Mode for flipping images used in data augmentation during training
+# choose one of ["horizontal, "vertical", "none"]
+_C.INPUT.RANDOM_FLIP = "horizontal"
+
+# `True` if cropping is used for data augmentation during training
+_C.INPUT.CROP = CN({"ENABLED": False})
+# Cropping type. See documentation of `detectron2.data.transforms.RandomCrop` for explanation.
+_C.INPUT.CROP.TYPE = "relative_range"
+# Size of crop in range (0, 1] if CROP.TYPE is "relative" or "relative_range" and in number of
+# pixels if CROP.TYPE is "absolute"
+_C.INPUT.CROP.SIZE = [0.9, 0.9]
+
+
+# Whether the model needs RGB, YUV, HSV etc.
+# Should be one of the modes defined here, as we use PIL to read the image:
+# https://pillow.readthedocs.io/en/stable/handbook/concepts.html#concept-modes
+# with BGR being the one exception. One can set image format to BGR, we will
+# internally use RGB for conversion and flip the channels over
+_C.INPUT.FORMAT = "BGR"
+# The ground truth mask format that the model will use.
+# Mask R-CNN supports either "polygon" or "bitmask" as ground truth.
+_C.INPUT.MASK_FORMAT = "polygon" # alternative: "bitmask"
+
+
+# -----------------------------------------------------------------------------
+# Dataset
+# -----------------------------------------------------------------------------
+_C.DATASETS = CN()
+# List of the dataset names for training. Must be registered in DatasetCatalog
+# Samples from these datasets will be merged and used as one dataset.
+_C.DATASETS.TRAIN = ()
+# List of the pre-computed proposal files for training, which must be consistent
+# with datasets listed in DATASETS.TRAIN.
+_C.DATASETS.PROPOSAL_FILES_TRAIN = ()
+# Number of top scoring precomputed proposals to keep for training
+_C.DATASETS.PRECOMPUTED_PROPOSAL_TOPK_TRAIN = 2000
+# List of the dataset names for testing. Must be registered in DatasetCatalog
+_C.DATASETS.TEST = ()
+# List of the pre-computed proposal files for test, which must be consistent
+# with datasets listed in DATASETS.TEST.
+_C.DATASETS.PROPOSAL_FILES_TEST = ()
+# Number of top scoring precomputed proposals to keep for test
+_C.DATASETS.PRECOMPUTED_PROPOSAL_TOPK_TEST = 1000
+
+# -----------------------------------------------------------------------------
+# DataLoader
+# -----------------------------------------------------------------------------
+_C.DATALOADER = CN()
+# Number of data loading threads
+_C.DATALOADER.NUM_WORKERS = 4
+# If True, each batch should contain only images for which the aspect ratio
+# is compatible. This groups portrait images together, and landscape images
+# are not batched with portrait images.
+_C.DATALOADER.ASPECT_RATIO_GROUPING = True
+# Options: TrainingSampler, RepeatFactorTrainingSampler
+_C.DATALOADER.SAMPLER_TRAIN = "TrainingSampler"
+# Repeat threshold for RepeatFactorTrainingSampler
+_C.DATALOADER.REPEAT_THRESHOLD = 0.0
+# Tf True, when working on datasets that have instance annotations, the
+# training dataloader will filter out images without associated annotations
+_C.DATALOADER.FILTER_EMPTY_ANNOTATIONS = True
+
+# ---------------------------------------------------------------------------- #
+# Backbone options
+# ---------------------------------------------------------------------------- #
+_C.MODEL.BACKBONE = CN()
+
+_C.MODEL.BACKBONE.NAME = "build_resnet_backbone"
+# Freeze the first several stages so they are not trained.
+# There are 5 stages in ResNet. The first is a convolution, and the following
+# stages are each group of residual blocks.
+_C.MODEL.BACKBONE.FREEZE_AT = 2
+
+
+# ---------------------------------------------------------------------------- #
+# FPN options
+# ---------------------------------------------------------------------------- #
+_C.MODEL.FPN = CN()
+# Names of the input feature maps to be used by FPN
+# They must have contiguous power of 2 strides
+# e.g., ["res2", "res3", "res4", "res5"]
+_C.MODEL.FPN.IN_FEATURES = []
+_C.MODEL.FPN.OUT_CHANNELS = 256
+
+# Options: "" (no norm), "GN"
+_C.MODEL.FPN.NORM = ""
+
+# Types for fusing the FPN top-down and lateral features. Can be either "sum" or "avg"
+_C.MODEL.FPN.FUSE_TYPE = "sum"
+
+
+# ---------------------------------------------------------------------------- #
+# Proposal generator options
+# ---------------------------------------------------------------------------- #
+_C.MODEL.PROPOSAL_GENERATOR = CN()
+# Current proposal generators include "RPN", "RRPN" and "PrecomputedProposals"
+_C.MODEL.PROPOSAL_GENERATOR.NAME = "RPN"
+# Proposal height and width both need to be greater than MIN_SIZE
+# (a the scale used during training or inference)
+_C.MODEL.PROPOSAL_GENERATOR.MIN_SIZE = 0
+
+
+# ---------------------------------------------------------------------------- #
+# Anchor generator options
+# ---------------------------------------------------------------------------- #
+_C.MODEL.ANCHOR_GENERATOR = CN()
+# The generator can be any name in the ANCHOR_GENERATOR registry
+_C.MODEL.ANCHOR_GENERATOR.NAME = "DefaultAnchorGenerator"
+# Anchor sizes (i.e. sqrt of area) in absolute pixels w.r.t. the network input.
+# Format: list[list[float]]. SIZES[i] specifies the list of sizes to use for
+# IN_FEATURES[i]; len(SIZES) must be equal to len(IN_FEATURES) or 1.
+# When len(SIZES) == 1, SIZES[0] is used for all IN_FEATURES.
+_C.MODEL.ANCHOR_GENERATOR.SIZES = [[32, 64, 128, 256, 512]]
+# Anchor aspect ratios. For each area given in `SIZES`, anchors with different aspect
+# ratios are generated by an anchor generator.
+# Format: list[list[float]]. ASPECT_RATIOS[i] specifies the list of aspect ratios (H/W)
+# to use for IN_FEATURES[i]; len(ASPECT_RATIOS) == len(IN_FEATURES) must be true,
+# or len(ASPECT_RATIOS) == 1 is true and aspect ratio list ASPECT_RATIOS[0] is used
+# for all IN_FEATURES.
+_C.MODEL.ANCHOR_GENERATOR.ASPECT_RATIOS = [[0.5, 1.0, 2.0]]
+# Anchor angles.
+# list[list[float]], the angle in degrees, for each input feature map.
+# ANGLES[i] specifies the list of angles for IN_FEATURES[i].
+_C.MODEL.ANCHOR_GENERATOR.ANGLES = [[-90, 0, 90]]
+# Relative offset between the center of the first anchor and the top-left corner of the image
+# Value has to be in [0, 1). Recommend to use 0.5, which means half stride.
+# The value is not expected to affect model accuracy.
+_C.MODEL.ANCHOR_GENERATOR.OFFSET = 0.0
+
+# ---------------------------------------------------------------------------- #
+# RPN options
+# ---------------------------------------------------------------------------- #
+_C.MODEL.RPN = CN()
+_C.MODEL.RPN.HEAD_NAME = "StandardRPNHead" # used by RPN_HEAD_REGISTRY
+
+# Names of the input feature maps to be used by RPN
+# e.g., ["p2", "p3", "p4", "p5", "p6"] for FPN
+_C.MODEL.RPN.IN_FEATURES = ["res4"]
+# Remove RPN anchors that go outside the image by BOUNDARY_THRESH pixels
+# Set to -1 or a large value, e.g. 100000, to disable pruning anchors
+_C.MODEL.RPN.BOUNDARY_THRESH = -1
+# IOU overlap ratios [BG_IOU_THRESHOLD, FG_IOU_THRESHOLD]
+# Minimum overlap required between an anchor and ground-truth box for the
+# (anchor, gt box) pair to be a positive example (IoU >= FG_IOU_THRESHOLD
+# ==> positive RPN example: 1)
+# Maximum overlap allowed between an anchor and ground-truth box for the
+# (anchor, gt box) pair to be a negative examples (IoU < BG_IOU_THRESHOLD
+# ==> negative RPN example: 0)
+# Anchors with overlap in between (BG_IOU_THRESHOLD <= IoU < FG_IOU_THRESHOLD)
+# are ignored (-1)
+_C.MODEL.RPN.IOU_THRESHOLDS = [0.3, 0.7]
+_C.MODEL.RPN.IOU_LABELS = [0, -1, 1]
+# Number of regions per image used to train RPN
+_C.MODEL.RPN.BATCH_SIZE_PER_IMAGE = 256
+# Target fraction of foreground (positive) examples per RPN minibatch
+_C.MODEL.RPN.POSITIVE_FRACTION = 0.5
+# Options are: "smooth_l1", "giou", "diou", "ciou"
+_C.MODEL.RPN.BBOX_REG_LOSS_TYPE = "smooth_l1"
+_C.MODEL.RPN.BBOX_REG_LOSS_WEIGHT = 1.0
+# Weights on (dx, dy, dw, dh) for normalizing RPN anchor regression targets
+_C.MODEL.RPN.BBOX_REG_WEIGHTS = (1.0, 1.0, 1.0, 1.0)
+# The transition point from L1 to L2 loss. Set to 0.0 to make the loss simply L1.
+_C.MODEL.RPN.SMOOTH_L1_BETA = 0.0
+_C.MODEL.RPN.LOSS_WEIGHT = 1.0
+# Number of top scoring RPN proposals to keep before applying NMS
+# When FPN is used, this is *per FPN level* (not total)
+_C.MODEL.RPN.PRE_NMS_TOPK_TRAIN = 12000
+_C.MODEL.RPN.PRE_NMS_TOPK_TEST = 6000
+# Number of top scoring RPN proposals to keep after applying NMS
+# When FPN is used, this limit is applied per level and then again to the union
+# of proposals from all levels
+# NOTE: When FPN is used, the meaning of this config is different from Detectron1.
+# It means per-batch topk in Detectron1, but per-image topk here.
+# See the "find_top_rpn_proposals" function for details.
+_C.MODEL.RPN.POST_NMS_TOPK_TRAIN = 2000
+_C.MODEL.RPN.POST_NMS_TOPK_TEST = 1000
+# NMS threshold used on RPN proposals
+_C.MODEL.RPN.NMS_THRESH = 0.7
+# Set this to -1 to use the same number of output channels as input channels.
+_C.MODEL.RPN.CONV_DIMS = [-1]
+
+# ---------------------------------------------------------------------------- #
+# ROI HEADS options
+# ---------------------------------------------------------------------------- #
+_C.MODEL.ROI_HEADS = CN()
+_C.MODEL.ROI_HEADS.NAME = "Res5ROIHeads"
+# Number of foreground classes
+_C.MODEL.ROI_HEADS.NUM_CLASSES = 80
+# Names of the input feature maps to be used by ROI heads
+# Currently all heads (box, mask, ...) use the same input feature map list
+# e.g., ["p2", "p3", "p4", "p5"] is commonly used for FPN
+_C.MODEL.ROI_HEADS.IN_FEATURES = ["res4"]
+# IOU overlap ratios [IOU_THRESHOLD]
+# Overlap threshold for an RoI to be considered background (if < IOU_THRESHOLD)
+# Overlap threshold for an RoI to be considered foreground (if >= IOU_THRESHOLD)
+_C.MODEL.ROI_HEADS.IOU_THRESHOLDS = [0.5]
+_C.MODEL.ROI_HEADS.IOU_LABELS = [0, 1]
+# RoI minibatch size *per image* (number of regions of interest [ROIs]) during training
+# Total number of RoIs per training minibatch =
+# ROI_HEADS.BATCH_SIZE_PER_IMAGE * SOLVER.IMS_PER_BATCH
+# E.g., a common configuration is: 512 * 16 = 8192
+_C.MODEL.ROI_HEADS.BATCH_SIZE_PER_IMAGE = 512
+# Target fraction of RoI minibatch that is labeled foreground (i.e. class > 0)
+_C.MODEL.ROI_HEADS.POSITIVE_FRACTION = 0.25
+
+# Only used on test mode
+
+# Minimum score threshold (assuming scores in a [0, 1] range); a value chosen to
+# balance obtaining high recall with not having too many low precision
+# detections that will slow down inference post processing steps (like NMS)
+# A default threshold of 0.0 increases AP by ~0.2-0.3 but significantly slows down
+# inference.
+_C.MODEL.ROI_HEADS.SCORE_THRESH_TEST = 0.05
+# Overlap threshold used for non-maximum suppression (suppress boxes with
+# IoU >= this threshold)
+_C.MODEL.ROI_HEADS.NMS_THRESH_TEST = 0.5
+# If True, augment proposals with ground-truth boxes before sampling proposals to
+# train ROI heads.
+_C.MODEL.ROI_HEADS.PROPOSAL_APPEND_GT = True
+
+# ---------------------------------------------------------------------------- #
+# Box Head
+# ---------------------------------------------------------------------------- #
+_C.MODEL.ROI_BOX_HEAD = CN()
+# C4 don't use head name option
+# Options for non-C4 models: FastRCNNConvFCHead,
+_C.MODEL.ROI_BOX_HEAD.NAME = ""
+# Options are: "smooth_l1", "giou", "diou", "ciou"
+_C.MODEL.ROI_BOX_HEAD.BBOX_REG_LOSS_TYPE = "smooth_l1"
+# The final scaling coefficient on the box regression loss, used to balance the magnitude of its
+# gradients with other losses in the model. See also `MODEL.ROI_KEYPOINT_HEAD.LOSS_WEIGHT`.
+_C.MODEL.ROI_BOX_HEAD.BBOX_REG_LOSS_WEIGHT = 1.0
+# Default weights on (dx, dy, dw, dh) for normalizing bbox regression targets
+# These are empirically chosen to approximately lead to unit variance targets
+_C.MODEL.ROI_BOX_HEAD.BBOX_REG_WEIGHTS = (10.0, 10.0, 5.0, 5.0)
+# The transition point from L1 to L2 loss. Set to 0.0 to make the loss simply L1.
+_C.MODEL.ROI_BOX_HEAD.SMOOTH_L1_BETA = 0.0
+_C.MODEL.ROI_BOX_HEAD.POOLER_RESOLUTION = 14
+_C.MODEL.ROI_BOX_HEAD.POOLER_SAMPLING_RATIO = 0
+# Type of pooling operation applied to the incoming feature map for each RoI
+_C.MODEL.ROI_BOX_HEAD.POOLER_TYPE = "ROIAlignV2"
+
+_C.MODEL.ROI_BOX_HEAD.NUM_FC = 0
+# Hidden layer dimension for FC layers in the RoI box head
+_C.MODEL.ROI_BOX_HEAD.FC_DIM = 1024
+_C.MODEL.ROI_BOX_HEAD.NUM_CONV = 0
+# Channel dimension for Conv layers in the RoI box head
+_C.MODEL.ROI_BOX_HEAD.CONV_DIM = 256
+# Normalization method for the convolution layers.
+# Options: "" (no norm), "GN", "SyncBN".
+_C.MODEL.ROI_BOX_HEAD.NORM = ""
+# Whether to use class agnostic for bbox regression
+_C.MODEL.ROI_BOX_HEAD.CLS_AGNOSTIC_BBOX_REG = False
+# If true, RoI heads use bounding boxes predicted by the box head rather than proposal boxes.
+_C.MODEL.ROI_BOX_HEAD.TRAIN_ON_PRED_BOXES = False
+
+# Federated loss can be used to improve the training of LVIS
+_C.MODEL.ROI_BOX_HEAD.USE_FED_LOSS = False
+# Sigmoid cross entrophy is used with federated loss
+_C.MODEL.ROI_BOX_HEAD.USE_SIGMOID_CE = False
+# The power value applied to image_count when calcualting frequency weight
+_C.MODEL.ROI_BOX_HEAD.FED_LOSS_FREQ_WEIGHT_POWER = 0.5
+# Number of classes to keep in total
+_C.MODEL.ROI_BOX_HEAD.FED_LOSS_NUM_CLASSES = 50
+
+# ---------------------------------------------------------------------------- #
+# Cascaded Box Head
+# ---------------------------------------------------------------------------- #
+_C.MODEL.ROI_BOX_CASCADE_HEAD = CN()
+# The number of cascade stages is implicitly defined by the length of the following two configs.
+_C.MODEL.ROI_BOX_CASCADE_HEAD.BBOX_REG_WEIGHTS = (
+ (10.0, 10.0, 5.0, 5.0),
+ (20.0, 20.0, 10.0, 10.0),
+ (30.0, 30.0, 15.0, 15.0),
+)
+_C.MODEL.ROI_BOX_CASCADE_HEAD.IOUS = (0.5, 0.6, 0.7)
+
+
+# ---------------------------------------------------------------------------- #
+# Mask Head
+# ---------------------------------------------------------------------------- #
+_C.MODEL.ROI_MASK_HEAD = CN()
+_C.MODEL.ROI_MASK_HEAD.NAME = "MaskRCNNConvUpsampleHead"
+_C.MODEL.ROI_MASK_HEAD.POOLER_RESOLUTION = 14
+_C.MODEL.ROI_MASK_HEAD.POOLER_SAMPLING_RATIO = 0
+_C.MODEL.ROI_MASK_HEAD.NUM_CONV = 0 # The number of convs in the mask head
+_C.MODEL.ROI_MASK_HEAD.CONV_DIM = 256
+# Normalization method for the convolution layers.
+# Options: "" (no norm), "GN", "SyncBN".
+_C.MODEL.ROI_MASK_HEAD.NORM = ""
+# Whether to use class agnostic for mask prediction
+_C.MODEL.ROI_MASK_HEAD.CLS_AGNOSTIC_MASK = False
+# Type of pooling operation applied to the incoming feature map for each RoI
+_C.MODEL.ROI_MASK_HEAD.POOLER_TYPE = "ROIAlignV2"
+
+
+# ---------------------------------------------------------------------------- #
+# Keypoint Head
+# ---------------------------------------------------------------------------- #
+_C.MODEL.ROI_KEYPOINT_HEAD = CN()
+_C.MODEL.ROI_KEYPOINT_HEAD.NAME = "KRCNNConvDeconvUpsampleHead"
+_C.MODEL.ROI_KEYPOINT_HEAD.POOLER_RESOLUTION = 14
+_C.MODEL.ROI_KEYPOINT_HEAD.POOLER_SAMPLING_RATIO = 0
+_C.MODEL.ROI_KEYPOINT_HEAD.CONV_DIMS = tuple(512 for _ in range(8))
+_C.MODEL.ROI_KEYPOINT_HEAD.NUM_KEYPOINTS = 17 # 17 is the number of keypoints in COCO.
+
+# Images with too few (or no) keypoints are excluded from training.
+_C.MODEL.ROI_KEYPOINT_HEAD.MIN_KEYPOINTS_PER_IMAGE = 1
+# Normalize by the total number of visible keypoints in the minibatch if True.
+# Otherwise, normalize by the total number of keypoints that could ever exist
+# in the minibatch.
+# The keypoint softmax loss is only calculated on visible keypoints.
+# Since the number of visible keypoints can vary significantly between
+# minibatches, this has the effect of up-weighting the importance of
+# minibatches with few visible keypoints. (Imagine the extreme case of
+# only one visible keypoint versus N: in the case of N, each one
+# contributes 1/N to the gradient compared to the single keypoint
+# determining the gradient direction). Instead, we can normalize the
+# loss by the total number of keypoints, if it were the case that all
+# keypoints were visible in a full minibatch. (Returning to the example,
+# this means that the one visible keypoint contributes as much as each
+# of the N keypoints.)
+_C.MODEL.ROI_KEYPOINT_HEAD.NORMALIZE_LOSS_BY_VISIBLE_KEYPOINTS = True
+# Multi-task loss weight to use for keypoints
+# Recommended values:
+# - use 1.0 if NORMALIZE_LOSS_BY_VISIBLE_KEYPOINTS is True
+# - use 4.0 if NORMALIZE_LOSS_BY_VISIBLE_KEYPOINTS is False
+_C.MODEL.ROI_KEYPOINT_HEAD.LOSS_WEIGHT = 1.0
+# Type of pooling operation applied to the incoming feature map for each RoI
+_C.MODEL.ROI_KEYPOINT_HEAD.POOLER_TYPE = "ROIAlignV2"
+
+# ---------------------------------------------------------------------------- #
+# Semantic Segmentation Head
+# ---------------------------------------------------------------------------- #
+_C.MODEL.SEM_SEG_HEAD = CN()
+_C.MODEL.SEM_SEG_HEAD.NAME = "SemSegFPNHead"
+_C.MODEL.SEM_SEG_HEAD.IN_FEATURES = ["p2", "p3", "p4", "p5"]
+# Label in the semantic segmentation ground truth that is ignored, i.e., no loss is calculated for
+# the correposnding pixel.
+_C.MODEL.SEM_SEG_HEAD.IGNORE_VALUE = 255
+# Number of classes in the semantic segmentation head
+_C.MODEL.SEM_SEG_HEAD.NUM_CLASSES = 54
+# Number of channels in the 3x3 convs inside semantic-FPN heads.
+_C.MODEL.SEM_SEG_HEAD.CONVS_DIM = 128
+# Outputs from semantic-FPN heads are up-scaled to the COMMON_STRIDE stride.
+_C.MODEL.SEM_SEG_HEAD.COMMON_STRIDE = 4
+# Normalization method for the convolution layers. Options: "" (no norm), "GN".
+_C.MODEL.SEM_SEG_HEAD.NORM = "GN"
+_C.MODEL.SEM_SEG_HEAD.LOSS_WEIGHT = 1.0
+
+_C.MODEL.PANOPTIC_FPN = CN()
+# Scaling of all losses from instance detection / segmentation head.
+_C.MODEL.PANOPTIC_FPN.INSTANCE_LOSS_WEIGHT = 1.0
+
+# options when combining instance & semantic segmentation outputs
+_C.MODEL.PANOPTIC_FPN.COMBINE = CN({"ENABLED": True}) # "COMBINE.ENABLED" is deprecated & not used
+_C.MODEL.PANOPTIC_FPN.COMBINE.OVERLAP_THRESH = 0.5
+_C.MODEL.PANOPTIC_FPN.COMBINE.STUFF_AREA_LIMIT = 4096
+_C.MODEL.PANOPTIC_FPN.COMBINE.INSTANCES_CONFIDENCE_THRESH = 0.5
+
+
+# ---------------------------------------------------------------------------- #
+# RetinaNet Head
+# ---------------------------------------------------------------------------- #
+_C.MODEL.RETINANET = CN()
+
+# This is the number of foreground classes.
+_C.MODEL.RETINANET.NUM_CLASSES = 80
+
+_C.MODEL.RETINANET.IN_FEATURES = ["p3", "p4", "p5", "p6", "p7"]
+
+# Convolutions to use in the cls and bbox tower
+# NOTE: this doesn't include the last conv for logits
+_C.MODEL.RETINANET.NUM_CONVS = 4
+
+# IoU overlap ratio [bg, fg] for labeling anchors.
+# Anchors with < bg are labeled negative (0)
+# Anchors with >= bg and < fg are ignored (-1)
+# Anchors with >= fg are labeled positive (1)
+_C.MODEL.RETINANET.IOU_THRESHOLDS = [0.4, 0.5]
+_C.MODEL.RETINANET.IOU_LABELS = [0, -1, 1]
+
+# Prior prob for rare case (i.e. foreground) at the beginning of training.
+# This is used to set the bias for the logits layer of the classifier subnet.
+# This improves training stability in the case of heavy class imbalance.
+_C.MODEL.RETINANET.PRIOR_PROB = 0.01
+
+# Inference cls score threshold, only anchors with score > INFERENCE_TH are
+# considered for inference (to improve speed)
+_C.MODEL.RETINANET.SCORE_THRESH_TEST = 0.05
+# Select topk candidates before NMS
+_C.MODEL.RETINANET.TOPK_CANDIDATES_TEST = 1000
+_C.MODEL.RETINANET.NMS_THRESH_TEST = 0.5
+
+# Weights on (dx, dy, dw, dh) for normalizing Retinanet anchor regression targets
+_C.MODEL.RETINANET.BBOX_REG_WEIGHTS = (1.0, 1.0, 1.0, 1.0)
+
+# Loss parameters
+_C.MODEL.RETINANET.FOCAL_LOSS_GAMMA = 2.0
+_C.MODEL.RETINANET.FOCAL_LOSS_ALPHA = 0.25
+_C.MODEL.RETINANET.SMOOTH_L1_LOSS_BETA = 0.1
+# Options are: "smooth_l1", "giou", "diou", "ciou"
+_C.MODEL.RETINANET.BBOX_REG_LOSS_TYPE = "smooth_l1"
+
+# One of BN, SyncBN, FrozenBN, GN
+# Only supports GN until unshared norm is implemented
+_C.MODEL.RETINANET.NORM = ""
+
+
+# ---------------------------------------------------------------------------- #
+# ResNe[X]t options (ResNets = {ResNet, ResNeXt}
+# Note that parts of a resnet may be used for both the backbone and the head
+# These options apply to both
+# ---------------------------------------------------------------------------- #
+_C.MODEL.RESNETS = CN()
+
+_C.MODEL.RESNETS.DEPTH = 50
+_C.MODEL.RESNETS.OUT_FEATURES = ["res4"] # res4 for C4 backbone, res2..5 for FPN backbone
+
+# Number of groups to use; 1 ==> ResNet; > 1 ==> ResNeXt
+_C.MODEL.RESNETS.NUM_GROUPS = 1
+
+# Options: FrozenBN, GN, "SyncBN", "BN"
+_C.MODEL.RESNETS.NORM = "FrozenBN"
+
+# Baseline width of each group.
+# Scaling this parameters will scale the width of all bottleneck layers.
+_C.MODEL.RESNETS.WIDTH_PER_GROUP = 64
+
+# Place the stride 2 conv on the 1x1 filter
+# Use True only for the original MSRA ResNet; use False for C2 and Torch models
+_C.MODEL.RESNETS.STRIDE_IN_1X1 = True
+
+# Apply dilation in stage "res5"
+_C.MODEL.RESNETS.RES5_DILATION = 1
+
+# Output width of res2. Scaling this parameters will scale the width of all 1x1 convs in ResNet
+# For R18 and R34, this needs to be set to 64
+_C.MODEL.RESNETS.RES2_OUT_CHANNELS = 256
+_C.MODEL.RESNETS.STEM_OUT_CHANNELS = 64
+
+# Apply Deformable Convolution in stages
+# Specify if apply deform_conv on Res2, Res3, Res4, Res5
+_C.MODEL.RESNETS.DEFORM_ON_PER_STAGE = [False, False, False, False]
+# Use True to use modulated deform_conv (DeformableV2, https://arxiv.org/abs/1811.11168);
+# Use False for DeformableV1.
+_C.MODEL.RESNETS.DEFORM_MODULATED = False
+# Number of groups in deformable conv.
+_C.MODEL.RESNETS.DEFORM_NUM_GROUPS = 1
+
+
+# ---------------------------------------------------------------------------- #
+# Solver
+# ---------------------------------------------------------------------------- #
+_C.SOLVER = CN()
+
+# Options: WarmupMultiStepLR, WarmupCosineLR.
+# See detectron2/solver/build.py for definition.
+_C.SOLVER.LR_SCHEDULER_NAME = "WarmupMultiStepLR"
+
+_C.SOLVER.MAX_ITER = 40000
+
+_C.SOLVER.BASE_LR = 0.001
+# The end lr, only used by WarmupCosineLR
+_C.SOLVER.BASE_LR_END = 0.0
+
+_C.SOLVER.MOMENTUM = 0.9
+
+_C.SOLVER.NESTEROV = False
+
+_C.SOLVER.WEIGHT_DECAY = 0.0001
+# The weight decay that's applied to parameters of normalization layers
+# (typically the affine transformation)
+_C.SOLVER.WEIGHT_DECAY_NORM = 0.0
+
+_C.SOLVER.GAMMA = 0.1
+# The iteration number to decrease learning rate by GAMMA.
+_C.SOLVER.STEPS = (30000,)
+# Number of decays in WarmupStepWithFixedGammaLR schedule
+_C.SOLVER.NUM_DECAYS = 3
+
+_C.SOLVER.WARMUP_FACTOR = 1.0 / 1000
+_C.SOLVER.WARMUP_ITERS = 1000
+_C.SOLVER.WARMUP_METHOD = "linear"
+# Whether to rescale the interval for the learning schedule after warmup
+_C.SOLVER.RESCALE_INTERVAL = False
+
+# Save a checkpoint after every this number of iterations
+_C.SOLVER.CHECKPOINT_PERIOD = 5000
+
+# Number of images per batch across all machines. This is also the number
+# of training images per step (i.e. per iteration). If we use 16 GPUs
+# and IMS_PER_BATCH = 32, each GPU will see 2 images per batch.
+# May be adjusted automatically if REFERENCE_WORLD_SIZE is set.
+_C.SOLVER.IMS_PER_BATCH = 16
+
+# The reference number of workers (GPUs) this config is meant to train with.
+# It takes no effect when set to 0.
+# With a non-zero value, it will be used by DefaultTrainer to compute a desired
+# per-worker batch size, and then scale the other related configs (total batch size,
+# learning rate, etc) to match the per-worker batch size.
+# See documentation of `DefaultTrainer.auto_scale_workers` for details:
+_C.SOLVER.REFERENCE_WORLD_SIZE = 0
+
+# Detectron v1 (and previous detection code) used a 2x higher LR and 0 WD for
+# biases. This is not useful (at least for recent models). You should avoid
+# changing these and they exist only to reproduce Detectron v1 training if
+# desired.
+_C.SOLVER.BIAS_LR_FACTOR = 1.0
+_C.SOLVER.WEIGHT_DECAY_BIAS = None # None means following WEIGHT_DECAY
+
+# Gradient clipping
+_C.SOLVER.CLIP_GRADIENTS = CN({"ENABLED": False})
+# Type of gradient clipping, currently 2 values are supported:
+# - "value": the absolute values of elements of each gradients are clipped
+# - "norm": the norm of the gradient for each parameter is clipped thus
+# affecting all elements in the parameter
+_C.SOLVER.CLIP_GRADIENTS.CLIP_TYPE = "value"
+# Maximum absolute value used for clipping gradients
+_C.SOLVER.CLIP_GRADIENTS.CLIP_VALUE = 1.0
+# Floating point number p for L-p norm to be used with the "norm"
+# gradient clipping type; for L-inf, please specify .inf
+_C.SOLVER.CLIP_GRADIENTS.NORM_TYPE = 2.0
+
+# Enable automatic mixed precision for training
+# Note that this does not change model's inference behavior.
+# To use AMP in inference, run inference under autocast()
+_C.SOLVER.AMP = CN({"ENABLED": False})
+
+# ---------------------------------------------------------------------------- #
+# Specific test options
+# ---------------------------------------------------------------------------- #
+_C.TEST = CN()
+# For end-to-end tests to verify the expected accuracy.
+# Each item is [task, metric, value, tolerance]
+# e.g.: [['bbox', 'AP', 38.5, 0.2]]
+_C.TEST.EXPECTED_RESULTS = []
+# The period (in terms of steps) to evaluate the model during training.
+# Set to 0 to disable.
+_C.TEST.EVAL_PERIOD = 0
+# The sigmas used to calculate keypoint OKS. See http://cocodataset.org/#keypoints-eval
+# When empty, it will use the defaults in COCO.
+# Otherwise it should be a list[float] with the same length as ROI_KEYPOINT_HEAD.NUM_KEYPOINTS.
+_C.TEST.KEYPOINT_OKS_SIGMAS = []
+# Maximum number of detections to return per image during inference (100 is
+# based on the limit established for the COCO dataset).
+_C.TEST.DETECTIONS_PER_IMAGE = 100
+
+_C.TEST.AUG = CN({"ENABLED": False})
+_C.TEST.AUG.MIN_SIZES = (400, 500, 600, 700, 800, 900, 1000, 1100, 1200)
+_C.TEST.AUG.MAX_SIZE = 4000
+_C.TEST.AUG.FLIP = True
+
+_C.TEST.PRECISE_BN = CN({"ENABLED": False})
+_C.TEST.PRECISE_BN.NUM_ITER = 200
+
+# ---------------------------------------------------------------------------- #
+# Misc options
+# ---------------------------------------------------------------------------- #
+# Directory where output files are written
+_C.OUTPUT_DIR = "./output"
+# Set seed to negative to fully randomize everything.
+# Set seed to positive to use a fixed seed. Note that a fixed seed increases
+# reproducibility but does not guarantee fully deterministic behavior.
+# Disabling all parallelism further increases reproducibility.
+_C.SEED = -1
+# Benchmark different cudnn algorithms.
+# If input images have very different sizes, this option will have large overhead
+# for about 10k iterations. It usually hurts total time, but can benefit for certain models.
+# If input images have the same or similar sizes, benchmark is often helpful.
+_C.CUDNN_BENCHMARK = False
+# The period (in terms of steps) for minibatch visualization at train time.
+# Set to 0 to disable.
+_C.VIS_PERIOD = 0
+
+# global config is for quick hack purposes.
+# You can set them in command line or config files,
+# and access it with:
+#
+# from custom_detectron2.config import global_cfg
+# print(global_cfg.HACK)
+#
+# Do not commit any configs into it.
+_C.GLOBAL = CN()
+_C.GLOBAL.HACK = 1.0
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/config/instantiate.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/config/instantiate.py
new file mode 100644
index 0000000000000000000000000000000000000000..24528d71bf09c7ae768aa80df3cf413c8d02598b
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/config/instantiate.py
@@ -0,0 +1,88 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import collections.abc as abc
+import dataclasses
+import logging
+from typing import Any
+
+from custom_detectron2.utils.registry import _convert_target_to_string, locate
+
+__all__ = ["dump_dataclass", "instantiate"]
+
+
+def dump_dataclass(obj: Any):
+ """
+ Dump a dataclass recursively into a dict that can be later instantiated.
+
+ Args:
+ obj: a dataclass object
+
+ Returns:
+ dict
+ """
+ assert dataclasses.is_dataclass(obj) and not isinstance(
+ obj, type
+ ), "dump_dataclass() requires an instance of a dataclass."
+ ret = {"_target_": _convert_target_to_string(type(obj))}
+ for f in dataclasses.fields(obj):
+ v = getattr(obj, f.name)
+ if dataclasses.is_dataclass(v):
+ v = dump_dataclass(v)
+ if isinstance(v, (list, tuple)):
+ v = [dump_dataclass(x) if dataclasses.is_dataclass(x) else x for x in v]
+ ret[f.name] = v
+ return ret
+
+
+def instantiate(cfg):
+ """
+ Recursively instantiate objects defined in dictionaries by
+ "_target_" and arguments.
+
+ Args:
+ cfg: a dict-like object with "_target_" that defines the caller, and
+ other keys that define the arguments
+
+ Returns:
+ object instantiated by cfg
+ """
+ from omegaconf import ListConfig, DictConfig, OmegaConf
+
+ if isinstance(cfg, ListConfig):
+ lst = [instantiate(x) for x in cfg]
+ return ListConfig(lst, flags={"allow_objects": True})
+ if isinstance(cfg, list):
+ # Specialize for list, because many classes take
+ # list[objects] as arguments, such as ResNet, DatasetMapper
+ return [instantiate(x) for x in cfg]
+
+ # If input is a DictConfig backed by dataclasses (i.e. omegaconf's structured config),
+ # instantiate it to the actual dataclass.
+ if isinstance(cfg, DictConfig) and dataclasses.is_dataclass(cfg._metadata.object_type):
+ return OmegaConf.to_object(cfg)
+
+ if isinstance(cfg, abc.Mapping) and "_target_" in cfg:
+ # conceptually equivalent to hydra.utils.instantiate(cfg) with _convert_=all,
+ # but faster: https://github.com/facebookresearch/hydra/issues/1200
+ cfg = {k: instantiate(v) for k, v in cfg.items()}
+ cls = cfg.pop("_target_")
+ cls = instantiate(cls)
+
+ if isinstance(cls, str):
+ cls_name = cls
+ cls = locate(cls_name)
+ assert cls is not None, cls_name
+ else:
+ try:
+ cls_name = cls.__module__ + "." + cls.__qualname__
+ except Exception:
+ # target could be anything, so the above could fail
+ cls_name = str(cls)
+ assert callable(cls), f"_target_ {cls} does not define a callable object"
+ try:
+ return cls(**cfg)
+ except TypeError:
+ logger = logging.getLogger(__name__)
+ logger.error(f"Error when instantiating {cls_name}!")
+ raise
+ return cfg # return as-is if don't know what to do
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/config/lazy.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/config/lazy.py
new file mode 100644
index 0000000000000000000000000000000000000000..6a8944f206ea5d6d1e0fa98153f8655819101efa
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/config/lazy.py
@@ -0,0 +1,435 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import ast
+import builtins
+import collections.abc as abc
+import importlib
+import inspect
+import logging
+import os
+import uuid
+from contextlib import contextmanager
+from copy import deepcopy
+from dataclasses import is_dataclass
+from typing import List, Tuple, Union
+import yaml
+from omegaconf import DictConfig, ListConfig, OmegaConf, SCMode
+
+from custom_detectron2.utils.file_io import PathManager
+from custom_detectron2.utils.registry import _convert_target_to_string
+
+__all__ = ["LazyCall", "LazyConfig"]
+
+
+class LazyCall:
+ """
+ Wrap a callable so that when it's called, the call will not be executed,
+ but returns a dict that describes the call.
+
+ LazyCall object has to be called with only keyword arguments. Positional
+ arguments are not yet supported.
+
+ Examples:
+ ::
+ from custom_detectron2.config import instantiate, LazyCall
+
+ layer_cfg = LazyCall(nn.Conv2d)(in_channels=32, out_channels=32)
+ layer_cfg.out_channels = 64 # can edit it afterwards
+ layer = instantiate(layer_cfg)
+ """
+
+ def __init__(self, target):
+ if not (callable(target) or isinstance(target, (str, abc.Mapping))):
+ raise TypeError(
+ f"target of LazyCall must be a callable or defines a callable! Got {target}"
+ )
+ self._target = target
+
+ def __call__(self, **kwargs):
+ if is_dataclass(self._target):
+ # omegaconf object cannot hold dataclass type
+ # https://github.com/omry/omegaconf/issues/784
+ target = _convert_target_to_string(self._target)
+ else:
+ target = self._target
+ kwargs["_target_"] = target
+
+ return DictConfig(content=kwargs, flags={"allow_objects": True})
+
+
+def _visit_dict_config(cfg, func):
+ """
+ Apply func recursively to all DictConfig in cfg.
+ """
+ if isinstance(cfg, DictConfig):
+ func(cfg)
+ for v in cfg.values():
+ _visit_dict_config(v, func)
+ elif isinstance(cfg, ListConfig):
+ for v in cfg:
+ _visit_dict_config(v, func)
+
+
+def _validate_py_syntax(filename):
+ # see also https://github.com/open-mmlab/mmcv/blob/master/mmcv/utils/config.py
+ with PathManager.open(filename, "r") as f:
+ content = f.read()
+ try:
+ ast.parse(content)
+ except SyntaxError as e:
+ raise SyntaxError(f"Config file {filename} has syntax error!") from e
+
+
+def _cast_to_config(obj):
+ # if given a dict, return DictConfig instead
+ if isinstance(obj, dict):
+ return DictConfig(obj, flags={"allow_objects": True})
+ return obj
+
+
+_CFG_PACKAGE_NAME = "detectron2._cfg_loader"
+"""
+A namespace to put all imported config into.
+"""
+
+
+def _random_package_name(filename):
+ # generate a random package name when loading config files
+ return _CFG_PACKAGE_NAME + str(uuid.uuid4())[:4] + "." + os.path.basename(filename)
+
+
+@contextmanager
+def _patch_import():
+ """
+ Enhance relative import statements in config files, so that they:
+ 1. locate files purely based on relative location, regardless of packages.
+ e.g. you can import file without having __init__
+ 2. do not cache modules globally; modifications of module states has no side effect
+ 3. support other storage system through PathManager, so config files can be in the cloud
+ 4. imported dict are turned into omegaconf.DictConfig automatically
+ """
+ old_import = builtins.__import__
+
+ def find_relative_file(original_file, relative_import_path, level):
+ # NOTE: "from . import x" is not handled. Because then it's unclear
+ # if such import should produce `x` as a python module or DictConfig.
+ # This can be discussed further if needed.
+ relative_import_err = """
+Relative import of directories is not allowed within config files.
+Within a config file, relative import can only import other config files.
+""".replace(
+ "\n", " "
+ )
+ if not len(relative_import_path):
+ raise ImportError(relative_import_err)
+
+ cur_file = os.path.dirname(original_file)
+ for _ in range(level - 1):
+ cur_file = os.path.dirname(cur_file)
+ cur_name = relative_import_path.lstrip(".")
+ for part in cur_name.split("."):
+ cur_file = os.path.join(cur_file, part)
+ if not cur_file.endswith(".py"):
+ cur_file += ".py"
+ if not PathManager.isfile(cur_file):
+ cur_file_no_suffix = cur_file[: -len(".py")]
+ if PathManager.isdir(cur_file_no_suffix):
+ raise ImportError(f"Cannot import from {cur_file_no_suffix}." + relative_import_err)
+ else:
+ raise ImportError(
+ f"Cannot import name {relative_import_path} from "
+ f"{original_file}: {cur_file} does not exist."
+ )
+ return cur_file
+
+ def new_import(name, globals=None, locals=None, fromlist=(), level=0):
+ if (
+ # Only deal with relative imports inside config files
+ level != 0
+ and globals is not None
+ and (globals.get("__package__", "") or "").startswith(_CFG_PACKAGE_NAME)
+ ):
+ cur_file = find_relative_file(globals["__file__"], name, level)
+ _validate_py_syntax(cur_file)
+ spec = importlib.machinery.ModuleSpec(
+ _random_package_name(cur_file), None, origin=cur_file
+ )
+ module = importlib.util.module_from_spec(spec)
+ module.__file__ = cur_file
+ with PathManager.open(cur_file) as f:
+ content = f.read()
+ exec(compile(content, cur_file, "exec"), module.__dict__)
+ for name in fromlist: # turn imported dict into DictConfig automatically
+ val = _cast_to_config(module.__dict__[name])
+ module.__dict__[name] = val
+ return module
+ return old_import(name, globals, locals, fromlist=fromlist, level=level)
+
+ builtins.__import__ = new_import
+ yield new_import
+ builtins.__import__ = old_import
+
+
+class LazyConfig:
+ """
+ Provide methods to save, load, and overrides an omegaconf config object
+ which may contain definition of lazily-constructed objects.
+ """
+
+ @staticmethod
+ def load_rel(filename: str, keys: Union[None, str, Tuple[str, ...]] = None):
+ """
+ Similar to :meth:`load()`, but load path relative to the caller's
+ source file.
+
+ This has the same functionality as a relative import, except that this method
+ accepts filename as a string, so more characters are allowed in the filename.
+ """
+ caller_frame = inspect.stack()[1]
+ caller_fname = caller_frame[0].f_code.co_filename
+ assert caller_fname != "", "load_rel Unable to find caller"
+ caller_dir = os.path.dirname(caller_fname)
+ filename = os.path.join(caller_dir, filename)
+ return LazyConfig.load(filename, keys)
+
+ @staticmethod
+ def load(filename: str, keys: Union[None, str, Tuple[str, ...]] = None):
+ """
+ Load a config file.
+
+ Args:
+ filename: absolute path or relative path w.r.t. the current working directory
+ keys: keys to load and return. If not given, return all keys
+ (whose values are config objects) in a dict.
+ """
+ has_keys = keys is not None
+ filename = filename.replace("/./", "/") # redundant
+ if os.path.splitext(filename)[1] not in [".py", ".yaml", ".yml"]:
+ raise ValueError(f"Config file {filename} has to be a python or yaml file.")
+ if filename.endswith(".py"):
+ _validate_py_syntax(filename)
+
+ with _patch_import():
+ # Record the filename
+ module_namespace = {
+ "__file__": filename,
+ "__package__": _random_package_name(filename),
+ }
+ with PathManager.open(filename) as f:
+ content = f.read()
+ # Compile first with filename to:
+ # 1. make filename appears in stacktrace
+ # 2. make load_rel able to find its parent's (possibly remote) location
+ exec(compile(content, filename, "exec"), module_namespace)
+
+ ret = module_namespace
+ else:
+ with PathManager.open(filename) as f:
+ obj = yaml.unsafe_load(f)
+ ret = OmegaConf.create(obj, flags={"allow_objects": True})
+
+ if has_keys:
+ if isinstance(keys, str):
+ return _cast_to_config(ret[keys])
+ else:
+ return tuple(_cast_to_config(ret[a]) for a in keys)
+ else:
+ if filename.endswith(".py"):
+ # when not specified, only load those that are config objects
+ ret = DictConfig(
+ {
+ name: _cast_to_config(value)
+ for name, value in ret.items()
+ if isinstance(value, (DictConfig, ListConfig, dict))
+ and not name.startswith("_")
+ },
+ flags={"allow_objects": True},
+ )
+ return ret
+
+ @staticmethod
+ def save(cfg, filename: str):
+ """
+ Save a config object to a yaml file.
+ Note that when the config dictionary contains complex objects (e.g. lambda),
+ it can't be saved to yaml. In that case we will print an error and
+ attempt to save to a pkl file instead.
+
+ Args:
+ cfg: an omegaconf config object
+ filename: yaml file name to save the config file
+ """
+ logger = logging.getLogger(__name__)
+ try:
+ cfg = deepcopy(cfg)
+ except Exception:
+ pass
+ else:
+ # if it's deep-copyable, then...
+ def _replace_type_by_name(x):
+ if "_target_" in x and callable(x._target_):
+ try:
+ x._target_ = _convert_target_to_string(x._target_)
+ except AttributeError:
+ pass
+
+ # not necessary, but makes yaml looks nicer
+ _visit_dict_config(cfg, _replace_type_by_name)
+
+ save_pkl = False
+ try:
+ dict = OmegaConf.to_container(
+ cfg,
+ # Do not resolve interpolation when saving, i.e. do not turn ${a} into
+ # actual values when saving.
+ resolve=False,
+ # Save structures (dataclasses) in a format that can be instantiated later.
+ # Without this option, the type information of the dataclass will be erased.
+ structured_config_mode=SCMode.INSTANTIATE,
+ )
+ dumped = yaml.dump(dict, default_flow_style=None, allow_unicode=True, width=9999)
+ with PathManager.open(filename, "w") as f:
+ f.write(dumped)
+
+ try:
+ _ = yaml.unsafe_load(dumped) # test that it is loadable
+ except Exception:
+ logger.warning(
+ "The config contains objects that cannot serialize to a valid yaml. "
+ f"{filename} is human-readable but cannot be loaded."
+ )
+ save_pkl = True
+ except Exception:
+ logger.exception("Unable to serialize the config to yaml. Error:")
+ save_pkl = True
+
+ if save_pkl:
+ new_filename = filename + ".pkl"
+ # try:
+ # # retry by pickle
+ # with PathManager.open(new_filename, "wb") as f:
+ # cloudpickle.dump(cfg, f)
+ # logger.warning(f"Config is saved using cloudpickle at {new_filename}.")
+ # except Exception:
+ # pass
+
+ @staticmethod
+ def apply_overrides(cfg, overrides: List[str]):
+ """
+ In-place override contents of cfg.
+
+ Args:
+ cfg: an omegaconf config object
+ overrides: list of strings in the format of "a=b" to override configs.
+ See https://hydra.cc/docs/next/advanced/override_grammar/basic/
+ for syntax.
+
+ Returns:
+ the cfg object
+ """
+
+ def safe_update(cfg, key, value):
+ parts = key.split(".")
+ for idx in range(1, len(parts)):
+ prefix = ".".join(parts[:idx])
+ v = OmegaConf.select(cfg, prefix, default=None)
+ if v is None:
+ break
+ if not OmegaConf.is_config(v):
+ raise KeyError(
+ f"Trying to update key {key}, but {prefix} "
+ f"is not a config, but has type {type(v)}."
+ )
+ OmegaConf.update(cfg, key, value, merge=True)
+
+ try:
+ from hydra.core.override_parser.overrides_parser import OverridesParser
+
+ has_hydra = True
+ except ImportError:
+ has_hydra = False
+
+ if has_hydra:
+ parser = OverridesParser.create()
+ overrides = parser.parse_overrides(overrides)
+ for o in overrides:
+ key = o.key_or_group
+ value = o.value()
+ if o.is_delete():
+ # TODO support this
+ raise NotImplementedError("deletion is not yet a supported override")
+ safe_update(cfg, key, value)
+ else:
+ # Fallback. Does not support all the features and error checking like hydra.
+ for o in overrides:
+ key, value = o.split("=")
+ try:
+ value = eval(value, {})
+ except NameError:
+ pass
+ safe_update(cfg, key, value)
+ return cfg
+
+ # @staticmethod
+ # def to_py(cfg, prefix: str = "cfg."):
+ # """
+ # Try to convert a config object into Python-like psuedo code.
+ #
+ # Note that perfect conversion is not always possible. So the returned
+ # results are mainly meant to be human-readable, and not meant to be executed.
+ #
+ # Args:
+ # cfg: an omegaconf config object
+ # prefix: root name for the resulting code (default: "cfg.")
+ #
+ #
+ # Returns:
+ # str of formatted Python code
+ # """
+ # import black
+ #
+ # cfg = OmegaConf.to_container(cfg, resolve=True)
+ #
+ # def _to_str(obj, prefix=None, inside_call=False):
+ # if prefix is None:
+ # prefix = []
+ # if isinstance(obj, abc.Mapping) and "_target_" in obj:
+ # # Dict representing a function call
+ # target = _convert_target_to_string(obj.pop("_target_"))
+ # args = []
+ # for k, v in sorted(obj.items()):
+ # args.append(f"{k}={_to_str(v, inside_call=True)}")
+ # args = ", ".join(args)
+ # call = f"{target}({args})"
+ # return "".join(prefix) + call
+ # elif isinstance(obj, abc.Mapping) and not inside_call:
+ # # Dict that is not inside a call is a list of top-level config objects that we
+ # # render as one object per line with dot separated prefixes
+ # key_list = []
+ # for k, v in sorted(obj.items()):
+ # if isinstance(v, abc.Mapping) and "_target_" not in v:
+ # key_list.append(_to_str(v, prefix=prefix + [k + "."]))
+ # else:
+ # key = "".join(prefix) + k
+ # key_list.append(f"{key}={_to_str(v)}")
+ # return "\n".join(key_list)
+ # elif isinstance(obj, abc.Mapping):
+ # # Dict that is inside a call is rendered as a regular dict
+ # return (
+ # "{"
+ # + ",".join(
+ # f"{repr(k)}: {_to_str(v, inside_call=inside_call)}"
+ # for k, v in sorted(obj.items())
+ # )
+ # + "}"
+ # )
+ # elif isinstance(obj, list):
+ # return "[" + ",".join(_to_str(x, inside_call=inside_call) for x in obj) + "]"
+ # else:
+ # return repr(obj)
+ #
+ # py_str = _to_str(cfg, prefix=[prefix])
+ # try:
+ # return black.format_str(py_str, mode=black.Mode())
+ # except black.InvalidInput:
+ # return py_str
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..259f669b78bd05815cb8d3351fd6c5fc9a1b85a1
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/__init__.py
@@ -0,0 +1,19 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+from . import transforms # isort:skip
+
+from .build import (
+ build_batch_data_loader,
+ build_detection_test_loader,
+ build_detection_train_loader,
+ get_detection_dataset_dicts,
+ load_proposals_into_dataset,
+ print_instances_class_histogram,
+)
+from .catalog import DatasetCatalog, MetadataCatalog, Metadata
+from .common import DatasetFromList, MapDataset, ToIterableDataset
+from .dataset_mapper import DatasetMapper
+
+# ensure the builtin datasets are registered
+from . import datasets, samplers # isort:skip
+
+__all__ = [k for k in globals().keys() if not k.startswith("_")]
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/benchmark.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/benchmark.py
new file mode 100644
index 0000000000000000000000000000000000000000..eda0bb5c751ee961a1cef8190d6ed966f06f190a
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/benchmark.py
@@ -0,0 +1,225 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import logging
+import numpy as np
+from itertools import count
+from typing import List, Tuple
+import torch
+import tqdm
+from fvcore.common.timer import Timer
+
+from custom_detectron2.utils import comm
+
+from .build import build_batch_data_loader
+from .common import DatasetFromList, MapDataset
+from .samplers import TrainingSampler
+
+logger = logging.getLogger(__name__)
+
+
+class _EmptyMapDataset(torch.utils.data.Dataset):
+ """
+ Map anything to emptiness.
+ """
+
+ def __init__(self, dataset):
+ self.ds = dataset
+
+ def __len__(self):
+ return len(self.ds)
+
+ def __getitem__(self, idx):
+ _ = self.ds[idx]
+ return [0]
+
+
+def iter_benchmark(
+ iterator, num_iter: int, warmup: int = 5, max_time_seconds: float = 60
+) -> Tuple[float, List[float]]:
+ """
+ Benchmark an iterator/iterable for `num_iter` iterations with an extra
+ `warmup` iterations of warmup.
+ End early if `max_time_seconds` time is spent on iterations.
+
+ Returns:
+ float: average time (seconds) per iteration
+ list[float]: time spent on each iteration. Sometimes useful for further analysis.
+ """
+ num_iter, warmup = int(num_iter), int(warmup)
+
+ iterator = iter(iterator)
+ for _ in range(warmup):
+ next(iterator)
+ timer = Timer()
+ all_times = []
+ for curr_iter in tqdm.trange(num_iter):
+ start = timer.seconds()
+ if start > max_time_seconds:
+ num_iter = curr_iter
+ break
+ next(iterator)
+ all_times.append(timer.seconds() - start)
+ avg = timer.seconds() / num_iter
+ return avg, all_times
+
+
+class DataLoaderBenchmark:
+ """
+ Some common benchmarks that help understand perf bottleneck of a standard dataloader
+ made of dataset, mapper and sampler.
+ """
+
+ def __init__(
+ self,
+ dataset,
+ *,
+ mapper,
+ sampler=None,
+ total_batch_size,
+ num_workers=0,
+ max_time_seconds: int = 90,
+ ):
+ """
+ Args:
+ max_time_seconds (int): maximum time to spent for each benchmark
+ other args: same as in `build.py:build_detection_train_loader`
+ """
+ if isinstance(dataset, list):
+ dataset = DatasetFromList(dataset, copy=False, serialize=True)
+ if sampler is None:
+ sampler = TrainingSampler(len(dataset))
+
+ self.dataset = dataset
+ self.mapper = mapper
+ self.sampler = sampler
+ self.total_batch_size = total_batch_size
+ self.num_workers = num_workers
+ self.per_gpu_batch_size = self.total_batch_size // comm.get_world_size()
+
+ self.max_time_seconds = max_time_seconds
+
+ def _benchmark(self, iterator, num_iter, warmup, msg=None):
+ avg, all_times = iter_benchmark(iterator, num_iter, warmup, self.max_time_seconds)
+ if msg is not None:
+ self._log_time(msg, avg, all_times)
+ return avg, all_times
+
+ def _log_time(self, msg, avg, all_times, distributed=False):
+ percentiles = [np.percentile(all_times, k, interpolation="nearest") for k in [1, 5, 95, 99]]
+ if not distributed:
+ logger.info(
+ f"{msg}: avg={1.0/avg:.1f} it/s, "
+ f"p1={percentiles[0]:.2g}s, p5={percentiles[1]:.2g}s, "
+ f"p95={percentiles[2]:.2g}s, p99={percentiles[3]:.2g}s."
+ )
+ return
+ avg_per_gpu = comm.all_gather(avg)
+ percentiles_per_gpu = comm.all_gather(percentiles)
+ if comm.get_rank() > 0:
+ return
+ for idx, avg, percentiles in zip(count(), avg_per_gpu, percentiles_per_gpu):
+ logger.info(
+ f"GPU{idx} {msg}: avg={1.0/avg:.1f} it/s, "
+ f"p1={percentiles[0]:.2g}s, p5={percentiles[1]:.2g}s, "
+ f"p95={percentiles[2]:.2g}s, p99={percentiles[3]:.2g}s."
+ )
+
+ def benchmark_dataset(self, num_iter, warmup=5):
+ """
+ Benchmark the speed of taking raw samples from the dataset.
+ """
+
+ def loader():
+ while True:
+ for k in self.sampler:
+ yield self.dataset[k]
+
+ self._benchmark(loader(), num_iter, warmup, "Dataset Alone")
+
+ def benchmark_mapper(self, num_iter, warmup=5):
+ """
+ Benchmark the speed of taking raw samples from the dataset and map
+ them in a single process.
+ """
+
+ def loader():
+ while True:
+ for k in self.sampler:
+ yield self.mapper(self.dataset[k])
+
+ self._benchmark(loader(), num_iter, warmup, "Single Process Mapper (sec/sample)")
+
+ def benchmark_workers(self, num_iter, warmup=10):
+ """
+ Benchmark the dataloader by tuning num_workers to [0, 1, self.num_workers].
+ """
+ candidates = [0, 1]
+ if self.num_workers not in candidates:
+ candidates.append(self.num_workers)
+
+ dataset = MapDataset(self.dataset, self.mapper)
+ for n in candidates:
+ loader = build_batch_data_loader(
+ dataset,
+ self.sampler,
+ self.total_batch_size,
+ num_workers=n,
+ )
+ self._benchmark(
+ iter(loader),
+ num_iter * max(n, 1),
+ warmup * max(n, 1),
+ f"DataLoader ({n} workers, bs={self.per_gpu_batch_size})",
+ )
+ del loader
+
+ def benchmark_IPC(self, num_iter, warmup=10):
+ """
+ Benchmark the dataloader where each worker outputs nothing. This
+ eliminates the IPC overhead compared to the regular dataloader.
+
+ PyTorch multiprocessing's IPC only optimizes for torch tensors.
+ Large numpy arrays or other data structure may incur large IPC overhead.
+ """
+ n = self.num_workers
+ dataset = _EmptyMapDataset(MapDataset(self.dataset, self.mapper))
+ loader = build_batch_data_loader(
+ dataset, self.sampler, self.total_batch_size, num_workers=n
+ )
+ self._benchmark(
+ iter(loader),
+ num_iter * max(n, 1),
+ warmup * max(n, 1),
+ f"DataLoader ({n} workers, bs={self.per_gpu_batch_size}) w/o comm",
+ )
+
+ def benchmark_distributed(self, num_iter, warmup=10):
+ """
+ Benchmark the dataloader in each distributed worker, and log results of
+ all workers. This helps understand the final performance as well as
+ the variances among workers.
+
+ It also prints startup time (first iter) of the dataloader.
+ """
+ gpu = comm.get_world_size()
+ dataset = MapDataset(self.dataset, self.mapper)
+ n = self.num_workers
+ loader = build_batch_data_loader(
+ dataset, self.sampler, self.total_batch_size, num_workers=n
+ )
+
+ timer = Timer()
+ loader = iter(loader)
+ next(loader)
+ startup_time = timer.seconds()
+ logger.info("Dataloader startup time: {:.2f} seconds".format(startup_time))
+
+ comm.synchronize()
+
+ avg, all_times = self._benchmark(loader, num_iter * max(n, 1), warmup * max(n, 1))
+ del loader
+ self._log_time(
+ f"DataLoader ({gpu} GPUs x {n} workers, total bs={self.total_batch_size})",
+ avg,
+ all_times,
+ True,
+ )
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/build.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/build.py
new file mode 100644
index 0000000000000000000000000000000000000000..aaa44834b76e9347f0ad5c452f0d8e2f532d1795
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/build.py
@@ -0,0 +1,556 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import itertools
+import logging
+import numpy as np
+import operator
+import pickle
+from typing import Any, Callable, Dict, List, Optional, Union
+import torch
+import torch.utils.data as torchdata
+from tabulate import tabulate
+from termcolor import colored
+
+from custom_detectron2.config import configurable
+from custom_detectron2.structures import BoxMode
+from custom_detectron2.utils.comm import get_world_size
+from custom_detectron2.utils.env import seed_all_rng
+from custom_detectron2.utils.file_io import PathManager
+from custom_detectron2.utils.logger import _log_api_usage, log_first_n
+
+from .catalog import DatasetCatalog, MetadataCatalog
+from .common import AspectRatioGroupedDataset, DatasetFromList, MapDataset, ToIterableDataset
+from .dataset_mapper import DatasetMapper
+from .detection_utils import check_metadata_consistency
+from .samplers import (
+ InferenceSampler,
+ RandomSubsetTrainingSampler,
+ RepeatFactorTrainingSampler,
+ TrainingSampler,
+)
+
+"""
+This file contains the default logic to build a dataloader for training or testing.
+"""
+
+__all__ = [
+ "build_batch_data_loader",
+ "build_detection_train_loader",
+ "build_detection_test_loader",
+ "get_detection_dataset_dicts",
+ "load_proposals_into_dataset",
+ "print_instances_class_histogram",
+]
+
+
+def filter_images_with_only_crowd_annotations(dataset_dicts):
+ """
+ Filter out images with none annotations or only crowd annotations
+ (i.e., images without non-crowd annotations).
+ A common training-time preprocessing on COCO dataset.
+
+ Args:
+ dataset_dicts (list[dict]): annotations in Detectron2 Dataset format.
+
+ Returns:
+ list[dict]: the same format, but filtered.
+ """
+ num_before = len(dataset_dicts)
+
+ def valid(anns):
+ for ann in anns:
+ if ann.get("iscrowd", 0) == 0:
+ return True
+ return False
+
+ dataset_dicts = [x for x in dataset_dicts if valid(x["annotations"])]
+ num_after = len(dataset_dicts)
+ logger = logging.getLogger(__name__)
+ logger.info(
+ "Removed {} images with no usable annotations. {} images left.".format(
+ num_before - num_after, num_after
+ )
+ )
+ return dataset_dicts
+
+
+def filter_images_with_few_keypoints(dataset_dicts, min_keypoints_per_image):
+ """
+ Filter out images with too few number of keypoints.
+
+ Args:
+ dataset_dicts (list[dict]): annotations in Detectron2 Dataset format.
+
+ Returns:
+ list[dict]: the same format as dataset_dicts, but filtered.
+ """
+ num_before = len(dataset_dicts)
+
+ def visible_keypoints_in_image(dic):
+ # Each keypoints field has the format [x1, y1, v1, ...], where v is visibility
+ annotations = dic["annotations"]
+ return sum(
+ (np.array(ann["keypoints"][2::3]) > 0).sum()
+ for ann in annotations
+ if "keypoints" in ann
+ )
+
+ dataset_dicts = [
+ x for x in dataset_dicts if visible_keypoints_in_image(x) >= min_keypoints_per_image
+ ]
+ num_after = len(dataset_dicts)
+ logger = logging.getLogger(__name__)
+ logger.info(
+ "Removed {} images with fewer than {} keypoints.".format(
+ num_before - num_after, min_keypoints_per_image
+ )
+ )
+ return dataset_dicts
+
+
+def load_proposals_into_dataset(dataset_dicts, proposal_file):
+ """
+ Load precomputed object proposals into the dataset.
+
+ The proposal file should be a pickled dict with the following keys:
+
+ - "ids": list[int] or list[str], the image ids
+ - "boxes": list[np.ndarray], each is an Nx4 array of boxes corresponding to the image id
+ - "objectness_logits": list[np.ndarray], each is an N sized array of objectness scores
+ corresponding to the boxes.
+ - "bbox_mode": the BoxMode of the boxes array. Defaults to ``BoxMode.XYXY_ABS``.
+
+ Args:
+ dataset_dicts (list[dict]): annotations in Detectron2 Dataset format.
+ proposal_file (str): file path of pre-computed proposals, in pkl format.
+
+ Returns:
+ list[dict]: the same format as dataset_dicts, but added proposal field.
+ """
+ logger = logging.getLogger(__name__)
+ logger.info("Loading proposals from: {}".format(proposal_file))
+
+ with PathManager.open(proposal_file, "rb") as f:
+ proposals = pickle.load(f, encoding="latin1")
+
+ # Rename the key names in D1 proposal files
+ rename_keys = {"indexes": "ids", "scores": "objectness_logits"}
+ for key in rename_keys:
+ if key in proposals:
+ proposals[rename_keys[key]] = proposals.pop(key)
+
+ # Fetch the indexes of all proposals that are in the dataset
+ # Convert image_id to str since they could be int.
+ img_ids = set({str(record["image_id"]) for record in dataset_dicts})
+ id_to_index = {str(id): i for i, id in enumerate(proposals["ids"]) if str(id) in img_ids}
+
+ # Assuming default bbox_mode of precomputed proposals are 'XYXY_ABS'
+ bbox_mode = BoxMode(proposals["bbox_mode"]) if "bbox_mode" in proposals else BoxMode.XYXY_ABS
+
+ for record in dataset_dicts:
+ # Get the index of the proposal
+ i = id_to_index[str(record["image_id"])]
+
+ boxes = proposals["boxes"][i]
+ objectness_logits = proposals["objectness_logits"][i]
+ # Sort the proposals in descending order of the scores
+ inds = objectness_logits.argsort()[::-1]
+ record["proposal_boxes"] = boxes[inds]
+ record["proposal_objectness_logits"] = objectness_logits[inds]
+ record["proposal_bbox_mode"] = bbox_mode
+
+ return dataset_dicts
+
+
+def print_instances_class_histogram(dataset_dicts, class_names):
+ """
+ Args:
+ dataset_dicts (list[dict]): list of dataset dicts.
+ class_names (list[str]): list of class names (zero-indexed).
+ """
+ num_classes = len(class_names)
+ hist_bins = np.arange(num_classes + 1)
+ histogram = np.zeros((num_classes,), dtype=np.int)
+ for entry in dataset_dicts:
+ annos = entry["annotations"]
+ classes = np.asarray(
+ [x["category_id"] for x in annos if not x.get("iscrowd", 0)], dtype=np.int
+ )
+ if len(classes):
+ assert classes.min() >= 0, f"Got an invalid category_id={classes.min()}"
+ assert (
+ classes.max() < num_classes
+ ), f"Got an invalid category_id={classes.max()} for a dataset of {num_classes} classes"
+ histogram += np.histogram(classes, bins=hist_bins)[0]
+
+ N_COLS = min(6, len(class_names) * 2)
+
+ def short_name(x):
+ # make long class names shorter. useful for lvis
+ if len(x) > 13:
+ return x[:11] + ".."
+ return x
+
+ data = list(
+ itertools.chain(*[[short_name(class_names[i]), int(v)] for i, v in enumerate(histogram)])
+ )
+ total_num_instances = sum(data[1::2])
+ data.extend([None] * (N_COLS - (len(data) % N_COLS)))
+ if num_classes > 1:
+ data.extend(["total", total_num_instances])
+ data = itertools.zip_longest(*[data[i::N_COLS] for i in range(N_COLS)])
+ table = tabulate(
+ data,
+ headers=["category", "#instances"] * (N_COLS // 2),
+ tablefmt="pipe",
+ numalign="left",
+ stralign="center",
+ )
+ log_first_n(
+ logging.INFO,
+ "Distribution of instances among all {} categories:\n".format(num_classes)
+ + colored(table, "cyan"),
+ key="message",
+ )
+
+
+def get_detection_dataset_dicts(
+ names,
+ filter_empty=True,
+ min_keypoints=0,
+ proposal_files=None,
+ check_consistency=True,
+):
+ """
+ Load and prepare dataset dicts for instance detection/segmentation and semantic segmentation.
+
+ Args:
+ names (str or list[str]): a dataset name or a list of dataset names
+ filter_empty (bool): whether to filter out images without instance annotations
+ min_keypoints (int): filter out images with fewer keypoints than
+ `min_keypoints`. Set to 0 to do nothing.
+ proposal_files (list[str]): if given, a list of object proposal files
+ that match each dataset in `names`.
+ check_consistency (bool): whether to check if datasets have consistent metadata.
+
+ Returns:
+ list[dict]: a list of dicts following the standard dataset dict format.
+ """
+ if isinstance(names, str):
+ names = [names]
+ assert len(names), names
+ dataset_dicts = [DatasetCatalog.get(dataset_name) for dataset_name in names]
+
+ if isinstance(dataset_dicts[0], torchdata.Dataset):
+ if len(dataset_dicts) > 1:
+ # ConcatDataset does not work for iterable style dataset.
+ # We could support concat for iterable as well, but it's often
+ # not a good idea to concat iterables anyway.
+ return torchdata.ConcatDataset(dataset_dicts)
+ return dataset_dicts[0]
+
+ for dataset_name, dicts in zip(names, dataset_dicts):
+ assert len(dicts), "Dataset '{}' is empty!".format(dataset_name)
+
+ if proposal_files is not None:
+ assert len(names) == len(proposal_files)
+ # load precomputed proposals from proposal files
+ dataset_dicts = [
+ load_proposals_into_dataset(dataset_i_dicts, proposal_file)
+ for dataset_i_dicts, proposal_file in zip(dataset_dicts, proposal_files)
+ ]
+
+ dataset_dicts = list(itertools.chain.from_iterable(dataset_dicts))
+
+ has_instances = "annotations" in dataset_dicts[0]
+ if filter_empty and has_instances:
+ dataset_dicts = filter_images_with_only_crowd_annotations(dataset_dicts)
+ if min_keypoints > 0 and has_instances:
+ dataset_dicts = filter_images_with_few_keypoints(dataset_dicts, min_keypoints)
+
+ if check_consistency and has_instances:
+ try:
+ class_names = MetadataCatalog.get(names[0]).thing_classes
+ check_metadata_consistency("thing_classes", names)
+ print_instances_class_histogram(dataset_dicts, class_names)
+ except AttributeError: # class names are not available for this dataset
+ pass
+
+ assert len(dataset_dicts), "No valid data found in {}.".format(",".join(names))
+ return dataset_dicts
+
+
+def build_batch_data_loader(
+ dataset,
+ sampler,
+ total_batch_size,
+ *,
+ aspect_ratio_grouping=False,
+ num_workers=0,
+ collate_fn=None,
+):
+ """
+ Build a batched dataloader. The main differences from `torch.utils.data.DataLoader` are:
+ 1. support aspect ratio grouping options
+ 2. use no "batch collation", because this is common for detection training
+
+ Args:
+ dataset (torch.utils.data.Dataset): a pytorch map-style or iterable dataset.
+ sampler (torch.utils.data.sampler.Sampler or None): a sampler that produces indices.
+ Must be provided iff. ``dataset`` is a map-style dataset.
+ total_batch_size, aspect_ratio_grouping, num_workers, collate_fn: see
+ :func:`build_detection_train_loader`.
+
+ Returns:
+ iterable[list]. Length of each list is the batch size of the current
+ GPU. Each element in the list comes from the dataset.
+ """
+ world_size = get_world_size()
+ assert (
+ total_batch_size > 0 and total_batch_size % world_size == 0
+ ), "Total batch size ({}) must be divisible by the number of gpus ({}).".format(
+ total_batch_size, world_size
+ )
+ batch_size = total_batch_size // world_size
+
+ if isinstance(dataset, torchdata.IterableDataset):
+ assert sampler is None, "sampler must be None if dataset is IterableDataset"
+ else:
+ dataset = ToIterableDataset(dataset, sampler)
+
+ if aspect_ratio_grouping:
+ data_loader = torchdata.DataLoader(
+ dataset,
+ num_workers=num_workers,
+ collate_fn=operator.itemgetter(0), # don't batch, but yield individual elements
+ worker_init_fn=worker_init_reset_seed,
+ ) # yield individual mapped dict
+ data_loader = AspectRatioGroupedDataset(data_loader, batch_size)
+ if collate_fn is None:
+ return data_loader
+ return MapDataset(data_loader, collate_fn)
+ else:
+ return torchdata.DataLoader(
+ dataset,
+ batch_size=batch_size,
+ drop_last=True,
+ num_workers=num_workers,
+ collate_fn=trivial_batch_collator if collate_fn is None else collate_fn,
+ worker_init_fn=worker_init_reset_seed,
+ )
+
+
+def _train_loader_from_config(cfg, mapper=None, *, dataset=None, sampler=None):
+ if dataset is None:
+ dataset = get_detection_dataset_dicts(
+ cfg.DATASETS.TRAIN,
+ filter_empty=cfg.DATALOADER.FILTER_EMPTY_ANNOTATIONS,
+ min_keypoints=cfg.MODEL.ROI_KEYPOINT_HEAD.MIN_KEYPOINTS_PER_IMAGE
+ if cfg.MODEL.KEYPOINT_ON
+ else 0,
+ proposal_files=cfg.DATASETS.PROPOSAL_FILES_TRAIN if cfg.MODEL.LOAD_PROPOSALS else None,
+ )
+ _log_api_usage("dataset." + cfg.DATASETS.TRAIN[0])
+
+ if mapper is None:
+ mapper = DatasetMapper(cfg, True)
+
+ if sampler is None:
+ sampler_name = cfg.DATALOADER.SAMPLER_TRAIN
+ logger = logging.getLogger(__name__)
+ if isinstance(dataset, torchdata.IterableDataset):
+ logger.info("Not using any sampler since the dataset is IterableDataset.")
+ sampler = None
+ else:
+ logger.info("Using training sampler {}".format(sampler_name))
+ if sampler_name == "TrainingSampler":
+ sampler = TrainingSampler(len(dataset))
+ elif sampler_name == "RepeatFactorTrainingSampler":
+ repeat_factors = RepeatFactorTrainingSampler.repeat_factors_from_category_frequency(
+ dataset, cfg.DATALOADER.REPEAT_THRESHOLD
+ )
+ sampler = RepeatFactorTrainingSampler(repeat_factors)
+ elif sampler_name == "RandomSubsetTrainingSampler":
+ sampler = RandomSubsetTrainingSampler(
+ len(dataset), cfg.DATALOADER.RANDOM_SUBSET_RATIO
+ )
+ else:
+ raise ValueError("Unknown training sampler: {}".format(sampler_name))
+
+ return {
+ "dataset": dataset,
+ "sampler": sampler,
+ "mapper": mapper,
+ "total_batch_size": cfg.SOLVER.IMS_PER_BATCH,
+ "aspect_ratio_grouping": cfg.DATALOADER.ASPECT_RATIO_GROUPING,
+ "num_workers": cfg.DATALOADER.NUM_WORKERS,
+ }
+
+
+@configurable(from_config=_train_loader_from_config)
+def build_detection_train_loader(
+ dataset,
+ *,
+ mapper,
+ sampler=None,
+ total_batch_size,
+ aspect_ratio_grouping=True,
+ num_workers=0,
+ collate_fn=None,
+):
+ """
+ Build a dataloader for object detection with some default features.
+
+ Args:
+ dataset (list or torch.utils.data.Dataset): a list of dataset dicts,
+ or a pytorch dataset (either map-style or iterable). It can be obtained
+ by using :func:`DatasetCatalog.get` or :func:`get_detection_dataset_dicts`.
+ mapper (callable): a callable which takes a sample (dict) from dataset and
+ returns the format to be consumed by the model.
+ When using cfg, the default choice is ``DatasetMapper(cfg, is_train=True)``.
+ sampler (torch.utils.data.sampler.Sampler or None): a sampler that produces
+ indices to be applied on ``dataset``.
+ If ``dataset`` is map-style, the default sampler is a :class:`TrainingSampler`,
+ which coordinates an infinite random shuffle sequence across all workers.
+ Sampler must be None if ``dataset`` is iterable.
+ total_batch_size (int): total batch size across all workers.
+ aspect_ratio_grouping (bool): whether to group images with similar
+ aspect ratio for efficiency. When enabled, it requires each
+ element in dataset be a dict with keys "width" and "height".
+ num_workers (int): number of parallel data loading workers
+ collate_fn: a function that determines how to do batching, same as the argument of
+ `torch.utils.data.DataLoader`. Defaults to do no collation and return a list of
+ data. No collation is OK for small batch size and simple data structures.
+ If your batch size is large and each sample contains too many small tensors,
+ it's more efficient to collate them in data loader.
+
+ Returns:
+ torch.utils.data.DataLoader:
+ a dataloader. Each output from it is a ``list[mapped_element]`` of length
+ ``total_batch_size / num_workers``, where ``mapped_element`` is produced
+ by the ``mapper``.
+ """
+ if isinstance(dataset, list):
+ dataset = DatasetFromList(dataset, copy=False)
+ if mapper is not None:
+ dataset = MapDataset(dataset, mapper)
+
+ if isinstance(dataset, torchdata.IterableDataset):
+ assert sampler is None, "sampler must be None if dataset is IterableDataset"
+ else:
+ if sampler is None:
+ sampler = TrainingSampler(len(dataset))
+ assert isinstance(sampler, torchdata.Sampler), f"Expect a Sampler but got {type(sampler)}"
+ return build_batch_data_loader(
+ dataset,
+ sampler,
+ total_batch_size,
+ aspect_ratio_grouping=aspect_ratio_grouping,
+ num_workers=num_workers,
+ collate_fn=collate_fn,
+ )
+
+
+def _test_loader_from_config(cfg, dataset_name, mapper=None):
+ """
+ Uses the given `dataset_name` argument (instead of the names in cfg), because the
+ standard practice is to evaluate each test set individually (not combining them).
+ """
+ if isinstance(dataset_name, str):
+ dataset_name = [dataset_name]
+
+ dataset = get_detection_dataset_dicts(
+ dataset_name,
+ filter_empty=False,
+ proposal_files=[
+ cfg.DATASETS.PROPOSAL_FILES_TEST[list(cfg.DATASETS.TEST).index(x)] for x in dataset_name
+ ]
+ if cfg.MODEL.LOAD_PROPOSALS
+ else None,
+ )
+ if mapper is None:
+ mapper = DatasetMapper(cfg, False)
+ return {
+ "dataset": dataset,
+ "mapper": mapper,
+ "num_workers": cfg.DATALOADER.NUM_WORKERS,
+ "sampler": InferenceSampler(len(dataset))
+ if not isinstance(dataset, torchdata.IterableDataset)
+ else None,
+ }
+
+
+@configurable(from_config=_test_loader_from_config)
+def build_detection_test_loader(
+ dataset: Union[List[Any], torchdata.Dataset],
+ *,
+ mapper: Callable[[Dict[str, Any]], Any],
+ sampler: Optional[torchdata.Sampler] = None,
+ batch_size: int = 1,
+ num_workers: int = 0,
+ collate_fn: Optional[Callable[[List[Any]], Any]] = None,
+) -> torchdata.DataLoader:
+ """
+ Similar to `build_detection_train_loader`, with default batch size = 1,
+ and sampler = :class:`InferenceSampler`. This sampler coordinates all workers
+ to produce the exact set of all samples.
+
+ Args:
+ dataset: a list of dataset dicts,
+ or a pytorch dataset (either map-style or iterable). They can be obtained
+ by using :func:`DatasetCatalog.get` or :func:`get_detection_dataset_dicts`.
+ mapper: a callable which takes a sample (dict) from dataset
+ and returns the format to be consumed by the model.
+ When using cfg, the default choice is ``DatasetMapper(cfg, is_train=False)``.
+ sampler: a sampler that produces
+ indices to be applied on ``dataset``. Default to :class:`InferenceSampler`,
+ which splits the dataset across all workers. Sampler must be None
+ if `dataset` is iterable.
+ batch_size: the batch size of the data loader to be created.
+ Default to 1 image per worker since this is the standard when reporting
+ inference time in papers.
+ num_workers: number of parallel data loading workers
+ collate_fn: same as the argument of `torch.utils.data.DataLoader`.
+ Defaults to do no collation and return a list of data.
+
+ Returns:
+ DataLoader: a torch DataLoader, that loads the given detection
+ dataset, with test-time transformation and batching.
+
+ Examples:
+ ::
+ data_loader = build_detection_test_loader(
+ DatasetRegistry.get("my_test"),
+ mapper=DatasetMapper(...))
+
+ # or, instantiate with a CfgNode:
+ data_loader = build_detection_test_loader(cfg, "my_test")
+ """
+ if isinstance(dataset, list):
+ dataset = DatasetFromList(dataset, copy=False)
+ if mapper is not None:
+ dataset = MapDataset(dataset, mapper)
+ if isinstance(dataset, torchdata.IterableDataset):
+ assert sampler is None, "sampler must be None if dataset is IterableDataset"
+ else:
+ if sampler is None:
+ sampler = InferenceSampler(len(dataset))
+ return torchdata.DataLoader(
+ dataset,
+ batch_size=batch_size,
+ sampler=sampler,
+ drop_last=False,
+ num_workers=num_workers,
+ collate_fn=trivial_batch_collator if collate_fn is None else collate_fn,
+ )
+
+
+def trivial_batch_collator(batch):
+ """
+ A batch collator that does nothing.
+ """
+ return batch
+
+
+def worker_init_reset_seed(worker_id):
+ initial_seed = torch.initial_seed() % 2**31
+ seed_all_rng(initial_seed + worker_id)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/catalog.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/catalog.py
new file mode 100644
index 0000000000000000000000000000000000000000..8a5507b8e3bd75eb2acba1b6218e7832090ca985
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/catalog.py
@@ -0,0 +1,236 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import copy
+import logging
+import types
+from collections import UserDict
+from typing import List
+
+from custom_detectron2.utils.logger import log_first_n
+
+__all__ = ["DatasetCatalog", "MetadataCatalog", "Metadata"]
+
+
+class _DatasetCatalog(UserDict):
+ """
+ A global dictionary that stores information about the datasets and how to obtain them.
+
+ It contains a mapping from strings
+ (which are names that identify a dataset, e.g. "coco_2014_train")
+ to a function which parses the dataset and returns the samples in the
+ format of `list[dict]`.
+
+ The returned dicts should be in Detectron2 Dataset format (See DATASETS.md for details)
+ if used with the data loader functionalities in `data/build.py,data/detection_transform.py`.
+
+ The purpose of having this catalog is to make it easy to choose
+ different datasets, by just using the strings in the config.
+ """
+
+ def register(self, name, func):
+ """
+ Args:
+ name (str): the name that identifies a dataset, e.g. "coco_2014_train".
+ func (callable): a callable which takes no arguments and returns a list of dicts.
+ It must return the same results if called multiple times.
+ """
+ assert callable(func), "You must register a function with `DatasetCatalog.register`!"
+ assert name not in self, "Dataset '{}' is already registered!".format(name)
+ self[name] = func
+
+ def get(self, name):
+ """
+ Call the registered function and return its results.
+
+ Args:
+ name (str): the name that identifies a dataset, e.g. "coco_2014_train".
+
+ Returns:
+ list[dict]: dataset annotations.
+ """
+ try:
+ f = self[name]
+ except KeyError as e:
+ raise KeyError(
+ "Dataset '{}' is not registered! Available datasets are: {}".format(
+ name, ", ".join(list(self.keys()))
+ )
+ ) from e
+ return f()
+
+ def list(self) -> List[str]:
+ """
+ List all registered datasets.
+
+ Returns:
+ list[str]
+ """
+ return list(self.keys())
+
+ def remove(self, name):
+ """
+ Alias of ``pop``.
+ """
+ self.pop(name)
+
+ def __str__(self):
+ return "DatasetCatalog(registered datasets: {})".format(", ".join(self.keys()))
+
+ __repr__ = __str__
+
+
+DatasetCatalog = _DatasetCatalog()
+DatasetCatalog.__doc__ = (
+ _DatasetCatalog.__doc__
+ + """
+ .. automethod:: detectron2.data.catalog.DatasetCatalog.register
+ .. automethod:: detectron2.data.catalog.DatasetCatalog.get
+"""
+)
+
+
+class Metadata(types.SimpleNamespace):
+ """
+ A class that supports simple attribute setter/getter.
+ It is intended for storing metadata of a dataset and make it accessible globally.
+
+ Examples:
+ ::
+ # somewhere when you load the data:
+ MetadataCatalog.get("mydataset").thing_classes = ["person", "dog"]
+
+ # somewhere when you print statistics or visualize:
+ classes = MetadataCatalog.get("mydataset").thing_classes
+ """
+
+ # the name of the dataset
+ # set default to N/A so that `self.name` in the errors will not trigger getattr again
+ name: str = "N/A"
+
+ _RENAMED = {
+ "class_names": "thing_classes",
+ "dataset_id_to_contiguous_id": "thing_dataset_id_to_contiguous_id",
+ "stuff_class_names": "stuff_classes",
+ }
+
+ def __getattr__(self, key):
+ if key in self._RENAMED:
+ log_first_n(
+ logging.WARNING,
+ "Metadata '{}' was renamed to '{}'!".format(key, self._RENAMED[key]),
+ n=10,
+ )
+ return getattr(self, self._RENAMED[key])
+
+ # "name" exists in every metadata
+ if len(self.__dict__) > 1:
+ raise AttributeError(
+ "Attribute '{}' does not exist in the metadata of dataset '{}'. Available "
+ "keys are {}.".format(key, self.name, str(self.__dict__.keys()))
+ )
+ else:
+ raise AttributeError(
+ f"Attribute '{key}' does not exist in the metadata of dataset '{self.name}': "
+ "metadata is empty."
+ )
+
+ def __setattr__(self, key, val):
+ if key in self._RENAMED:
+ log_first_n(
+ logging.WARNING,
+ "Metadata '{}' was renamed to '{}'!".format(key, self._RENAMED[key]),
+ n=10,
+ )
+ setattr(self, self._RENAMED[key], val)
+
+ # Ensure that metadata of the same name stays consistent
+ try:
+ oldval = getattr(self, key)
+ assert oldval == val, (
+ "Attribute '{}' in the metadata of '{}' cannot be set "
+ "to a different value!\n{} != {}".format(key, self.name, oldval, val)
+ )
+ except AttributeError:
+ super().__setattr__(key, val)
+
+ def as_dict(self):
+ """
+ Returns all the metadata as a dict.
+ Note that modifications to the returned dict will not reflect on the Metadata object.
+ """
+ return copy.copy(self.__dict__)
+
+ def set(self, **kwargs):
+ """
+ Set multiple metadata with kwargs.
+ """
+ for k, v in kwargs.items():
+ setattr(self, k, v)
+ return self
+
+ def get(self, key, default=None):
+ """
+ Access an attribute and return its value if exists.
+ Otherwise return default.
+ """
+ try:
+ return getattr(self, key)
+ except AttributeError:
+ return default
+
+
+class _MetadataCatalog(UserDict):
+ """
+ MetadataCatalog is a global dictionary that provides access to
+ :class:`Metadata` of a given dataset.
+
+ The metadata associated with a certain name is a singleton: once created, the
+ metadata will stay alive and will be returned by future calls to ``get(name)``.
+
+ It's like global variables, so don't abuse it.
+ It's meant for storing knowledge that's constant and shared across the execution
+ of the program, e.g.: the class names in COCO.
+ """
+
+ def get(self, name):
+ """
+ Args:
+ name (str): name of a dataset (e.g. coco_2014_train).
+
+ Returns:
+ Metadata: The :class:`Metadata` instance associated with this name,
+ or create an empty one if none is available.
+ """
+ assert len(name)
+ r = super().get(name, None)
+ if r is None:
+ r = self[name] = Metadata(name=name)
+ return r
+
+ def list(self):
+ """
+ List all registered metadata.
+
+ Returns:
+ list[str]: keys (names of datasets) of all registered metadata
+ """
+ return list(self.keys())
+
+ def remove(self, name):
+ """
+ Alias of ``pop``.
+ """
+ self.pop(name)
+
+ def __str__(self):
+ return "MetadataCatalog(registered metadata: {})".format(", ".join(self.keys()))
+
+ __repr__ = __str__
+
+
+MetadataCatalog = _MetadataCatalog()
+MetadataCatalog.__doc__ = (
+ _MetadataCatalog.__doc__
+ + """
+ .. automethod:: detectron2.data.catalog.MetadataCatalog.get
+"""
+)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/common.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/common.py
new file mode 100644
index 0000000000000000000000000000000000000000..42d15cfa7fa1bbf04afce3c616a590ac48554d2e
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/common.py
@@ -0,0 +1,301 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import contextlib
+import copy
+import itertools
+import logging
+import numpy as np
+import pickle
+import random
+from typing import Callable, Union
+import torch
+import torch.utils.data as data
+from torch.utils.data.sampler import Sampler
+
+from custom_detectron2.utils.serialize import PicklableWrapper
+
+__all__ = ["MapDataset", "DatasetFromList", "AspectRatioGroupedDataset", "ToIterableDataset"]
+
+logger = logging.getLogger(__name__)
+
+
+def _shard_iterator_dataloader_worker(iterable):
+ # Shard the iterable if we're currently inside pytorch dataloader worker.
+ worker_info = data.get_worker_info()
+ if worker_info is None or worker_info.num_workers == 1:
+ # do nothing
+ yield from iterable
+ else:
+ yield from itertools.islice(iterable, worker_info.id, None, worker_info.num_workers)
+
+
+class _MapIterableDataset(data.IterableDataset):
+ """
+ Map a function over elements in an IterableDataset.
+
+ Similar to pytorch's MapIterDataPipe, but support filtering when map_func
+ returns None.
+
+ This class is not public-facing. Will be called by `MapDataset`.
+ """
+
+ def __init__(self, dataset, map_func):
+ self._dataset = dataset
+ self._map_func = PicklableWrapper(map_func) # wrap so that a lambda will work
+
+ def __len__(self):
+ return len(self._dataset)
+
+ def __iter__(self):
+ for x in map(self._map_func, self._dataset):
+ if x is not None:
+ yield x
+
+
+class MapDataset(data.Dataset):
+ """
+ Map a function over the elements in a dataset.
+ """
+
+ def __init__(self, dataset, map_func):
+ """
+ Args:
+ dataset: a dataset where map function is applied. Can be either
+ map-style or iterable dataset. When given an iterable dataset,
+ the returned object will also be an iterable dataset.
+ map_func: a callable which maps the element in dataset. map_func can
+ return None to skip the data (e.g. in case of errors).
+ How None is handled depends on the style of `dataset`.
+ If `dataset` is map-style, it randomly tries other elements.
+ If `dataset` is iterable, it skips the data and tries the next.
+ """
+ self._dataset = dataset
+ self._map_func = PicklableWrapper(map_func) # wrap so that a lambda will work
+
+ self._rng = random.Random(42)
+ self._fallback_candidates = set(range(len(dataset)))
+
+ def __new__(cls, dataset, map_func):
+ is_iterable = isinstance(dataset, data.IterableDataset)
+ if is_iterable:
+ return _MapIterableDataset(dataset, map_func)
+ else:
+ return super().__new__(cls)
+
+ def __getnewargs__(self):
+ return self._dataset, self._map_func
+
+ def __len__(self):
+ return len(self._dataset)
+
+ def __getitem__(self, idx):
+ retry_count = 0
+ cur_idx = int(idx)
+
+ while True:
+ data = self._map_func(self._dataset[cur_idx])
+ if data is not None:
+ self._fallback_candidates.add(cur_idx)
+ return data
+
+ # _map_func fails for this idx, use a random new index from the pool
+ retry_count += 1
+ self._fallback_candidates.discard(cur_idx)
+ cur_idx = self._rng.sample(self._fallback_candidates, k=1)[0]
+
+ if retry_count >= 3:
+ logger = logging.getLogger(__name__)
+ logger.warning(
+ "Failed to apply `_map_func` for idx: {}, retry count: {}".format(
+ idx, retry_count
+ )
+ )
+
+
+class _TorchSerializedList(object):
+ """
+ A list-like object whose items are serialized and stored in a torch tensor. When
+ launching a process that uses TorchSerializedList with "fork" start method,
+ the subprocess can read the same buffer without triggering copy-on-access. When
+ launching a process that uses TorchSerializedList with "spawn/forkserver" start
+ method, the list will be pickled by a special ForkingPickler registered by PyTorch
+ that moves data to shared memory. In both cases, this allows parent and child
+ processes to share RAM for the list data, hence avoids the issue in
+ https://github.com/pytorch/pytorch/issues/13246.
+
+ See also https://ppwwyyxx.com/blog/2022/Demystify-RAM-Usage-in-Multiprocess-DataLoader/
+ on how it works.
+ """
+
+ def __init__(self, lst: list):
+ self._lst = lst
+
+ def _serialize(data):
+ buffer = pickle.dumps(data, protocol=-1)
+ return np.frombuffer(buffer, dtype=np.uint8)
+
+ logger.info(
+ "Serializing {} elements to byte tensors and concatenating them all ...".format(
+ len(self._lst)
+ )
+ )
+ self._lst = [_serialize(x) for x in self._lst]
+ self._addr = np.asarray([len(x) for x in self._lst], dtype=np.int64)
+ self._addr = torch.from_numpy(np.cumsum(self._addr))
+ self._lst = torch.from_numpy(np.concatenate(self._lst))
+ logger.info("Serialized dataset takes {:.2f} MiB".format(len(self._lst) / 1024**2))
+
+ def __len__(self):
+ return len(self._addr)
+
+ def __getitem__(self, idx):
+ start_addr = 0 if idx == 0 else self._addr[idx - 1].item()
+ end_addr = self._addr[idx].item()
+ bytes = memoryview(self._lst[start_addr:end_addr].numpy())
+
+ # @lint-ignore PYTHONPICKLEISBAD
+ return pickle.loads(bytes)
+
+
+_DEFAULT_DATASET_FROM_LIST_SERIALIZE_METHOD = _TorchSerializedList
+
+
+@contextlib.contextmanager
+def set_default_dataset_from_list_serialize_method(new):
+ """
+ Context manager for using custom serialize function when creating DatasetFromList
+ """
+
+ global _DEFAULT_DATASET_FROM_LIST_SERIALIZE_METHOD
+ orig = _DEFAULT_DATASET_FROM_LIST_SERIALIZE_METHOD
+ _DEFAULT_DATASET_FROM_LIST_SERIALIZE_METHOD = new
+ yield
+ _DEFAULT_DATASET_FROM_LIST_SERIALIZE_METHOD = orig
+
+
+class DatasetFromList(data.Dataset):
+ """
+ Wrap a list to a torch Dataset. It produces elements of the list as data.
+ """
+
+ def __init__(
+ self,
+ lst: list,
+ copy: bool = True,
+ serialize: Union[bool, Callable] = True,
+ ):
+ """
+ Args:
+ lst (list): a list which contains elements to produce.
+ copy (bool): whether to deepcopy the element when producing it,
+ so that the result can be modified in place without affecting the
+ source in the list.
+ serialize (bool or callable): whether to serialize the stroage to other
+ backend. If `True`, the default serialize method will be used, if given
+ a callable, the callable will be used as serialize method.
+ """
+ self._lst = lst
+ self._copy = copy
+ if not isinstance(serialize, (bool, Callable)):
+ raise TypeError(f"Unsupported type for argument `serailzie`: {serialize}")
+ self._serialize = serialize is not False
+
+ if self._serialize:
+ serialize_method = (
+ serialize
+ if isinstance(serialize, Callable)
+ else _DEFAULT_DATASET_FROM_LIST_SERIALIZE_METHOD
+ )
+ logger.info(f"Serializing the dataset using: {serialize_method}")
+ self._lst = serialize_method(self._lst)
+
+ def __len__(self):
+ return len(self._lst)
+
+ def __getitem__(self, idx):
+ if self._copy and not self._serialize:
+ return copy.deepcopy(self._lst[idx])
+ else:
+ return self._lst[idx]
+
+
+class ToIterableDataset(data.IterableDataset):
+ """
+ Convert an old indices-based (also called map-style) dataset
+ to an iterable-style dataset.
+ """
+
+ def __init__(self, dataset: data.Dataset, sampler: Sampler, shard_sampler: bool = True):
+ """
+ Args:
+ dataset: an old-style dataset with ``__getitem__``
+ sampler: a cheap iterable that produces indices to be applied on ``dataset``.
+ shard_sampler: whether to shard the sampler based on the current pytorch data loader
+ worker id. When an IterableDataset is forked by pytorch's DataLoader into multiple
+ workers, it is responsible for sharding its data based on worker id so that workers
+ don't produce identical data.
+
+ Most samplers (like our TrainingSampler) do not shard based on dataloader worker id
+ and this argument should be set to True. But certain samplers may be already
+ sharded, in that case this argument should be set to False.
+ """
+ assert not isinstance(dataset, data.IterableDataset), dataset
+ assert isinstance(sampler, Sampler), sampler
+ self.dataset = dataset
+ self.sampler = sampler
+ self.shard_sampler = shard_sampler
+
+ def __iter__(self):
+ if not self.shard_sampler:
+ sampler = self.sampler
+ else:
+ # With map-style dataset, `DataLoader(dataset, sampler)` runs the
+ # sampler in main process only. But `DataLoader(ToIterableDataset(dataset, sampler))`
+ # will run sampler in every of the N worker. So we should only keep 1/N of the ids on
+ # each worker. The assumption is that sampler is cheap to iterate so it's fine to
+ # discard ids in workers.
+ sampler = _shard_iterator_dataloader_worker(self.sampler)
+ for idx in sampler:
+ yield self.dataset[idx]
+
+ def __len__(self):
+ return len(self.sampler)
+
+
+class AspectRatioGroupedDataset(data.IterableDataset):
+ """
+ Batch data that have similar aspect ratio together.
+ In this implementation, images whose aspect ratio < (or >) 1 will
+ be batched together.
+ This improves training speed because the images then need less padding
+ to form a batch.
+
+ It assumes the underlying dataset produces dicts with "width" and "height" keys.
+ It will then produce a list of original dicts with length = batch_size,
+ all with similar aspect ratios.
+ """
+
+ def __init__(self, dataset, batch_size):
+ """
+ Args:
+ dataset: an iterable. Each element must be a dict with keys
+ "width" and "height", which will be used to batch data.
+ batch_size (int):
+ """
+ self.dataset = dataset
+ self.batch_size = batch_size
+ self._buckets = [[] for _ in range(2)]
+ # Hard-coded two aspect ratio groups: w > h and w < h.
+ # Can add support for more aspect ratio groups, but doesn't seem useful
+
+ def __iter__(self):
+ for d in self.dataset:
+ w, h = d["width"], d["height"]
+ bucket_id = 0 if w > h else 1
+ bucket = self._buckets[bucket_id]
+ bucket.append(d)
+ if len(bucket) == self.batch_size:
+ data = bucket[:]
+ # Clear bucket first, because code after yield is not
+ # guaranteed to execute
+ del bucket[:]
+ yield data
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/dataset_mapper.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/dataset_mapper.py
new file mode 100644
index 0000000000000000000000000000000000000000..884bbae6e2093974677c8ee8aebb2cae8f43d1b6
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/dataset_mapper.py
@@ -0,0 +1,191 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import copy
+import logging
+import numpy as np
+from typing import List, Optional, Union
+import torch
+
+from custom_detectron2.config import configurable
+
+from . import detection_utils as utils
+from . import transforms as T
+
+"""
+This file contains the default mapping that's applied to "dataset dicts".
+"""
+
+__all__ = ["DatasetMapper"]
+
+
+class DatasetMapper:
+ """
+ A callable which takes a dataset dict in Detectron2 Dataset format,
+ and map it into a format used by the model.
+
+ This is the default callable to be used to map your dataset dict into training data.
+ You may need to follow it to implement your own one for customized logic,
+ such as a different way to read or transform images.
+ See :doc:`/tutorials/data_loading` for details.
+
+ The callable currently does the following:
+
+ 1. Read the image from "file_name"
+ 2. Applies cropping/geometric transforms to the image and annotations
+ 3. Prepare data and annotations to Tensor and :class:`Instances`
+ """
+
+ @configurable
+ def __init__(
+ self,
+ is_train: bool,
+ *,
+ augmentations: List[Union[T.Augmentation, T.Transform]],
+ image_format: str,
+ use_instance_mask: bool = False,
+ use_keypoint: bool = False,
+ instance_mask_format: str = "polygon",
+ keypoint_hflip_indices: Optional[np.ndarray] = None,
+ precomputed_proposal_topk: Optional[int] = None,
+ recompute_boxes: bool = False,
+ ):
+ """
+ NOTE: this interface is experimental.
+
+ Args:
+ is_train: whether it's used in training or inference
+ augmentations: a list of augmentations or deterministic transforms to apply
+ image_format: an image format supported by :func:`detection_utils.read_image`.
+ use_instance_mask: whether to process instance segmentation annotations, if available
+ use_keypoint: whether to process keypoint annotations if available
+ instance_mask_format: one of "polygon" or "bitmask". Process instance segmentation
+ masks into this format.
+ keypoint_hflip_indices: see :func:`detection_utils.create_keypoint_hflip_indices`
+ precomputed_proposal_topk: if given, will load pre-computed
+ proposals from dataset_dict and keep the top k proposals for each image.
+ recompute_boxes: whether to overwrite bounding box annotations
+ by computing tight bounding boxes from instance mask annotations.
+ """
+ if recompute_boxes:
+ assert use_instance_mask, "recompute_boxes requires instance masks"
+ # fmt: off
+ self.is_train = is_train
+ self.augmentations = T.AugmentationList(augmentations)
+ self.image_format = image_format
+ self.use_instance_mask = use_instance_mask
+ self.instance_mask_format = instance_mask_format
+ self.use_keypoint = use_keypoint
+ self.keypoint_hflip_indices = keypoint_hflip_indices
+ self.proposal_topk = precomputed_proposal_topk
+ self.recompute_boxes = recompute_boxes
+ # fmt: on
+ logger = logging.getLogger(__name__)
+ mode = "training" if is_train else "inference"
+ logger.info(f"[DatasetMapper] Augmentations used in {mode}: {augmentations}")
+
+ @classmethod
+ def from_config(cls, cfg, is_train: bool = True):
+ augs = utils.build_augmentation(cfg, is_train)
+ if cfg.INPUT.CROP.ENABLED and is_train:
+ augs.insert(0, T.RandomCrop(cfg.INPUT.CROP.TYPE, cfg.INPUT.CROP.SIZE))
+ recompute_boxes = cfg.MODEL.MASK_ON
+ else:
+ recompute_boxes = False
+
+ ret = {
+ "is_train": is_train,
+ "augmentations": augs,
+ "image_format": cfg.INPUT.FORMAT,
+ "use_instance_mask": cfg.MODEL.MASK_ON,
+ "instance_mask_format": cfg.INPUT.MASK_FORMAT,
+ "use_keypoint": cfg.MODEL.KEYPOINT_ON,
+ "recompute_boxes": recompute_boxes,
+ }
+
+ if cfg.MODEL.KEYPOINT_ON:
+ ret["keypoint_hflip_indices"] = utils.create_keypoint_hflip_indices(cfg.DATASETS.TRAIN)
+
+ if cfg.MODEL.LOAD_PROPOSALS:
+ ret["precomputed_proposal_topk"] = (
+ cfg.DATASETS.PRECOMPUTED_PROPOSAL_TOPK_TRAIN
+ if is_train
+ else cfg.DATASETS.PRECOMPUTED_PROPOSAL_TOPK_TEST
+ )
+ return ret
+
+ def _transform_annotations(self, dataset_dict, transforms, image_shape):
+ # USER: Modify this if you want to keep them for some reason.
+ for anno in dataset_dict["annotations"]:
+ if not self.use_instance_mask:
+ anno.pop("segmentation", None)
+ if not self.use_keypoint:
+ anno.pop("keypoints", None)
+
+ # USER: Implement additional transformations if you have other types of data
+ annos = [
+ utils.transform_instance_annotations(
+ obj, transforms, image_shape, keypoint_hflip_indices=self.keypoint_hflip_indices
+ )
+ for obj in dataset_dict.pop("annotations")
+ if obj.get("iscrowd", 0) == 0
+ ]
+ instances = utils.annotations_to_instances(
+ annos, image_shape, mask_format=self.instance_mask_format
+ )
+
+ # After transforms such as cropping are applied, the bounding box may no longer
+ # tightly bound the object. As an example, imagine a triangle object
+ # [(0,0), (2,0), (0,2)] cropped by a box [(1,0),(2,2)] (XYXY format). The tight
+ # bounding box of the cropped triangle should be [(1,0),(2,1)], which is not equal to
+ # the intersection of original bounding box and the cropping box.
+ if self.recompute_boxes:
+ instances.gt_boxes = instances.gt_masks.get_bounding_boxes()
+ dataset_dict["instances"] = utils.filter_empty_instances(instances)
+
+ def __call__(self, dataset_dict):
+ """
+ Args:
+ dataset_dict (dict): Metadata of one image, in Detectron2 Dataset format.
+
+ Returns:
+ dict: a format that builtin models in detectron2 accept
+ """
+ dataset_dict = copy.deepcopy(dataset_dict) # it will be modified by code below
+ # USER: Write your own image loading if it's not from a file
+ image = utils.read_image(dataset_dict["file_name"], format=self.image_format)
+ utils.check_image_size(dataset_dict, image)
+
+ # USER: Remove if you don't do semantic/panoptic segmentation.
+ if "sem_seg_file_name" in dataset_dict:
+ sem_seg_gt = utils.read_image(dataset_dict.pop("sem_seg_file_name"), "L").squeeze(2)
+ else:
+ sem_seg_gt = None
+
+ aug_input = T.AugInput(image, sem_seg=sem_seg_gt)
+ transforms = self.augmentations(aug_input)
+ image, sem_seg_gt = aug_input.image, aug_input.sem_seg
+
+ image_shape = image.shape[:2] # h, w
+ # Pytorch's dataloader is efficient on torch.Tensor due to shared-memory,
+ # but not efficient on large generic data structures due to the use of pickle & mp.Queue.
+ # Therefore it's important to use torch.Tensor.
+ dataset_dict["image"] = torch.as_tensor(np.ascontiguousarray(image.transpose(2, 0, 1)))
+ if sem_seg_gt is not None:
+ dataset_dict["sem_seg"] = torch.as_tensor(sem_seg_gt.astype("long"))
+
+ # USER: Remove if you don't use pre-computed proposals.
+ # Most users would not need this feature.
+ if self.proposal_topk is not None:
+ utils.transform_proposals(
+ dataset_dict, image_shape, transforms, proposal_topk=self.proposal_topk
+ )
+
+ if not self.is_train:
+ # USER: Modify this if you want to keep them for some reason.
+ dataset_dict.pop("annotations", None)
+ dataset_dict.pop("sem_seg_file_name", None)
+ return dataset_dict
+
+ if "annotations" in dataset_dict:
+ self._transform_annotations(dataset_dict, transforms, image_shape)
+
+ return dataset_dict
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/README.md b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..9fb3e4f7afec17137c95c78be6ef06d520ec8032
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/README.md
@@ -0,0 +1,9 @@
+
+
+### Common Datasets
+
+The dataset implemented here do not need to load the data into the final format.
+It should provide the minimal data structure needed to use the dataset, so it can be very efficient.
+
+For example, for an image dataset, just provide the file names and labels, but don't read the images.
+Let the downstream decide how to read.
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..a44bedc15e5f0e762fc4d77efd6f1b07c6ff77d0
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/__init__.py
@@ -0,0 +1,9 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+from .coco import load_coco_json, load_sem_seg, register_coco_instances, convert_to_coco_json
+from .coco_panoptic import register_coco_panoptic, register_coco_panoptic_separated
+from .lvis import load_lvis_json, register_lvis_instances, get_lvis_instances_meta
+from .pascal_voc import load_voc_instances, register_pascal_voc
+from . import builtin as _builtin # ensure the builtin datasets are registered
+
+
+__all__ = [k for k in globals().keys() if not k.startswith("_")]
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/builtin.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/builtin.py
new file mode 100644
index 0000000000000000000000000000000000000000..1e7409b577b28b3f793143291ab2381bef6e0b20
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/builtin.py
@@ -0,0 +1,259 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+
+"""
+This file registers pre-defined datasets at hard-coded paths, and their metadata.
+
+We hard-code metadata for common datasets. This will enable:
+1. Consistency check when loading the datasets
+2. Use models on these standard datasets directly and run demos,
+ without having to download the dataset annotations
+
+We hard-code some paths to the dataset that's assumed to
+exist in "./datasets/".
+
+Users SHOULD NOT use this file to create new dataset / metadata for new dataset.
+To add new dataset, refer to the tutorial "docs/DATASETS.md".
+"""
+
+import os
+
+from custom_detectron2.data import DatasetCatalog, MetadataCatalog
+
+from .builtin_meta import ADE20K_SEM_SEG_CATEGORIES, _get_builtin_metadata
+from .cityscapes import load_cityscapes_instances, load_cityscapes_semantic
+from .cityscapes_panoptic import register_all_cityscapes_panoptic
+from .coco import load_sem_seg, register_coco_instances
+from .coco_panoptic import register_coco_panoptic, register_coco_panoptic_separated
+from .lvis import get_lvis_instances_meta, register_lvis_instances
+from .pascal_voc import register_pascal_voc
+
+# ==== Predefined datasets and splits for COCO ==========
+
+_PREDEFINED_SPLITS_COCO = {}
+_PREDEFINED_SPLITS_COCO["coco"] = {
+ "coco_2014_train": ("coco/train2014", "coco/annotations/instances_train2014.json"),
+ "coco_2014_val": ("coco/val2014", "coco/annotations/instances_val2014.json"),
+ "coco_2014_minival": ("coco/val2014", "coco/annotations/instances_minival2014.json"),
+ "coco_2014_valminusminival": (
+ "coco/val2014",
+ "coco/annotations/instances_valminusminival2014.json",
+ ),
+ "coco_2017_train": ("coco/train2017", "coco/annotations/instances_train2017.json"),
+ "coco_2017_val": ("coco/val2017", "coco/annotations/instances_val2017.json"),
+ "coco_2017_test": ("coco/test2017", "coco/annotations/image_info_test2017.json"),
+ "coco_2017_test-dev": ("coco/test2017", "coco/annotations/image_info_test-dev2017.json"),
+ "coco_2017_val_100": ("coco/val2017", "coco/annotations/instances_val2017_100.json"),
+}
+
+_PREDEFINED_SPLITS_COCO["coco_person"] = {
+ "keypoints_coco_2014_train": (
+ "coco/train2014",
+ "coco/annotations/person_keypoints_train2014.json",
+ ),
+ "keypoints_coco_2014_val": ("coco/val2014", "coco/annotations/person_keypoints_val2014.json"),
+ "keypoints_coco_2014_minival": (
+ "coco/val2014",
+ "coco/annotations/person_keypoints_minival2014.json",
+ ),
+ "keypoints_coco_2014_valminusminival": (
+ "coco/val2014",
+ "coco/annotations/person_keypoints_valminusminival2014.json",
+ ),
+ "keypoints_coco_2017_train": (
+ "coco/train2017",
+ "coco/annotations/person_keypoints_train2017.json",
+ ),
+ "keypoints_coco_2017_val": ("coco/val2017", "coco/annotations/person_keypoints_val2017.json"),
+ "keypoints_coco_2017_val_100": (
+ "coco/val2017",
+ "coco/annotations/person_keypoints_val2017_100.json",
+ ),
+}
+
+
+_PREDEFINED_SPLITS_COCO_PANOPTIC = {
+ "coco_2017_train_panoptic": (
+ # This is the original panoptic annotation directory
+ "coco/panoptic_train2017",
+ "coco/annotations/panoptic_train2017.json",
+ # This directory contains semantic annotations that are
+ # converted from panoptic annotations.
+ # It is used by PanopticFPN.
+ # You can use the script at detectron2/datasets/prepare_panoptic_fpn.py
+ # to create these directories.
+ "coco/panoptic_stuff_train2017",
+ ),
+ "coco_2017_val_panoptic": (
+ "coco/panoptic_val2017",
+ "coco/annotations/panoptic_val2017.json",
+ "coco/panoptic_stuff_val2017",
+ ),
+ "coco_2017_val_100_panoptic": (
+ "coco/panoptic_val2017_100",
+ "coco/annotations/panoptic_val2017_100.json",
+ "coco/panoptic_stuff_val2017_100",
+ ),
+}
+
+
+def register_all_coco(root):
+ for dataset_name, splits_per_dataset in _PREDEFINED_SPLITS_COCO.items():
+ for key, (image_root, json_file) in splits_per_dataset.items():
+ # Assume pre-defined datasets live in `./datasets`.
+ register_coco_instances(
+ key,
+ _get_builtin_metadata(dataset_name),
+ os.path.join(root, json_file) if "://" not in json_file else json_file,
+ os.path.join(root, image_root),
+ )
+
+ for (
+ prefix,
+ (panoptic_root, panoptic_json, semantic_root),
+ ) in _PREDEFINED_SPLITS_COCO_PANOPTIC.items():
+ prefix_instances = prefix[: -len("_panoptic")]
+ instances_meta = MetadataCatalog.get(prefix_instances)
+ image_root, instances_json = instances_meta.image_root, instances_meta.json_file
+ # The "separated" version of COCO panoptic segmentation dataset,
+ # e.g. used by Panoptic FPN
+ register_coco_panoptic_separated(
+ prefix,
+ _get_builtin_metadata("coco_panoptic_separated"),
+ image_root,
+ os.path.join(root, panoptic_root),
+ os.path.join(root, panoptic_json),
+ os.path.join(root, semantic_root),
+ instances_json,
+ )
+ # The "standard" version of COCO panoptic segmentation dataset,
+ # e.g. used by Panoptic-DeepLab
+ register_coco_panoptic(
+ prefix,
+ _get_builtin_metadata("coco_panoptic_standard"),
+ image_root,
+ os.path.join(root, panoptic_root),
+ os.path.join(root, panoptic_json),
+ instances_json,
+ )
+
+
+# ==== Predefined datasets and splits for LVIS ==========
+
+
+_PREDEFINED_SPLITS_LVIS = {
+ "lvis_v1": {
+ "lvis_v1_train": ("coco/", "lvis/lvis_v1_train.json"),
+ "lvis_v1_val": ("coco/", "lvis/lvis_v1_val.json"),
+ "lvis_v1_test_dev": ("coco/", "lvis/lvis_v1_image_info_test_dev.json"),
+ "lvis_v1_test_challenge": ("coco/", "lvis/lvis_v1_image_info_test_challenge.json"),
+ },
+ "lvis_v0.5": {
+ "lvis_v0.5_train": ("coco/", "lvis/lvis_v0.5_train.json"),
+ "lvis_v0.5_val": ("coco/", "lvis/lvis_v0.5_val.json"),
+ "lvis_v0.5_val_rand_100": ("coco/", "lvis/lvis_v0.5_val_rand_100.json"),
+ "lvis_v0.5_test": ("coco/", "lvis/lvis_v0.5_image_info_test.json"),
+ },
+ "lvis_v0.5_cocofied": {
+ "lvis_v0.5_train_cocofied": ("coco/", "lvis/lvis_v0.5_train_cocofied.json"),
+ "lvis_v0.5_val_cocofied": ("coco/", "lvis/lvis_v0.5_val_cocofied.json"),
+ },
+}
+
+
+def register_all_lvis(root):
+ for dataset_name, splits_per_dataset in _PREDEFINED_SPLITS_LVIS.items():
+ for key, (image_root, json_file) in splits_per_dataset.items():
+ register_lvis_instances(
+ key,
+ get_lvis_instances_meta(dataset_name),
+ os.path.join(root, json_file) if "://" not in json_file else json_file,
+ os.path.join(root, image_root),
+ )
+
+
+# ==== Predefined splits for raw cityscapes images ===========
+_RAW_CITYSCAPES_SPLITS = {
+ "cityscapes_fine_{task}_train": ("cityscapes/leftImg8bit/train/", "cityscapes/gtFine/train/"),
+ "cityscapes_fine_{task}_val": ("cityscapes/leftImg8bit/val/", "cityscapes/gtFine/val/"),
+ "cityscapes_fine_{task}_test": ("cityscapes/leftImg8bit/test/", "cityscapes/gtFine/test/"),
+}
+
+
+def register_all_cityscapes(root):
+ for key, (image_dir, gt_dir) in _RAW_CITYSCAPES_SPLITS.items():
+ meta = _get_builtin_metadata("cityscapes")
+ image_dir = os.path.join(root, image_dir)
+ gt_dir = os.path.join(root, gt_dir)
+
+ inst_key = key.format(task="instance_seg")
+ DatasetCatalog.register(
+ inst_key,
+ lambda x=image_dir, y=gt_dir: load_cityscapes_instances(
+ x, y, from_json=True, to_polygons=True
+ ),
+ )
+ MetadataCatalog.get(inst_key).set(
+ image_dir=image_dir, gt_dir=gt_dir, evaluator_type="cityscapes_instance", **meta
+ )
+
+ sem_key = key.format(task="sem_seg")
+ DatasetCatalog.register(
+ sem_key, lambda x=image_dir, y=gt_dir: load_cityscapes_semantic(x, y)
+ )
+ MetadataCatalog.get(sem_key).set(
+ image_dir=image_dir,
+ gt_dir=gt_dir,
+ evaluator_type="cityscapes_sem_seg",
+ ignore_label=255,
+ **meta,
+ )
+
+
+# ==== Predefined splits for PASCAL VOC ===========
+def register_all_pascal_voc(root):
+ SPLITS = [
+ ("voc_2007_trainval", "VOC2007", "trainval"),
+ ("voc_2007_train", "VOC2007", "train"),
+ ("voc_2007_val", "VOC2007", "val"),
+ ("voc_2007_test", "VOC2007", "test"),
+ ("voc_2012_trainval", "VOC2012", "trainval"),
+ ("voc_2012_train", "VOC2012", "train"),
+ ("voc_2012_val", "VOC2012", "val"),
+ ]
+ for name, dirname, split in SPLITS:
+ year = 2007 if "2007" in name else 2012
+ register_pascal_voc(name, os.path.join(root, dirname), split, year)
+ MetadataCatalog.get(name).evaluator_type = "pascal_voc"
+
+
+def register_all_ade20k(root):
+ root = os.path.join(root, "ADEChallengeData2016")
+ for name, dirname in [("train", "training"), ("val", "validation")]:
+ image_dir = os.path.join(root, "images", dirname)
+ gt_dir = os.path.join(root, "annotations_detectron2", dirname)
+ name = f"ade20k_sem_seg_{name}"
+ DatasetCatalog.register(
+ name, lambda x=image_dir, y=gt_dir: load_sem_seg(y, x, gt_ext="png", image_ext="jpg")
+ )
+ MetadataCatalog.get(name).set(
+ stuff_classes=ADE20K_SEM_SEG_CATEGORIES[:],
+ image_root=image_dir,
+ sem_seg_root=gt_dir,
+ evaluator_type="sem_seg",
+ ignore_label=255,
+ )
+
+
+# True for open source;
+# Internally at fb, we register them elsewhere
+if __name__.endswith(".builtin"):
+ # Assume pre-defined datasets live in `./datasets`.
+ _root = os.path.expanduser(os.getenv("DETECTRON2_DATASETS", "datasets"))
+ register_all_coco(_root)
+ register_all_lvis(_root)
+ register_all_cityscapes(_root)
+ register_all_cityscapes_panoptic(_root)
+ register_all_pascal_voc(_root)
+ register_all_ade20k(_root)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/builtin_meta.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/builtin_meta.py
new file mode 100644
index 0000000000000000000000000000000000000000..63c7a1a31b31dd89b82011effee26471faccacf5
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/builtin_meta.py
@@ -0,0 +1,350 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+"""
+Note:
+For your custom dataset, there is no need to hard-code metadata anywhere in the code.
+For example, for COCO-format dataset, metadata will be obtained automatically
+when calling `load_coco_json`. For other dataset, metadata may also be obtained in other ways
+during loading.
+
+However, we hard-coded metadata for a few common dataset here.
+The only goal is to allow users who don't have these dataset to use pre-trained models.
+Users don't have to download a COCO json (which contains metadata), in order to visualize a
+COCO model (with correct class names and colors).
+"""
+
+
+# All coco categories, together with their nice-looking visualization colors
+# It's from https://github.com/cocodataset/panopticapi/blob/master/panoptic_coco_categories.json
+COCO_CATEGORIES = [
+ {"color": [220, 20, 60], "isthing": 1, "id": 1, "name": "person"},
+ {"color": [119, 11, 32], "isthing": 1, "id": 2, "name": "bicycle"},
+ {"color": [0, 0, 142], "isthing": 1, "id": 3, "name": "car"},
+ {"color": [0, 0, 230], "isthing": 1, "id": 4, "name": "motorcycle"},
+ {"color": [106, 0, 228], "isthing": 1, "id": 5, "name": "airplane"},
+ {"color": [0, 60, 100], "isthing": 1, "id": 6, "name": "bus"},
+ {"color": [0, 80, 100], "isthing": 1, "id": 7, "name": "train"},
+ {"color": [0, 0, 70], "isthing": 1, "id": 8, "name": "truck"},
+ {"color": [0, 0, 192], "isthing": 1, "id": 9, "name": "boat"},
+ {"color": [250, 170, 30], "isthing": 1, "id": 10, "name": "traffic light"},
+ {"color": [100, 170, 30], "isthing": 1, "id": 11, "name": "fire hydrant"},
+ {"color": [220, 220, 0], "isthing": 1, "id": 13, "name": "stop sign"},
+ {"color": [175, 116, 175], "isthing": 1, "id": 14, "name": "parking meter"},
+ {"color": [250, 0, 30], "isthing": 1, "id": 15, "name": "bench"},
+ {"color": [165, 42, 42], "isthing": 1, "id": 16, "name": "bird"},
+ {"color": [255, 77, 255], "isthing": 1, "id": 17, "name": "cat"},
+ {"color": [0, 226, 252], "isthing": 1, "id": 18, "name": "dog"},
+ {"color": [182, 182, 255], "isthing": 1, "id": 19, "name": "horse"},
+ {"color": [0, 82, 0], "isthing": 1, "id": 20, "name": "sheep"},
+ {"color": [120, 166, 157], "isthing": 1, "id": 21, "name": "cow"},
+ {"color": [110, 76, 0], "isthing": 1, "id": 22, "name": "elephant"},
+ {"color": [174, 57, 255], "isthing": 1, "id": 23, "name": "bear"},
+ {"color": [199, 100, 0], "isthing": 1, "id": 24, "name": "zebra"},
+ {"color": [72, 0, 118], "isthing": 1, "id": 25, "name": "giraffe"},
+ {"color": [255, 179, 240], "isthing": 1, "id": 27, "name": "backpack"},
+ {"color": [0, 125, 92], "isthing": 1, "id": 28, "name": "umbrella"},
+ {"color": [209, 0, 151], "isthing": 1, "id": 31, "name": "handbag"},
+ {"color": [188, 208, 182], "isthing": 1, "id": 32, "name": "tie"},
+ {"color": [0, 220, 176], "isthing": 1, "id": 33, "name": "suitcase"},
+ {"color": [255, 99, 164], "isthing": 1, "id": 34, "name": "frisbee"},
+ {"color": [92, 0, 73], "isthing": 1, "id": 35, "name": "skis"},
+ {"color": [133, 129, 255], "isthing": 1, "id": 36, "name": "snowboard"},
+ {"color": [78, 180, 255], "isthing": 1, "id": 37, "name": "sports ball"},
+ {"color": [0, 228, 0], "isthing": 1, "id": 38, "name": "kite"},
+ {"color": [174, 255, 243], "isthing": 1, "id": 39, "name": "baseball bat"},
+ {"color": [45, 89, 255], "isthing": 1, "id": 40, "name": "baseball glove"},
+ {"color": [134, 134, 103], "isthing": 1, "id": 41, "name": "skateboard"},
+ {"color": [145, 148, 174], "isthing": 1, "id": 42, "name": "surfboard"},
+ {"color": [255, 208, 186], "isthing": 1, "id": 43, "name": "tennis racket"},
+ {"color": [197, 226, 255], "isthing": 1, "id": 44, "name": "bottle"},
+ {"color": [171, 134, 1], "isthing": 1, "id": 46, "name": "wine glass"},
+ {"color": [109, 63, 54], "isthing": 1, "id": 47, "name": "cup"},
+ {"color": [207, 138, 255], "isthing": 1, "id": 48, "name": "fork"},
+ {"color": [151, 0, 95], "isthing": 1, "id": 49, "name": "knife"},
+ {"color": [9, 80, 61], "isthing": 1, "id": 50, "name": "spoon"},
+ {"color": [84, 105, 51], "isthing": 1, "id": 51, "name": "bowl"},
+ {"color": [74, 65, 105], "isthing": 1, "id": 52, "name": "banana"},
+ {"color": [166, 196, 102], "isthing": 1, "id": 53, "name": "apple"},
+ {"color": [208, 195, 210], "isthing": 1, "id": 54, "name": "sandwich"},
+ {"color": [255, 109, 65], "isthing": 1, "id": 55, "name": "orange"},
+ {"color": [0, 143, 149], "isthing": 1, "id": 56, "name": "broccoli"},
+ {"color": [179, 0, 194], "isthing": 1, "id": 57, "name": "carrot"},
+ {"color": [209, 99, 106], "isthing": 1, "id": 58, "name": "hot dog"},
+ {"color": [5, 121, 0], "isthing": 1, "id": 59, "name": "pizza"},
+ {"color": [227, 255, 205], "isthing": 1, "id": 60, "name": "donut"},
+ {"color": [147, 186, 208], "isthing": 1, "id": 61, "name": "cake"},
+ {"color": [153, 69, 1], "isthing": 1, "id": 62, "name": "chair"},
+ {"color": [3, 95, 161], "isthing": 1, "id": 63, "name": "couch"},
+ {"color": [163, 255, 0], "isthing": 1, "id": 64, "name": "potted plant"},
+ {"color": [119, 0, 170], "isthing": 1, "id": 65, "name": "bed"},
+ {"color": [0, 182, 199], "isthing": 1, "id": 67, "name": "dining table"},
+ {"color": [0, 165, 120], "isthing": 1, "id": 70, "name": "toilet"},
+ {"color": [183, 130, 88], "isthing": 1, "id": 72, "name": "tv"},
+ {"color": [95, 32, 0], "isthing": 1, "id": 73, "name": "laptop"},
+ {"color": [130, 114, 135], "isthing": 1, "id": 74, "name": "mouse"},
+ {"color": [110, 129, 133], "isthing": 1, "id": 75, "name": "remote"},
+ {"color": [166, 74, 118], "isthing": 1, "id": 76, "name": "keyboard"},
+ {"color": [219, 142, 185], "isthing": 1, "id": 77, "name": "cell phone"},
+ {"color": [79, 210, 114], "isthing": 1, "id": 78, "name": "microwave"},
+ {"color": [178, 90, 62], "isthing": 1, "id": 79, "name": "oven"},
+ {"color": [65, 70, 15], "isthing": 1, "id": 80, "name": "toaster"},
+ {"color": [127, 167, 115], "isthing": 1, "id": 81, "name": "sink"},
+ {"color": [59, 105, 106], "isthing": 1, "id": 82, "name": "refrigerator"},
+ {"color": [142, 108, 45], "isthing": 1, "id": 84, "name": "book"},
+ {"color": [196, 172, 0], "isthing": 1, "id": 85, "name": "clock"},
+ {"color": [95, 54, 80], "isthing": 1, "id": 86, "name": "vase"},
+ {"color": [128, 76, 255], "isthing": 1, "id": 87, "name": "scissors"},
+ {"color": [201, 57, 1], "isthing": 1, "id": 88, "name": "teddy bear"},
+ {"color": [246, 0, 122], "isthing": 1, "id": 89, "name": "hair drier"},
+ {"color": [191, 162, 208], "isthing": 1, "id": 90, "name": "toothbrush"},
+ {"color": [255, 255, 128], "isthing": 0, "id": 92, "name": "banner"},
+ {"color": [147, 211, 203], "isthing": 0, "id": 93, "name": "blanket"},
+ {"color": [150, 100, 100], "isthing": 0, "id": 95, "name": "bridge"},
+ {"color": [168, 171, 172], "isthing": 0, "id": 100, "name": "cardboard"},
+ {"color": [146, 112, 198], "isthing": 0, "id": 107, "name": "counter"},
+ {"color": [210, 170, 100], "isthing": 0, "id": 109, "name": "curtain"},
+ {"color": [92, 136, 89], "isthing": 0, "id": 112, "name": "door-stuff"},
+ {"color": [218, 88, 184], "isthing": 0, "id": 118, "name": "floor-wood"},
+ {"color": [241, 129, 0], "isthing": 0, "id": 119, "name": "flower"},
+ {"color": [217, 17, 255], "isthing": 0, "id": 122, "name": "fruit"},
+ {"color": [124, 74, 181], "isthing": 0, "id": 125, "name": "gravel"},
+ {"color": [70, 70, 70], "isthing": 0, "id": 128, "name": "house"},
+ {"color": [255, 228, 255], "isthing": 0, "id": 130, "name": "light"},
+ {"color": [154, 208, 0], "isthing": 0, "id": 133, "name": "mirror-stuff"},
+ {"color": [193, 0, 92], "isthing": 0, "id": 138, "name": "net"},
+ {"color": [76, 91, 113], "isthing": 0, "id": 141, "name": "pillow"},
+ {"color": [255, 180, 195], "isthing": 0, "id": 144, "name": "platform"},
+ {"color": [106, 154, 176], "isthing": 0, "id": 145, "name": "playingfield"},
+ {"color": [230, 150, 140], "isthing": 0, "id": 147, "name": "railroad"},
+ {"color": [60, 143, 255], "isthing": 0, "id": 148, "name": "river"},
+ {"color": [128, 64, 128], "isthing": 0, "id": 149, "name": "road"},
+ {"color": [92, 82, 55], "isthing": 0, "id": 151, "name": "roof"},
+ {"color": [254, 212, 124], "isthing": 0, "id": 154, "name": "sand"},
+ {"color": [73, 77, 174], "isthing": 0, "id": 155, "name": "sea"},
+ {"color": [255, 160, 98], "isthing": 0, "id": 156, "name": "shelf"},
+ {"color": [255, 255, 255], "isthing": 0, "id": 159, "name": "snow"},
+ {"color": [104, 84, 109], "isthing": 0, "id": 161, "name": "stairs"},
+ {"color": [169, 164, 131], "isthing": 0, "id": 166, "name": "tent"},
+ {"color": [225, 199, 255], "isthing": 0, "id": 168, "name": "towel"},
+ {"color": [137, 54, 74], "isthing": 0, "id": 171, "name": "wall-brick"},
+ {"color": [135, 158, 223], "isthing": 0, "id": 175, "name": "wall-stone"},
+ {"color": [7, 246, 231], "isthing": 0, "id": 176, "name": "wall-tile"},
+ {"color": [107, 255, 200], "isthing": 0, "id": 177, "name": "wall-wood"},
+ {"color": [58, 41, 149], "isthing": 0, "id": 178, "name": "water-other"},
+ {"color": [183, 121, 142], "isthing": 0, "id": 180, "name": "window-blind"},
+ {"color": [255, 73, 97], "isthing": 0, "id": 181, "name": "window-other"},
+ {"color": [107, 142, 35], "isthing": 0, "id": 184, "name": "tree-merged"},
+ {"color": [190, 153, 153], "isthing": 0, "id": 185, "name": "fence-merged"},
+ {"color": [146, 139, 141], "isthing": 0, "id": 186, "name": "ceiling-merged"},
+ {"color": [70, 130, 180], "isthing": 0, "id": 187, "name": "sky-other-merged"},
+ {"color": [134, 199, 156], "isthing": 0, "id": 188, "name": "cabinet-merged"},
+ {"color": [209, 226, 140], "isthing": 0, "id": 189, "name": "table-merged"},
+ {"color": [96, 36, 108], "isthing": 0, "id": 190, "name": "floor-other-merged"},
+ {"color": [96, 96, 96], "isthing": 0, "id": 191, "name": "pavement-merged"},
+ {"color": [64, 170, 64], "isthing": 0, "id": 192, "name": "mountain-merged"},
+ {"color": [152, 251, 152], "isthing": 0, "id": 193, "name": "grass-merged"},
+ {"color": [208, 229, 228], "isthing": 0, "id": 194, "name": "dirt-merged"},
+ {"color": [206, 186, 171], "isthing": 0, "id": 195, "name": "paper-merged"},
+ {"color": [152, 161, 64], "isthing": 0, "id": 196, "name": "food-other-merged"},
+ {"color": [116, 112, 0], "isthing": 0, "id": 197, "name": "building-other-merged"},
+ {"color": [0, 114, 143], "isthing": 0, "id": 198, "name": "rock-merged"},
+ {"color": [102, 102, 156], "isthing": 0, "id": 199, "name": "wall-other-merged"},
+ {"color": [250, 141, 255], "isthing": 0, "id": 200, "name": "rug-merged"},
+]
+
+# fmt: off
+COCO_PERSON_KEYPOINT_NAMES = (
+ "nose",
+ "left_eye", "right_eye",
+ "left_ear", "right_ear",
+ "left_shoulder", "right_shoulder",
+ "left_elbow", "right_elbow",
+ "left_wrist", "right_wrist",
+ "left_hip", "right_hip",
+ "left_knee", "right_knee",
+ "left_ankle", "right_ankle",
+)
+# fmt: on
+
+# Pairs of keypoints that should be exchanged under horizontal flipping
+COCO_PERSON_KEYPOINT_FLIP_MAP = (
+ ("left_eye", "right_eye"),
+ ("left_ear", "right_ear"),
+ ("left_shoulder", "right_shoulder"),
+ ("left_elbow", "right_elbow"),
+ ("left_wrist", "right_wrist"),
+ ("left_hip", "right_hip"),
+ ("left_knee", "right_knee"),
+ ("left_ankle", "right_ankle"),
+)
+
+# rules for pairs of keypoints to draw a line between, and the line color to use.
+KEYPOINT_CONNECTION_RULES = [
+ # face
+ ("left_ear", "left_eye", (102, 204, 255)),
+ ("right_ear", "right_eye", (51, 153, 255)),
+ ("left_eye", "nose", (102, 0, 204)),
+ ("nose", "right_eye", (51, 102, 255)),
+ # upper-body
+ ("left_shoulder", "right_shoulder", (255, 128, 0)),
+ ("left_shoulder", "left_elbow", (153, 255, 204)),
+ ("right_shoulder", "right_elbow", (128, 229, 255)),
+ ("left_elbow", "left_wrist", (153, 255, 153)),
+ ("right_elbow", "right_wrist", (102, 255, 224)),
+ # lower-body
+ ("left_hip", "right_hip", (255, 102, 0)),
+ ("left_hip", "left_knee", (255, 255, 77)),
+ ("right_hip", "right_knee", (153, 255, 204)),
+ ("left_knee", "left_ankle", (191, 255, 128)),
+ ("right_knee", "right_ankle", (255, 195, 77)),
+]
+
+# All Cityscapes categories, together with their nice-looking visualization colors
+# It's from https://github.com/mcordts/cityscapesScripts/blob/master/cityscapesscripts/helpers/labels.py # noqa
+CITYSCAPES_CATEGORIES = [
+ {"color": (128, 64, 128), "isthing": 0, "id": 7, "trainId": 0, "name": "road"},
+ {"color": (244, 35, 232), "isthing": 0, "id": 8, "trainId": 1, "name": "sidewalk"},
+ {"color": (70, 70, 70), "isthing": 0, "id": 11, "trainId": 2, "name": "building"},
+ {"color": (102, 102, 156), "isthing": 0, "id": 12, "trainId": 3, "name": "wall"},
+ {"color": (190, 153, 153), "isthing": 0, "id": 13, "trainId": 4, "name": "fence"},
+ {"color": (153, 153, 153), "isthing": 0, "id": 17, "trainId": 5, "name": "pole"},
+ {"color": (250, 170, 30), "isthing": 0, "id": 19, "trainId": 6, "name": "traffic light"},
+ {"color": (220, 220, 0), "isthing": 0, "id": 20, "trainId": 7, "name": "traffic sign"},
+ {"color": (107, 142, 35), "isthing": 0, "id": 21, "trainId": 8, "name": "vegetation"},
+ {"color": (152, 251, 152), "isthing": 0, "id": 22, "trainId": 9, "name": "terrain"},
+ {"color": (70, 130, 180), "isthing": 0, "id": 23, "trainId": 10, "name": "sky"},
+ {"color": (220, 20, 60), "isthing": 1, "id": 24, "trainId": 11, "name": "person"},
+ {"color": (255, 0, 0), "isthing": 1, "id": 25, "trainId": 12, "name": "rider"},
+ {"color": (0, 0, 142), "isthing": 1, "id": 26, "trainId": 13, "name": "car"},
+ {"color": (0, 0, 70), "isthing": 1, "id": 27, "trainId": 14, "name": "truck"},
+ {"color": (0, 60, 100), "isthing": 1, "id": 28, "trainId": 15, "name": "bus"},
+ {"color": (0, 80, 100), "isthing": 1, "id": 31, "trainId": 16, "name": "train"},
+ {"color": (0, 0, 230), "isthing": 1, "id": 32, "trainId": 17, "name": "motorcycle"},
+ {"color": (119, 11, 32), "isthing": 1, "id": 33, "trainId": 18, "name": "bicycle"},
+]
+
+# fmt: off
+ADE20K_SEM_SEG_CATEGORIES = [
+ "wall", "building", "sky", "floor", "tree", "ceiling", "road, route", "bed", "window ", "grass", "cabinet", "sidewalk, pavement", "person", "earth, ground", "door", "table", "mountain, mount", "plant", "curtain", "chair", "car", "water", "painting, picture", "sofa", "shelf", "house", "sea", "mirror", "rug", "field", "armchair", "seat", "fence", "desk", "rock, stone", "wardrobe, closet, press", "lamp", "tub", "rail", "cushion", "base, pedestal, stand", "box", "column, pillar", "signboard, sign", "chest of drawers, chest, bureau, dresser", "counter", "sand", "sink", "skyscraper", "fireplace", "refrigerator, icebox", "grandstand, covered stand", "path", "stairs", "runway", "case, display case, showcase, vitrine", "pool table, billiard table, snooker table", "pillow", "screen door, screen", "stairway, staircase", "river", "bridge, span", "bookcase", "blind, screen", "coffee table", "toilet, can, commode, crapper, pot, potty, stool, throne", "flower", "book", "hill", "bench", "countertop", "stove", "palm, palm tree", "kitchen island", "computer", "swivel chair", "boat", "bar", "arcade machine", "hovel, hut, hutch, shack, shanty", "bus", "towel", "light", "truck", "tower", "chandelier", "awning, sunshade, sunblind", "street lamp", "booth", "tv", "plane", "dirt track", "clothes", "pole", "land, ground, soil", "bannister, banister, balustrade, balusters, handrail", "escalator, moving staircase, moving stairway", "ottoman, pouf, pouffe, puff, hassock", "bottle", "buffet, counter, sideboard", "poster, posting, placard, notice, bill, card", "stage", "van", "ship", "fountain", "conveyer belt, conveyor belt, conveyer, conveyor, transporter", "canopy", "washer, automatic washer, washing machine", "plaything, toy", "pool", "stool", "barrel, cask", "basket, handbasket", "falls", "tent", "bag", "minibike, motorbike", "cradle", "oven", "ball", "food, solid food", "step, stair", "tank, storage tank", "trade name", "microwave", "pot", "animal", "bicycle", "lake", "dishwasher", "screen", "blanket, cover", "sculpture", "hood, exhaust hood", "sconce", "vase", "traffic light", "tray", "trash can", "fan", "pier", "crt screen", "plate", "monitor", "bulletin board", "shower", "radiator", "glass, drinking glass", "clock", "flag", # noqa
+]
+# After processed by `prepare_ade20k_sem_seg.py`, id 255 means ignore
+# fmt: on
+
+
+def _get_coco_instances_meta():
+ thing_ids = [k["id"] for k in COCO_CATEGORIES if k["isthing"] == 1]
+ thing_colors = [k["color"] for k in COCO_CATEGORIES if k["isthing"] == 1]
+ assert len(thing_ids) == 80, len(thing_ids)
+ # Mapping from the incontiguous COCO category id to an id in [0, 79]
+ thing_dataset_id_to_contiguous_id = {k: i for i, k in enumerate(thing_ids)}
+ thing_classes = [k["name"] for k in COCO_CATEGORIES if k["isthing"] == 1]
+ ret = {
+ "thing_dataset_id_to_contiguous_id": thing_dataset_id_to_contiguous_id,
+ "thing_classes": thing_classes,
+ "thing_colors": thing_colors,
+ }
+ return ret
+
+
+def _get_coco_panoptic_separated_meta():
+ """
+ Returns metadata for "separated" version of the panoptic segmentation dataset.
+ """
+ stuff_ids = [k["id"] for k in COCO_CATEGORIES if k["isthing"] == 0]
+ assert len(stuff_ids) == 53, len(stuff_ids)
+
+ # For semantic segmentation, this mapping maps from contiguous stuff id
+ # (in [0, 53], used in models) to ids in the dataset (used for processing results)
+ # The id 0 is mapped to an extra category "thing".
+ stuff_dataset_id_to_contiguous_id = {k: i + 1 for i, k in enumerate(stuff_ids)}
+ # When converting COCO panoptic annotations to semantic annotations
+ # We label the "thing" category to 0
+ stuff_dataset_id_to_contiguous_id[0] = 0
+
+ # 54 names for COCO stuff categories (including "things")
+ stuff_classes = ["things"] + [
+ k["name"].replace("-other", "").replace("-merged", "")
+ for k in COCO_CATEGORIES
+ if k["isthing"] == 0
+ ]
+
+ # NOTE: I randomly picked a color for things
+ stuff_colors = [[82, 18, 128]] + [k["color"] for k in COCO_CATEGORIES if k["isthing"] == 0]
+ ret = {
+ "stuff_dataset_id_to_contiguous_id": stuff_dataset_id_to_contiguous_id,
+ "stuff_classes": stuff_classes,
+ "stuff_colors": stuff_colors,
+ }
+ ret.update(_get_coco_instances_meta())
+ return ret
+
+
+def _get_builtin_metadata(dataset_name):
+ if dataset_name == "coco":
+ return _get_coco_instances_meta()
+ if dataset_name == "coco_panoptic_separated":
+ return _get_coco_panoptic_separated_meta()
+ elif dataset_name == "coco_panoptic_standard":
+ meta = {}
+ # The following metadata maps contiguous id from [0, #thing categories +
+ # #stuff categories) to their names and colors. We have to replica of the
+ # same name and color under "thing_*" and "stuff_*" because the current
+ # visualization function in D2 handles thing and class classes differently
+ # due to some heuristic used in Panoptic FPN. We keep the same naming to
+ # enable reusing existing visualization functions.
+ thing_classes = [k["name"] for k in COCO_CATEGORIES]
+ thing_colors = [k["color"] for k in COCO_CATEGORIES]
+ stuff_classes = [k["name"] for k in COCO_CATEGORIES]
+ stuff_colors = [k["color"] for k in COCO_CATEGORIES]
+
+ meta["thing_classes"] = thing_classes
+ meta["thing_colors"] = thing_colors
+ meta["stuff_classes"] = stuff_classes
+ meta["stuff_colors"] = stuff_colors
+
+ # Convert category id for training:
+ # category id: like semantic segmentation, it is the class id for each
+ # pixel. Since there are some classes not used in evaluation, the category
+ # id is not always contiguous and thus we have two set of category ids:
+ # - original category id: category id in the original dataset, mainly
+ # used for evaluation.
+ # - contiguous category id: [0, #classes), in order to train the linear
+ # softmax classifier.
+ thing_dataset_id_to_contiguous_id = {}
+ stuff_dataset_id_to_contiguous_id = {}
+
+ for i, cat in enumerate(COCO_CATEGORIES):
+ if cat["isthing"]:
+ thing_dataset_id_to_contiguous_id[cat["id"]] = i
+ else:
+ stuff_dataset_id_to_contiguous_id[cat["id"]] = i
+
+ meta["thing_dataset_id_to_contiguous_id"] = thing_dataset_id_to_contiguous_id
+ meta["stuff_dataset_id_to_contiguous_id"] = stuff_dataset_id_to_contiguous_id
+
+ return meta
+ elif dataset_name == "coco_person":
+ return {
+ "thing_classes": ["person"],
+ "keypoint_names": COCO_PERSON_KEYPOINT_NAMES,
+ "keypoint_flip_map": COCO_PERSON_KEYPOINT_FLIP_MAP,
+ "keypoint_connection_rules": KEYPOINT_CONNECTION_RULES,
+ }
+ elif dataset_name == "cityscapes":
+ # fmt: off
+ CITYSCAPES_THING_CLASSES = [
+ "person", "rider", "car", "truck",
+ "bus", "train", "motorcycle", "bicycle",
+ ]
+ CITYSCAPES_STUFF_CLASSES = [
+ "road", "sidewalk", "building", "wall", "fence", "pole", "traffic light",
+ "traffic sign", "vegetation", "terrain", "sky", "person", "rider", "car",
+ "truck", "bus", "train", "motorcycle", "bicycle",
+ ]
+ # fmt: on
+ return {
+ "thing_classes": CITYSCAPES_THING_CLASSES,
+ "stuff_classes": CITYSCAPES_STUFF_CLASSES,
+ }
+ raise KeyError("No built-in metadata for dataset {}".format(dataset_name))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/cityscapes.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/cityscapes.py
new file mode 100644
index 0000000000000000000000000000000000000000..ddaa4e0e5057bc061edb5d965122a1b478c580ab
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/cityscapes.py
@@ -0,0 +1,329 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import functools
+import json
+import logging
+import multiprocessing as mp
+import numpy as np
+import os
+from itertools import chain
+import custom_pycocotools.mask as mask_util
+from PIL import Image
+
+from custom_detectron2.structures import BoxMode
+from custom_detectron2.utils.comm import get_world_size
+from custom_detectron2.utils.file_io import PathManager
+from custom_detectron2.utils.logger import setup_logger
+
+try:
+ import cv2 # noqa
+except ImportError:
+ # OpenCV is an optional dependency at the moment
+ pass
+
+
+logger = logging.getLogger(__name__)
+
+
+def _get_cityscapes_files(image_dir, gt_dir):
+ files = []
+ # scan through the directory
+ cities = PathManager.ls(image_dir)
+ logger.info(f"{len(cities)} cities found in '{image_dir}'.")
+ for city in cities:
+ city_img_dir = os.path.join(image_dir, city)
+ city_gt_dir = os.path.join(gt_dir, city)
+ for basename in PathManager.ls(city_img_dir):
+ image_file = os.path.join(city_img_dir, basename)
+
+ suffix = "leftImg8bit.png"
+ assert basename.endswith(suffix), basename
+ basename = basename[: -len(suffix)]
+
+ instance_file = os.path.join(city_gt_dir, basename + "gtFine_instanceIds.png")
+ label_file = os.path.join(city_gt_dir, basename + "gtFine_labelIds.png")
+ json_file = os.path.join(city_gt_dir, basename + "gtFine_polygons.json")
+
+ files.append((image_file, instance_file, label_file, json_file))
+ assert len(files), "No images found in {}".format(image_dir)
+ for f in files[0]:
+ assert PathManager.isfile(f), f
+ return files
+
+
+def load_cityscapes_instances(image_dir, gt_dir, from_json=True, to_polygons=True):
+ """
+ Args:
+ image_dir (str): path to the raw dataset. e.g., "~/cityscapes/leftImg8bit/train".
+ gt_dir (str): path to the raw annotations. e.g., "~/cityscapes/gtFine/train".
+ from_json (bool): whether to read annotations from the raw json file or the png files.
+ to_polygons (bool): whether to represent the segmentation as polygons
+ (COCO's format) instead of masks (cityscapes's format).
+
+ Returns:
+ list[dict]: a list of dicts in Detectron2 standard format. (See
+ `Using Custom Datasets `_ )
+ """
+ if from_json:
+ assert to_polygons, (
+ "Cityscapes's json annotations are in polygon format. "
+ "Converting to mask format is not supported now."
+ )
+ files = _get_cityscapes_files(image_dir, gt_dir)
+
+ logger.info("Preprocessing cityscapes annotations ...")
+ # This is still not fast: all workers will execute duplicate works and will
+ # take up to 10m on a 8GPU server.
+ pool = mp.Pool(processes=max(mp.cpu_count() // get_world_size() // 2, 4))
+
+ ret = pool.map(
+ functools.partial(_cityscapes_files_to_dict, from_json=from_json, to_polygons=to_polygons),
+ files,
+ )
+ logger.info("Loaded {} images from {}".format(len(ret), image_dir))
+
+ # Map cityscape ids to contiguous ids
+ from cityscapesscripts.helpers.labels import labels
+
+ labels = [l for l in labels if l.hasInstances and not l.ignoreInEval]
+ dataset_id_to_contiguous_id = {l.id: idx for idx, l in enumerate(labels)}
+ for dict_per_image in ret:
+ for anno in dict_per_image["annotations"]:
+ anno["category_id"] = dataset_id_to_contiguous_id[anno["category_id"]]
+ return ret
+
+
+def load_cityscapes_semantic(image_dir, gt_dir):
+ """
+ Args:
+ image_dir (str): path to the raw dataset. e.g., "~/cityscapes/leftImg8bit/train".
+ gt_dir (str): path to the raw annotations. e.g., "~/cityscapes/gtFine/train".
+
+ Returns:
+ list[dict]: a list of dict, each has "file_name" and
+ "sem_seg_file_name".
+ """
+ ret = []
+ # gt_dir is small and contain many small files. make sense to fetch to local first
+ gt_dir = PathManager.get_local_path(gt_dir)
+ for image_file, _, label_file, json_file in _get_cityscapes_files(image_dir, gt_dir):
+ label_file = label_file.replace("labelIds", "labelTrainIds")
+
+ with PathManager.open(json_file, "r") as f:
+ jsonobj = json.load(f)
+ ret.append(
+ {
+ "file_name": image_file,
+ "sem_seg_file_name": label_file,
+ "height": jsonobj["imgHeight"],
+ "width": jsonobj["imgWidth"],
+ }
+ )
+ assert len(ret), f"No images found in {image_dir}!"
+ assert PathManager.isfile(
+ ret[0]["sem_seg_file_name"]
+ ), "Please generate labelTrainIds.png with cityscapesscripts/preparation/createTrainIdLabelImgs.py" # noqa
+ return ret
+
+
+def _cityscapes_files_to_dict(files, from_json, to_polygons):
+ """
+ Parse cityscapes annotation files to a instance segmentation dataset dict.
+
+ Args:
+ files (tuple): consists of (image_file, instance_id_file, label_id_file, json_file)
+ from_json (bool): whether to read annotations from the raw json file or the png files.
+ to_polygons (bool): whether to represent the segmentation as polygons
+ (COCO's format) instead of masks (cityscapes's format).
+
+ Returns:
+ A dict in Detectron2 Dataset format.
+ """
+ from cityscapesscripts.helpers.labels import id2label, name2label
+
+ image_file, instance_id_file, _, json_file = files
+
+ annos = []
+
+ if from_json:
+ from shapely.geometry import MultiPolygon, Polygon
+
+ with PathManager.open(json_file, "r") as f:
+ jsonobj = json.load(f)
+ ret = {
+ "file_name": image_file,
+ "image_id": os.path.basename(image_file),
+ "height": jsonobj["imgHeight"],
+ "width": jsonobj["imgWidth"],
+ }
+
+ # `polygons_union` contains the union of all valid polygons.
+ polygons_union = Polygon()
+
+ # CityscapesScripts draw the polygons in sequential order
+ # and each polygon *overwrites* existing ones. See
+ # (https://github.com/mcordts/cityscapesScripts/blob/master/cityscapesscripts/preparation/json2instanceImg.py) # noqa
+ # We use reverse order, and each polygon *avoids* early ones.
+ # This will resolve the ploygon overlaps in the same way as CityscapesScripts.
+ for obj in jsonobj["objects"][::-1]:
+ if "deleted" in obj: # cityscapes data format specific
+ continue
+ label_name = obj["label"]
+
+ try:
+ label = name2label[label_name]
+ except KeyError:
+ if label_name.endswith("group"): # crowd area
+ label = name2label[label_name[: -len("group")]]
+ else:
+ raise
+ if label.id < 0: # cityscapes data format
+ continue
+
+ # Cityscapes's raw annotations uses integer coordinates
+ # Therefore +0.5 here
+ poly_coord = np.asarray(obj["polygon"], dtype="f4") + 0.5
+ # CityscapesScript uses PIL.ImageDraw.polygon to rasterize
+ # polygons for evaluation. This function operates in integer space
+ # and draws each pixel whose center falls into the polygon.
+ # Therefore it draws a polygon which is 0.5 "fatter" in expectation.
+ # We therefore dilate the input polygon by 0.5 as our input.
+ poly = Polygon(poly_coord).buffer(0.5, resolution=4)
+
+ if not label.hasInstances or label.ignoreInEval:
+ # even if we won't store the polygon it still contributes to overlaps resolution
+ polygons_union = polygons_union.union(poly)
+ continue
+
+ # Take non-overlapping part of the polygon
+ poly_wo_overlaps = poly.difference(polygons_union)
+ if poly_wo_overlaps.is_empty:
+ continue
+ polygons_union = polygons_union.union(poly)
+
+ anno = {}
+ anno["iscrowd"] = label_name.endswith("group")
+ anno["category_id"] = label.id
+
+ if isinstance(poly_wo_overlaps, Polygon):
+ poly_list = [poly_wo_overlaps]
+ elif isinstance(poly_wo_overlaps, MultiPolygon):
+ poly_list = poly_wo_overlaps.geoms
+ else:
+ raise NotImplementedError("Unknown geometric structure {}".format(poly_wo_overlaps))
+
+ poly_coord = []
+ for poly_el in poly_list:
+ # COCO API can work only with exterior boundaries now, hence we store only them.
+ # TODO: store both exterior and interior boundaries once other parts of the
+ # codebase support holes in polygons.
+ poly_coord.append(list(chain(*poly_el.exterior.coords)))
+ anno["segmentation"] = poly_coord
+ (xmin, ymin, xmax, ymax) = poly_wo_overlaps.bounds
+
+ anno["bbox"] = (xmin, ymin, xmax, ymax)
+ anno["bbox_mode"] = BoxMode.XYXY_ABS
+
+ annos.append(anno)
+ else:
+ # See also the official annotation parsing scripts at
+ # https://github.com/mcordts/cityscapesScripts/blob/master/cityscapesscripts/evaluation/instances2dict.py # noqa
+ with PathManager.open(instance_id_file, "rb") as f:
+ inst_image = np.asarray(Image.open(f), order="F")
+ # ids < 24 are stuff labels (filtering them first is about 5% faster)
+ flattened_ids = np.unique(inst_image[inst_image >= 24])
+
+ ret = {
+ "file_name": image_file,
+ "image_id": os.path.basename(image_file),
+ "height": inst_image.shape[0],
+ "width": inst_image.shape[1],
+ }
+
+ for instance_id in flattened_ids:
+ # For non-crowd annotations, instance_id // 1000 is the label_id
+ # Crowd annotations have <1000 instance ids
+ label_id = instance_id // 1000 if instance_id >= 1000 else instance_id
+ label = id2label[label_id]
+ if not label.hasInstances or label.ignoreInEval:
+ continue
+
+ anno = {}
+ anno["iscrowd"] = instance_id < 1000
+ anno["category_id"] = label.id
+
+ mask = np.asarray(inst_image == instance_id, dtype=np.uint8, order="F")
+
+ inds = np.nonzero(mask)
+ ymin, ymax = inds[0].min(), inds[0].max()
+ xmin, xmax = inds[1].min(), inds[1].max()
+ anno["bbox"] = (xmin, ymin, xmax, ymax)
+ if xmax <= xmin or ymax <= ymin:
+ continue
+ anno["bbox_mode"] = BoxMode.XYXY_ABS
+ if to_polygons:
+ # This conversion comes from D4809743 and D5171122,
+ # when Mask-RCNN was first developed.
+ contours = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[
+ -2
+ ]
+ polygons = [c.reshape(-1).tolist() for c in contours if len(c) >= 3]
+ # opencv's can produce invalid polygons
+ if len(polygons) == 0:
+ continue
+ anno["segmentation"] = polygons
+ else:
+ anno["segmentation"] = mask_util.encode(mask[:, :, None])[0]
+ annos.append(anno)
+ ret["annotations"] = annos
+ return ret
+
+
+if __name__ == "__main__":
+ """
+ Test the cityscapes dataset loader.
+
+ Usage:
+ python -m detectron2.data.datasets.cityscapes \
+ cityscapes/leftImg8bit/train cityscapes/gtFine/train
+ """
+ import argparse
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument("image_dir")
+ parser.add_argument("gt_dir")
+ parser.add_argument("--type", choices=["instance", "semantic"], default="instance")
+ args = parser.parse_args()
+ from custom_detectron2.data.catalog import Metadata
+ from custom_detectron2.utils.visualizer import Visualizer
+ from cityscapesscripts.helpers.labels import labels
+
+ logger = setup_logger(name=__name__)
+
+ dirname = "cityscapes-data-vis"
+ os.makedirs(dirname, exist_ok=True)
+
+ if args.type == "instance":
+ dicts = load_cityscapes_instances(
+ args.image_dir, args.gt_dir, from_json=True, to_polygons=True
+ )
+ logger.info("Done loading {} samples.".format(len(dicts)))
+
+ thing_classes = [k.name for k in labels if k.hasInstances and not k.ignoreInEval]
+ meta = Metadata().set(thing_classes=thing_classes)
+
+ else:
+ dicts = load_cityscapes_semantic(args.image_dir, args.gt_dir)
+ logger.info("Done loading {} samples.".format(len(dicts)))
+
+ stuff_classes = [k.name for k in labels if k.trainId != 255]
+ stuff_colors = [k.color for k in labels if k.trainId != 255]
+ meta = Metadata().set(stuff_classes=stuff_classes, stuff_colors=stuff_colors)
+
+ for d in dicts:
+ img = np.array(Image.open(PathManager.open(d["file_name"], "rb")))
+ visualizer = Visualizer(img, metadata=meta)
+ vis = visualizer.draw_dataset_dict(d)
+ # cv2.imshow("a", vis.get_image()[:, :, ::-1])
+ # cv2.waitKey()
+ fpath = os.path.join(dirname, os.path.basename(d["file_name"]))
+ vis.save(fpath)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/cityscapes_panoptic.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/cityscapes_panoptic.py
new file mode 100644
index 0000000000000000000000000000000000000000..efb8a30ce9c9cf62bc6e585dc029c9b003f6569c
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/cityscapes_panoptic.py
@@ -0,0 +1,187 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import json
+import logging
+import os
+
+from custom_detectron2.data import DatasetCatalog, MetadataCatalog
+from custom_detectron2.data.datasets.builtin_meta import CITYSCAPES_CATEGORIES
+from custom_detectron2.utils.file_io import PathManager
+
+"""
+This file contains functions to register the Cityscapes panoptic dataset to the DatasetCatalog.
+"""
+
+
+logger = logging.getLogger(__name__)
+
+
+def get_cityscapes_panoptic_files(image_dir, gt_dir, json_info):
+ files = []
+ # scan through the directory
+ cities = PathManager.ls(image_dir)
+ logger.info(f"{len(cities)} cities found in '{image_dir}'.")
+ image_dict = {}
+ for city in cities:
+ city_img_dir = os.path.join(image_dir, city)
+ for basename in PathManager.ls(city_img_dir):
+ image_file = os.path.join(city_img_dir, basename)
+
+ suffix = "_leftImg8bit.png"
+ assert basename.endswith(suffix), basename
+ basename = os.path.basename(basename)[: -len(suffix)]
+
+ image_dict[basename] = image_file
+
+ for ann in json_info["annotations"]:
+ image_file = image_dict.get(ann["image_id"], None)
+ assert image_file is not None, "No image {} found for annotation {}".format(
+ ann["image_id"], ann["file_name"]
+ )
+ label_file = os.path.join(gt_dir, ann["file_name"])
+ segments_info = ann["segments_info"]
+
+ files.append((image_file, label_file, segments_info))
+
+ assert len(files), "No images found in {}".format(image_dir)
+ assert PathManager.isfile(files[0][0]), files[0][0]
+ assert PathManager.isfile(files[0][1]), files[0][1]
+ return files
+
+
+def load_cityscapes_panoptic(image_dir, gt_dir, gt_json, meta):
+ """
+ Args:
+ image_dir (str): path to the raw dataset. e.g., "~/cityscapes/leftImg8bit/train".
+ gt_dir (str): path to the raw annotations. e.g.,
+ "~/cityscapes/gtFine/cityscapes_panoptic_train".
+ gt_json (str): path to the json file. e.g.,
+ "~/cityscapes/gtFine/cityscapes_panoptic_train.json".
+ meta (dict): dictionary containing "thing_dataset_id_to_contiguous_id"
+ and "stuff_dataset_id_to_contiguous_id" to map category ids to
+ contiguous ids for training.
+
+ Returns:
+ list[dict]: a list of dicts in Detectron2 standard format. (See
+ `Using Custom Datasets `_ )
+ """
+
+ def _convert_category_id(segment_info, meta):
+ if segment_info["category_id"] in meta["thing_dataset_id_to_contiguous_id"]:
+ segment_info["category_id"] = meta["thing_dataset_id_to_contiguous_id"][
+ segment_info["category_id"]
+ ]
+ else:
+ segment_info["category_id"] = meta["stuff_dataset_id_to_contiguous_id"][
+ segment_info["category_id"]
+ ]
+ return segment_info
+
+ assert os.path.exists(
+ gt_json
+ ), "Please run `python cityscapesscripts/preparation/createPanopticImgs.py` to generate label files." # noqa
+ with open(gt_json) as f:
+ json_info = json.load(f)
+ files = get_cityscapes_panoptic_files(image_dir, gt_dir, json_info)
+ ret = []
+ for image_file, label_file, segments_info in files:
+ sem_label_file = (
+ image_file.replace("leftImg8bit", "gtFine").split(".")[0] + "_labelTrainIds.png"
+ )
+ segments_info = [_convert_category_id(x, meta) for x in segments_info]
+ ret.append(
+ {
+ "file_name": image_file,
+ "image_id": "_".join(
+ os.path.splitext(os.path.basename(image_file))[0].split("_")[:3]
+ ),
+ "sem_seg_file_name": sem_label_file,
+ "pan_seg_file_name": label_file,
+ "segments_info": segments_info,
+ }
+ )
+ assert len(ret), f"No images found in {image_dir}!"
+ assert PathManager.isfile(
+ ret[0]["sem_seg_file_name"]
+ ), "Please generate labelTrainIds.png with cityscapesscripts/preparation/createTrainIdLabelImgs.py" # noqa
+ assert PathManager.isfile(
+ ret[0]["pan_seg_file_name"]
+ ), "Please generate panoptic annotation with python cityscapesscripts/preparation/createPanopticImgs.py" # noqa
+ return ret
+
+
+_RAW_CITYSCAPES_PANOPTIC_SPLITS = {
+ "cityscapes_fine_panoptic_train": (
+ "cityscapes/leftImg8bit/train",
+ "cityscapes/gtFine/cityscapes_panoptic_train",
+ "cityscapes/gtFine/cityscapes_panoptic_train.json",
+ ),
+ "cityscapes_fine_panoptic_val": (
+ "cityscapes/leftImg8bit/val",
+ "cityscapes/gtFine/cityscapes_panoptic_val",
+ "cityscapes/gtFine/cityscapes_panoptic_val.json",
+ ),
+ # "cityscapes_fine_panoptic_test": not supported yet
+}
+
+
+def register_all_cityscapes_panoptic(root):
+ meta = {}
+ # The following metadata maps contiguous id from [0, #thing categories +
+ # #stuff categories) to their names and colors. We have to replica of the
+ # same name and color under "thing_*" and "stuff_*" because the current
+ # visualization function in D2 handles thing and class classes differently
+ # due to some heuristic used in Panoptic FPN. We keep the same naming to
+ # enable reusing existing visualization functions.
+ thing_classes = [k["name"] for k in CITYSCAPES_CATEGORIES]
+ thing_colors = [k["color"] for k in CITYSCAPES_CATEGORIES]
+ stuff_classes = [k["name"] for k in CITYSCAPES_CATEGORIES]
+ stuff_colors = [k["color"] for k in CITYSCAPES_CATEGORIES]
+
+ meta["thing_classes"] = thing_classes
+ meta["thing_colors"] = thing_colors
+ meta["stuff_classes"] = stuff_classes
+ meta["stuff_colors"] = stuff_colors
+
+ # There are three types of ids in cityscapes panoptic segmentation:
+ # (1) category id: like semantic segmentation, it is the class id for each
+ # pixel. Since there are some classes not used in evaluation, the category
+ # id is not always contiguous and thus we have two set of category ids:
+ # - original category id: category id in the original dataset, mainly
+ # used for evaluation.
+ # - contiguous category id: [0, #classes), in order to train the classifier
+ # (2) instance id: this id is used to differentiate different instances from
+ # the same category. For "stuff" classes, the instance id is always 0; for
+ # "thing" classes, the instance id starts from 1 and 0 is reserved for
+ # ignored instances (e.g. crowd annotation).
+ # (3) panoptic id: this is the compact id that encode both category and
+ # instance id by: category_id * 1000 + instance_id.
+ thing_dataset_id_to_contiguous_id = {}
+ stuff_dataset_id_to_contiguous_id = {}
+
+ for k in CITYSCAPES_CATEGORIES:
+ if k["isthing"] == 1:
+ thing_dataset_id_to_contiguous_id[k["id"]] = k["trainId"]
+ else:
+ stuff_dataset_id_to_contiguous_id[k["id"]] = k["trainId"]
+
+ meta["thing_dataset_id_to_contiguous_id"] = thing_dataset_id_to_contiguous_id
+ meta["stuff_dataset_id_to_contiguous_id"] = stuff_dataset_id_to_contiguous_id
+
+ for key, (image_dir, gt_dir, gt_json) in _RAW_CITYSCAPES_PANOPTIC_SPLITS.items():
+ image_dir = os.path.join(root, image_dir)
+ gt_dir = os.path.join(root, gt_dir)
+ gt_json = os.path.join(root, gt_json)
+
+ DatasetCatalog.register(
+ key, lambda x=image_dir, y=gt_dir, z=gt_json: load_cityscapes_panoptic(x, y, z, meta)
+ )
+ MetadataCatalog.get(key).set(
+ panoptic_root=gt_dir,
+ image_root=image_dir,
+ panoptic_json=gt_json,
+ gt_dir=gt_dir.replace("cityscapes_panoptic_", ""),
+ evaluator_type="cityscapes_panoptic_seg",
+ ignore_label=255,
+ label_divisor=1000,
+ **meta,
+ )
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/coco.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/coco.py
new file mode 100644
index 0000000000000000000000000000000000000000..13d8bf49c454a0b29746e0c16794c12afb28ccf8
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/coco.py
@@ -0,0 +1,539 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import contextlib
+import datetime
+import io
+import json
+import logging
+import numpy as np
+import os
+import shutil
+import custom_pycocotools.mask as mask_util
+from fvcore.common.timer import Timer
+from iopath.common.file_io import file_lock
+from PIL import Image
+
+from custom_detectron2.structures import Boxes, BoxMode, PolygonMasks, RotatedBoxes
+from custom_detectron2.utils.file_io import PathManager
+
+from .. import DatasetCatalog, MetadataCatalog
+
+"""
+This file contains functions to parse COCO-format annotations into dicts in "Detectron2 format".
+"""
+
+
+logger = logging.getLogger(__name__)
+
+__all__ = ["load_coco_json", "load_sem_seg", "convert_to_coco_json", "register_coco_instances"]
+
+
+def load_coco_json(json_file, image_root, dataset_name=None, extra_annotation_keys=None):
+ """
+ Load a json file with COCO's instances annotation format.
+ Currently supports instance detection, instance segmentation,
+ and person keypoints annotations.
+
+ Args:
+ json_file (str): full path to the json file in COCO instances annotation format.
+ image_root (str or path-like): the directory where the images in this json file exists.
+ dataset_name (str or None): the name of the dataset (e.g., coco_2017_train).
+ When provided, this function will also do the following:
+
+ * Put "thing_classes" into the metadata associated with this dataset.
+ * Map the category ids into a contiguous range (needed by standard dataset format),
+ and add "thing_dataset_id_to_contiguous_id" to the metadata associated
+ with this dataset.
+
+ This option should usually be provided, unless users need to load
+ the original json content and apply more processing manually.
+ extra_annotation_keys (list[str]): list of per-annotation keys that should also be
+ loaded into the dataset dict (besides "iscrowd", "bbox", "keypoints",
+ "category_id", "segmentation"). The values for these keys will be returned as-is.
+ For example, the densepose annotations are loaded in this way.
+
+ Returns:
+ list[dict]: a list of dicts in Detectron2 standard dataset dicts format (See
+ `Using Custom Datasets `_ ) when `dataset_name` is not None.
+ If `dataset_name` is None, the returned `category_ids` may be
+ incontiguous and may not conform to the Detectron2 standard format.
+
+ Notes:
+ 1. This function does not read the image files.
+ The results do not have the "image" field.
+ """
+ from custom_pycocotools.coco import COCO
+
+ timer = Timer()
+ json_file = PathManager.get_local_path(json_file)
+ with contextlib.redirect_stdout(io.StringIO()):
+ coco_api = COCO(json_file)
+ if timer.seconds() > 1:
+ logger.info("Loading {} takes {:.2f} seconds.".format(json_file, timer.seconds()))
+
+ id_map = None
+ if dataset_name is not None:
+ meta = MetadataCatalog.get(dataset_name)
+ cat_ids = sorted(coco_api.getCatIds())
+ cats = coco_api.loadCats(cat_ids)
+ # The categories in a custom json file may not be sorted.
+ thing_classes = [c["name"] for c in sorted(cats, key=lambda x: x["id"])]
+ meta.thing_classes = thing_classes
+
+ # In COCO, certain category ids are artificially removed,
+ # and by convention they are always ignored.
+ # We deal with COCO's id issue and translate
+ # the category ids to contiguous ids in [0, 80).
+
+ # It works by looking at the "categories" field in the json, therefore
+ # if users' own json also have incontiguous ids, we'll
+ # apply this mapping as well but print a warning.
+ if not (min(cat_ids) == 1 and max(cat_ids) == len(cat_ids)):
+ if "coco" not in dataset_name:
+ logger.warning(
+ """
+Category ids in annotations are not in [1, #categories]! We'll apply a mapping for you.
+"""
+ )
+ id_map = {v: i for i, v in enumerate(cat_ids)}
+ meta.thing_dataset_id_to_contiguous_id = id_map
+
+ # sort indices for reproducible results
+ img_ids = sorted(coco_api.imgs.keys())
+ # imgs is a list of dicts, each looks something like:
+ # {'license': 4,
+ # 'url': 'http://farm6.staticflickr.com/5454/9413846304_881d5e5c3b_z.jpg',
+ # 'file_name': 'COCO_val2014_000000001268.jpg',
+ # 'height': 427,
+ # 'width': 640,
+ # 'date_captured': '2013-11-17 05:57:24',
+ # 'id': 1268}
+ imgs = coco_api.loadImgs(img_ids)
+ # anns is a list[list[dict]], where each dict is an annotation
+ # record for an object. The inner list enumerates the objects in an image
+ # and the outer list enumerates over images. Example of anns[0]:
+ # [{'segmentation': [[192.81,
+ # 247.09,
+ # ...
+ # 219.03,
+ # 249.06]],
+ # 'area': 1035.749,
+ # 'iscrowd': 0,
+ # 'image_id': 1268,
+ # 'bbox': [192.81, 224.8, 74.73, 33.43],
+ # 'category_id': 16,
+ # 'id': 42986},
+ # ...]
+ anns = [coco_api.imgToAnns[img_id] for img_id in img_ids]
+ total_num_valid_anns = sum([len(x) for x in anns])
+ total_num_anns = len(coco_api.anns)
+ if total_num_valid_anns < total_num_anns:
+ logger.warning(
+ f"{json_file} contains {total_num_anns} annotations, but only "
+ f"{total_num_valid_anns} of them match to images in the file."
+ )
+
+ if "minival" not in json_file:
+ # The popular valminusminival & minival annotations for COCO2014 contain this bug.
+ # However the ratio of buggy annotations there is tiny and does not affect accuracy.
+ # Therefore we explicitly white-list them.
+ ann_ids = [ann["id"] for anns_per_image in anns for ann in anns_per_image]
+ assert len(set(ann_ids)) == len(ann_ids), "Annotation ids in '{}' are not unique!".format(
+ json_file
+ )
+
+ imgs_anns = list(zip(imgs, anns))
+ logger.info("Loaded {} images in COCO format from {}".format(len(imgs_anns), json_file))
+
+ dataset_dicts = []
+
+ ann_keys = ["iscrowd", "bbox", "keypoints", "category_id"] + (extra_annotation_keys or [])
+
+ num_instances_without_valid_segmentation = 0
+
+ for (img_dict, anno_dict_list) in imgs_anns:
+ record = {}
+ record["file_name"] = os.path.join(image_root, img_dict["file_name"])
+ record["height"] = img_dict["height"]
+ record["width"] = img_dict["width"]
+ image_id = record["image_id"] = img_dict["id"]
+
+ objs = []
+ for anno in anno_dict_list:
+ # Check that the image_id in this annotation is the same as
+ # the image_id we're looking at.
+ # This fails only when the data parsing logic or the annotation file is buggy.
+
+ # The original COCO valminusminival2014 & minival2014 annotation files
+ # actually contains bugs that, together with certain ways of using COCO API,
+ # can trigger this assertion.
+ assert anno["image_id"] == image_id
+
+ assert anno.get("ignore", 0) == 0, '"ignore" in COCO json file is not supported.'
+
+ obj = {key: anno[key] for key in ann_keys if key in anno}
+ if "bbox" in obj and len(obj["bbox"]) == 0:
+ raise ValueError(
+ f"One annotation of image {image_id} contains empty 'bbox' value! "
+ "This json does not have valid COCO format."
+ )
+
+ segm = anno.get("segmentation", None)
+ if segm: # either list[list[float]] or dict(RLE)
+ if isinstance(segm, dict):
+ if isinstance(segm["counts"], list):
+ # convert to compressed RLE
+ segm = mask_util.frPyObjects(segm, *segm["size"])
+ else:
+ # filter out invalid polygons (< 3 points)
+ segm = [poly for poly in segm if len(poly) % 2 == 0 and len(poly) >= 6]
+ if len(segm) == 0:
+ num_instances_without_valid_segmentation += 1
+ continue # ignore this instance
+ obj["segmentation"] = segm
+
+ keypts = anno.get("keypoints", None)
+ if keypts: # list[int]
+ for idx, v in enumerate(keypts):
+ if idx % 3 != 2:
+ # COCO's segmentation coordinates are floating points in [0, H or W],
+ # but keypoint coordinates are integers in [0, H-1 or W-1]
+ # Therefore we assume the coordinates are "pixel indices" and
+ # add 0.5 to convert to floating point coordinates.
+ keypts[idx] = v + 0.5
+ obj["keypoints"] = keypts
+
+ obj["bbox_mode"] = BoxMode.XYWH_ABS
+ if id_map:
+ annotation_category_id = obj["category_id"]
+ try:
+ obj["category_id"] = id_map[annotation_category_id]
+ except KeyError as e:
+ raise KeyError(
+ f"Encountered category_id={annotation_category_id} "
+ "but this id does not exist in 'categories' of the json file."
+ ) from e
+ objs.append(obj)
+ record["annotations"] = objs
+ dataset_dicts.append(record)
+
+ if num_instances_without_valid_segmentation > 0:
+ logger.warning(
+ "Filtered out {} instances without valid segmentation. ".format(
+ num_instances_without_valid_segmentation
+ )
+ + "There might be issues in your dataset generation process. Please "
+ "check https://detectron2.readthedocs.io/en/latest/tutorials/datasets.html carefully"
+ )
+ return dataset_dicts
+
+
+def load_sem_seg(gt_root, image_root, gt_ext="png", image_ext="jpg"):
+ """
+ Load semantic segmentation datasets. All files under "gt_root" with "gt_ext" extension are
+ treated as ground truth annotations and all files under "image_root" with "image_ext" extension
+ as input images. Ground truth and input images are matched using file paths relative to
+ "gt_root" and "image_root" respectively without taking into account file extensions.
+ This works for COCO as well as some other datasets.
+
+ Args:
+ gt_root (str): full path to ground truth semantic segmentation files. Semantic segmentation
+ annotations are stored as images with integer values in pixels that represent
+ corresponding semantic labels.
+ image_root (str): the directory where the input images are.
+ gt_ext (str): file extension for ground truth annotations.
+ image_ext (str): file extension for input images.
+
+ Returns:
+ list[dict]:
+ a list of dicts in detectron2 standard format without instance-level
+ annotation.
+
+ Notes:
+ 1. This function does not read the image and ground truth files.
+ The results do not have the "image" and "sem_seg" fields.
+ """
+
+ # We match input images with ground truth based on their relative filepaths (without file
+ # extensions) starting from 'image_root' and 'gt_root' respectively.
+ def file2id(folder_path, file_path):
+ # extract relative path starting from `folder_path`
+ image_id = os.path.normpath(os.path.relpath(file_path, start=folder_path))
+ # remove file extension
+ image_id = os.path.splitext(image_id)[0]
+ return image_id
+
+ input_files = sorted(
+ (os.path.join(image_root, f) for f in PathManager.ls(image_root) if f.endswith(image_ext)),
+ key=lambda file_path: file2id(image_root, file_path),
+ )
+ gt_files = sorted(
+ (os.path.join(gt_root, f) for f in PathManager.ls(gt_root) if f.endswith(gt_ext)),
+ key=lambda file_path: file2id(gt_root, file_path),
+ )
+
+ assert len(gt_files) > 0, "No annotations found in {}.".format(gt_root)
+
+ # Use the intersection, so that val2017_100 annotations can run smoothly with val2017 images
+ if len(input_files) != len(gt_files):
+ logger.warn(
+ "Directory {} and {} has {} and {} files, respectively.".format(
+ image_root, gt_root, len(input_files), len(gt_files)
+ )
+ )
+ input_basenames = [os.path.basename(f)[: -len(image_ext)] for f in input_files]
+ gt_basenames = [os.path.basename(f)[: -len(gt_ext)] for f in gt_files]
+ intersect = list(set(input_basenames) & set(gt_basenames))
+ # sort, otherwise each worker may obtain a list[dict] in different order
+ intersect = sorted(intersect)
+ logger.warn("Will use their intersection of {} files.".format(len(intersect)))
+ input_files = [os.path.join(image_root, f + image_ext) for f in intersect]
+ gt_files = [os.path.join(gt_root, f + gt_ext) for f in intersect]
+
+ logger.info(
+ "Loaded {} images with semantic segmentation from {}".format(len(input_files), image_root)
+ )
+
+ dataset_dicts = []
+ for (img_path, gt_path) in zip(input_files, gt_files):
+ record = {}
+ record["file_name"] = img_path
+ record["sem_seg_file_name"] = gt_path
+ dataset_dicts.append(record)
+
+ return dataset_dicts
+
+
+def convert_to_coco_dict(dataset_name):
+ """
+ Convert an instance detection/segmentation or keypoint detection dataset
+ in detectron2's standard format into COCO json format.
+
+ Generic dataset description can be found here:
+ https://detectron2.readthedocs.io/tutorials/datasets.html#register-a-dataset
+
+ COCO data format description can be found here:
+ http://cocodataset.org/#format-data
+
+ Args:
+ dataset_name (str):
+ name of the source dataset
+ Must be registered in DatastCatalog and in detectron2's standard format.
+ Must have corresponding metadata "thing_classes"
+ Returns:
+ coco_dict: serializable dict in COCO json format
+ """
+
+ dataset_dicts = DatasetCatalog.get(dataset_name)
+ metadata = MetadataCatalog.get(dataset_name)
+
+ # unmap the category mapping ids for COCO
+ if hasattr(metadata, "thing_dataset_id_to_contiguous_id"):
+ reverse_id_mapping = {v: k for k, v in metadata.thing_dataset_id_to_contiguous_id.items()}
+ reverse_id_mapper = lambda contiguous_id: reverse_id_mapping[contiguous_id] # noqa
+ else:
+ reverse_id_mapper = lambda contiguous_id: contiguous_id # noqa
+
+ categories = [
+ {"id": reverse_id_mapper(id), "name": name}
+ for id, name in enumerate(metadata.thing_classes)
+ ]
+
+ logger.info("Converting dataset dicts into COCO format")
+ coco_images = []
+ coco_annotations = []
+
+ for image_id, image_dict in enumerate(dataset_dicts):
+ coco_image = {
+ "id": image_dict.get("image_id", image_id),
+ "width": int(image_dict["width"]),
+ "height": int(image_dict["height"]),
+ "file_name": str(image_dict["file_name"]),
+ }
+ coco_images.append(coco_image)
+
+ anns_per_image = image_dict.get("annotations", [])
+ for annotation in anns_per_image:
+ # create a new dict with only COCO fields
+ coco_annotation = {}
+
+ # COCO requirement: XYWH box format for axis-align and XYWHA for rotated
+ bbox = annotation["bbox"]
+ if isinstance(bbox, np.ndarray):
+ if bbox.ndim != 1:
+ raise ValueError(f"bbox has to be 1-dimensional. Got shape={bbox.shape}.")
+ bbox = bbox.tolist()
+ if len(bbox) not in [4, 5]:
+ raise ValueError(f"bbox has to has length 4 or 5. Got {bbox}.")
+ from_bbox_mode = annotation["bbox_mode"]
+ to_bbox_mode = BoxMode.XYWH_ABS if len(bbox) == 4 else BoxMode.XYWHA_ABS
+ bbox = BoxMode.convert(bbox, from_bbox_mode, to_bbox_mode)
+
+ # COCO requirement: instance area
+ if "segmentation" in annotation:
+ # Computing areas for instances by counting the pixels
+ segmentation = annotation["segmentation"]
+ # TODO: check segmentation type: RLE, BinaryMask or Polygon
+ if isinstance(segmentation, list):
+ polygons = PolygonMasks([segmentation])
+ area = polygons.area()[0].item()
+ elif isinstance(segmentation, dict): # RLE
+ area = mask_util.area(segmentation).item()
+ else:
+ raise TypeError(f"Unknown segmentation type {type(segmentation)}!")
+ else:
+ # Computing areas using bounding boxes
+ if to_bbox_mode == BoxMode.XYWH_ABS:
+ bbox_xy = BoxMode.convert(bbox, to_bbox_mode, BoxMode.XYXY_ABS)
+ area = Boxes([bbox_xy]).area()[0].item()
+ else:
+ area = RotatedBoxes([bbox]).area()[0].item()
+
+ if "keypoints" in annotation:
+ keypoints = annotation["keypoints"] # list[int]
+ for idx, v in enumerate(keypoints):
+ if idx % 3 != 2:
+ # COCO's segmentation coordinates are floating points in [0, H or W],
+ # but keypoint coordinates are integers in [0, H-1 or W-1]
+ # For COCO format consistency we substract 0.5
+ # https://github.com/facebookresearch/detectron2/pull/175#issuecomment-551202163
+ keypoints[idx] = v - 0.5
+ if "num_keypoints" in annotation:
+ num_keypoints = annotation["num_keypoints"]
+ else:
+ num_keypoints = sum(kp > 0 for kp in keypoints[2::3])
+
+ # COCO requirement:
+ # linking annotations to images
+ # "id" field must start with 1
+ coco_annotation["id"] = len(coco_annotations) + 1
+ coco_annotation["image_id"] = coco_image["id"]
+ coco_annotation["bbox"] = [round(float(x), 3) for x in bbox]
+ coco_annotation["area"] = float(area)
+ coco_annotation["iscrowd"] = int(annotation.get("iscrowd", 0))
+ coco_annotation["category_id"] = int(reverse_id_mapper(annotation["category_id"]))
+
+ # Add optional fields
+ if "keypoints" in annotation:
+ coco_annotation["keypoints"] = keypoints
+ coco_annotation["num_keypoints"] = num_keypoints
+
+ if "segmentation" in annotation:
+ seg = coco_annotation["segmentation"] = annotation["segmentation"]
+ if isinstance(seg, dict): # RLE
+ counts = seg["counts"]
+ if not isinstance(counts, str):
+ # make it json-serializable
+ seg["counts"] = counts.decode("ascii")
+
+ coco_annotations.append(coco_annotation)
+
+ logger.info(
+ "Conversion finished, "
+ f"#images: {len(coco_images)}, #annotations: {len(coco_annotations)}"
+ )
+
+ info = {
+ "date_created": str(datetime.datetime.now()),
+ "description": "Automatically generated COCO json file for Detectron2.",
+ }
+ coco_dict = {"info": info, "images": coco_images, "categories": categories, "licenses": None}
+ if len(coco_annotations) > 0:
+ coco_dict["annotations"] = coco_annotations
+ return coco_dict
+
+
+def convert_to_coco_json(dataset_name, output_file, allow_cached=True):
+ """
+ Converts dataset into COCO format and saves it to a json file.
+ dataset_name must be registered in DatasetCatalog and in detectron2's standard format.
+
+ Args:
+ dataset_name:
+ reference from the config file to the catalogs
+ must be registered in DatasetCatalog and in detectron2's standard format
+ output_file: path of json file that will be saved to
+ allow_cached: if json file is already present then skip conversion
+ """
+
+ # TODO: The dataset or the conversion script *may* change,
+ # a checksum would be useful for validating the cached data
+
+ PathManager.mkdirs(os.path.dirname(output_file))
+ with file_lock(output_file):
+ if PathManager.exists(output_file) and allow_cached:
+ logger.warning(
+ f"Using previously cached COCO format annotations at '{output_file}'. "
+ "You need to clear the cache file if your dataset has been modified."
+ )
+ else:
+ logger.info(f"Converting annotations of dataset '{dataset_name}' to COCO format ...)")
+ coco_dict = convert_to_coco_dict(dataset_name)
+
+ logger.info(f"Caching COCO format annotations at '{output_file}' ...")
+ tmp_file = output_file + ".tmp"
+ with PathManager.open(tmp_file, "w") as f:
+ json.dump(coco_dict, f)
+ shutil.move(tmp_file, output_file)
+
+
+def register_coco_instances(name, metadata, json_file, image_root):
+ """
+ Register a dataset in COCO's json annotation format for
+ instance detection, instance segmentation and keypoint detection.
+ (i.e., Type 1 and 2 in http://cocodataset.org/#format-data.
+ `instances*.json` and `person_keypoints*.json` in the dataset).
+
+ This is an example of how to register a new dataset.
+ You can do something similar to this function, to register new datasets.
+
+ Args:
+ name (str): the name that identifies a dataset, e.g. "coco_2014_train".
+ metadata (dict): extra metadata associated with this dataset. You can
+ leave it as an empty dict.
+ json_file (str): path to the json instance annotation file.
+ image_root (str or path-like): directory which contains all the images.
+ """
+ assert isinstance(name, str), name
+ assert isinstance(json_file, (str, os.PathLike)), json_file
+ assert isinstance(image_root, (str, os.PathLike)), image_root
+ # 1. register a function which returns dicts
+ DatasetCatalog.register(name, lambda: load_coco_json(json_file, image_root, name))
+
+ # 2. Optionally, add metadata about this dataset,
+ # since they might be useful in evaluation, visualization or logging
+ MetadataCatalog.get(name).set(
+ json_file=json_file, image_root=image_root, evaluator_type="coco", **metadata
+ )
+
+
+if __name__ == "__main__":
+ """
+ Test the COCO json dataset loader.
+
+ Usage:
+ python -m detectron2.data.datasets.coco \
+ path/to/json path/to/image_root dataset_name
+
+ "dataset_name" can be "coco_2014_minival_100", or other
+ pre-registered ones
+ """
+ from custom_detectron2.utils.logger import setup_logger
+ from custom_detectron2.utils.visualizer import Visualizer
+ import custom_detectron2.data.datasets # noqa # add pre-defined metadata
+ import sys
+
+ logger = setup_logger(name=__name__)
+ assert sys.argv[3] in DatasetCatalog.list()
+ meta = MetadataCatalog.get(sys.argv[3])
+
+ dicts = load_coco_json(sys.argv[1], sys.argv[2], sys.argv[3])
+ logger.info("Done loading {} samples.".format(len(dicts)))
+
+ dirname = "coco-data-vis"
+ os.makedirs(dirname, exist_ok=True)
+ for d in dicts:
+ img = np.array(Image.open(d["file_name"]))
+ visualizer = Visualizer(img, metadata=meta)
+ vis = visualizer.draw_dataset_dict(d)
+ fpath = os.path.join(dirname, os.path.basename(d["file_name"]))
+ vis.save(fpath)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/coco_panoptic.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/coco_panoptic.py
new file mode 100644
index 0000000000000000000000000000000000000000..e194be84d25c20fa83e1f93a6bf81bd6fd991970
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/coco_panoptic.py
@@ -0,0 +1,228 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import copy
+import json
+import os
+
+from custom_detectron2.data import DatasetCatalog, MetadataCatalog
+from custom_detectron2.utils.file_io import PathManager
+
+from .coco import load_coco_json, load_sem_seg
+
+__all__ = ["register_coco_panoptic", "register_coco_panoptic_separated"]
+
+
+def load_coco_panoptic_json(json_file, image_dir, gt_dir, meta):
+ """
+ Args:
+ image_dir (str): path to the raw dataset. e.g., "~/coco/train2017".
+ gt_dir (str): path to the raw annotations. e.g., "~/coco/panoptic_train2017".
+ json_file (str): path to the json file. e.g., "~/coco/annotations/panoptic_train2017.json".
+
+ Returns:
+ list[dict]: a list of dicts in Detectron2 standard format. (See
+ `Using Custom Datasets `_ )
+ """
+
+ def _convert_category_id(segment_info, meta):
+ if segment_info["category_id"] in meta["thing_dataset_id_to_contiguous_id"]:
+ segment_info["category_id"] = meta["thing_dataset_id_to_contiguous_id"][
+ segment_info["category_id"]
+ ]
+ segment_info["isthing"] = True
+ else:
+ segment_info["category_id"] = meta["stuff_dataset_id_to_contiguous_id"][
+ segment_info["category_id"]
+ ]
+ segment_info["isthing"] = False
+ return segment_info
+
+ with PathManager.open(json_file) as f:
+ json_info = json.load(f)
+
+ ret = []
+ for ann in json_info["annotations"]:
+ image_id = int(ann["image_id"])
+ # TODO: currently we assume image and label has the same filename but
+ # different extension, and images have extension ".jpg" for COCO. Need
+ # to make image extension a user-provided argument if we extend this
+ # function to support other COCO-like datasets.
+ image_file = os.path.join(image_dir, os.path.splitext(ann["file_name"])[0] + ".jpg")
+ label_file = os.path.join(gt_dir, ann["file_name"])
+ segments_info = [_convert_category_id(x, meta) for x in ann["segments_info"]]
+ ret.append(
+ {
+ "file_name": image_file,
+ "image_id": image_id,
+ "pan_seg_file_name": label_file,
+ "segments_info": segments_info,
+ }
+ )
+ assert len(ret), f"No images found in {image_dir}!"
+ assert PathManager.isfile(ret[0]["file_name"]), ret[0]["file_name"]
+ assert PathManager.isfile(ret[0]["pan_seg_file_name"]), ret[0]["pan_seg_file_name"]
+ return ret
+
+
+def register_coco_panoptic(
+ name, metadata, image_root, panoptic_root, panoptic_json, instances_json=None
+):
+ """
+ Register a "standard" version of COCO panoptic segmentation dataset named `name`.
+ The dictionaries in this registered dataset follows detectron2's standard format.
+ Hence it's called "standard".
+
+ Args:
+ name (str): the name that identifies a dataset,
+ e.g. "coco_2017_train_panoptic"
+ metadata (dict): extra metadata associated with this dataset.
+ image_root (str): directory which contains all the images
+ panoptic_root (str): directory which contains panoptic annotation images in COCO format
+ panoptic_json (str): path to the json panoptic annotation file in COCO format
+ sem_seg_root (none): not used, to be consistent with
+ `register_coco_panoptic_separated`.
+ instances_json (str): path to the json instance annotation file
+ """
+ panoptic_name = name
+ DatasetCatalog.register(
+ panoptic_name,
+ lambda: load_coco_panoptic_json(panoptic_json, image_root, panoptic_root, metadata),
+ )
+ MetadataCatalog.get(panoptic_name).set(
+ panoptic_root=panoptic_root,
+ image_root=image_root,
+ panoptic_json=panoptic_json,
+ json_file=instances_json,
+ evaluator_type="coco_panoptic_seg",
+ ignore_label=255,
+ label_divisor=1000,
+ **metadata,
+ )
+
+
+def register_coco_panoptic_separated(
+ name, metadata, image_root, panoptic_root, panoptic_json, sem_seg_root, instances_json
+):
+ """
+ Register a "separated" version of COCO panoptic segmentation dataset named `name`.
+ The annotations in this registered dataset will contain both instance annotations and
+ semantic annotations, each with its own contiguous ids. Hence it's called "separated".
+
+ It follows the setting used by the PanopticFPN paper:
+
+ 1. The instance annotations directly come from polygons in the COCO
+ instances annotation task, rather than from the masks in the COCO panoptic annotations.
+
+ The two format have small differences:
+ Polygons in the instance annotations may have overlaps.
+ The mask annotations are produced by labeling the overlapped polygons
+ with depth ordering.
+
+ 2. The semantic annotations are converted from panoptic annotations, where
+ all "things" are assigned a semantic id of 0.
+ All semantic categories will therefore have ids in contiguous
+ range [1, #stuff_categories].
+
+ This function will also register a pure semantic segmentation dataset
+ named ``name + '_stuffonly'``.
+
+ Args:
+ name (str): the name that identifies a dataset,
+ e.g. "coco_2017_train_panoptic"
+ metadata (dict): extra metadata associated with this dataset.
+ image_root (str): directory which contains all the images
+ panoptic_root (str): directory which contains panoptic annotation images
+ panoptic_json (str): path to the json panoptic annotation file
+ sem_seg_root (str): directory which contains all the ground truth segmentation annotations.
+ instances_json (str): path to the json instance annotation file
+ """
+ panoptic_name = name + "_separated"
+ DatasetCatalog.register(
+ panoptic_name,
+ lambda: merge_to_panoptic(
+ load_coco_json(instances_json, image_root, panoptic_name),
+ load_sem_seg(sem_seg_root, image_root),
+ ),
+ )
+ MetadataCatalog.get(panoptic_name).set(
+ panoptic_root=panoptic_root,
+ image_root=image_root,
+ panoptic_json=panoptic_json,
+ sem_seg_root=sem_seg_root,
+ json_file=instances_json, # TODO rename
+ evaluator_type="coco_panoptic_seg",
+ ignore_label=255,
+ **metadata,
+ )
+
+ semantic_name = name + "_stuffonly"
+ DatasetCatalog.register(semantic_name, lambda: load_sem_seg(sem_seg_root, image_root))
+ MetadataCatalog.get(semantic_name).set(
+ sem_seg_root=sem_seg_root,
+ image_root=image_root,
+ evaluator_type="sem_seg",
+ ignore_label=255,
+ **metadata,
+ )
+
+
+def merge_to_panoptic(detection_dicts, sem_seg_dicts):
+ """
+ Create dataset dicts for panoptic segmentation, by
+ merging two dicts using "file_name" field to match their entries.
+
+ Args:
+ detection_dicts (list[dict]): lists of dicts for object detection or instance segmentation.
+ sem_seg_dicts (list[dict]): lists of dicts for semantic segmentation.
+
+ Returns:
+ list[dict] (one per input image): Each dict contains all (key, value) pairs from dicts in
+ both detection_dicts and sem_seg_dicts that correspond to the same image.
+ The function assumes that the same key in different dicts has the same value.
+ """
+ results = []
+ sem_seg_file_to_entry = {x["file_name"]: x for x in sem_seg_dicts}
+ assert len(sem_seg_file_to_entry) > 0
+
+ for det_dict in detection_dicts:
+ dic = copy.copy(det_dict)
+ dic.update(sem_seg_file_to_entry[dic["file_name"]])
+ results.append(dic)
+ return results
+
+
+if __name__ == "__main__":
+ """
+ Test the COCO panoptic dataset loader.
+
+ Usage:
+ python -m detectron2.data.datasets.coco_panoptic \
+ path/to/image_root path/to/panoptic_root path/to/panoptic_json dataset_name 10
+
+ "dataset_name" can be "coco_2017_train_panoptic", or other
+ pre-registered ones
+ """
+ from custom_detectron2.utils.logger import setup_logger
+ from custom_detectron2.utils.visualizer import Visualizer
+ import custom_detectron2.data.datasets # noqa # add pre-defined metadata
+ import sys
+ from PIL import Image
+ import numpy as np
+
+ logger = setup_logger(name=__name__)
+ assert sys.argv[4] in DatasetCatalog.list()
+ meta = MetadataCatalog.get(sys.argv[4])
+
+ dicts = load_coco_panoptic_json(sys.argv[3], sys.argv[1], sys.argv[2], meta.as_dict())
+ logger.info("Done loading {} samples.".format(len(dicts)))
+
+ dirname = "coco-data-vis"
+ os.makedirs(dirname, exist_ok=True)
+ num_imgs_to_vis = int(sys.argv[5])
+ for i, d in enumerate(dicts):
+ img = np.array(Image.open(d["file_name"]))
+ visualizer = Visualizer(img, metadata=meta)
+ vis = visualizer.draw_dataset_dict(d)
+ fpath = os.path.join(dirname, os.path.basename(d["file_name"]))
+ vis.save(fpath)
+ if i + 1 >= num_imgs_to_vis:
+ break
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/lvis.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/lvis.py
new file mode 100644
index 0000000000000000000000000000000000000000..337917c1e053c97163a300022702afb8ec2215c5
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/lvis.py
@@ -0,0 +1,241 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import logging
+import os
+from fvcore.common.timer import Timer
+
+from custom_detectron2.data import DatasetCatalog, MetadataCatalog
+from custom_detectron2.structures import BoxMode
+from custom_detectron2.utils.file_io import PathManager
+
+from .builtin_meta import _get_coco_instances_meta
+from .lvis_v0_5_categories import LVIS_CATEGORIES as LVIS_V0_5_CATEGORIES
+from .lvis_v1_categories import LVIS_CATEGORIES as LVIS_V1_CATEGORIES
+from .lvis_v1_category_image_count import LVIS_CATEGORY_IMAGE_COUNT as LVIS_V1_CATEGORY_IMAGE_COUNT
+
+"""
+This file contains functions to parse LVIS-format annotations into dicts in the
+"Detectron2 format".
+"""
+
+logger = logging.getLogger(__name__)
+
+__all__ = ["load_lvis_json", "register_lvis_instances", "get_lvis_instances_meta"]
+
+
+def register_lvis_instances(name, metadata, json_file, image_root):
+ """
+ Register a dataset in LVIS's json annotation format for instance detection and segmentation.
+
+ Args:
+ name (str): a name that identifies the dataset, e.g. "lvis_v0.5_train".
+ metadata (dict): extra metadata associated with this dataset. It can be an empty dict.
+ json_file (str): path to the json instance annotation file.
+ image_root (str or path-like): directory which contains all the images.
+ """
+ DatasetCatalog.register(name, lambda: load_lvis_json(json_file, image_root, name))
+ MetadataCatalog.get(name).set(
+ json_file=json_file, image_root=image_root, evaluator_type="lvis", **metadata
+ )
+
+
+def load_lvis_json(json_file, image_root, dataset_name=None, extra_annotation_keys=None):
+ """
+ Load a json file in LVIS's annotation format.
+
+ Args:
+ json_file (str): full path to the LVIS json annotation file.
+ image_root (str): the directory where the images in this json file exists.
+ dataset_name (str): the name of the dataset (e.g., "lvis_v0.5_train").
+ If provided, this function will put "thing_classes" into the metadata
+ associated with this dataset.
+ extra_annotation_keys (list[str]): list of per-annotation keys that should also be
+ loaded into the dataset dict (besides "bbox", "bbox_mode", "category_id",
+ "segmentation"). The values for these keys will be returned as-is.
+
+ Returns:
+ list[dict]: a list of dicts in Detectron2 standard format. (See
+ `Using Custom Datasets `_ )
+
+ Notes:
+ 1. This function does not read the image files.
+ The results do not have the "image" field.
+ """
+ from lvis import LVIS
+
+ json_file = PathManager.get_local_path(json_file)
+
+ timer = Timer()
+ lvis_api = LVIS(json_file)
+ if timer.seconds() > 1:
+ logger.info("Loading {} takes {:.2f} seconds.".format(json_file, timer.seconds()))
+
+ if dataset_name is not None:
+ meta = get_lvis_instances_meta(dataset_name)
+ MetadataCatalog.get(dataset_name).set(**meta)
+
+ # sort indices for reproducible results
+ img_ids = sorted(lvis_api.imgs.keys())
+ # imgs is a list of dicts, each looks something like:
+ # {'license': 4,
+ # 'url': 'http://farm6.staticflickr.com/5454/9413846304_881d5e5c3b_z.jpg',
+ # 'file_name': 'COCO_val2014_000000001268.jpg',
+ # 'height': 427,
+ # 'width': 640,
+ # 'date_captured': '2013-11-17 05:57:24',
+ # 'id': 1268}
+ imgs = lvis_api.load_imgs(img_ids)
+ # anns is a list[list[dict]], where each dict is an annotation
+ # record for an object. The inner list enumerates the objects in an image
+ # and the outer list enumerates over images. Example of anns[0]:
+ # [{'segmentation': [[192.81,
+ # 247.09,
+ # ...
+ # 219.03,
+ # 249.06]],
+ # 'area': 1035.749,
+ # 'image_id': 1268,
+ # 'bbox': [192.81, 224.8, 74.73, 33.43],
+ # 'category_id': 16,
+ # 'id': 42986},
+ # ...]
+ anns = [lvis_api.img_ann_map[img_id] for img_id in img_ids]
+
+ # Sanity check that each annotation has a unique id
+ ann_ids = [ann["id"] for anns_per_image in anns for ann in anns_per_image]
+ assert len(set(ann_ids)) == len(ann_ids), "Annotation ids in '{}' are not unique".format(
+ json_file
+ )
+
+ imgs_anns = list(zip(imgs, anns))
+
+ logger.info("Loaded {} images in the LVIS format from {}".format(len(imgs_anns), json_file))
+
+ if extra_annotation_keys:
+ logger.info(
+ "The following extra annotation keys will be loaded: {} ".format(extra_annotation_keys)
+ )
+ else:
+ extra_annotation_keys = []
+
+ def get_file_name(img_root, img_dict):
+ # Determine the path including the split folder ("train2017", "val2017", "test2017") from
+ # the coco_url field. Example:
+ # 'coco_url': 'http://images.cocodataset.org/train2017/000000155379.jpg'
+ split_folder, file_name = img_dict["coco_url"].split("/")[-2:]
+ return os.path.join(img_root + split_folder, file_name)
+
+ dataset_dicts = []
+
+ for (img_dict, anno_dict_list) in imgs_anns:
+ record = {}
+ record["file_name"] = get_file_name(image_root, img_dict)
+ record["height"] = img_dict["height"]
+ record["width"] = img_dict["width"]
+ record["not_exhaustive_category_ids"] = img_dict.get("not_exhaustive_category_ids", [])
+ record["neg_category_ids"] = img_dict.get("neg_category_ids", [])
+ image_id = record["image_id"] = img_dict["id"]
+
+ objs = []
+ for anno in anno_dict_list:
+ # Check that the image_id in this annotation is the same as
+ # the image_id we're looking at.
+ # This fails only when the data parsing logic or the annotation file is buggy.
+ assert anno["image_id"] == image_id
+ obj = {"bbox": anno["bbox"], "bbox_mode": BoxMode.XYWH_ABS}
+ # LVIS data loader can be used to load COCO dataset categories. In this case `meta`
+ # variable will have a field with COCO-specific category mapping.
+ if dataset_name is not None and "thing_dataset_id_to_contiguous_id" in meta:
+ obj["category_id"] = meta["thing_dataset_id_to_contiguous_id"][anno["category_id"]]
+ else:
+ obj["category_id"] = anno["category_id"] - 1 # Convert 1-indexed to 0-indexed
+ segm = anno["segmentation"] # list[list[float]]
+ # filter out invalid polygons (< 3 points)
+ valid_segm = [poly for poly in segm if len(poly) % 2 == 0 and len(poly) >= 6]
+ assert len(segm) == len(
+ valid_segm
+ ), "Annotation contains an invalid polygon with < 3 points"
+ assert len(segm) > 0
+ obj["segmentation"] = segm
+ for extra_ann_key in extra_annotation_keys:
+ obj[extra_ann_key] = anno[extra_ann_key]
+ objs.append(obj)
+ record["annotations"] = objs
+ dataset_dicts.append(record)
+
+ return dataset_dicts
+
+
+def get_lvis_instances_meta(dataset_name):
+ """
+ Load LVIS metadata.
+
+ Args:
+ dataset_name (str): LVIS dataset name without the split name (e.g., "lvis_v0.5").
+
+ Returns:
+ dict: LVIS metadata with keys: thing_classes
+ """
+ if "cocofied" in dataset_name:
+ return _get_coco_instances_meta()
+ if "v0.5" in dataset_name:
+ return _get_lvis_instances_meta_v0_5()
+ elif "v1" in dataset_name:
+ return _get_lvis_instances_meta_v1()
+ raise ValueError("No built-in metadata for dataset {}".format(dataset_name))
+
+
+def _get_lvis_instances_meta_v0_5():
+ assert len(LVIS_V0_5_CATEGORIES) == 1230
+ cat_ids = [k["id"] for k in LVIS_V0_5_CATEGORIES]
+ assert min(cat_ids) == 1 and max(cat_ids) == len(
+ cat_ids
+ ), "Category ids are not in [1, #categories], as expected"
+ # Ensure that the category list is sorted by id
+ lvis_categories = sorted(LVIS_V0_5_CATEGORIES, key=lambda x: x["id"])
+ thing_classes = [k["synonyms"][0] for k in lvis_categories]
+ meta = {"thing_classes": thing_classes}
+ return meta
+
+
+def _get_lvis_instances_meta_v1():
+ assert len(LVIS_V1_CATEGORIES) == 1203
+ cat_ids = [k["id"] for k in LVIS_V1_CATEGORIES]
+ assert min(cat_ids) == 1 and max(cat_ids) == len(
+ cat_ids
+ ), "Category ids are not in [1, #categories], as expected"
+ # Ensure that the category list is sorted by id
+ lvis_categories = sorted(LVIS_V1_CATEGORIES, key=lambda x: x["id"])
+ thing_classes = [k["synonyms"][0] for k in lvis_categories]
+ meta = {"thing_classes": thing_classes, "class_image_count": LVIS_V1_CATEGORY_IMAGE_COUNT}
+ return meta
+
+
+if __name__ == "__main__":
+ """
+ Test the LVIS json dataset loader.
+
+ Usage:
+ python -m detectron2.data.datasets.lvis \
+ path/to/json path/to/image_root dataset_name vis_limit
+ """
+ import sys
+ import numpy as np
+ from custom_detectron2.utils.logger import setup_logger
+ from PIL import Image
+ import custom_detectron2.data.datasets # noqa # add pre-defined metadata
+ from custom_detectron2.utils.visualizer import Visualizer
+
+ logger = setup_logger(name=__name__)
+ meta = MetadataCatalog.get(sys.argv[3])
+
+ dicts = load_lvis_json(sys.argv[1], sys.argv[2], sys.argv[3])
+ logger.info("Done loading {} samples.".format(len(dicts)))
+
+ dirname = "lvis-data-vis"
+ os.makedirs(dirname, exist_ok=True)
+ for d in dicts[: int(sys.argv[4])]:
+ img = np.array(Image.open(d["file_name"]))
+ visualizer = Visualizer(img, metadata=meta)
+ vis = visualizer.draw_dataset_dict(d)
+ fpath = os.path.join(dirname, os.path.basename(d["file_name"]))
+ vis.save(fpath)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/lvis_v0_5_categories.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/lvis_v0_5_categories.py
new file mode 100644
index 0000000000000000000000000000000000000000..d3dab6198da614937b08682f4c9edf52bdf1d236
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/lvis_v0_5_categories.py
@@ -0,0 +1,13 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# Autogen with
+# with open("lvis_v0.5_val.json", "r") as f:
+# a = json.load(f)
+# c = a["categories"]
+# for x in c:
+# del x["image_count"]
+# del x["instance_count"]
+# LVIS_CATEGORIES = repr(c) + " # noqa"
+
+# fmt: off
+LVIS_CATEGORIES = [{'frequency': 'r', 'id': 1, 'synset': 'acorn.n.01', 'synonyms': ['acorn'], 'def': 'nut from an oak tree', 'name': 'acorn'}, {'frequency': 'c', 'id': 2, 'synset': 'aerosol.n.02', 'synonyms': ['aerosol_can', 'spray_can'], 'def': 'a dispenser that holds a substance under pressure', 'name': 'aerosol_can'}, {'frequency': 'f', 'id': 3, 'synset': 'air_conditioner.n.01', 'synonyms': ['air_conditioner'], 'def': 'a machine that keeps air cool and dry', 'name': 'air_conditioner'}, {'frequency': 'f', 'id': 4, 'synset': 'airplane.n.01', 'synonyms': ['airplane', 'aeroplane'], 'def': 'an aircraft that has a fixed wing and is powered by propellers or jets', 'name': 'airplane'}, {'frequency': 'c', 'id': 5, 'synset': 'alarm_clock.n.01', 'synonyms': ['alarm_clock'], 'def': 'a clock that wakes a sleeper at some preset time', 'name': 'alarm_clock'}, {'frequency': 'c', 'id': 6, 'synset': 'alcohol.n.01', 'synonyms': ['alcohol', 'alcoholic_beverage'], 'def': 'a liquor or brew containing alcohol as the active agent', 'name': 'alcohol'}, {'frequency': 'r', 'id': 7, 'synset': 'alligator.n.02', 'synonyms': ['alligator', 'gator'], 'def': 'amphibious reptiles related to crocodiles but with shorter broader snouts', 'name': 'alligator'}, {'frequency': 'c', 'id': 8, 'synset': 'almond.n.02', 'synonyms': ['almond'], 'def': 'oval-shaped edible seed of the almond tree', 'name': 'almond'}, {'frequency': 'c', 'id': 9, 'synset': 'ambulance.n.01', 'synonyms': ['ambulance'], 'def': 'a vehicle that takes people to and from hospitals', 'name': 'ambulance'}, {'frequency': 'r', 'id': 10, 'synset': 'amplifier.n.01', 'synonyms': ['amplifier'], 'def': 'electronic equipment that increases strength of signals', 'name': 'amplifier'}, {'frequency': 'c', 'id': 11, 'synset': 'anklet.n.03', 'synonyms': ['anklet', 'ankle_bracelet'], 'def': 'an ornament worn around the ankle', 'name': 'anklet'}, {'frequency': 'f', 'id': 12, 'synset': 'antenna.n.01', 'synonyms': ['antenna', 'aerial', 'transmitting_aerial'], 'def': 'an electrical device that sends or receives radio or television signals', 'name': 'antenna'}, {'frequency': 'f', 'id': 13, 'synset': 'apple.n.01', 'synonyms': ['apple'], 'def': 'fruit with red or yellow or green skin and sweet to tart crisp whitish flesh', 'name': 'apple'}, {'frequency': 'r', 'id': 14, 'synset': 'apple_juice.n.01', 'synonyms': ['apple_juice'], 'def': 'the juice of apples', 'name': 'apple_juice'}, {'frequency': 'r', 'id': 15, 'synset': 'applesauce.n.01', 'synonyms': ['applesauce'], 'def': 'puree of stewed apples usually sweetened and spiced', 'name': 'applesauce'}, {'frequency': 'r', 'id': 16, 'synset': 'apricot.n.02', 'synonyms': ['apricot'], 'def': 'downy yellow to rosy-colored fruit resembling a small peach', 'name': 'apricot'}, {'frequency': 'f', 'id': 17, 'synset': 'apron.n.01', 'synonyms': ['apron'], 'def': 'a garment of cloth that is tied about the waist and worn to protect clothing', 'name': 'apron'}, {'frequency': 'c', 'id': 18, 'synset': 'aquarium.n.01', 'synonyms': ['aquarium', 'fish_tank'], 'def': 'a tank/pool/bowl filled with water for keeping live fish and underwater animals', 'name': 'aquarium'}, {'frequency': 'c', 'id': 19, 'synset': 'armband.n.02', 'synonyms': ['armband'], 'def': 'a band worn around the upper arm', 'name': 'armband'}, {'frequency': 'f', 'id': 20, 'synset': 'armchair.n.01', 'synonyms': ['armchair'], 'def': 'chair with a support on each side for arms', 'name': 'armchair'}, {'frequency': 'r', 'id': 21, 'synset': 'armoire.n.01', 'synonyms': ['armoire'], 'def': 'a large wardrobe or cabinet', 'name': 'armoire'}, {'frequency': 'r', 'id': 22, 'synset': 'armor.n.01', 'synonyms': ['armor', 'armour'], 'def': 'protective covering made of metal and used in combat', 'name': 'armor'}, {'frequency': 'c', 'id': 23, 'synset': 'artichoke.n.02', 'synonyms': ['artichoke'], 'def': 'a thistlelike flower head with edible fleshy leaves and heart', 'name': 'artichoke'}, {'frequency': 'f', 'id': 24, 'synset': 'ashcan.n.01', 'synonyms': ['trash_can', 'garbage_can', 'wastebin', 'dustbin', 'trash_barrel', 'trash_bin'], 'def': 'a bin that holds rubbish until it is collected', 'name': 'trash_can'}, {'frequency': 'c', 'id': 25, 'synset': 'ashtray.n.01', 'synonyms': ['ashtray'], 'def': "a receptacle for the ash from smokers' cigars or cigarettes", 'name': 'ashtray'}, {'frequency': 'c', 'id': 26, 'synset': 'asparagus.n.02', 'synonyms': ['asparagus'], 'def': 'edible young shoots of the asparagus plant', 'name': 'asparagus'}, {'frequency': 'c', 'id': 27, 'synset': 'atomizer.n.01', 'synonyms': ['atomizer', 'atomiser', 'spray', 'sprayer', 'nebulizer', 'nebuliser'], 'def': 'a dispenser that turns a liquid (such as perfume) into a fine mist', 'name': 'atomizer'}, {'frequency': 'c', 'id': 28, 'synset': 'avocado.n.01', 'synonyms': ['avocado'], 'def': 'a pear-shaped fruit with green or blackish skin and rich yellowish pulp enclosing a single large seed', 'name': 'avocado'}, {'frequency': 'c', 'id': 29, 'synset': 'award.n.02', 'synonyms': ['award', 'accolade'], 'def': 'a tangible symbol signifying approval or distinction', 'name': 'award'}, {'frequency': 'f', 'id': 30, 'synset': 'awning.n.01', 'synonyms': ['awning'], 'def': 'a canopy made of canvas to shelter people or things from rain or sun', 'name': 'awning'}, {'frequency': 'r', 'id': 31, 'synset': 'ax.n.01', 'synonyms': ['ax', 'axe'], 'def': 'an edge tool with a heavy bladed head mounted across a handle', 'name': 'ax'}, {'frequency': 'f', 'id': 32, 'synset': 'baby_buggy.n.01', 'synonyms': ['baby_buggy', 'baby_carriage', 'perambulator', 'pram', 'stroller'], 'def': 'a small vehicle with four wheels in which a baby or child is pushed around', 'name': 'baby_buggy'}, {'frequency': 'c', 'id': 33, 'synset': 'backboard.n.01', 'synonyms': ['basketball_backboard'], 'def': 'a raised vertical board with basket attached; used to play basketball', 'name': 'basketball_backboard'}, {'frequency': 'f', 'id': 34, 'synset': 'backpack.n.01', 'synonyms': ['backpack', 'knapsack', 'packsack', 'rucksack', 'haversack'], 'def': 'a bag carried by a strap on your back or shoulder', 'name': 'backpack'}, {'frequency': 'f', 'id': 35, 'synset': 'bag.n.04', 'synonyms': ['handbag', 'purse', 'pocketbook'], 'def': 'a container used for carrying money and small personal items or accessories', 'name': 'handbag'}, {'frequency': 'f', 'id': 36, 'synset': 'bag.n.06', 'synonyms': ['suitcase', 'baggage', 'luggage'], 'def': 'cases used to carry belongings when traveling', 'name': 'suitcase'}, {'frequency': 'c', 'id': 37, 'synset': 'bagel.n.01', 'synonyms': ['bagel', 'beigel'], 'def': 'glazed yeast-raised doughnut-shaped roll with hard crust', 'name': 'bagel'}, {'frequency': 'r', 'id': 38, 'synset': 'bagpipe.n.01', 'synonyms': ['bagpipe'], 'def': 'a tubular wind instrument; the player blows air into a bag and squeezes it out', 'name': 'bagpipe'}, {'frequency': 'r', 'id': 39, 'synset': 'baguet.n.01', 'synonyms': ['baguet', 'baguette'], 'def': 'narrow French stick loaf', 'name': 'baguet'}, {'frequency': 'r', 'id': 40, 'synset': 'bait.n.02', 'synonyms': ['bait', 'lure'], 'def': 'something used to lure fish or other animals into danger so they can be trapped or killed', 'name': 'bait'}, {'frequency': 'f', 'id': 41, 'synset': 'ball.n.06', 'synonyms': ['ball'], 'def': 'a spherical object used as a plaything', 'name': 'ball'}, {'frequency': 'r', 'id': 42, 'synset': 'ballet_skirt.n.01', 'synonyms': ['ballet_skirt', 'tutu'], 'def': 'very short skirt worn by ballerinas', 'name': 'ballet_skirt'}, {'frequency': 'f', 'id': 43, 'synset': 'balloon.n.01', 'synonyms': ['balloon'], 'def': 'large tough nonrigid bag filled with gas or heated air', 'name': 'balloon'}, {'frequency': 'c', 'id': 44, 'synset': 'bamboo.n.02', 'synonyms': ['bamboo'], 'def': 'woody tropical grass having hollow woody stems', 'name': 'bamboo'}, {'frequency': 'f', 'id': 45, 'synset': 'banana.n.02', 'synonyms': ['banana'], 'def': 'elongated crescent-shaped yellow fruit with soft sweet flesh', 'name': 'banana'}, {'frequency': 'r', 'id': 46, 'synset': 'band_aid.n.01', 'synonyms': ['Band_Aid'], 'def': 'trade name for an adhesive bandage to cover small cuts or blisters', 'name': 'Band_Aid'}, {'frequency': 'c', 'id': 47, 'synset': 'bandage.n.01', 'synonyms': ['bandage'], 'def': 'a piece of soft material that covers and protects an injured part of the body', 'name': 'bandage'}, {'frequency': 'c', 'id': 48, 'synset': 'bandanna.n.01', 'synonyms': ['bandanna', 'bandana'], 'def': 'large and brightly colored handkerchief; often used as a neckerchief', 'name': 'bandanna'}, {'frequency': 'r', 'id': 49, 'synset': 'banjo.n.01', 'synonyms': ['banjo'], 'def': 'a stringed instrument of the guitar family with a long neck and circular body', 'name': 'banjo'}, {'frequency': 'f', 'id': 50, 'synset': 'banner.n.01', 'synonyms': ['banner', 'streamer'], 'def': 'long strip of cloth or paper used for decoration or advertising', 'name': 'banner'}, {'frequency': 'r', 'id': 51, 'synset': 'barbell.n.01', 'synonyms': ['barbell'], 'def': 'a bar to which heavy discs are attached at each end; used in weightlifting', 'name': 'barbell'}, {'frequency': 'r', 'id': 52, 'synset': 'barge.n.01', 'synonyms': ['barge'], 'def': 'a flatbottom boat for carrying heavy loads (especially on canals)', 'name': 'barge'}, {'frequency': 'f', 'id': 53, 'synset': 'barrel.n.02', 'synonyms': ['barrel', 'cask'], 'def': 'a cylindrical container that holds liquids', 'name': 'barrel'}, {'frequency': 'c', 'id': 54, 'synset': 'barrette.n.01', 'synonyms': ['barrette'], 'def': "a pin for holding women's hair in place", 'name': 'barrette'}, {'frequency': 'c', 'id': 55, 'synset': 'barrow.n.03', 'synonyms': ['barrow', 'garden_cart', 'lawn_cart', 'wheelbarrow'], 'def': 'a cart for carrying small loads; has handles and one or more wheels', 'name': 'barrow'}, {'frequency': 'f', 'id': 56, 'synset': 'base.n.03', 'synonyms': ['baseball_base'], 'def': 'a place that the runner must touch before scoring', 'name': 'baseball_base'}, {'frequency': 'f', 'id': 57, 'synset': 'baseball.n.02', 'synonyms': ['baseball'], 'def': 'a ball used in playing baseball', 'name': 'baseball'}, {'frequency': 'f', 'id': 58, 'synset': 'baseball_bat.n.01', 'synonyms': ['baseball_bat'], 'def': 'an implement used in baseball by the batter', 'name': 'baseball_bat'}, {'frequency': 'f', 'id': 59, 'synset': 'baseball_cap.n.01', 'synonyms': ['baseball_cap', 'jockey_cap', 'golf_cap'], 'def': 'a cap with a bill', 'name': 'baseball_cap'}, {'frequency': 'f', 'id': 60, 'synset': 'baseball_glove.n.01', 'synonyms': ['baseball_glove', 'baseball_mitt'], 'def': 'the handwear used by fielders in playing baseball', 'name': 'baseball_glove'}, {'frequency': 'f', 'id': 61, 'synset': 'basket.n.01', 'synonyms': ['basket', 'handbasket'], 'def': 'a container that is usually woven and has handles', 'name': 'basket'}, {'frequency': 'c', 'id': 62, 'synset': 'basket.n.03', 'synonyms': ['basketball_hoop'], 'def': 'metal hoop supporting a net through which players try to throw the basketball', 'name': 'basketball_hoop'}, {'frequency': 'c', 'id': 63, 'synset': 'basketball.n.02', 'synonyms': ['basketball'], 'def': 'an inflated ball used in playing basketball', 'name': 'basketball'}, {'frequency': 'r', 'id': 64, 'synset': 'bass_horn.n.01', 'synonyms': ['bass_horn', 'sousaphone', 'tuba'], 'def': 'the lowest brass wind instrument', 'name': 'bass_horn'}, {'frequency': 'r', 'id': 65, 'synset': 'bat.n.01', 'synonyms': ['bat_(animal)'], 'def': 'nocturnal mouselike mammal with forelimbs modified to form membranous wings', 'name': 'bat_(animal)'}, {'frequency': 'f', 'id': 66, 'synset': 'bath_mat.n.01', 'synonyms': ['bath_mat'], 'def': 'a heavy towel or mat to stand on while drying yourself after a bath', 'name': 'bath_mat'}, {'frequency': 'f', 'id': 67, 'synset': 'bath_towel.n.01', 'synonyms': ['bath_towel'], 'def': 'a large towel; to dry yourself after a bath', 'name': 'bath_towel'}, {'frequency': 'c', 'id': 68, 'synset': 'bathrobe.n.01', 'synonyms': ['bathrobe'], 'def': 'a loose-fitting robe of towelling; worn after a bath or swim', 'name': 'bathrobe'}, {'frequency': 'f', 'id': 69, 'synset': 'bathtub.n.01', 'synonyms': ['bathtub', 'bathing_tub'], 'def': 'a large open container that you fill with water and use to wash the body', 'name': 'bathtub'}, {'frequency': 'r', 'id': 70, 'synset': 'batter.n.02', 'synonyms': ['batter_(food)'], 'def': 'a liquid or semiliquid mixture, as of flour, eggs, and milk, used in cooking', 'name': 'batter_(food)'}, {'frequency': 'c', 'id': 71, 'synset': 'battery.n.02', 'synonyms': ['battery'], 'def': 'a portable device that produces electricity', 'name': 'battery'}, {'frequency': 'r', 'id': 72, 'synset': 'beach_ball.n.01', 'synonyms': ['beachball'], 'def': 'large and light ball; for play at the seaside', 'name': 'beachball'}, {'frequency': 'c', 'id': 73, 'synset': 'bead.n.01', 'synonyms': ['bead'], 'def': 'a small ball with a hole through the middle used for ornamentation, jewellery, etc.', 'name': 'bead'}, {'frequency': 'r', 'id': 74, 'synset': 'beaker.n.01', 'synonyms': ['beaker'], 'def': 'a flatbottomed jar made of glass or plastic; used for chemistry', 'name': 'beaker'}, {'frequency': 'c', 'id': 75, 'synset': 'bean_curd.n.01', 'synonyms': ['bean_curd', 'tofu'], 'def': 'cheeselike food made of curdled soybean milk', 'name': 'bean_curd'}, {'frequency': 'c', 'id': 76, 'synset': 'beanbag.n.01', 'synonyms': ['beanbag'], 'def': 'a bag filled with dried beans or similar items; used in games or to sit on', 'name': 'beanbag'}, {'frequency': 'f', 'id': 77, 'synset': 'beanie.n.01', 'synonyms': ['beanie', 'beany'], 'def': 'a small skullcap; formerly worn by schoolboys and college freshmen', 'name': 'beanie'}, {'frequency': 'f', 'id': 78, 'synset': 'bear.n.01', 'synonyms': ['bear'], 'def': 'large carnivorous or omnivorous mammals with shaggy coats and claws', 'name': 'bear'}, {'frequency': 'f', 'id': 79, 'synset': 'bed.n.01', 'synonyms': ['bed'], 'def': 'a piece of furniture that provides a place to sleep', 'name': 'bed'}, {'frequency': 'c', 'id': 80, 'synset': 'bedspread.n.01', 'synonyms': ['bedspread', 'bedcover', 'bed_covering', 'counterpane', 'spread'], 'def': 'decorative cover for a bed', 'name': 'bedspread'}, {'frequency': 'f', 'id': 81, 'synset': 'beef.n.01', 'synonyms': ['cow'], 'def': 'cattle that are reared for their meat', 'name': 'cow'}, {'frequency': 'c', 'id': 82, 'synset': 'beef.n.02', 'synonyms': ['beef_(food)', 'boeuf_(food)'], 'def': 'meat from an adult domestic bovine', 'name': 'beef_(food)'}, {'frequency': 'r', 'id': 83, 'synset': 'beeper.n.01', 'synonyms': ['beeper', 'pager'], 'def': 'an device that beeps when the person carrying it is being paged', 'name': 'beeper'}, {'frequency': 'f', 'id': 84, 'synset': 'beer_bottle.n.01', 'synonyms': ['beer_bottle'], 'def': 'a bottle that holds beer', 'name': 'beer_bottle'}, {'frequency': 'c', 'id': 85, 'synset': 'beer_can.n.01', 'synonyms': ['beer_can'], 'def': 'a can that holds beer', 'name': 'beer_can'}, {'frequency': 'r', 'id': 86, 'synset': 'beetle.n.01', 'synonyms': ['beetle'], 'def': 'insect with hard wing covers', 'name': 'beetle'}, {'frequency': 'f', 'id': 87, 'synset': 'bell.n.01', 'synonyms': ['bell'], 'def': 'a hollow device made of metal that makes a ringing sound when struck', 'name': 'bell'}, {'frequency': 'f', 'id': 88, 'synset': 'bell_pepper.n.02', 'synonyms': ['bell_pepper', 'capsicum'], 'def': 'large bell-shaped sweet pepper in green or red or yellow or orange or black varieties', 'name': 'bell_pepper'}, {'frequency': 'f', 'id': 89, 'synset': 'belt.n.02', 'synonyms': ['belt'], 'def': 'a band to tie or buckle around the body (usually at the waist)', 'name': 'belt'}, {'frequency': 'f', 'id': 90, 'synset': 'belt_buckle.n.01', 'synonyms': ['belt_buckle'], 'def': 'the buckle used to fasten a belt', 'name': 'belt_buckle'}, {'frequency': 'f', 'id': 91, 'synset': 'bench.n.01', 'synonyms': ['bench'], 'def': 'a long seat for more than one person', 'name': 'bench'}, {'frequency': 'c', 'id': 92, 'synset': 'beret.n.01', 'synonyms': ['beret'], 'def': 'a cap with no brim or bill; made of soft cloth', 'name': 'beret'}, {'frequency': 'c', 'id': 93, 'synset': 'bib.n.02', 'synonyms': ['bib'], 'def': 'a napkin tied under the chin of a child while eating', 'name': 'bib'}, {'frequency': 'r', 'id': 94, 'synset': 'bible.n.01', 'synonyms': ['Bible'], 'def': 'the sacred writings of the Christian religions', 'name': 'Bible'}, {'frequency': 'f', 'id': 95, 'synset': 'bicycle.n.01', 'synonyms': ['bicycle', 'bike_(bicycle)'], 'def': 'a wheeled vehicle that has two wheels and is moved by foot pedals', 'name': 'bicycle'}, {'frequency': 'f', 'id': 96, 'synset': 'bill.n.09', 'synonyms': ['visor', 'vizor'], 'def': 'a brim that projects to the front to shade the eyes', 'name': 'visor'}, {'frequency': 'c', 'id': 97, 'synset': 'binder.n.03', 'synonyms': ['binder', 'ring-binder'], 'def': 'holds loose papers or magazines', 'name': 'binder'}, {'frequency': 'c', 'id': 98, 'synset': 'binoculars.n.01', 'synonyms': ['binoculars', 'field_glasses', 'opera_glasses'], 'def': 'an optical instrument designed for simultaneous use by both eyes', 'name': 'binoculars'}, {'frequency': 'f', 'id': 99, 'synset': 'bird.n.01', 'synonyms': ['bird'], 'def': 'animal characterized by feathers and wings', 'name': 'bird'}, {'frequency': 'r', 'id': 100, 'synset': 'bird_feeder.n.01', 'synonyms': ['birdfeeder'], 'def': 'an outdoor device that supplies food for wild birds', 'name': 'birdfeeder'}, {'frequency': 'r', 'id': 101, 'synset': 'birdbath.n.01', 'synonyms': ['birdbath'], 'def': 'an ornamental basin (usually in a garden) for birds to bathe in', 'name': 'birdbath'}, {'frequency': 'c', 'id': 102, 'synset': 'birdcage.n.01', 'synonyms': ['birdcage'], 'def': 'a cage in which a bird can be kept', 'name': 'birdcage'}, {'frequency': 'c', 'id': 103, 'synset': 'birdhouse.n.01', 'synonyms': ['birdhouse'], 'def': 'a shelter for birds', 'name': 'birdhouse'}, {'frequency': 'f', 'id': 104, 'synset': 'birthday_cake.n.01', 'synonyms': ['birthday_cake'], 'def': 'decorated cake served at a birthday party', 'name': 'birthday_cake'}, {'frequency': 'r', 'id': 105, 'synset': 'birthday_card.n.01', 'synonyms': ['birthday_card'], 'def': 'a card expressing a birthday greeting', 'name': 'birthday_card'}, {'frequency': 'r', 'id': 106, 'synset': 'biscuit.n.01', 'synonyms': ['biscuit_(bread)'], 'def': 'small round bread leavened with baking-powder or soda', 'name': 'biscuit_(bread)'}, {'frequency': 'r', 'id': 107, 'synset': 'black_flag.n.01', 'synonyms': ['pirate_flag'], 'def': 'a flag usually bearing a white skull and crossbones on a black background', 'name': 'pirate_flag'}, {'frequency': 'c', 'id': 108, 'synset': 'black_sheep.n.02', 'synonyms': ['black_sheep'], 'def': 'sheep with a black coat', 'name': 'black_sheep'}, {'frequency': 'c', 'id': 109, 'synset': 'blackboard.n.01', 'synonyms': ['blackboard', 'chalkboard'], 'def': 'sheet of slate; for writing with chalk', 'name': 'blackboard'}, {'frequency': 'f', 'id': 110, 'synset': 'blanket.n.01', 'synonyms': ['blanket'], 'def': 'bedding that keeps a person warm in bed', 'name': 'blanket'}, {'frequency': 'c', 'id': 111, 'synset': 'blazer.n.01', 'synonyms': ['blazer', 'sport_jacket', 'sport_coat', 'sports_jacket', 'sports_coat'], 'def': 'lightweight jacket; often striped in the colors of a club or school', 'name': 'blazer'}, {'frequency': 'f', 'id': 112, 'synset': 'blender.n.01', 'synonyms': ['blender', 'liquidizer', 'liquidiser'], 'def': 'an electrically powered mixer that mix or chop or liquefy foods', 'name': 'blender'}, {'frequency': 'r', 'id': 113, 'synset': 'blimp.n.02', 'synonyms': ['blimp'], 'def': 'a small nonrigid airship used for observation or as a barrage balloon', 'name': 'blimp'}, {'frequency': 'c', 'id': 114, 'synset': 'blinker.n.01', 'synonyms': ['blinker', 'flasher'], 'def': 'a light that flashes on and off; used as a signal or to send messages', 'name': 'blinker'}, {'frequency': 'c', 'id': 115, 'synset': 'blueberry.n.02', 'synonyms': ['blueberry'], 'def': 'sweet edible dark-blue berries of blueberry plants', 'name': 'blueberry'}, {'frequency': 'r', 'id': 116, 'synset': 'boar.n.02', 'synonyms': ['boar'], 'def': 'an uncastrated male hog', 'name': 'boar'}, {'frequency': 'r', 'id': 117, 'synset': 'board.n.09', 'synonyms': ['gameboard'], 'def': 'a flat portable surface (usually rectangular) designed for board games', 'name': 'gameboard'}, {'frequency': 'f', 'id': 118, 'synset': 'boat.n.01', 'synonyms': ['boat', 'ship_(boat)'], 'def': 'a vessel for travel on water', 'name': 'boat'}, {'frequency': 'c', 'id': 119, 'synset': 'bobbin.n.01', 'synonyms': ['bobbin', 'spool', 'reel'], 'def': 'a thing around which thread/tape/film or other flexible materials can be wound', 'name': 'bobbin'}, {'frequency': 'r', 'id': 120, 'synset': 'bobby_pin.n.01', 'synonyms': ['bobby_pin', 'hairgrip'], 'def': 'a flat wire hairpin used to hold bobbed hair in place', 'name': 'bobby_pin'}, {'frequency': 'c', 'id': 121, 'synset': 'boiled_egg.n.01', 'synonyms': ['boiled_egg', 'coddled_egg'], 'def': 'egg cooked briefly in the shell in gently boiling water', 'name': 'boiled_egg'}, {'frequency': 'r', 'id': 122, 'synset': 'bolo_tie.n.01', 'synonyms': ['bolo_tie', 'bolo', 'bola_tie', 'bola'], 'def': 'a cord fastened around the neck with an ornamental clasp and worn as a necktie', 'name': 'bolo_tie'}, {'frequency': 'c', 'id': 123, 'synset': 'bolt.n.03', 'synonyms': ['deadbolt'], 'def': 'the part of a lock that is engaged or withdrawn with a key', 'name': 'deadbolt'}, {'frequency': 'f', 'id': 124, 'synset': 'bolt.n.06', 'synonyms': ['bolt'], 'def': 'a screw that screws into a nut to form a fastener', 'name': 'bolt'}, {'frequency': 'r', 'id': 125, 'synset': 'bonnet.n.01', 'synonyms': ['bonnet'], 'def': 'a hat tied under the chin', 'name': 'bonnet'}, {'frequency': 'f', 'id': 126, 'synset': 'book.n.01', 'synonyms': ['book'], 'def': 'a written work or composition that has been published', 'name': 'book'}, {'frequency': 'r', 'id': 127, 'synset': 'book_bag.n.01', 'synonyms': ['book_bag'], 'def': 'a bag in which students carry their books', 'name': 'book_bag'}, {'frequency': 'c', 'id': 128, 'synset': 'bookcase.n.01', 'synonyms': ['bookcase'], 'def': 'a piece of furniture with shelves for storing books', 'name': 'bookcase'}, {'frequency': 'c', 'id': 129, 'synset': 'booklet.n.01', 'synonyms': ['booklet', 'brochure', 'leaflet', 'pamphlet'], 'def': 'a small book usually having a paper cover', 'name': 'booklet'}, {'frequency': 'r', 'id': 130, 'synset': 'bookmark.n.01', 'synonyms': ['bookmark', 'bookmarker'], 'def': 'a marker (a piece of paper or ribbon) placed between the pages of a book', 'name': 'bookmark'}, {'frequency': 'r', 'id': 131, 'synset': 'boom.n.04', 'synonyms': ['boom_microphone', 'microphone_boom'], 'def': 'a pole carrying an overhead microphone projected over a film or tv set', 'name': 'boom_microphone'}, {'frequency': 'f', 'id': 132, 'synset': 'boot.n.01', 'synonyms': ['boot'], 'def': 'footwear that covers the whole foot and lower leg', 'name': 'boot'}, {'frequency': 'f', 'id': 133, 'synset': 'bottle.n.01', 'synonyms': ['bottle'], 'def': 'a glass or plastic vessel used for storing drinks or other liquids', 'name': 'bottle'}, {'frequency': 'c', 'id': 134, 'synset': 'bottle_opener.n.01', 'synonyms': ['bottle_opener'], 'def': 'an opener for removing caps or corks from bottles', 'name': 'bottle_opener'}, {'frequency': 'c', 'id': 135, 'synset': 'bouquet.n.01', 'synonyms': ['bouquet'], 'def': 'an arrangement of flowers that is usually given as a present', 'name': 'bouquet'}, {'frequency': 'r', 'id': 136, 'synset': 'bow.n.04', 'synonyms': ['bow_(weapon)'], 'def': 'a weapon for shooting arrows', 'name': 'bow_(weapon)'}, {'frequency': 'f', 'id': 137, 'synset': 'bow.n.08', 'synonyms': ['bow_(decorative_ribbons)'], 'def': 'a decorative interlacing of ribbons', 'name': 'bow_(decorative_ribbons)'}, {'frequency': 'f', 'id': 138, 'synset': 'bow_tie.n.01', 'synonyms': ['bow-tie', 'bowtie'], 'def': "a man's tie that ties in a bow", 'name': 'bow-tie'}, {'frequency': 'f', 'id': 139, 'synset': 'bowl.n.03', 'synonyms': ['bowl'], 'def': 'a dish that is round and open at the top for serving foods', 'name': 'bowl'}, {'frequency': 'r', 'id': 140, 'synset': 'bowl.n.08', 'synonyms': ['pipe_bowl'], 'def': 'a small round container that is open at the top for holding tobacco', 'name': 'pipe_bowl'}, {'frequency': 'c', 'id': 141, 'synset': 'bowler_hat.n.01', 'synonyms': ['bowler_hat', 'bowler', 'derby_hat', 'derby', 'plug_hat'], 'def': 'a felt hat that is round and hard with a narrow brim', 'name': 'bowler_hat'}, {'frequency': 'r', 'id': 142, 'synset': 'bowling_ball.n.01', 'synonyms': ['bowling_ball'], 'def': 'a large ball with finger holes used in the sport of bowling', 'name': 'bowling_ball'}, {'frequency': 'r', 'id': 143, 'synset': 'bowling_pin.n.01', 'synonyms': ['bowling_pin'], 'def': 'a club-shaped wooden object used in bowling', 'name': 'bowling_pin'}, {'frequency': 'r', 'id': 144, 'synset': 'boxing_glove.n.01', 'synonyms': ['boxing_glove'], 'def': 'large glove coverings the fists of a fighter worn for the sport of boxing', 'name': 'boxing_glove'}, {'frequency': 'c', 'id': 145, 'synset': 'brace.n.06', 'synonyms': ['suspenders'], 'def': 'elastic straps that hold trousers up (usually used in the plural)', 'name': 'suspenders'}, {'frequency': 'f', 'id': 146, 'synset': 'bracelet.n.02', 'synonyms': ['bracelet', 'bangle'], 'def': 'jewelry worn around the wrist for decoration', 'name': 'bracelet'}, {'frequency': 'r', 'id': 147, 'synset': 'brass.n.07', 'synonyms': ['brass_plaque'], 'def': 'a memorial made of brass', 'name': 'brass_plaque'}, {'frequency': 'c', 'id': 148, 'synset': 'brassiere.n.01', 'synonyms': ['brassiere', 'bra', 'bandeau'], 'def': 'an undergarment worn by women to support their breasts', 'name': 'brassiere'}, {'frequency': 'c', 'id': 149, 'synset': 'bread-bin.n.01', 'synonyms': ['bread-bin', 'breadbox'], 'def': 'a container used to keep bread or cake in', 'name': 'bread-bin'}, {'frequency': 'r', 'id': 150, 'synset': 'breechcloth.n.01', 'synonyms': ['breechcloth', 'breechclout', 'loincloth'], 'def': 'a garment that provides covering for the loins', 'name': 'breechcloth'}, {'frequency': 'c', 'id': 151, 'synset': 'bridal_gown.n.01', 'synonyms': ['bridal_gown', 'wedding_gown', 'wedding_dress'], 'def': 'a gown worn by the bride at a wedding', 'name': 'bridal_gown'}, {'frequency': 'c', 'id': 152, 'synset': 'briefcase.n.01', 'synonyms': ['briefcase'], 'def': 'a case with a handle; for carrying papers or files or books', 'name': 'briefcase'}, {'frequency': 'c', 'id': 153, 'synset': 'bristle_brush.n.01', 'synonyms': ['bristle_brush'], 'def': 'a brush that is made with the short stiff hairs of an animal or plant', 'name': 'bristle_brush'}, {'frequency': 'f', 'id': 154, 'synset': 'broccoli.n.01', 'synonyms': ['broccoli'], 'def': 'plant with dense clusters of tight green flower buds', 'name': 'broccoli'}, {'frequency': 'r', 'id': 155, 'synset': 'brooch.n.01', 'synonyms': ['broach'], 'def': 'a decorative pin worn by women', 'name': 'broach'}, {'frequency': 'c', 'id': 156, 'synset': 'broom.n.01', 'synonyms': ['broom'], 'def': 'bundle of straws or twigs attached to a long handle; used for cleaning', 'name': 'broom'}, {'frequency': 'c', 'id': 157, 'synset': 'brownie.n.03', 'synonyms': ['brownie'], 'def': 'square or bar of very rich chocolate cake usually with nuts', 'name': 'brownie'}, {'frequency': 'c', 'id': 158, 'synset': 'brussels_sprouts.n.01', 'synonyms': ['brussels_sprouts'], 'def': 'the small edible cabbage-like buds growing along a stalk', 'name': 'brussels_sprouts'}, {'frequency': 'r', 'id': 159, 'synset': 'bubble_gum.n.01', 'synonyms': ['bubble_gum'], 'def': 'a kind of chewing gum that can be blown into bubbles', 'name': 'bubble_gum'}, {'frequency': 'f', 'id': 160, 'synset': 'bucket.n.01', 'synonyms': ['bucket', 'pail'], 'def': 'a roughly cylindrical vessel that is open at the top', 'name': 'bucket'}, {'frequency': 'r', 'id': 161, 'synset': 'buggy.n.01', 'synonyms': ['horse_buggy'], 'def': 'a small lightweight carriage; drawn by a single horse', 'name': 'horse_buggy'}, {'frequency': 'c', 'id': 162, 'synset': 'bull.n.11', 'synonyms': ['bull'], 'def': 'mature male cow', 'name': 'bull'}, {'frequency': 'r', 'id': 163, 'synset': 'bulldog.n.01', 'synonyms': ['bulldog'], 'def': 'a thickset short-haired dog with a large head and strong undershot lower jaw', 'name': 'bulldog'}, {'frequency': 'r', 'id': 164, 'synset': 'bulldozer.n.01', 'synonyms': ['bulldozer', 'dozer'], 'def': 'large powerful tractor; a large blade in front flattens areas of ground', 'name': 'bulldozer'}, {'frequency': 'c', 'id': 165, 'synset': 'bullet_train.n.01', 'synonyms': ['bullet_train'], 'def': 'a high-speed passenger train', 'name': 'bullet_train'}, {'frequency': 'c', 'id': 166, 'synset': 'bulletin_board.n.02', 'synonyms': ['bulletin_board', 'notice_board'], 'def': 'a board that hangs on a wall; displays announcements', 'name': 'bulletin_board'}, {'frequency': 'r', 'id': 167, 'synset': 'bulletproof_vest.n.01', 'synonyms': ['bulletproof_vest'], 'def': 'a vest capable of resisting the impact of a bullet', 'name': 'bulletproof_vest'}, {'frequency': 'c', 'id': 168, 'synset': 'bullhorn.n.01', 'synonyms': ['bullhorn', 'megaphone'], 'def': 'a portable loudspeaker with built-in microphone and amplifier', 'name': 'bullhorn'}, {'frequency': 'r', 'id': 169, 'synset': 'bully_beef.n.01', 'synonyms': ['corned_beef', 'corn_beef'], 'def': 'beef cured or pickled in brine', 'name': 'corned_beef'}, {'frequency': 'f', 'id': 170, 'synset': 'bun.n.01', 'synonyms': ['bun', 'roll'], 'def': 'small rounded bread either plain or sweet', 'name': 'bun'}, {'frequency': 'c', 'id': 171, 'synset': 'bunk_bed.n.01', 'synonyms': ['bunk_bed'], 'def': 'beds built one above the other', 'name': 'bunk_bed'}, {'frequency': 'f', 'id': 172, 'synset': 'buoy.n.01', 'synonyms': ['buoy'], 'def': 'a float attached by rope to the seabed to mark channels in a harbor or underwater hazards', 'name': 'buoy'}, {'frequency': 'r', 'id': 173, 'synset': 'burrito.n.01', 'synonyms': ['burrito'], 'def': 'a flour tortilla folded around a filling', 'name': 'burrito'}, {'frequency': 'f', 'id': 174, 'synset': 'bus.n.01', 'synonyms': ['bus_(vehicle)', 'autobus', 'charabanc', 'double-decker', 'motorbus', 'motorcoach'], 'def': 'a vehicle carrying many passengers; used for public transport', 'name': 'bus_(vehicle)'}, {'frequency': 'c', 'id': 175, 'synset': 'business_card.n.01', 'synonyms': ['business_card'], 'def': "a card on which are printed the person's name and business affiliation", 'name': 'business_card'}, {'frequency': 'c', 'id': 176, 'synset': 'butcher_knife.n.01', 'synonyms': ['butcher_knife'], 'def': 'a large sharp knife for cutting or trimming meat', 'name': 'butcher_knife'}, {'frequency': 'c', 'id': 177, 'synset': 'butter.n.01', 'synonyms': ['butter'], 'def': 'an edible emulsion of fat globules made by churning milk or cream; for cooking and table use', 'name': 'butter'}, {'frequency': 'c', 'id': 178, 'synset': 'butterfly.n.01', 'synonyms': ['butterfly'], 'def': 'insect typically having a slender body with knobbed antennae and broad colorful wings', 'name': 'butterfly'}, {'frequency': 'f', 'id': 179, 'synset': 'button.n.01', 'synonyms': ['button'], 'def': 'a round fastener sewn to shirts and coats etc to fit through buttonholes', 'name': 'button'}, {'frequency': 'f', 'id': 180, 'synset': 'cab.n.03', 'synonyms': ['cab_(taxi)', 'taxi', 'taxicab'], 'def': 'a car that takes passengers where they want to go in exchange for money', 'name': 'cab_(taxi)'}, {'frequency': 'r', 'id': 181, 'synset': 'cabana.n.01', 'synonyms': ['cabana'], 'def': 'a small tent used as a dressing room beside the sea or a swimming pool', 'name': 'cabana'}, {'frequency': 'r', 'id': 182, 'synset': 'cabin_car.n.01', 'synonyms': ['cabin_car', 'caboose'], 'def': 'a car on a freight train for use of the train crew; usually the last car on the train', 'name': 'cabin_car'}, {'frequency': 'f', 'id': 183, 'synset': 'cabinet.n.01', 'synonyms': ['cabinet'], 'def': 'a piece of furniture resembling a cupboard with doors and shelves and drawers', 'name': 'cabinet'}, {'frequency': 'r', 'id': 184, 'synset': 'cabinet.n.03', 'synonyms': ['locker', 'storage_locker'], 'def': 'a storage compartment for clothes and valuables; usually it has a lock', 'name': 'locker'}, {'frequency': 'f', 'id': 185, 'synset': 'cake.n.03', 'synonyms': ['cake'], 'def': 'baked goods made from or based on a mixture of flour, sugar, eggs, and fat', 'name': 'cake'}, {'frequency': 'c', 'id': 186, 'synset': 'calculator.n.02', 'synonyms': ['calculator'], 'def': 'a small machine that is used for mathematical calculations', 'name': 'calculator'}, {'frequency': 'f', 'id': 187, 'synset': 'calendar.n.02', 'synonyms': ['calendar'], 'def': 'a list or register of events (appointments/social events/court cases, etc)', 'name': 'calendar'}, {'frequency': 'c', 'id': 188, 'synset': 'calf.n.01', 'synonyms': ['calf'], 'def': 'young of domestic cattle', 'name': 'calf'}, {'frequency': 'c', 'id': 189, 'synset': 'camcorder.n.01', 'synonyms': ['camcorder'], 'def': 'a portable television camera and videocassette recorder', 'name': 'camcorder'}, {'frequency': 'c', 'id': 190, 'synset': 'camel.n.01', 'synonyms': ['camel'], 'def': 'cud-chewing mammal used as a draft or saddle animal in desert regions', 'name': 'camel'}, {'frequency': 'f', 'id': 191, 'synset': 'camera.n.01', 'synonyms': ['camera'], 'def': 'equipment for taking photographs', 'name': 'camera'}, {'frequency': 'c', 'id': 192, 'synset': 'camera_lens.n.01', 'synonyms': ['camera_lens'], 'def': 'a lens that focuses the image in a camera', 'name': 'camera_lens'}, {'frequency': 'c', 'id': 193, 'synset': 'camper.n.02', 'synonyms': ['camper_(vehicle)', 'camping_bus', 'motor_home'], 'def': 'a recreational vehicle equipped for camping out while traveling', 'name': 'camper_(vehicle)'}, {'frequency': 'f', 'id': 194, 'synset': 'can.n.01', 'synonyms': ['can', 'tin_can'], 'def': 'airtight sealed metal container for food or drink or paint etc.', 'name': 'can'}, {'frequency': 'c', 'id': 195, 'synset': 'can_opener.n.01', 'synonyms': ['can_opener', 'tin_opener'], 'def': 'a device for cutting cans open', 'name': 'can_opener'}, {'frequency': 'r', 'id': 196, 'synset': 'candelabrum.n.01', 'synonyms': ['candelabrum', 'candelabra'], 'def': 'branched candlestick; ornamental; has several lights', 'name': 'candelabrum'}, {'frequency': 'f', 'id': 197, 'synset': 'candle.n.01', 'synonyms': ['candle', 'candlestick'], 'def': 'stick of wax with a wick in the middle', 'name': 'candle'}, {'frequency': 'f', 'id': 198, 'synset': 'candlestick.n.01', 'synonyms': ['candle_holder'], 'def': 'a holder with sockets for candles', 'name': 'candle_holder'}, {'frequency': 'r', 'id': 199, 'synset': 'candy_bar.n.01', 'synonyms': ['candy_bar'], 'def': 'a candy shaped as a bar', 'name': 'candy_bar'}, {'frequency': 'c', 'id': 200, 'synset': 'candy_cane.n.01', 'synonyms': ['candy_cane'], 'def': 'a hard candy in the shape of a rod (usually with stripes)', 'name': 'candy_cane'}, {'frequency': 'c', 'id': 201, 'synset': 'cane.n.01', 'synonyms': ['walking_cane'], 'def': 'a stick that people can lean on to help them walk', 'name': 'walking_cane'}, {'frequency': 'c', 'id': 202, 'synset': 'canister.n.02', 'synonyms': ['canister', 'cannister'], 'def': 'metal container for storing dry foods such as tea or flour', 'name': 'canister'}, {'frequency': 'r', 'id': 203, 'synset': 'cannon.n.02', 'synonyms': ['cannon'], 'def': 'heavy gun fired from a tank', 'name': 'cannon'}, {'frequency': 'c', 'id': 204, 'synset': 'canoe.n.01', 'synonyms': ['canoe'], 'def': 'small and light boat; pointed at both ends; propelled with a paddle', 'name': 'canoe'}, {'frequency': 'r', 'id': 205, 'synset': 'cantaloup.n.02', 'synonyms': ['cantaloup', 'cantaloupe'], 'def': 'the fruit of a cantaloup vine; small to medium-sized melon with yellowish flesh', 'name': 'cantaloup'}, {'frequency': 'r', 'id': 206, 'synset': 'canteen.n.01', 'synonyms': ['canteen'], 'def': 'a flask for carrying water; used by soldiers or travelers', 'name': 'canteen'}, {'frequency': 'c', 'id': 207, 'synset': 'cap.n.01', 'synonyms': ['cap_(headwear)'], 'def': 'a tight-fitting headwear', 'name': 'cap_(headwear)'}, {'frequency': 'f', 'id': 208, 'synset': 'cap.n.02', 'synonyms': ['bottle_cap', 'cap_(container_lid)'], 'def': 'a top (as for a bottle)', 'name': 'bottle_cap'}, {'frequency': 'r', 'id': 209, 'synset': 'cape.n.02', 'synonyms': ['cape'], 'def': 'a sleeveless garment like a cloak but shorter', 'name': 'cape'}, {'frequency': 'c', 'id': 210, 'synset': 'cappuccino.n.01', 'synonyms': ['cappuccino', 'coffee_cappuccino'], 'def': 'equal parts of espresso and steamed milk', 'name': 'cappuccino'}, {'frequency': 'f', 'id': 211, 'synset': 'car.n.01', 'synonyms': ['car_(automobile)', 'auto_(automobile)', 'automobile'], 'def': 'a motor vehicle with four wheels', 'name': 'car_(automobile)'}, {'frequency': 'f', 'id': 212, 'synset': 'car.n.02', 'synonyms': ['railcar_(part_of_a_train)', 'railway_car_(part_of_a_train)', 'railroad_car_(part_of_a_train)'], 'def': 'a wheeled vehicle adapted to the rails of railroad', 'name': 'railcar_(part_of_a_train)'}, {'frequency': 'r', 'id': 213, 'synset': 'car.n.04', 'synonyms': ['elevator_car'], 'def': 'where passengers ride up and down', 'name': 'elevator_car'}, {'frequency': 'r', 'id': 214, 'synset': 'car_battery.n.01', 'synonyms': ['car_battery', 'automobile_battery'], 'def': 'a battery in a motor vehicle', 'name': 'car_battery'}, {'frequency': 'c', 'id': 215, 'synset': 'card.n.02', 'synonyms': ['identity_card'], 'def': 'a card certifying the identity of the bearer', 'name': 'identity_card'}, {'frequency': 'c', 'id': 216, 'synset': 'card.n.03', 'synonyms': ['card'], 'def': 'a rectangular piece of paper used to send messages (e.g. greetings or pictures)', 'name': 'card'}, {'frequency': 'r', 'id': 217, 'synset': 'cardigan.n.01', 'synonyms': ['cardigan'], 'def': 'knitted jacket that is fastened up the front with buttons or a zipper', 'name': 'cardigan'}, {'frequency': 'r', 'id': 218, 'synset': 'cargo_ship.n.01', 'synonyms': ['cargo_ship', 'cargo_vessel'], 'def': 'a ship designed to carry cargo', 'name': 'cargo_ship'}, {'frequency': 'r', 'id': 219, 'synset': 'carnation.n.01', 'synonyms': ['carnation'], 'def': 'plant with pink to purple-red spice-scented usually double flowers', 'name': 'carnation'}, {'frequency': 'c', 'id': 220, 'synset': 'carriage.n.02', 'synonyms': ['horse_carriage'], 'def': 'a vehicle with wheels drawn by one or more horses', 'name': 'horse_carriage'}, {'frequency': 'f', 'id': 221, 'synset': 'carrot.n.01', 'synonyms': ['carrot'], 'def': 'deep orange edible root of the cultivated carrot plant', 'name': 'carrot'}, {'frequency': 'c', 'id': 222, 'synset': 'carryall.n.01', 'synonyms': ['tote_bag'], 'def': 'a capacious bag or basket', 'name': 'tote_bag'}, {'frequency': 'c', 'id': 223, 'synset': 'cart.n.01', 'synonyms': ['cart'], 'def': 'a heavy open wagon usually having two wheels and drawn by an animal', 'name': 'cart'}, {'frequency': 'c', 'id': 224, 'synset': 'carton.n.02', 'synonyms': ['carton'], 'def': 'a box made of cardboard; opens by flaps on top', 'name': 'carton'}, {'frequency': 'c', 'id': 225, 'synset': 'cash_register.n.01', 'synonyms': ['cash_register', 'register_(for_cash_transactions)'], 'def': 'a cashbox with an adding machine to register transactions', 'name': 'cash_register'}, {'frequency': 'r', 'id': 226, 'synset': 'casserole.n.01', 'synonyms': ['casserole'], 'def': 'food cooked and served in a casserole', 'name': 'casserole'}, {'frequency': 'r', 'id': 227, 'synset': 'cassette.n.01', 'synonyms': ['cassette'], 'def': 'a container that holds a magnetic tape used for recording or playing sound or video', 'name': 'cassette'}, {'frequency': 'c', 'id': 228, 'synset': 'cast.n.05', 'synonyms': ['cast', 'plaster_cast', 'plaster_bandage'], 'def': 'bandage consisting of a firm covering that immobilizes broken bones while they heal', 'name': 'cast'}, {'frequency': 'f', 'id': 229, 'synset': 'cat.n.01', 'synonyms': ['cat'], 'def': 'a domestic house cat', 'name': 'cat'}, {'frequency': 'c', 'id': 230, 'synset': 'cauliflower.n.02', 'synonyms': ['cauliflower'], 'def': 'edible compact head of white undeveloped flowers', 'name': 'cauliflower'}, {'frequency': 'r', 'id': 231, 'synset': 'caviar.n.01', 'synonyms': ['caviar', 'caviare'], 'def': "salted roe of sturgeon or other large fish; usually served as an hors d'oeuvre", 'name': 'caviar'}, {'frequency': 'c', 'id': 232, 'synset': 'cayenne.n.02', 'synonyms': ['cayenne_(spice)', 'cayenne_pepper_(spice)', 'red_pepper_(spice)'], 'def': 'ground pods and seeds of pungent red peppers of the genus Capsicum', 'name': 'cayenne_(spice)'}, {'frequency': 'c', 'id': 233, 'synset': 'cd_player.n.01', 'synonyms': ['CD_player'], 'def': 'electronic equipment for playing compact discs (CDs)', 'name': 'CD_player'}, {'frequency': 'c', 'id': 234, 'synset': 'celery.n.01', 'synonyms': ['celery'], 'def': 'widely cultivated herb with aromatic leaf stalks that are eaten raw or cooked', 'name': 'celery'}, {'frequency': 'f', 'id': 235, 'synset': 'cellular_telephone.n.01', 'synonyms': ['cellular_telephone', 'cellular_phone', 'cellphone', 'mobile_phone', 'smart_phone'], 'def': 'a hand-held mobile telephone', 'name': 'cellular_telephone'}, {'frequency': 'r', 'id': 236, 'synset': 'chain_mail.n.01', 'synonyms': ['chain_mail', 'ring_mail', 'chain_armor', 'chain_armour', 'ring_armor', 'ring_armour'], 'def': '(Middle Ages) flexible armor made of interlinked metal rings', 'name': 'chain_mail'}, {'frequency': 'f', 'id': 237, 'synset': 'chair.n.01', 'synonyms': ['chair'], 'def': 'a seat for one person, with a support for the back', 'name': 'chair'}, {'frequency': 'r', 'id': 238, 'synset': 'chaise_longue.n.01', 'synonyms': ['chaise_longue', 'chaise', 'daybed'], 'def': 'a long chair; for reclining', 'name': 'chaise_longue'}, {'frequency': 'r', 'id': 239, 'synset': 'champagne.n.01', 'synonyms': ['champagne'], 'def': 'a white sparkling wine produced in Champagne or resembling that produced there', 'name': 'champagne'}, {'frequency': 'f', 'id': 240, 'synset': 'chandelier.n.01', 'synonyms': ['chandelier'], 'def': 'branched lighting fixture; often ornate; hangs from the ceiling', 'name': 'chandelier'}, {'frequency': 'r', 'id': 241, 'synset': 'chap.n.04', 'synonyms': ['chap'], 'def': 'leather leggings without a seat; worn over trousers by cowboys to protect their legs', 'name': 'chap'}, {'frequency': 'r', 'id': 242, 'synset': 'checkbook.n.01', 'synonyms': ['checkbook', 'chequebook'], 'def': 'a book issued to holders of checking accounts', 'name': 'checkbook'}, {'frequency': 'r', 'id': 243, 'synset': 'checkerboard.n.01', 'synonyms': ['checkerboard'], 'def': 'a board having 64 squares of two alternating colors', 'name': 'checkerboard'}, {'frequency': 'c', 'id': 244, 'synset': 'cherry.n.03', 'synonyms': ['cherry'], 'def': 'a red fruit with a single hard stone', 'name': 'cherry'}, {'frequency': 'r', 'id': 245, 'synset': 'chessboard.n.01', 'synonyms': ['chessboard'], 'def': 'a checkerboard used to play chess', 'name': 'chessboard'}, {'frequency': 'r', 'id': 246, 'synset': 'chest_of_drawers.n.01', 'synonyms': ['chest_of_drawers_(furniture)', 'bureau_(furniture)', 'chest_(furniture)'], 'def': 'furniture with drawers for keeping clothes', 'name': 'chest_of_drawers_(furniture)'}, {'frequency': 'c', 'id': 247, 'synset': 'chicken.n.02', 'synonyms': ['chicken_(animal)'], 'def': 'a domestic fowl bred for flesh or eggs', 'name': 'chicken_(animal)'}, {'frequency': 'c', 'id': 248, 'synset': 'chicken_wire.n.01', 'synonyms': ['chicken_wire'], 'def': 'a galvanized wire network with a hexagonal mesh; used to build fences', 'name': 'chicken_wire'}, {'frequency': 'r', 'id': 249, 'synset': 'chickpea.n.01', 'synonyms': ['chickpea', 'garbanzo'], 'def': 'the seed of the chickpea plant; usually dried', 'name': 'chickpea'}, {'frequency': 'r', 'id': 250, 'synset': 'chihuahua.n.03', 'synonyms': ['Chihuahua'], 'def': 'an old breed of tiny short-haired dog with protruding eyes from Mexico', 'name': 'Chihuahua'}, {'frequency': 'r', 'id': 251, 'synset': 'chili.n.02', 'synonyms': ['chili_(vegetable)', 'chili_pepper_(vegetable)', 'chilli_(vegetable)', 'chilly_(vegetable)', 'chile_(vegetable)'], 'def': 'very hot and finely tapering pepper of special pungency', 'name': 'chili_(vegetable)'}, {'frequency': 'r', 'id': 252, 'synset': 'chime.n.01', 'synonyms': ['chime', 'gong'], 'def': 'an instrument consisting of a set of bells that are struck with a hammer', 'name': 'chime'}, {'frequency': 'r', 'id': 253, 'synset': 'chinaware.n.01', 'synonyms': ['chinaware'], 'def': 'dishware made of high quality porcelain', 'name': 'chinaware'}, {'frequency': 'c', 'id': 254, 'synset': 'chip.n.04', 'synonyms': ['crisp_(potato_chip)', 'potato_chip'], 'def': 'a thin crisp slice of potato fried in deep fat', 'name': 'crisp_(potato_chip)'}, {'frequency': 'r', 'id': 255, 'synset': 'chip.n.06', 'synonyms': ['poker_chip'], 'def': 'a small disk-shaped counter used to represent money when gambling', 'name': 'poker_chip'}, {'frequency': 'c', 'id': 256, 'synset': 'chocolate_bar.n.01', 'synonyms': ['chocolate_bar'], 'def': 'a bar of chocolate candy', 'name': 'chocolate_bar'}, {'frequency': 'c', 'id': 257, 'synset': 'chocolate_cake.n.01', 'synonyms': ['chocolate_cake'], 'def': 'cake containing chocolate', 'name': 'chocolate_cake'}, {'frequency': 'r', 'id': 258, 'synset': 'chocolate_milk.n.01', 'synonyms': ['chocolate_milk'], 'def': 'milk flavored with chocolate syrup', 'name': 'chocolate_milk'}, {'frequency': 'r', 'id': 259, 'synset': 'chocolate_mousse.n.01', 'synonyms': ['chocolate_mousse'], 'def': 'dessert mousse made with chocolate', 'name': 'chocolate_mousse'}, {'frequency': 'f', 'id': 260, 'synset': 'choker.n.03', 'synonyms': ['choker', 'collar', 'neckband'], 'def': 'necklace that fits tightly around the neck', 'name': 'choker'}, {'frequency': 'f', 'id': 261, 'synset': 'chopping_board.n.01', 'synonyms': ['chopping_board', 'cutting_board', 'chopping_block'], 'def': 'a wooden board where meats or vegetables can be cut', 'name': 'chopping_board'}, {'frequency': 'c', 'id': 262, 'synset': 'chopstick.n.01', 'synonyms': ['chopstick'], 'def': 'one of a pair of slender sticks used as oriental tableware to eat food with', 'name': 'chopstick'}, {'frequency': 'f', 'id': 263, 'synset': 'christmas_tree.n.05', 'synonyms': ['Christmas_tree'], 'def': 'an ornamented evergreen used as a Christmas decoration', 'name': 'Christmas_tree'}, {'frequency': 'c', 'id': 264, 'synset': 'chute.n.02', 'synonyms': ['slide'], 'def': 'sloping channel through which things can descend', 'name': 'slide'}, {'frequency': 'r', 'id': 265, 'synset': 'cider.n.01', 'synonyms': ['cider', 'cyder'], 'def': 'a beverage made from juice pressed from apples', 'name': 'cider'}, {'frequency': 'r', 'id': 266, 'synset': 'cigar_box.n.01', 'synonyms': ['cigar_box'], 'def': 'a box for holding cigars', 'name': 'cigar_box'}, {'frequency': 'c', 'id': 267, 'synset': 'cigarette.n.01', 'synonyms': ['cigarette'], 'def': 'finely ground tobacco wrapped in paper; for smoking', 'name': 'cigarette'}, {'frequency': 'c', 'id': 268, 'synset': 'cigarette_case.n.01', 'synonyms': ['cigarette_case', 'cigarette_pack'], 'def': 'a small flat case for holding cigarettes', 'name': 'cigarette_case'}, {'frequency': 'f', 'id': 269, 'synset': 'cistern.n.02', 'synonyms': ['cistern', 'water_tank'], 'def': 'a tank that holds the water used to flush a toilet', 'name': 'cistern'}, {'frequency': 'r', 'id': 270, 'synset': 'clarinet.n.01', 'synonyms': ['clarinet'], 'def': 'a single-reed instrument with a straight tube', 'name': 'clarinet'}, {'frequency': 'r', 'id': 271, 'synset': 'clasp.n.01', 'synonyms': ['clasp'], 'def': 'a fastener (as a buckle or hook) that is used to hold two things together', 'name': 'clasp'}, {'frequency': 'c', 'id': 272, 'synset': 'cleansing_agent.n.01', 'synonyms': ['cleansing_agent', 'cleanser', 'cleaner'], 'def': 'a preparation used in cleaning something', 'name': 'cleansing_agent'}, {'frequency': 'r', 'id': 273, 'synset': 'clementine.n.01', 'synonyms': ['clementine'], 'def': 'a variety of mandarin orange', 'name': 'clementine'}, {'frequency': 'c', 'id': 274, 'synset': 'clip.n.03', 'synonyms': ['clip'], 'def': 'any of various small fasteners used to hold loose articles together', 'name': 'clip'}, {'frequency': 'c', 'id': 275, 'synset': 'clipboard.n.01', 'synonyms': ['clipboard'], 'def': 'a small writing board with a clip at the top for holding papers', 'name': 'clipboard'}, {'frequency': 'f', 'id': 276, 'synset': 'clock.n.01', 'synonyms': ['clock', 'timepiece', 'timekeeper'], 'def': 'a timepiece that shows the time of day', 'name': 'clock'}, {'frequency': 'f', 'id': 277, 'synset': 'clock_tower.n.01', 'synonyms': ['clock_tower'], 'def': 'a tower with a large clock visible high up on an outside face', 'name': 'clock_tower'}, {'frequency': 'c', 'id': 278, 'synset': 'clothes_hamper.n.01', 'synonyms': ['clothes_hamper', 'laundry_basket', 'clothes_basket'], 'def': 'a hamper that holds dirty clothes to be washed or wet clothes to be dried', 'name': 'clothes_hamper'}, {'frequency': 'c', 'id': 279, 'synset': 'clothespin.n.01', 'synonyms': ['clothespin', 'clothes_peg'], 'def': 'wood or plastic fastener; for holding clothes on a clothesline', 'name': 'clothespin'}, {'frequency': 'r', 'id': 280, 'synset': 'clutch_bag.n.01', 'synonyms': ['clutch_bag'], 'def': "a woman's strapless purse that is carried in the hand", 'name': 'clutch_bag'}, {'frequency': 'f', 'id': 281, 'synset': 'coaster.n.03', 'synonyms': ['coaster'], 'def': 'a covering (plate or mat) that protects the surface of a table', 'name': 'coaster'}, {'frequency': 'f', 'id': 282, 'synset': 'coat.n.01', 'synonyms': ['coat'], 'def': 'an outer garment that has sleeves and covers the body from shoulder down', 'name': 'coat'}, {'frequency': 'c', 'id': 283, 'synset': 'coat_hanger.n.01', 'synonyms': ['coat_hanger', 'clothes_hanger', 'dress_hanger'], 'def': "a hanger that is shaped like a person's shoulders", 'name': 'coat_hanger'}, {'frequency': 'r', 'id': 284, 'synset': 'coatrack.n.01', 'synonyms': ['coatrack', 'hatrack'], 'def': 'a rack with hooks for temporarily holding coats and hats', 'name': 'coatrack'}, {'frequency': 'c', 'id': 285, 'synset': 'cock.n.04', 'synonyms': ['cock', 'rooster'], 'def': 'adult male chicken', 'name': 'cock'}, {'frequency': 'c', 'id': 286, 'synset': 'coconut.n.02', 'synonyms': ['coconut', 'cocoanut'], 'def': 'large hard-shelled brown oval nut with a fibrous husk', 'name': 'coconut'}, {'frequency': 'r', 'id': 287, 'synset': 'coffee_filter.n.01', 'synonyms': ['coffee_filter'], 'def': 'filter (usually of paper) that passes the coffee and retains the coffee grounds', 'name': 'coffee_filter'}, {'frequency': 'f', 'id': 288, 'synset': 'coffee_maker.n.01', 'synonyms': ['coffee_maker', 'coffee_machine'], 'def': 'a kitchen appliance for brewing coffee automatically', 'name': 'coffee_maker'}, {'frequency': 'f', 'id': 289, 'synset': 'coffee_table.n.01', 'synonyms': ['coffee_table', 'cocktail_table'], 'def': 'low table where magazines can be placed and coffee or cocktails are served', 'name': 'coffee_table'}, {'frequency': 'c', 'id': 290, 'synset': 'coffeepot.n.01', 'synonyms': ['coffeepot'], 'def': 'tall pot in which coffee is brewed', 'name': 'coffeepot'}, {'frequency': 'r', 'id': 291, 'synset': 'coil.n.05', 'synonyms': ['coil'], 'def': 'tubing that is wound in a spiral', 'name': 'coil'}, {'frequency': 'c', 'id': 292, 'synset': 'coin.n.01', 'synonyms': ['coin'], 'def': 'a flat metal piece (usually a disc) used as money', 'name': 'coin'}, {'frequency': 'r', 'id': 293, 'synset': 'colander.n.01', 'synonyms': ['colander', 'cullender'], 'def': 'bowl-shaped strainer; used to wash or drain foods', 'name': 'colander'}, {'frequency': 'c', 'id': 294, 'synset': 'coleslaw.n.01', 'synonyms': ['coleslaw', 'slaw'], 'def': 'basically shredded cabbage', 'name': 'coleslaw'}, {'frequency': 'r', 'id': 295, 'synset': 'coloring_material.n.01', 'synonyms': ['coloring_material', 'colouring_material'], 'def': 'any material used for its color', 'name': 'coloring_material'}, {'frequency': 'r', 'id': 296, 'synset': 'combination_lock.n.01', 'synonyms': ['combination_lock'], 'def': 'lock that can be opened only by turning dials in a special sequence', 'name': 'combination_lock'}, {'frequency': 'c', 'id': 297, 'synset': 'comforter.n.04', 'synonyms': ['pacifier', 'teething_ring'], 'def': 'device used for an infant to suck or bite on', 'name': 'pacifier'}, {'frequency': 'r', 'id': 298, 'synset': 'comic_book.n.01', 'synonyms': ['comic_book'], 'def': 'a magazine devoted to comic strips', 'name': 'comic_book'}, {'frequency': 'f', 'id': 299, 'synset': 'computer_keyboard.n.01', 'synonyms': ['computer_keyboard', 'keyboard_(computer)'], 'def': 'a keyboard that is a data input device for computers', 'name': 'computer_keyboard'}, {'frequency': 'r', 'id': 300, 'synset': 'concrete_mixer.n.01', 'synonyms': ['concrete_mixer', 'cement_mixer'], 'def': 'a machine with a large revolving drum in which cement/concrete is mixed', 'name': 'concrete_mixer'}, {'frequency': 'f', 'id': 301, 'synset': 'cone.n.01', 'synonyms': ['cone', 'traffic_cone'], 'def': 'a cone-shaped object used to direct traffic', 'name': 'cone'}, {'frequency': 'f', 'id': 302, 'synset': 'control.n.09', 'synonyms': ['control', 'controller'], 'def': 'a mechanism that controls the operation of a machine', 'name': 'control'}, {'frequency': 'r', 'id': 303, 'synset': 'convertible.n.01', 'synonyms': ['convertible_(automobile)'], 'def': 'a car that has top that can be folded or removed', 'name': 'convertible_(automobile)'}, {'frequency': 'r', 'id': 304, 'synset': 'convertible.n.03', 'synonyms': ['sofa_bed'], 'def': 'a sofa that can be converted into a bed', 'name': 'sofa_bed'}, {'frequency': 'c', 'id': 305, 'synset': 'cookie.n.01', 'synonyms': ['cookie', 'cooky', 'biscuit_(cookie)'], 'def': "any of various small flat sweet cakes (`biscuit' is the British term)", 'name': 'cookie'}, {'frequency': 'r', 'id': 306, 'synset': 'cookie_jar.n.01', 'synonyms': ['cookie_jar', 'cooky_jar'], 'def': 'a jar in which cookies are kept (and sometimes money is hidden)', 'name': 'cookie_jar'}, {'frequency': 'r', 'id': 307, 'synset': 'cooking_utensil.n.01', 'synonyms': ['cooking_utensil'], 'def': 'a kitchen utensil made of material that does not melt easily; used for cooking', 'name': 'cooking_utensil'}, {'frequency': 'f', 'id': 308, 'synset': 'cooler.n.01', 'synonyms': ['cooler_(for_food)', 'ice_chest'], 'def': 'an insulated box for storing food often with ice', 'name': 'cooler_(for_food)'}, {'frequency': 'c', 'id': 309, 'synset': 'cork.n.04', 'synonyms': ['cork_(bottle_plug)', 'bottle_cork'], 'def': 'the plug in the mouth of a bottle (especially a wine bottle)', 'name': 'cork_(bottle_plug)'}, {'frequency': 'r', 'id': 310, 'synset': 'corkboard.n.01', 'synonyms': ['corkboard'], 'def': 'a sheet consisting of cork granules', 'name': 'corkboard'}, {'frequency': 'r', 'id': 311, 'synset': 'corkscrew.n.01', 'synonyms': ['corkscrew', 'bottle_screw'], 'def': 'a bottle opener that pulls corks', 'name': 'corkscrew'}, {'frequency': 'c', 'id': 312, 'synset': 'corn.n.03', 'synonyms': ['edible_corn', 'corn', 'maize'], 'def': 'ears of corn that can be prepared and served for human food', 'name': 'edible_corn'}, {'frequency': 'r', 'id': 313, 'synset': 'cornbread.n.01', 'synonyms': ['cornbread'], 'def': 'bread made primarily of cornmeal', 'name': 'cornbread'}, {'frequency': 'c', 'id': 314, 'synset': 'cornet.n.01', 'synonyms': ['cornet', 'horn', 'trumpet'], 'def': 'a brass musical instrument with a narrow tube and a flared bell and many valves', 'name': 'cornet'}, {'frequency': 'c', 'id': 315, 'synset': 'cornice.n.01', 'synonyms': ['cornice', 'valance', 'valance_board', 'pelmet'], 'def': 'a decorative framework to conceal curtain fixtures at the top of a window casing', 'name': 'cornice'}, {'frequency': 'r', 'id': 316, 'synset': 'cornmeal.n.01', 'synonyms': ['cornmeal'], 'def': 'coarsely ground corn', 'name': 'cornmeal'}, {'frequency': 'r', 'id': 317, 'synset': 'corset.n.01', 'synonyms': ['corset', 'girdle'], 'def': "a woman's close-fitting foundation garment", 'name': 'corset'}, {'frequency': 'r', 'id': 318, 'synset': 'cos.n.02', 'synonyms': ['romaine_lettuce'], 'def': 'lettuce with long dark-green leaves in a loosely packed elongated head', 'name': 'romaine_lettuce'}, {'frequency': 'c', 'id': 319, 'synset': 'costume.n.04', 'synonyms': ['costume'], 'def': 'the attire characteristic of a country or a time or a social class', 'name': 'costume'}, {'frequency': 'r', 'id': 320, 'synset': 'cougar.n.01', 'synonyms': ['cougar', 'puma', 'catamount', 'mountain_lion', 'panther'], 'def': 'large American feline resembling a lion', 'name': 'cougar'}, {'frequency': 'r', 'id': 321, 'synset': 'coverall.n.01', 'synonyms': ['coverall'], 'def': 'a loose-fitting protective garment that is worn over other clothing', 'name': 'coverall'}, {'frequency': 'r', 'id': 322, 'synset': 'cowbell.n.01', 'synonyms': ['cowbell'], 'def': 'a bell hung around the neck of cow so that the cow can be easily located', 'name': 'cowbell'}, {'frequency': 'f', 'id': 323, 'synset': 'cowboy_hat.n.01', 'synonyms': ['cowboy_hat', 'ten-gallon_hat'], 'def': 'a hat with a wide brim and a soft crown; worn by American ranch hands', 'name': 'cowboy_hat'}, {'frequency': 'r', 'id': 324, 'synset': 'crab.n.01', 'synonyms': ['crab_(animal)'], 'def': 'decapod having eyes on short stalks and a broad flattened shell and pincers', 'name': 'crab_(animal)'}, {'frequency': 'c', 'id': 325, 'synset': 'cracker.n.01', 'synonyms': ['cracker'], 'def': 'a thin crisp wafer', 'name': 'cracker'}, {'frequency': 'r', 'id': 326, 'synset': 'crape.n.01', 'synonyms': ['crape', 'crepe', 'French_pancake'], 'def': 'small very thin pancake', 'name': 'crape'}, {'frequency': 'f', 'id': 327, 'synset': 'crate.n.01', 'synonyms': ['crate'], 'def': 'a rugged box (usually made of wood); used for shipping', 'name': 'crate'}, {'frequency': 'r', 'id': 328, 'synset': 'crayon.n.01', 'synonyms': ['crayon', 'wax_crayon'], 'def': 'writing or drawing implement made of a colored stick of composition wax', 'name': 'crayon'}, {'frequency': 'r', 'id': 329, 'synset': 'cream_pitcher.n.01', 'synonyms': ['cream_pitcher'], 'def': 'a small pitcher for serving cream', 'name': 'cream_pitcher'}, {'frequency': 'r', 'id': 330, 'synset': 'credit_card.n.01', 'synonyms': ['credit_card', 'charge_card', 'debit_card'], 'def': 'a card, usually plastic, used to pay for goods and services', 'name': 'credit_card'}, {'frequency': 'c', 'id': 331, 'synset': 'crescent_roll.n.01', 'synonyms': ['crescent_roll', 'croissant'], 'def': 'very rich flaky crescent-shaped roll', 'name': 'crescent_roll'}, {'frequency': 'c', 'id': 332, 'synset': 'crib.n.01', 'synonyms': ['crib', 'cot'], 'def': 'baby bed with high sides made of slats', 'name': 'crib'}, {'frequency': 'c', 'id': 333, 'synset': 'crock.n.03', 'synonyms': ['crock_pot', 'earthenware_jar'], 'def': 'an earthen jar (made of baked clay)', 'name': 'crock_pot'}, {'frequency': 'f', 'id': 334, 'synset': 'crossbar.n.01', 'synonyms': ['crossbar'], 'def': 'a horizontal bar that goes across something', 'name': 'crossbar'}, {'frequency': 'r', 'id': 335, 'synset': 'crouton.n.01', 'synonyms': ['crouton'], 'def': 'a small piece of toasted or fried bread; served in soup or salads', 'name': 'crouton'}, {'frequency': 'r', 'id': 336, 'synset': 'crow.n.01', 'synonyms': ['crow'], 'def': 'black birds having a raucous call', 'name': 'crow'}, {'frequency': 'c', 'id': 337, 'synset': 'crown.n.04', 'synonyms': ['crown'], 'def': 'an ornamental jeweled headdress signifying sovereignty', 'name': 'crown'}, {'frequency': 'c', 'id': 338, 'synset': 'crucifix.n.01', 'synonyms': ['crucifix'], 'def': 'representation of the cross on which Jesus died', 'name': 'crucifix'}, {'frequency': 'c', 'id': 339, 'synset': 'cruise_ship.n.01', 'synonyms': ['cruise_ship', 'cruise_liner'], 'def': 'a passenger ship used commercially for pleasure cruises', 'name': 'cruise_ship'}, {'frequency': 'c', 'id': 340, 'synset': 'cruiser.n.01', 'synonyms': ['police_cruiser', 'patrol_car', 'police_car', 'squad_car'], 'def': 'a car in which policemen cruise the streets', 'name': 'police_cruiser'}, {'frequency': 'c', 'id': 341, 'synset': 'crumb.n.03', 'synonyms': ['crumb'], 'def': 'small piece of e.g. bread or cake', 'name': 'crumb'}, {'frequency': 'r', 'id': 342, 'synset': 'crutch.n.01', 'synonyms': ['crutch'], 'def': 'a wooden or metal staff that fits under the armpit and reaches to the ground', 'name': 'crutch'}, {'frequency': 'c', 'id': 343, 'synset': 'cub.n.03', 'synonyms': ['cub_(animal)'], 'def': 'the young of certain carnivorous mammals such as the bear or wolf or lion', 'name': 'cub_(animal)'}, {'frequency': 'r', 'id': 344, 'synset': 'cube.n.05', 'synonyms': ['cube', 'square_block'], 'def': 'a block in the (approximate) shape of a cube', 'name': 'cube'}, {'frequency': 'f', 'id': 345, 'synset': 'cucumber.n.02', 'synonyms': ['cucumber', 'cuke'], 'def': 'cylindrical green fruit with thin green rind and white flesh eaten as a vegetable', 'name': 'cucumber'}, {'frequency': 'c', 'id': 346, 'synset': 'cufflink.n.01', 'synonyms': ['cufflink'], 'def': 'jewelry consisting of linked buttons used to fasten the cuffs of a shirt', 'name': 'cufflink'}, {'frequency': 'f', 'id': 347, 'synset': 'cup.n.01', 'synonyms': ['cup'], 'def': 'a small open container usually used for drinking; usually has a handle', 'name': 'cup'}, {'frequency': 'c', 'id': 348, 'synset': 'cup.n.08', 'synonyms': ['trophy_cup'], 'def': 'a metal vessel with handles that is awarded as a trophy to a competition winner', 'name': 'trophy_cup'}, {'frequency': 'c', 'id': 349, 'synset': 'cupcake.n.01', 'synonyms': ['cupcake'], 'def': 'small cake baked in a muffin tin', 'name': 'cupcake'}, {'frequency': 'r', 'id': 350, 'synset': 'curler.n.01', 'synonyms': ['hair_curler', 'hair_roller', 'hair_crimper'], 'def': 'a cylindrical tube around which the hair is wound to curl it', 'name': 'hair_curler'}, {'frequency': 'r', 'id': 351, 'synset': 'curling_iron.n.01', 'synonyms': ['curling_iron'], 'def': 'a cylindrical home appliance that heats hair that has been curled around it', 'name': 'curling_iron'}, {'frequency': 'f', 'id': 352, 'synset': 'curtain.n.01', 'synonyms': ['curtain', 'drapery'], 'def': 'hanging cloth used as a blind (especially for a window)', 'name': 'curtain'}, {'frequency': 'f', 'id': 353, 'synset': 'cushion.n.03', 'synonyms': ['cushion'], 'def': 'a soft bag filled with air or padding such as feathers or foam rubber', 'name': 'cushion'}, {'frequency': 'r', 'id': 354, 'synset': 'custard.n.01', 'synonyms': ['custard'], 'def': 'sweetened mixture of milk and eggs baked or boiled or frozen', 'name': 'custard'}, {'frequency': 'c', 'id': 355, 'synset': 'cutter.n.06', 'synonyms': ['cutting_tool'], 'def': 'a cutting implement; a tool for cutting', 'name': 'cutting_tool'}, {'frequency': 'r', 'id': 356, 'synset': 'cylinder.n.04', 'synonyms': ['cylinder'], 'def': 'a cylindrical container', 'name': 'cylinder'}, {'frequency': 'r', 'id': 357, 'synset': 'cymbal.n.01', 'synonyms': ['cymbal'], 'def': 'a percussion instrument consisting of a concave brass disk', 'name': 'cymbal'}, {'frequency': 'r', 'id': 358, 'synset': 'dachshund.n.01', 'synonyms': ['dachshund', 'dachsie', 'badger_dog'], 'def': 'small long-bodied short-legged breed of dog having a short sleek coat and long drooping ears', 'name': 'dachshund'}, {'frequency': 'r', 'id': 359, 'synset': 'dagger.n.01', 'synonyms': ['dagger'], 'def': 'a short knife with a pointed blade used for piercing or stabbing', 'name': 'dagger'}, {'frequency': 'r', 'id': 360, 'synset': 'dartboard.n.01', 'synonyms': ['dartboard'], 'def': 'a circular board of wood or cork used as the target in the game of darts', 'name': 'dartboard'}, {'frequency': 'r', 'id': 361, 'synset': 'date.n.08', 'synonyms': ['date_(fruit)'], 'def': 'sweet edible fruit of the date palm with a single long woody seed', 'name': 'date_(fruit)'}, {'frequency': 'f', 'id': 362, 'synset': 'deck_chair.n.01', 'synonyms': ['deck_chair', 'beach_chair'], 'def': 'a folding chair for use outdoors; a wooden frame supports a length of canvas', 'name': 'deck_chair'}, {'frequency': 'c', 'id': 363, 'synset': 'deer.n.01', 'synonyms': ['deer', 'cervid'], 'def': "distinguished from Bovidae by the male's having solid deciduous antlers", 'name': 'deer'}, {'frequency': 'c', 'id': 364, 'synset': 'dental_floss.n.01', 'synonyms': ['dental_floss', 'floss'], 'def': 'a soft thread for cleaning the spaces between the teeth', 'name': 'dental_floss'}, {'frequency': 'f', 'id': 365, 'synset': 'desk.n.01', 'synonyms': ['desk'], 'def': 'a piece of furniture with a writing surface and usually drawers or other compartments', 'name': 'desk'}, {'frequency': 'r', 'id': 366, 'synset': 'detergent.n.01', 'synonyms': ['detergent'], 'def': 'a surface-active chemical widely used in industry and laundering', 'name': 'detergent'}, {'frequency': 'c', 'id': 367, 'synset': 'diaper.n.01', 'synonyms': ['diaper'], 'def': 'garment consisting of a folded cloth drawn up between the legs and fastened at the waist', 'name': 'diaper'}, {'frequency': 'r', 'id': 368, 'synset': 'diary.n.01', 'synonyms': ['diary', 'journal'], 'def': 'a daily written record of (usually personal) experiences and observations', 'name': 'diary'}, {'frequency': 'r', 'id': 369, 'synset': 'die.n.01', 'synonyms': ['die', 'dice'], 'def': 'a small cube with 1 to 6 spots on the six faces; used in gambling', 'name': 'die'}, {'frequency': 'r', 'id': 370, 'synset': 'dinghy.n.01', 'synonyms': ['dinghy', 'dory', 'rowboat'], 'def': 'a small boat of shallow draft with seats and oars with which it is propelled', 'name': 'dinghy'}, {'frequency': 'f', 'id': 371, 'synset': 'dining_table.n.01', 'synonyms': ['dining_table'], 'def': 'a table at which meals are served', 'name': 'dining_table'}, {'frequency': 'r', 'id': 372, 'synset': 'dinner_jacket.n.01', 'synonyms': ['tux', 'tuxedo'], 'def': 'semiformal evening dress for men', 'name': 'tux'}, {'frequency': 'c', 'id': 373, 'synset': 'dish.n.01', 'synonyms': ['dish'], 'def': 'a piece of dishware normally used as a container for holding or serving food', 'name': 'dish'}, {'frequency': 'c', 'id': 374, 'synset': 'dish.n.05', 'synonyms': ['dish_antenna'], 'def': 'directional antenna consisting of a parabolic reflector', 'name': 'dish_antenna'}, {'frequency': 'c', 'id': 375, 'synset': 'dishrag.n.01', 'synonyms': ['dishrag', 'dishcloth'], 'def': 'a cloth for washing dishes', 'name': 'dishrag'}, {'frequency': 'c', 'id': 376, 'synset': 'dishtowel.n.01', 'synonyms': ['dishtowel', 'tea_towel'], 'def': 'a towel for drying dishes', 'name': 'dishtowel'}, {'frequency': 'f', 'id': 377, 'synset': 'dishwasher.n.01', 'synonyms': ['dishwasher', 'dishwashing_machine'], 'def': 'a machine for washing dishes', 'name': 'dishwasher'}, {'frequency': 'r', 'id': 378, 'synset': 'dishwasher_detergent.n.01', 'synonyms': ['dishwasher_detergent', 'dishwashing_detergent', 'dishwashing_liquid'], 'def': 'a low-sudsing detergent designed for use in dishwashers', 'name': 'dishwasher_detergent'}, {'frequency': 'r', 'id': 379, 'synset': 'diskette.n.01', 'synonyms': ['diskette', 'floppy', 'floppy_disk'], 'def': 'a small plastic magnetic disk enclosed in a stiff envelope used to store data', 'name': 'diskette'}, {'frequency': 'c', 'id': 380, 'synset': 'dispenser.n.01', 'synonyms': ['dispenser'], 'def': 'a container so designed that the contents can be used in prescribed amounts', 'name': 'dispenser'}, {'frequency': 'c', 'id': 381, 'synset': 'dixie_cup.n.01', 'synonyms': ['Dixie_cup', 'paper_cup'], 'def': 'a disposable cup made of paper; for holding drinks', 'name': 'Dixie_cup'}, {'frequency': 'f', 'id': 382, 'synset': 'dog.n.01', 'synonyms': ['dog'], 'def': 'a common domesticated dog', 'name': 'dog'}, {'frequency': 'f', 'id': 383, 'synset': 'dog_collar.n.01', 'synonyms': ['dog_collar'], 'def': 'a collar for a dog', 'name': 'dog_collar'}, {'frequency': 'c', 'id': 384, 'synset': 'doll.n.01', 'synonyms': ['doll'], 'def': 'a toy replica of a HUMAN (NOT AN ANIMAL)', 'name': 'doll'}, {'frequency': 'r', 'id': 385, 'synset': 'dollar.n.02', 'synonyms': ['dollar', 'dollar_bill', 'one_dollar_bill'], 'def': 'a piece of paper money worth one dollar', 'name': 'dollar'}, {'frequency': 'r', 'id': 386, 'synset': 'dolphin.n.02', 'synonyms': ['dolphin'], 'def': 'any of various small toothed whales with a beaklike snout; larger than porpoises', 'name': 'dolphin'}, {'frequency': 'c', 'id': 387, 'synset': 'domestic_ass.n.01', 'synonyms': ['domestic_ass', 'donkey'], 'def': 'domestic beast of burden descended from the African wild ass; patient but stubborn', 'name': 'domestic_ass'}, {'frequency': 'r', 'id': 388, 'synset': 'domino.n.03', 'synonyms': ['eye_mask'], 'def': 'a mask covering the upper part of the face but with holes for the eyes', 'name': 'eye_mask'}, {'frequency': 'r', 'id': 389, 'synset': 'doorbell.n.01', 'synonyms': ['doorbell', 'buzzer'], 'def': 'a button at an outer door that gives a ringing or buzzing signal when pushed', 'name': 'doorbell'}, {'frequency': 'f', 'id': 390, 'synset': 'doorknob.n.01', 'synonyms': ['doorknob', 'doorhandle'], 'def': "a knob used to open a door (often called `doorhandle' in Great Britain)", 'name': 'doorknob'}, {'frequency': 'c', 'id': 391, 'synset': 'doormat.n.02', 'synonyms': ['doormat', 'welcome_mat'], 'def': 'a mat placed outside an exterior door for wiping the shoes before entering', 'name': 'doormat'}, {'frequency': 'f', 'id': 392, 'synset': 'doughnut.n.02', 'synonyms': ['doughnut', 'donut'], 'def': 'a small ring-shaped friedcake', 'name': 'doughnut'}, {'frequency': 'r', 'id': 393, 'synset': 'dove.n.01', 'synonyms': ['dove'], 'def': 'any of numerous small pigeons', 'name': 'dove'}, {'frequency': 'r', 'id': 394, 'synset': 'dragonfly.n.01', 'synonyms': ['dragonfly'], 'def': 'slender-bodied non-stinging insect having iridescent wings that are outspread at rest', 'name': 'dragonfly'}, {'frequency': 'f', 'id': 395, 'synset': 'drawer.n.01', 'synonyms': ['drawer'], 'def': 'a boxlike container in a piece of furniture; made so as to slide in and out', 'name': 'drawer'}, {'frequency': 'c', 'id': 396, 'synset': 'drawers.n.01', 'synonyms': ['underdrawers', 'boxers', 'boxershorts'], 'def': 'underpants worn by men', 'name': 'underdrawers'}, {'frequency': 'f', 'id': 397, 'synset': 'dress.n.01', 'synonyms': ['dress', 'frock'], 'def': 'a one-piece garment for a woman; has skirt and bodice', 'name': 'dress'}, {'frequency': 'c', 'id': 398, 'synset': 'dress_hat.n.01', 'synonyms': ['dress_hat', 'high_hat', 'opera_hat', 'silk_hat', 'top_hat'], 'def': "a man's hat with a tall crown; usually covered with silk or with beaver fur", 'name': 'dress_hat'}, {'frequency': 'c', 'id': 399, 'synset': 'dress_suit.n.01', 'synonyms': ['dress_suit'], 'def': 'formalwear consisting of full evening dress for men', 'name': 'dress_suit'}, {'frequency': 'c', 'id': 400, 'synset': 'dresser.n.05', 'synonyms': ['dresser'], 'def': 'a cabinet with shelves', 'name': 'dresser'}, {'frequency': 'c', 'id': 401, 'synset': 'drill.n.01', 'synonyms': ['drill'], 'def': 'a tool with a sharp rotating point for making holes in hard materials', 'name': 'drill'}, {'frequency': 'r', 'id': 402, 'synset': 'drinking_fountain.n.01', 'synonyms': ['drinking_fountain'], 'def': 'a public fountain to provide a jet of drinking water', 'name': 'drinking_fountain'}, {'frequency': 'r', 'id': 403, 'synset': 'drone.n.04', 'synonyms': ['drone'], 'def': 'an aircraft without a pilot that is operated by remote control', 'name': 'drone'}, {'frequency': 'r', 'id': 404, 'synset': 'dropper.n.01', 'synonyms': ['dropper', 'eye_dropper'], 'def': 'pipet consisting of a small tube with a vacuum bulb at one end for drawing liquid in and releasing it a drop at a time', 'name': 'dropper'}, {'frequency': 'c', 'id': 405, 'synset': 'drum.n.01', 'synonyms': ['drum_(musical_instrument)'], 'def': 'a musical percussion instrument; usually consists of a hollow cylinder with a membrane stretched across each end', 'name': 'drum_(musical_instrument)'}, {'frequency': 'r', 'id': 406, 'synset': 'drumstick.n.02', 'synonyms': ['drumstick'], 'def': 'a stick used for playing a drum', 'name': 'drumstick'}, {'frequency': 'f', 'id': 407, 'synset': 'duck.n.01', 'synonyms': ['duck'], 'def': 'small web-footed broad-billed swimming bird', 'name': 'duck'}, {'frequency': 'r', 'id': 408, 'synset': 'duckling.n.02', 'synonyms': ['duckling'], 'def': 'young duck', 'name': 'duckling'}, {'frequency': 'c', 'id': 409, 'synset': 'duct_tape.n.01', 'synonyms': ['duct_tape'], 'def': 'a wide silvery adhesive tape', 'name': 'duct_tape'}, {'frequency': 'f', 'id': 410, 'synset': 'duffel_bag.n.01', 'synonyms': ['duffel_bag', 'duffle_bag', 'duffel', 'duffle'], 'def': 'a large cylindrical bag of heavy cloth', 'name': 'duffel_bag'}, {'frequency': 'r', 'id': 411, 'synset': 'dumbbell.n.01', 'synonyms': ['dumbbell'], 'def': 'an exercising weight with two ball-like ends connected by a short handle', 'name': 'dumbbell'}, {'frequency': 'c', 'id': 412, 'synset': 'dumpster.n.01', 'synonyms': ['dumpster'], 'def': 'a container designed to receive and transport and dump waste', 'name': 'dumpster'}, {'frequency': 'r', 'id': 413, 'synset': 'dustpan.n.02', 'synonyms': ['dustpan'], 'def': 'a short-handled receptacle into which dust can be swept', 'name': 'dustpan'}, {'frequency': 'r', 'id': 414, 'synset': 'dutch_oven.n.02', 'synonyms': ['Dutch_oven'], 'def': 'iron or earthenware cooking pot; used for stews', 'name': 'Dutch_oven'}, {'frequency': 'c', 'id': 415, 'synset': 'eagle.n.01', 'synonyms': ['eagle'], 'def': 'large birds of prey noted for their broad wings and strong soaring flight', 'name': 'eagle'}, {'frequency': 'f', 'id': 416, 'synset': 'earphone.n.01', 'synonyms': ['earphone', 'earpiece', 'headphone'], 'def': 'device for listening to audio that is held over or inserted into the ear', 'name': 'earphone'}, {'frequency': 'r', 'id': 417, 'synset': 'earplug.n.01', 'synonyms': ['earplug'], 'def': 'a soft plug that is inserted into the ear canal to block sound', 'name': 'earplug'}, {'frequency': 'f', 'id': 418, 'synset': 'earring.n.01', 'synonyms': ['earring'], 'def': 'jewelry to ornament the ear', 'name': 'earring'}, {'frequency': 'c', 'id': 419, 'synset': 'easel.n.01', 'synonyms': ['easel'], 'def': "an upright tripod for displaying something (usually an artist's canvas)", 'name': 'easel'}, {'frequency': 'r', 'id': 420, 'synset': 'eclair.n.01', 'synonyms': ['eclair'], 'def': 'oblong cream puff', 'name': 'eclair'}, {'frequency': 'r', 'id': 421, 'synset': 'eel.n.01', 'synonyms': ['eel'], 'def': 'an elongate fish with fatty flesh', 'name': 'eel'}, {'frequency': 'f', 'id': 422, 'synset': 'egg.n.02', 'synonyms': ['egg', 'eggs'], 'def': 'oval reproductive body of a fowl (especially a hen) used as food', 'name': 'egg'}, {'frequency': 'r', 'id': 423, 'synset': 'egg_roll.n.01', 'synonyms': ['egg_roll', 'spring_roll'], 'def': 'minced vegetables and meat wrapped in a pancake and fried', 'name': 'egg_roll'}, {'frequency': 'c', 'id': 424, 'synset': 'egg_yolk.n.01', 'synonyms': ['egg_yolk', 'yolk_(egg)'], 'def': 'the yellow spherical part of an egg', 'name': 'egg_yolk'}, {'frequency': 'c', 'id': 425, 'synset': 'eggbeater.n.02', 'synonyms': ['eggbeater', 'eggwhisk'], 'def': 'a mixer for beating eggs or whipping cream', 'name': 'eggbeater'}, {'frequency': 'c', 'id': 426, 'synset': 'eggplant.n.01', 'synonyms': ['eggplant', 'aubergine'], 'def': 'egg-shaped vegetable having a shiny skin typically dark purple', 'name': 'eggplant'}, {'frequency': 'r', 'id': 427, 'synset': 'electric_chair.n.01', 'synonyms': ['electric_chair'], 'def': 'a chair-shaped instrument of execution by electrocution', 'name': 'electric_chair'}, {'frequency': 'f', 'id': 428, 'synset': 'electric_refrigerator.n.01', 'synonyms': ['refrigerator'], 'def': 'a refrigerator in which the coolant is pumped around by an electric motor', 'name': 'refrigerator'}, {'frequency': 'f', 'id': 429, 'synset': 'elephant.n.01', 'synonyms': ['elephant'], 'def': 'a common elephant', 'name': 'elephant'}, {'frequency': 'r', 'id': 430, 'synset': 'elk.n.01', 'synonyms': ['elk', 'moose'], 'def': 'large northern deer with enormous flattened antlers in the male', 'name': 'elk'}, {'frequency': 'c', 'id': 431, 'synset': 'envelope.n.01', 'synonyms': ['envelope'], 'def': 'a flat (usually rectangular) container for a letter, thin package, etc.', 'name': 'envelope'}, {'frequency': 'c', 'id': 432, 'synset': 'eraser.n.01', 'synonyms': ['eraser'], 'def': 'an implement used to erase something', 'name': 'eraser'}, {'frequency': 'r', 'id': 433, 'synset': 'escargot.n.01', 'synonyms': ['escargot'], 'def': 'edible snail usually served in the shell with a sauce of melted butter and garlic', 'name': 'escargot'}, {'frequency': 'r', 'id': 434, 'synset': 'eyepatch.n.01', 'synonyms': ['eyepatch'], 'def': 'a protective cloth covering for an injured eye', 'name': 'eyepatch'}, {'frequency': 'r', 'id': 435, 'synset': 'falcon.n.01', 'synonyms': ['falcon'], 'def': 'birds of prey having long pointed powerful wings adapted for swift flight', 'name': 'falcon'}, {'frequency': 'f', 'id': 436, 'synset': 'fan.n.01', 'synonyms': ['fan'], 'def': 'a device for creating a current of air by movement of a surface or surfaces', 'name': 'fan'}, {'frequency': 'f', 'id': 437, 'synset': 'faucet.n.01', 'synonyms': ['faucet', 'spigot', 'tap'], 'def': 'a regulator for controlling the flow of a liquid from a reservoir', 'name': 'faucet'}, {'frequency': 'r', 'id': 438, 'synset': 'fedora.n.01', 'synonyms': ['fedora'], 'def': 'a hat made of felt with a creased crown', 'name': 'fedora'}, {'frequency': 'r', 'id': 439, 'synset': 'ferret.n.02', 'synonyms': ['ferret'], 'def': 'domesticated albino variety of the European polecat bred for hunting rats and rabbits', 'name': 'ferret'}, {'frequency': 'c', 'id': 440, 'synset': 'ferris_wheel.n.01', 'synonyms': ['Ferris_wheel'], 'def': 'a large wheel with suspended seats that remain upright as the wheel rotates', 'name': 'Ferris_wheel'}, {'frequency': 'r', 'id': 441, 'synset': 'ferry.n.01', 'synonyms': ['ferry', 'ferryboat'], 'def': 'a boat that transports people or vehicles across a body of water and operates on a regular schedule', 'name': 'ferry'}, {'frequency': 'r', 'id': 442, 'synset': 'fig.n.04', 'synonyms': ['fig_(fruit)'], 'def': 'fleshy sweet pear-shaped yellowish or purple fruit eaten fresh or preserved or dried', 'name': 'fig_(fruit)'}, {'frequency': 'c', 'id': 443, 'synset': 'fighter.n.02', 'synonyms': ['fighter_jet', 'fighter_aircraft', 'attack_aircraft'], 'def': 'a high-speed military or naval airplane designed to destroy enemy targets', 'name': 'fighter_jet'}, {'frequency': 'f', 'id': 444, 'synset': 'figurine.n.01', 'synonyms': ['figurine'], 'def': 'a small carved or molded figure', 'name': 'figurine'}, {'frequency': 'c', 'id': 445, 'synset': 'file.n.03', 'synonyms': ['file_cabinet', 'filing_cabinet'], 'def': 'office furniture consisting of a container for keeping papers in order', 'name': 'file_cabinet'}, {'frequency': 'r', 'id': 446, 'synset': 'file.n.04', 'synonyms': ['file_(tool)'], 'def': 'a steel hand tool with small sharp teeth on some or all of its surfaces; used for smoothing wood or metal', 'name': 'file_(tool)'}, {'frequency': 'f', 'id': 447, 'synset': 'fire_alarm.n.02', 'synonyms': ['fire_alarm', 'smoke_alarm'], 'def': 'an alarm that is tripped off by fire or smoke', 'name': 'fire_alarm'}, {'frequency': 'c', 'id': 448, 'synset': 'fire_engine.n.01', 'synonyms': ['fire_engine', 'fire_truck'], 'def': 'large trucks that carry firefighters and equipment to the site of a fire', 'name': 'fire_engine'}, {'frequency': 'c', 'id': 449, 'synset': 'fire_extinguisher.n.01', 'synonyms': ['fire_extinguisher', 'extinguisher'], 'def': 'a manually operated device for extinguishing small fires', 'name': 'fire_extinguisher'}, {'frequency': 'c', 'id': 450, 'synset': 'fire_hose.n.01', 'synonyms': ['fire_hose'], 'def': 'a large hose that carries water from a fire hydrant to the site of the fire', 'name': 'fire_hose'}, {'frequency': 'f', 'id': 451, 'synset': 'fireplace.n.01', 'synonyms': ['fireplace'], 'def': 'an open recess in a wall at the base of a chimney where a fire can be built', 'name': 'fireplace'}, {'frequency': 'f', 'id': 452, 'synset': 'fireplug.n.01', 'synonyms': ['fireplug', 'fire_hydrant', 'hydrant'], 'def': 'an upright hydrant for drawing water to use in fighting a fire', 'name': 'fireplug'}, {'frequency': 'c', 'id': 453, 'synset': 'fish.n.01', 'synonyms': ['fish'], 'def': 'any of various mostly cold-blooded aquatic vertebrates usually having scales and breathing through gills', 'name': 'fish'}, {'frequency': 'r', 'id': 454, 'synset': 'fish.n.02', 'synonyms': ['fish_(food)'], 'def': 'the flesh of fish used as food', 'name': 'fish_(food)'}, {'frequency': 'r', 'id': 455, 'synset': 'fishbowl.n.02', 'synonyms': ['fishbowl', 'goldfish_bowl'], 'def': 'a transparent bowl in which small fish are kept', 'name': 'fishbowl'}, {'frequency': 'r', 'id': 456, 'synset': 'fishing_boat.n.01', 'synonyms': ['fishing_boat', 'fishing_vessel'], 'def': 'a vessel for fishing', 'name': 'fishing_boat'}, {'frequency': 'c', 'id': 457, 'synset': 'fishing_rod.n.01', 'synonyms': ['fishing_rod', 'fishing_pole'], 'def': 'a rod that is used in fishing to extend the fishing line', 'name': 'fishing_rod'}, {'frequency': 'f', 'id': 458, 'synset': 'flag.n.01', 'synonyms': ['flag'], 'def': 'emblem usually consisting of a rectangular piece of cloth of distinctive design (do not include pole)', 'name': 'flag'}, {'frequency': 'f', 'id': 459, 'synset': 'flagpole.n.02', 'synonyms': ['flagpole', 'flagstaff'], 'def': 'a tall staff or pole on which a flag is raised', 'name': 'flagpole'}, {'frequency': 'c', 'id': 460, 'synset': 'flamingo.n.01', 'synonyms': ['flamingo'], 'def': 'large pink web-footed bird with down-bent bill', 'name': 'flamingo'}, {'frequency': 'c', 'id': 461, 'synset': 'flannel.n.01', 'synonyms': ['flannel'], 'def': 'a soft light woolen fabric; used for clothing', 'name': 'flannel'}, {'frequency': 'r', 'id': 462, 'synset': 'flash.n.10', 'synonyms': ['flash', 'flashbulb'], 'def': 'a lamp for providing momentary light to take a photograph', 'name': 'flash'}, {'frequency': 'c', 'id': 463, 'synset': 'flashlight.n.01', 'synonyms': ['flashlight', 'torch'], 'def': 'a small portable battery-powered electric lamp', 'name': 'flashlight'}, {'frequency': 'r', 'id': 464, 'synset': 'fleece.n.03', 'synonyms': ['fleece'], 'def': 'a soft bulky fabric with deep pile; used chiefly for clothing', 'name': 'fleece'}, {'frequency': 'f', 'id': 465, 'synset': 'flip-flop.n.02', 'synonyms': ['flip-flop_(sandal)'], 'def': 'a backless sandal held to the foot by a thong between two toes', 'name': 'flip-flop_(sandal)'}, {'frequency': 'c', 'id': 466, 'synset': 'flipper.n.01', 'synonyms': ['flipper_(footwear)', 'fin_(footwear)'], 'def': 'a shoe to aid a person in swimming', 'name': 'flipper_(footwear)'}, {'frequency': 'f', 'id': 467, 'synset': 'flower_arrangement.n.01', 'synonyms': ['flower_arrangement', 'floral_arrangement'], 'def': 'a decorative arrangement of flowers', 'name': 'flower_arrangement'}, {'frequency': 'c', 'id': 468, 'synset': 'flute.n.02', 'synonyms': ['flute_glass', 'champagne_flute'], 'def': 'a tall narrow wineglass', 'name': 'flute_glass'}, {'frequency': 'r', 'id': 469, 'synset': 'foal.n.01', 'synonyms': ['foal'], 'def': 'a young horse', 'name': 'foal'}, {'frequency': 'c', 'id': 470, 'synset': 'folding_chair.n.01', 'synonyms': ['folding_chair'], 'def': 'a chair that can be folded flat for storage', 'name': 'folding_chair'}, {'frequency': 'c', 'id': 471, 'synset': 'food_processor.n.01', 'synonyms': ['food_processor'], 'def': 'a kitchen appliance for shredding, blending, chopping, or slicing food', 'name': 'food_processor'}, {'frequency': 'c', 'id': 472, 'synset': 'football.n.02', 'synonyms': ['football_(American)'], 'def': 'the inflated oblong ball used in playing American football', 'name': 'football_(American)'}, {'frequency': 'r', 'id': 473, 'synset': 'football_helmet.n.01', 'synonyms': ['football_helmet'], 'def': 'a padded helmet with a face mask to protect the head of football players', 'name': 'football_helmet'}, {'frequency': 'c', 'id': 474, 'synset': 'footstool.n.01', 'synonyms': ['footstool', 'footrest'], 'def': 'a low seat or a stool to rest the feet of a seated person', 'name': 'footstool'}, {'frequency': 'f', 'id': 475, 'synset': 'fork.n.01', 'synonyms': ['fork'], 'def': 'cutlery used for serving and eating food', 'name': 'fork'}, {'frequency': 'r', 'id': 476, 'synset': 'forklift.n.01', 'synonyms': ['forklift'], 'def': 'an industrial vehicle with a power operated fork in front that can be inserted under loads to lift and move them', 'name': 'forklift'}, {'frequency': 'r', 'id': 477, 'synset': 'freight_car.n.01', 'synonyms': ['freight_car'], 'def': 'a railway car that carries freight', 'name': 'freight_car'}, {'frequency': 'r', 'id': 478, 'synset': 'french_toast.n.01', 'synonyms': ['French_toast'], 'def': 'bread slice dipped in egg and milk and fried', 'name': 'French_toast'}, {'frequency': 'c', 'id': 479, 'synset': 'freshener.n.01', 'synonyms': ['freshener', 'air_freshener'], 'def': 'anything that freshens', 'name': 'freshener'}, {'frequency': 'f', 'id': 480, 'synset': 'frisbee.n.01', 'synonyms': ['frisbee'], 'def': 'a light, plastic disk propelled with a flip of the wrist for recreation or competition', 'name': 'frisbee'}, {'frequency': 'c', 'id': 481, 'synset': 'frog.n.01', 'synonyms': ['frog', 'toad', 'toad_frog'], 'def': 'a tailless stout-bodied amphibians with long hind limbs for leaping', 'name': 'frog'}, {'frequency': 'c', 'id': 482, 'synset': 'fruit_juice.n.01', 'synonyms': ['fruit_juice'], 'def': 'drink produced by squeezing or crushing fruit', 'name': 'fruit_juice'}, {'frequency': 'r', 'id': 483, 'synset': 'fruit_salad.n.01', 'synonyms': ['fruit_salad'], 'def': 'salad composed of fruits', 'name': 'fruit_salad'}, {'frequency': 'c', 'id': 484, 'synset': 'frying_pan.n.01', 'synonyms': ['frying_pan', 'frypan', 'skillet'], 'def': 'a pan used for frying foods', 'name': 'frying_pan'}, {'frequency': 'r', 'id': 485, 'synset': 'fudge.n.01', 'synonyms': ['fudge'], 'def': 'soft creamy candy', 'name': 'fudge'}, {'frequency': 'r', 'id': 486, 'synset': 'funnel.n.02', 'synonyms': ['funnel'], 'def': 'a cone-shaped utensil used to channel a substance into a container with a small mouth', 'name': 'funnel'}, {'frequency': 'c', 'id': 487, 'synset': 'futon.n.01', 'synonyms': ['futon'], 'def': 'a pad that is used for sleeping on the floor or on a raised frame', 'name': 'futon'}, {'frequency': 'r', 'id': 488, 'synset': 'gag.n.02', 'synonyms': ['gag', 'muzzle'], 'def': "restraint put into a person's mouth to prevent speaking or shouting", 'name': 'gag'}, {'frequency': 'r', 'id': 489, 'synset': 'garbage.n.03', 'synonyms': ['garbage'], 'def': 'a receptacle where waste can be discarded', 'name': 'garbage'}, {'frequency': 'c', 'id': 490, 'synset': 'garbage_truck.n.01', 'synonyms': ['garbage_truck'], 'def': 'a truck for collecting domestic refuse', 'name': 'garbage_truck'}, {'frequency': 'c', 'id': 491, 'synset': 'garden_hose.n.01', 'synonyms': ['garden_hose'], 'def': 'a hose used for watering a lawn or garden', 'name': 'garden_hose'}, {'frequency': 'c', 'id': 492, 'synset': 'gargle.n.01', 'synonyms': ['gargle', 'mouthwash'], 'def': 'a medicated solution used for gargling and rinsing the mouth', 'name': 'gargle'}, {'frequency': 'r', 'id': 493, 'synset': 'gargoyle.n.02', 'synonyms': ['gargoyle'], 'def': 'an ornament consisting of a grotesquely carved figure of a person or animal', 'name': 'gargoyle'}, {'frequency': 'c', 'id': 494, 'synset': 'garlic.n.02', 'synonyms': ['garlic', 'ail'], 'def': 'aromatic bulb used as seasoning', 'name': 'garlic'}, {'frequency': 'r', 'id': 495, 'synset': 'gasmask.n.01', 'synonyms': ['gasmask', 'respirator', 'gas_helmet'], 'def': 'a protective face mask with a filter', 'name': 'gasmask'}, {'frequency': 'r', 'id': 496, 'synset': 'gazelle.n.01', 'synonyms': ['gazelle'], 'def': 'small swift graceful antelope of Africa and Asia having lustrous eyes', 'name': 'gazelle'}, {'frequency': 'c', 'id': 497, 'synset': 'gelatin.n.02', 'synonyms': ['gelatin', 'jelly'], 'def': 'an edible jelly made with gelatin and used as a dessert or salad base or a coating for foods', 'name': 'gelatin'}, {'frequency': 'r', 'id': 498, 'synset': 'gem.n.02', 'synonyms': ['gemstone'], 'def': 'a crystalline rock that can be cut and polished for jewelry', 'name': 'gemstone'}, {'frequency': 'c', 'id': 499, 'synset': 'giant_panda.n.01', 'synonyms': ['giant_panda', 'panda', 'panda_bear'], 'def': 'large black-and-white herbivorous mammal of bamboo forests of China and Tibet', 'name': 'giant_panda'}, {'frequency': 'c', 'id': 500, 'synset': 'gift_wrap.n.01', 'synonyms': ['gift_wrap'], 'def': 'attractive wrapping paper suitable for wrapping gifts', 'name': 'gift_wrap'}, {'frequency': 'c', 'id': 501, 'synset': 'ginger.n.03', 'synonyms': ['ginger', 'gingerroot'], 'def': 'the root of the common ginger plant; used fresh as a seasoning', 'name': 'ginger'}, {'frequency': 'f', 'id': 502, 'synset': 'giraffe.n.01', 'synonyms': ['giraffe'], 'def': 'tall animal having a spotted coat and small horns and very long neck and legs', 'name': 'giraffe'}, {'frequency': 'c', 'id': 503, 'synset': 'girdle.n.02', 'synonyms': ['cincture', 'sash', 'waistband', 'waistcloth'], 'def': 'a band of material around the waist that strengthens a skirt or trousers', 'name': 'cincture'}, {'frequency': 'f', 'id': 504, 'synset': 'glass.n.02', 'synonyms': ['glass_(drink_container)', 'drinking_glass'], 'def': 'a container for holding liquids while drinking', 'name': 'glass_(drink_container)'}, {'frequency': 'c', 'id': 505, 'synset': 'globe.n.03', 'synonyms': ['globe'], 'def': 'a sphere on which a map (especially of the earth) is represented', 'name': 'globe'}, {'frequency': 'f', 'id': 506, 'synset': 'glove.n.02', 'synonyms': ['glove'], 'def': 'handwear covering the hand', 'name': 'glove'}, {'frequency': 'c', 'id': 507, 'synset': 'goat.n.01', 'synonyms': ['goat'], 'def': 'a common goat', 'name': 'goat'}, {'frequency': 'f', 'id': 508, 'synset': 'goggles.n.01', 'synonyms': ['goggles'], 'def': 'tight-fitting spectacles worn to protect the eyes', 'name': 'goggles'}, {'frequency': 'r', 'id': 509, 'synset': 'goldfish.n.01', 'synonyms': ['goldfish'], 'def': 'small golden or orange-red freshwater fishes used as pond or aquarium pets', 'name': 'goldfish'}, {'frequency': 'r', 'id': 510, 'synset': 'golf_club.n.02', 'synonyms': ['golf_club', 'golf-club'], 'def': 'golf equipment used by a golfer to hit a golf ball', 'name': 'golf_club'}, {'frequency': 'c', 'id': 511, 'synset': 'golfcart.n.01', 'synonyms': ['golfcart'], 'def': 'a small motor vehicle in which golfers can ride between shots', 'name': 'golfcart'}, {'frequency': 'r', 'id': 512, 'synset': 'gondola.n.02', 'synonyms': ['gondola_(boat)'], 'def': 'long narrow flat-bottomed boat propelled by sculling; traditionally used on canals of Venice', 'name': 'gondola_(boat)'}, {'frequency': 'c', 'id': 513, 'synset': 'goose.n.01', 'synonyms': ['goose'], 'def': 'loud, web-footed long-necked aquatic birds usually larger than ducks', 'name': 'goose'}, {'frequency': 'r', 'id': 514, 'synset': 'gorilla.n.01', 'synonyms': ['gorilla'], 'def': 'largest ape', 'name': 'gorilla'}, {'frequency': 'r', 'id': 515, 'synset': 'gourd.n.02', 'synonyms': ['gourd'], 'def': 'any of numerous inedible fruits with hard rinds', 'name': 'gourd'}, {'frequency': 'r', 'id': 516, 'synset': 'gown.n.04', 'synonyms': ['surgical_gown', 'scrubs_(surgical_clothing)'], 'def': 'protective garment worn by surgeons during operations', 'name': 'surgical_gown'}, {'frequency': 'f', 'id': 517, 'synset': 'grape.n.01', 'synonyms': ['grape'], 'def': 'any of various juicy fruit with green or purple skins; grow in clusters', 'name': 'grape'}, {'frequency': 'r', 'id': 518, 'synset': 'grasshopper.n.01', 'synonyms': ['grasshopper'], 'def': 'plant-eating insect with hind legs adapted for leaping', 'name': 'grasshopper'}, {'frequency': 'c', 'id': 519, 'synset': 'grater.n.01', 'synonyms': ['grater'], 'def': 'utensil with sharp perforations for shredding foods (as vegetables or cheese)', 'name': 'grater'}, {'frequency': 'c', 'id': 520, 'synset': 'gravestone.n.01', 'synonyms': ['gravestone', 'headstone', 'tombstone'], 'def': 'a stone that is used to mark a grave', 'name': 'gravestone'}, {'frequency': 'r', 'id': 521, 'synset': 'gravy_boat.n.01', 'synonyms': ['gravy_boat', 'gravy_holder'], 'def': 'a dish (often boat-shaped) for serving gravy or sauce', 'name': 'gravy_boat'}, {'frequency': 'c', 'id': 522, 'synset': 'green_bean.n.02', 'synonyms': ['green_bean'], 'def': 'a common bean plant cultivated for its slender green edible pods', 'name': 'green_bean'}, {'frequency': 'c', 'id': 523, 'synset': 'green_onion.n.01', 'synonyms': ['green_onion', 'spring_onion', 'scallion'], 'def': 'a young onion before the bulb has enlarged', 'name': 'green_onion'}, {'frequency': 'r', 'id': 524, 'synset': 'griddle.n.01', 'synonyms': ['griddle'], 'def': 'cooking utensil consisting of a flat heated surface on which food is cooked', 'name': 'griddle'}, {'frequency': 'r', 'id': 525, 'synset': 'grillroom.n.01', 'synonyms': ['grillroom', 'grill_(restaurant)'], 'def': 'a restaurant where food is cooked on a grill', 'name': 'grillroom'}, {'frequency': 'r', 'id': 526, 'synset': 'grinder.n.04', 'synonyms': ['grinder_(tool)'], 'def': 'a machine tool that polishes metal', 'name': 'grinder_(tool)'}, {'frequency': 'r', 'id': 527, 'synset': 'grits.n.01', 'synonyms': ['grits', 'hominy_grits'], 'def': 'coarsely ground corn boiled as a breakfast dish', 'name': 'grits'}, {'frequency': 'c', 'id': 528, 'synset': 'grizzly.n.01', 'synonyms': ['grizzly', 'grizzly_bear'], 'def': 'powerful brownish-yellow bear of the uplands of western North America', 'name': 'grizzly'}, {'frequency': 'c', 'id': 529, 'synset': 'grocery_bag.n.01', 'synonyms': ['grocery_bag'], 'def': "a sack for holding customer's groceries", 'name': 'grocery_bag'}, {'frequency': 'r', 'id': 530, 'synset': 'guacamole.n.01', 'synonyms': ['guacamole'], 'def': 'a dip made of mashed avocado mixed with chopped onions and other seasonings', 'name': 'guacamole'}, {'frequency': 'f', 'id': 531, 'synset': 'guitar.n.01', 'synonyms': ['guitar'], 'def': 'a stringed instrument usually having six strings; played by strumming or plucking', 'name': 'guitar'}, {'frequency': 'c', 'id': 532, 'synset': 'gull.n.02', 'synonyms': ['gull', 'seagull'], 'def': 'mostly white aquatic bird having long pointed wings and short legs', 'name': 'gull'}, {'frequency': 'c', 'id': 533, 'synset': 'gun.n.01', 'synonyms': ['gun'], 'def': 'a weapon that discharges a bullet at high velocity from a metal tube', 'name': 'gun'}, {'frequency': 'r', 'id': 534, 'synset': 'hair_spray.n.01', 'synonyms': ['hair_spray'], 'def': 'substance sprayed on the hair to hold it in place', 'name': 'hair_spray'}, {'frequency': 'c', 'id': 535, 'synset': 'hairbrush.n.01', 'synonyms': ['hairbrush'], 'def': "a brush used to groom a person's hair", 'name': 'hairbrush'}, {'frequency': 'c', 'id': 536, 'synset': 'hairnet.n.01', 'synonyms': ['hairnet'], 'def': 'a small net that someone wears over their hair to keep it in place', 'name': 'hairnet'}, {'frequency': 'c', 'id': 537, 'synset': 'hairpin.n.01', 'synonyms': ['hairpin'], 'def': "a double pronged pin used to hold women's hair in place", 'name': 'hairpin'}, {'frequency': 'f', 'id': 538, 'synset': 'ham.n.01', 'synonyms': ['ham', 'jambon', 'gammon'], 'def': 'meat cut from the thigh of a hog (usually smoked)', 'name': 'ham'}, {'frequency': 'c', 'id': 539, 'synset': 'hamburger.n.01', 'synonyms': ['hamburger', 'beefburger', 'burger'], 'def': 'a sandwich consisting of a patty of minced beef served on a bun', 'name': 'hamburger'}, {'frequency': 'c', 'id': 540, 'synset': 'hammer.n.02', 'synonyms': ['hammer'], 'def': 'a hand tool with a heavy head and a handle; used to deliver an impulsive force by striking', 'name': 'hammer'}, {'frequency': 'r', 'id': 541, 'synset': 'hammock.n.02', 'synonyms': ['hammock'], 'def': 'a hanging bed of canvas or rope netting (usually suspended between two trees)', 'name': 'hammock'}, {'frequency': 'r', 'id': 542, 'synset': 'hamper.n.02', 'synonyms': ['hamper'], 'def': 'a basket usually with a cover', 'name': 'hamper'}, {'frequency': 'r', 'id': 543, 'synset': 'hamster.n.01', 'synonyms': ['hamster'], 'def': 'short-tailed burrowing rodent with large cheek pouches', 'name': 'hamster'}, {'frequency': 'c', 'id': 544, 'synset': 'hand_blower.n.01', 'synonyms': ['hair_dryer'], 'def': 'a hand-held electric blower that can blow warm air onto the hair', 'name': 'hair_dryer'}, {'frequency': 'r', 'id': 545, 'synset': 'hand_glass.n.01', 'synonyms': ['hand_glass', 'hand_mirror'], 'def': 'a mirror intended to be held in the hand', 'name': 'hand_glass'}, {'frequency': 'f', 'id': 546, 'synset': 'hand_towel.n.01', 'synonyms': ['hand_towel', 'face_towel'], 'def': 'a small towel used to dry the hands or face', 'name': 'hand_towel'}, {'frequency': 'c', 'id': 547, 'synset': 'handcart.n.01', 'synonyms': ['handcart', 'pushcart', 'hand_truck'], 'def': 'wheeled vehicle that can be pushed by a person', 'name': 'handcart'}, {'frequency': 'r', 'id': 548, 'synset': 'handcuff.n.01', 'synonyms': ['handcuff'], 'def': 'shackle that consists of a metal loop that can be locked around the wrist', 'name': 'handcuff'}, {'frequency': 'c', 'id': 549, 'synset': 'handkerchief.n.01', 'synonyms': ['handkerchief'], 'def': 'a square piece of cloth used for wiping the eyes or nose or as a costume accessory', 'name': 'handkerchief'}, {'frequency': 'f', 'id': 550, 'synset': 'handle.n.01', 'synonyms': ['handle', 'grip', 'handgrip'], 'def': 'the appendage to an object that is designed to be held in order to use or move it', 'name': 'handle'}, {'frequency': 'r', 'id': 551, 'synset': 'handsaw.n.01', 'synonyms': ['handsaw', "carpenter's_saw"], 'def': 'a saw used with one hand for cutting wood', 'name': 'handsaw'}, {'frequency': 'r', 'id': 552, 'synset': 'hardback.n.01', 'synonyms': ['hardback_book', 'hardcover_book'], 'def': 'a book with cardboard or cloth or leather covers', 'name': 'hardback_book'}, {'frequency': 'r', 'id': 553, 'synset': 'harmonium.n.01', 'synonyms': ['harmonium', 'organ_(musical_instrument)', 'reed_organ_(musical_instrument)'], 'def': 'a free-reed instrument in which air is forced through the reeds by bellows', 'name': 'harmonium'}, {'frequency': 'f', 'id': 554, 'synset': 'hat.n.01', 'synonyms': ['hat'], 'def': 'headwear that protects the head from bad weather, sun, or worn for fashion', 'name': 'hat'}, {'frequency': 'r', 'id': 555, 'synset': 'hatbox.n.01', 'synonyms': ['hatbox'], 'def': 'a round piece of luggage for carrying hats', 'name': 'hatbox'}, {'frequency': 'r', 'id': 556, 'synset': 'hatch.n.03', 'synonyms': ['hatch'], 'def': 'a movable barrier covering a hatchway', 'name': 'hatch'}, {'frequency': 'c', 'id': 557, 'synset': 'head_covering.n.01', 'synonyms': ['veil'], 'def': 'a garment that covers the head and face', 'name': 'veil'}, {'frequency': 'f', 'id': 558, 'synset': 'headband.n.01', 'synonyms': ['headband'], 'def': 'a band worn around or over the head', 'name': 'headband'}, {'frequency': 'f', 'id': 559, 'synset': 'headboard.n.01', 'synonyms': ['headboard'], 'def': 'a vertical board or panel forming the head of a bedstead', 'name': 'headboard'}, {'frequency': 'f', 'id': 560, 'synset': 'headlight.n.01', 'synonyms': ['headlight', 'headlamp'], 'def': 'a powerful light with reflector; attached to the front of an automobile or locomotive', 'name': 'headlight'}, {'frequency': 'c', 'id': 561, 'synset': 'headscarf.n.01', 'synonyms': ['headscarf'], 'def': 'a kerchief worn over the head and tied under the chin', 'name': 'headscarf'}, {'frequency': 'r', 'id': 562, 'synset': 'headset.n.01', 'synonyms': ['headset'], 'def': 'receiver consisting of a pair of headphones', 'name': 'headset'}, {'frequency': 'c', 'id': 563, 'synset': 'headstall.n.01', 'synonyms': ['headstall_(for_horses)', 'headpiece_(for_horses)'], 'def': "the band that is the part of a bridle that fits around a horse's head", 'name': 'headstall_(for_horses)'}, {'frequency': 'r', 'id': 564, 'synset': 'hearing_aid.n.02', 'synonyms': ['hearing_aid'], 'def': 'an acoustic device used to direct sound to the ear of a hearing-impaired person', 'name': 'hearing_aid'}, {'frequency': 'c', 'id': 565, 'synset': 'heart.n.02', 'synonyms': ['heart'], 'def': 'a muscular organ; its contractions move the blood through the body', 'name': 'heart'}, {'frequency': 'c', 'id': 566, 'synset': 'heater.n.01', 'synonyms': ['heater', 'warmer'], 'def': 'device that heats water or supplies warmth to a room', 'name': 'heater'}, {'frequency': 'c', 'id': 567, 'synset': 'helicopter.n.01', 'synonyms': ['helicopter'], 'def': 'an aircraft without wings that obtains its lift from the rotation of overhead blades', 'name': 'helicopter'}, {'frequency': 'f', 'id': 568, 'synset': 'helmet.n.02', 'synonyms': ['helmet'], 'def': 'a protective headgear made of hard material to resist blows', 'name': 'helmet'}, {'frequency': 'r', 'id': 569, 'synset': 'heron.n.02', 'synonyms': ['heron'], 'def': 'grey or white wading bird with long neck and long legs and (usually) long bill', 'name': 'heron'}, {'frequency': 'c', 'id': 570, 'synset': 'highchair.n.01', 'synonyms': ['highchair', 'feeding_chair'], 'def': 'a chair for feeding a very young child', 'name': 'highchair'}, {'frequency': 'f', 'id': 571, 'synset': 'hinge.n.01', 'synonyms': ['hinge'], 'def': 'a joint that holds two parts together so that one can swing relative to the other', 'name': 'hinge'}, {'frequency': 'r', 'id': 572, 'synset': 'hippopotamus.n.01', 'synonyms': ['hippopotamus'], 'def': 'massive thick-skinned animal living in or around rivers of tropical Africa', 'name': 'hippopotamus'}, {'frequency': 'r', 'id': 573, 'synset': 'hockey_stick.n.01', 'synonyms': ['hockey_stick'], 'def': 'sports implement consisting of a stick used by hockey players to move the puck', 'name': 'hockey_stick'}, {'frequency': 'c', 'id': 574, 'synset': 'hog.n.03', 'synonyms': ['hog', 'pig'], 'def': 'domestic swine', 'name': 'hog'}, {'frequency': 'f', 'id': 575, 'synset': 'home_plate.n.01', 'synonyms': ['home_plate_(baseball)', 'home_base_(baseball)'], 'def': '(baseball) a rubber slab where the batter stands; it must be touched by a base runner in order to score', 'name': 'home_plate_(baseball)'}, {'frequency': 'c', 'id': 576, 'synset': 'honey.n.01', 'synonyms': ['honey'], 'def': 'a sweet yellow liquid produced by bees', 'name': 'honey'}, {'frequency': 'f', 'id': 577, 'synset': 'hood.n.06', 'synonyms': ['fume_hood', 'exhaust_hood'], 'def': 'metal covering leading to a vent that exhausts smoke or fumes', 'name': 'fume_hood'}, {'frequency': 'f', 'id': 578, 'synset': 'hook.n.05', 'synonyms': ['hook'], 'def': 'a curved or bent implement for suspending or pulling something', 'name': 'hook'}, {'frequency': 'f', 'id': 579, 'synset': 'horse.n.01', 'synonyms': ['horse'], 'def': 'a common horse', 'name': 'horse'}, {'frequency': 'f', 'id': 580, 'synset': 'hose.n.03', 'synonyms': ['hose', 'hosepipe'], 'def': 'a flexible pipe for conveying a liquid or gas', 'name': 'hose'}, {'frequency': 'r', 'id': 581, 'synset': 'hot-air_balloon.n.01', 'synonyms': ['hot-air_balloon'], 'def': 'balloon for travel through the air in a basket suspended below a large bag of heated air', 'name': 'hot-air_balloon'}, {'frequency': 'r', 'id': 582, 'synset': 'hot_plate.n.01', 'synonyms': ['hotplate'], 'def': 'a portable electric appliance for heating or cooking or keeping food warm', 'name': 'hotplate'}, {'frequency': 'c', 'id': 583, 'synset': 'hot_sauce.n.01', 'synonyms': ['hot_sauce'], 'def': 'a pungent peppery sauce', 'name': 'hot_sauce'}, {'frequency': 'r', 'id': 584, 'synset': 'hourglass.n.01', 'synonyms': ['hourglass'], 'def': 'a sandglass timer that runs for sixty minutes', 'name': 'hourglass'}, {'frequency': 'r', 'id': 585, 'synset': 'houseboat.n.01', 'synonyms': ['houseboat'], 'def': 'a barge that is designed and equipped for use as a dwelling', 'name': 'houseboat'}, {'frequency': 'r', 'id': 586, 'synset': 'hummingbird.n.01', 'synonyms': ['hummingbird'], 'def': 'tiny American bird having brilliant iridescent plumage and long slender bills', 'name': 'hummingbird'}, {'frequency': 'r', 'id': 587, 'synset': 'hummus.n.01', 'synonyms': ['hummus', 'humus', 'hommos', 'hoummos', 'humous'], 'def': 'a thick spread made from mashed chickpeas', 'name': 'hummus'}, {'frequency': 'c', 'id': 588, 'synset': 'ice_bear.n.01', 'synonyms': ['polar_bear'], 'def': 'white bear of Arctic regions', 'name': 'polar_bear'}, {'frequency': 'c', 'id': 589, 'synset': 'ice_cream.n.01', 'synonyms': ['icecream'], 'def': 'frozen dessert containing cream and sugar and flavoring', 'name': 'icecream'}, {'frequency': 'r', 'id': 590, 'synset': 'ice_lolly.n.01', 'synonyms': ['popsicle'], 'def': 'ice cream or water ice on a small wooden stick', 'name': 'popsicle'}, {'frequency': 'c', 'id': 591, 'synset': 'ice_maker.n.01', 'synonyms': ['ice_maker'], 'def': 'an appliance included in some electric refrigerators for making ice cubes', 'name': 'ice_maker'}, {'frequency': 'r', 'id': 592, 'synset': 'ice_pack.n.01', 'synonyms': ['ice_pack', 'ice_bag'], 'def': 'a waterproof bag filled with ice: applied to the body (especially the head) to cool or reduce swelling', 'name': 'ice_pack'}, {'frequency': 'r', 'id': 593, 'synset': 'ice_skate.n.01', 'synonyms': ['ice_skate'], 'def': 'skate consisting of a boot with a steel blade fitted to the sole', 'name': 'ice_skate'}, {'frequency': 'r', 'id': 594, 'synset': 'ice_tea.n.01', 'synonyms': ['ice_tea', 'iced_tea'], 'def': 'strong tea served over ice', 'name': 'ice_tea'}, {'frequency': 'c', 'id': 595, 'synset': 'igniter.n.01', 'synonyms': ['igniter', 'ignitor', 'lighter'], 'def': 'a substance or device used to start a fire', 'name': 'igniter'}, {'frequency': 'r', 'id': 596, 'synset': 'incense.n.01', 'synonyms': ['incense'], 'def': 'a substance that produces a fragrant odor when burned', 'name': 'incense'}, {'frequency': 'r', 'id': 597, 'synset': 'inhaler.n.01', 'synonyms': ['inhaler', 'inhalator'], 'def': 'a dispenser that produces a chemical vapor to be inhaled through mouth or nose', 'name': 'inhaler'}, {'frequency': 'c', 'id': 598, 'synset': 'ipod.n.01', 'synonyms': ['iPod'], 'def': 'a pocket-sized device used to play music files', 'name': 'iPod'}, {'frequency': 'c', 'id': 599, 'synset': 'iron.n.04', 'synonyms': ['iron_(for_clothing)', 'smoothing_iron_(for_clothing)'], 'def': 'home appliance consisting of a flat metal base that is heated and used to smooth cloth', 'name': 'iron_(for_clothing)'}, {'frequency': 'r', 'id': 600, 'synset': 'ironing_board.n.01', 'synonyms': ['ironing_board'], 'def': 'narrow padded board on collapsible supports; used for ironing clothes', 'name': 'ironing_board'}, {'frequency': 'f', 'id': 601, 'synset': 'jacket.n.01', 'synonyms': ['jacket'], 'def': 'a waist-length coat', 'name': 'jacket'}, {'frequency': 'r', 'id': 602, 'synset': 'jam.n.01', 'synonyms': ['jam'], 'def': 'preserve of crushed fruit', 'name': 'jam'}, {'frequency': 'f', 'id': 603, 'synset': 'jean.n.01', 'synonyms': ['jean', 'blue_jean', 'denim'], 'def': '(usually plural) close-fitting trousers of heavy denim for manual work or casual wear', 'name': 'jean'}, {'frequency': 'c', 'id': 604, 'synset': 'jeep.n.01', 'synonyms': ['jeep', 'landrover'], 'def': 'a car suitable for traveling over rough terrain', 'name': 'jeep'}, {'frequency': 'r', 'id': 605, 'synset': 'jelly_bean.n.01', 'synonyms': ['jelly_bean', 'jelly_egg'], 'def': 'sugar-glazed jellied candy', 'name': 'jelly_bean'}, {'frequency': 'f', 'id': 606, 'synset': 'jersey.n.03', 'synonyms': ['jersey', 'T-shirt', 'tee_shirt'], 'def': 'a close-fitting pullover shirt', 'name': 'jersey'}, {'frequency': 'c', 'id': 607, 'synset': 'jet.n.01', 'synonyms': ['jet_plane', 'jet-propelled_plane'], 'def': 'an airplane powered by one or more jet engines', 'name': 'jet_plane'}, {'frequency': 'c', 'id': 608, 'synset': 'jewelry.n.01', 'synonyms': ['jewelry', 'jewellery'], 'def': 'an adornment (as a bracelet or ring or necklace) made of precious metals and set with gems (or imitation gems)', 'name': 'jewelry'}, {'frequency': 'r', 'id': 609, 'synset': 'joystick.n.02', 'synonyms': ['joystick'], 'def': 'a control device for computers consisting of a vertical handle that can move freely in two directions', 'name': 'joystick'}, {'frequency': 'r', 'id': 610, 'synset': 'jump_suit.n.01', 'synonyms': ['jumpsuit'], 'def': "one-piece garment fashioned after a parachutist's uniform", 'name': 'jumpsuit'}, {'frequency': 'c', 'id': 611, 'synset': 'kayak.n.01', 'synonyms': ['kayak'], 'def': 'a small canoe consisting of a light frame made watertight with animal skins', 'name': 'kayak'}, {'frequency': 'r', 'id': 612, 'synset': 'keg.n.02', 'synonyms': ['keg'], 'def': 'small cask or barrel', 'name': 'keg'}, {'frequency': 'r', 'id': 613, 'synset': 'kennel.n.01', 'synonyms': ['kennel', 'doghouse'], 'def': 'outbuilding that serves as a shelter for a dog', 'name': 'kennel'}, {'frequency': 'c', 'id': 614, 'synset': 'kettle.n.01', 'synonyms': ['kettle', 'boiler'], 'def': 'a metal pot for stewing or boiling; usually has a lid', 'name': 'kettle'}, {'frequency': 'f', 'id': 615, 'synset': 'key.n.01', 'synonyms': ['key'], 'def': 'metal instrument used to unlock a lock', 'name': 'key'}, {'frequency': 'r', 'id': 616, 'synset': 'keycard.n.01', 'synonyms': ['keycard'], 'def': 'a plastic card used to gain access typically to a door', 'name': 'keycard'}, {'frequency': 'r', 'id': 617, 'synset': 'kilt.n.01', 'synonyms': ['kilt'], 'def': 'a knee-length pleated tartan skirt worn by men as part of the traditional dress in the Highlands of northern Scotland', 'name': 'kilt'}, {'frequency': 'c', 'id': 618, 'synset': 'kimono.n.01', 'synonyms': ['kimono'], 'def': 'a loose robe; imitated from robes originally worn by Japanese', 'name': 'kimono'}, {'frequency': 'f', 'id': 619, 'synset': 'kitchen_sink.n.01', 'synonyms': ['kitchen_sink'], 'def': 'a sink in a kitchen', 'name': 'kitchen_sink'}, {'frequency': 'c', 'id': 620, 'synset': 'kitchen_table.n.01', 'synonyms': ['kitchen_table'], 'def': 'a table in the kitchen', 'name': 'kitchen_table'}, {'frequency': 'f', 'id': 621, 'synset': 'kite.n.03', 'synonyms': ['kite'], 'def': 'plaything consisting of a light frame covered with tissue paper; flown in wind at end of a string', 'name': 'kite'}, {'frequency': 'c', 'id': 622, 'synset': 'kitten.n.01', 'synonyms': ['kitten', 'kitty'], 'def': 'young domestic cat', 'name': 'kitten'}, {'frequency': 'c', 'id': 623, 'synset': 'kiwi.n.03', 'synonyms': ['kiwi_fruit'], 'def': 'fuzzy brown egg-shaped fruit with slightly tart green flesh', 'name': 'kiwi_fruit'}, {'frequency': 'f', 'id': 624, 'synset': 'knee_pad.n.01', 'synonyms': ['knee_pad'], 'def': 'protective garment consisting of a pad worn by football or baseball or hockey players', 'name': 'knee_pad'}, {'frequency': 'f', 'id': 625, 'synset': 'knife.n.01', 'synonyms': ['knife'], 'def': 'tool with a blade and point used as a cutting instrument', 'name': 'knife'}, {'frequency': 'r', 'id': 626, 'synset': 'knight.n.02', 'synonyms': ['knight_(chess_piece)', 'horse_(chess_piece)'], 'def': 'a chess game piece shaped to resemble the head of a horse', 'name': 'knight_(chess_piece)'}, {'frequency': 'r', 'id': 627, 'synset': 'knitting_needle.n.01', 'synonyms': ['knitting_needle'], 'def': 'needle consisting of a slender rod with pointed ends; usually used in pairs', 'name': 'knitting_needle'}, {'frequency': 'f', 'id': 628, 'synset': 'knob.n.02', 'synonyms': ['knob'], 'def': 'a round handle often found on a door', 'name': 'knob'}, {'frequency': 'r', 'id': 629, 'synset': 'knocker.n.05', 'synonyms': ['knocker_(on_a_door)', 'doorknocker'], 'def': 'a device (usually metal and ornamental) attached by a hinge to a door', 'name': 'knocker_(on_a_door)'}, {'frequency': 'r', 'id': 630, 'synset': 'koala.n.01', 'synonyms': ['koala', 'koala_bear'], 'def': 'sluggish tailless Australian marsupial with grey furry ears and coat', 'name': 'koala'}, {'frequency': 'r', 'id': 631, 'synset': 'lab_coat.n.01', 'synonyms': ['lab_coat', 'laboratory_coat'], 'def': 'a light coat worn to protect clothing from substances used while working in a laboratory', 'name': 'lab_coat'}, {'frequency': 'f', 'id': 632, 'synset': 'ladder.n.01', 'synonyms': ['ladder'], 'def': 'steps consisting of two parallel members connected by rungs', 'name': 'ladder'}, {'frequency': 'c', 'id': 633, 'synset': 'ladle.n.01', 'synonyms': ['ladle'], 'def': 'a spoon-shaped vessel with a long handle frequently used to transfer liquids', 'name': 'ladle'}, {'frequency': 'r', 'id': 634, 'synset': 'ladybug.n.01', 'synonyms': ['ladybug', 'ladybeetle', 'ladybird_beetle'], 'def': 'small round bright-colored and spotted beetle, typically red and black', 'name': 'ladybug'}, {'frequency': 'c', 'id': 635, 'synset': 'lamb.n.01', 'synonyms': ['lamb_(animal)'], 'def': 'young sheep', 'name': 'lamb_(animal)'}, {'frequency': 'r', 'id': 636, 'synset': 'lamb_chop.n.01', 'synonyms': ['lamb-chop', 'lambchop'], 'def': 'chop cut from a lamb', 'name': 'lamb-chop'}, {'frequency': 'f', 'id': 637, 'synset': 'lamp.n.02', 'synonyms': ['lamp'], 'def': 'a piece of furniture holding one or more electric light bulbs', 'name': 'lamp'}, {'frequency': 'f', 'id': 638, 'synset': 'lamppost.n.01', 'synonyms': ['lamppost'], 'def': 'a metal post supporting an outdoor lamp (such as a streetlight)', 'name': 'lamppost'}, {'frequency': 'f', 'id': 639, 'synset': 'lampshade.n.01', 'synonyms': ['lampshade'], 'def': 'a protective ornamental shade used to screen a light bulb from direct view', 'name': 'lampshade'}, {'frequency': 'c', 'id': 640, 'synset': 'lantern.n.01', 'synonyms': ['lantern'], 'def': 'light in a transparent protective case', 'name': 'lantern'}, {'frequency': 'f', 'id': 641, 'synset': 'lanyard.n.02', 'synonyms': ['lanyard', 'laniard'], 'def': 'a cord worn around the neck to hold a knife or whistle, etc.', 'name': 'lanyard'}, {'frequency': 'f', 'id': 642, 'synset': 'laptop.n.01', 'synonyms': ['laptop_computer', 'notebook_computer'], 'def': 'a portable computer small enough to use in your lap', 'name': 'laptop_computer'}, {'frequency': 'r', 'id': 643, 'synset': 'lasagna.n.01', 'synonyms': ['lasagna', 'lasagne'], 'def': 'baked dish of layers of lasagna pasta with sauce and cheese and meat or vegetables', 'name': 'lasagna'}, {'frequency': 'c', 'id': 644, 'synset': 'latch.n.02', 'synonyms': ['latch'], 'def': 'a bar that can be lowered or slid into a groove to fasten a door or gate', 'name': 'latch'}, {'frequency': 'r', 'id': 645, 'synset': 'lawn_mower.n.01', 'synonyms': ['lawn_mower'], 'def': 'garden tool for mowing grass on lawns', 'name': 'lawn_mower'}, {'frequency': 'r', 'id': 646, 'synset': 'leather.n.01', 'synonyms': ['leather'], 'def': 'an animal skin made smooth and flexible by removing the hair and then tanning', 'name': 'leather'}, {'frequency': 'c', 'id': 647, 'synset': 'legging.n.01', 'synonyms': ['legging_(clothing)', 'leging_(clothing)', 'leg_covering'], 'def': 'a garment covering the leg (usually extending from the knee to the ankle)', 'name': 'legging_(clothing)'}, {'frequency': 'c', 'id': 648, 'synset': 'lego.n.01', 'synonyms': ['Lego', 'Lego_set'], 'def': "a child's plastic construction set for making models from blocks", 'name': 'Lego'}, {'frequency': 'f', 'id': 649, 'synset': 'lemon.n.01', 'synonyms': ['lemon'], 'def': 'yellow oval fruit with juicy acidic flesh', 'name': 'lemon'}, {'frequency': 'r', 'id': 650, 'synset': 'lemonade.n.01', 'synonyms': ['lemonade'], 'def': 'sweetened beverage of diluted lemon juice', 'name': 'lemonade'}, {'frequency': 'f', 'id': 651, 'synset': 'lettuce.n.02', 'synonyms': ['lettuce'], 'def': 'leafy plant commonly eaten in salad or on sandwiches', 'name': 'lettuce'}, {'frequency': 'f', 'id': 652, 'synset': 'license_plate.n.01', 'synonyms': ['license_plate', 'numberplate'], 'def': "a plate mounted on the front and back of car and bearing the car's registration number", 'name': 'license_plate'}, {'frequency': 'f', 'id': 653, 'synset': 'life_buoy.n.01', 'synonyms': ['life_buoy', 'lifesaver', 'life_belt', 'life_ring'], 'def': 'a ring-shaped life preserver used to prevent drowning (NOT a life-jacket or vest)', 'name': 'life_buoy'}, {'frequency': 'f', 'id': 654, 'synset': 'life_jacket.n.01', 'synonyms': ['life_jacket', 'life_vest'], 'def': 'life preserver consisting of a sleeveless jacket of buoyant or inflatable design', 'name': 'life_jacket'}, {'frequency': 'f', 'id': 655, 'synset': 'light_bulb.n.01', 'synonyms': ['lightbulb'], 'def': 'glass bulb or tube shaped electric device that emits light (DO NOT MARK LAMPS AS A WHOLE)', 'name': 'lightbulb'}, {'frequency': 'r', 'id': 656, 'synset': 'lightning_rod.n.02', 'synonyms': ['lightning_rod', 'lightning_conductor'], 'def': 'a metallic conductor that is attached to a high point and leads to the ground', 'name': 'lightning_rod'}, {'frequency': 'c', 'id': 657, 'synset': 'lime.n.06', 'synonyms': ['lime'], 'def': 'the green acidic fruit of any of various lime trees', 'name': 'lime'}, {'frequency': 'r', 'id': 658, 'synset': 'limousine.n.01', 'synonyms': ['limousine'], 'def': 'long luxurious car; usually driven by a chauffeur', 'name': 'limousine'}, {'frequency': 'r', 'id': 659, 'synset': 'linen.n.02', 'synonyms': ['linen_paper'], 'def': 'a high-quality paper made of linen fibers or with a linen finish', 'name': 'linen_paper'}, {'frequency': 'c', 'id': 660, 'synset': 'lion.n.01', 'synonyms': ['lion'], 'def': 'large gregarious predatory cat of Africa and India', 'name': 'lion'}, {'frequency': 'c', 'id': 661, 'synset': 'lip_balm.n.01', 'synonyms': ['lip_balm'], 'def': 'a balm applied to the lips', 'name': 'lip_balm'}, {'frequency': 'c', 'id': 662, 'synset': 'lipstick.n.01', 'synonyms': ['lipstick', 'lip_rouge'], 'def': 'makeup that is used to color the lips', 'name': 'lipstick'}, {'frequency': 'r', 'id': 663, 'synset': 'liquor.n.01', 'synonyms': ['liquor', 'spirits', 'hard_liquor', 'liqueur', 'cordial'], 'def': 'an alcoholic beverage that is distilled rather than fermented', 'name': 'liquor'}, {'frequency': 'r', 'id': 664, 'synset': 'lizard.n.01', 'synonyms': ['lizard'], 'def': 'a reptile with usually two pairs of legs and a tapering tail', 'name': 'lizard'}, {'frequency': 'r', 'id': 665, 'synset': 'loafer.n.02', 'synonyms': ['Loafer_(type_of_shoe)'], 'def': 'a low leather step-in shoe', 'name': 'Loafer_(type_of_shoe)'}, {'frequency': 'f', 'id': 666, 'synset': 'log.n.01', 'synonyms': ['log'], 'def': 'a segment of the trunk of a tree when stripped of branches', 'name': 'log'}, {'frequency': 'c', 'id': 667, 'synset': 'lollipop.n.02', 'synonyms': ['lollipop'], 'def': 'hard candy on a stick', 'name': 'lollipop'}, {'frequency': 'c', 'id': 668, 'synset': 'lotion.n.01', 'synonyms': ['lotion'], 'def': 'any of various cosmetic preparations that are applied to the skin', 'name': 'lotion'}, {'frequency': 'f', 'id': 669, 'synset': 'loudspeaker.n.01', 'synonyms': ['speaker_(stero_equipment)'], 'def': 'electronic device that produces sound often as part of a stereo system', 'name': 'speaker_(stero_equipment)'}, {'frequency': 'c', 'id': 670, 'synset': 'love_seat.n.01', 'synonyms': ['loveseat'], 'def': 'small sofa that seats two people', 'name': 'loveseat'}, {'frequency': 'r', 'id': 671, 'synset': 'machine_gun.n.01', 'synonyms': ['machine_gun'], 'def': 'a rapidly firing automatic gun', 'name': 'machine_gun'}, {'frequency': 'f', 'id': 672, 'synset': 'magazine.n.02', 'synonyms': ['magazine'], 'def': 'a paperback periodic publication', 'name': 'magazine'}, {'frequency': 'f', 'id': 673, 'synset': 'magnet.n.01', 'synonyms': ['magnet'], 'def': 'a device that attracts iron and produces a magnetic field', 'name': 'magnet'}, {'frequency': 'r', 'id': 674, 'synset': 'mail_slot.n.01', 'synonyms': ['mail_slot'], 'def': 'a slot (usually in a door) through which mail can be delivered', 'name': 'mail_slot'}, {'frequency': 'c', 'id': 675, 'synset': 'mailbox.n.01', 'synonyms': ['mailbox_(at_home)', 'letter_box_(at_home)'], 'def': 'a private box for delivery of mail', 'name': 'mailbox_(at_home)'}, {'frequency': 'r', 'id': 676, 'synset': 'mallet.n.01', 'synonyms': ['mallet'], 'def': 'a sports implement with a long handle and a hammer-like head used to hit a ball', 'name': 'mallet'}, {'frequency': 'r', 'id': 677, 'synset': 'mammoth.n.01', 'synonyms': ['mammoth'], 'def': 'any of numerous extinct elephants widely distributed in the Pleistocene', 'name': 'mammoth'}, {'frequency': 'c', 'id': 678, 'synset': 'mandarin.n.05', 'synonyms': ['mandarin_orange'], 'def': 'a somewhat flat reddish-orange loose skinned citrus of China', 'name': 'mandarin_orange'}, {'frequency': 'c', 'id': 679, 'synset': 'manger.n.01', 'synonyms': ['manger', 'trough'], 'def': 'a container (usually in a barn or stable) from which cattle or horses feed', 'name': 'manger'}, {'frequency': 'f', 'id': 680, 'synset': 'manhole.n.01', 'synonyms': ['manhole'], 'def': 'a hole (usually with a flush cover) through which a person can gain access to an underground structure', 'name': 'manhole'}, {'frequency': 'c', 'id': 681, 'synset': 'map.n.01', 'synonyms': ['map'], 'def': "a diagrammatic representation of the earth's surface (or part of it)", 'name': 'map'}, {'frequency': 'c', 'id': 682, 'synset': 'marker.n.03', 'synonyms': ['marker'], 'def': 'a writing implement for making a mark', 'name': 'marker'}, {'frequency': 'r', 'id': 683, 'synset': 'martini.n.01', 'synonyms': ['martini'], 'def': 'a cocktail made of gin (or vodka) with dry vermouth', 'name': 'martini'}, {'frequency': 'r', 'id': 684, 'synset': 'mascot.n.01', 'synonyms': ['mascot'], 'def': 'a person or animal that is adopted by a team or other group as a symbolic figure', 'name': 'mascot'}, {'frequency': 'c', 'id': 685, 'synset': 'mashed_potato.n.01', 'synonyms': ['mashed_potato'], 'def': 'potato that has been peeled and boiled and then mashed', 'name': 'mashed_potato'}, {'frequency': 'r', 'id': 686, 'synset': 'masher.n.02', 'synonyms': ['masher'], 'def': 'a kitchen utensil used for mashing (e.g. potatoes)', 'name': 'masher'}, {'frequency': 'f', 'id': 687, 'synset': 'mask.n.04', 'synonyms': ['mask', 'facemask'], 'def': 'a protective covering worn over the face', 'name': 'mask'}, {'frequency': 'f', 'id': 688, 'synset': 'mast.n.01', 'synonyms': ['mast'], 'def': 'a vertical spar for supporting sails', 'name': 'mast'}, {'frequency': 'c', 'id': 689, 'synset': 'mat.n.03', 'synonyms': ['mat_(gym_equipment)', 'gym_mat'], 'def': 'sports equipment consisting of a piece of thick padding on the floor for gymnastics', 'name': 'mat_(gym_equipment)'}, {'frequency': 'r', 'id': 690, 'synset': 'matchbox.n.01', 'synonyms': ['matchbox'], 'def': 'a box for holding matches', 'name': 'matchbox'}, {'frequency': 'f', 'id': 691, 'synset': 'mattress.n.01', 'synonyms': ['mattress'], 'def': 'a thick pad filled with resilient material used as a bed or part of a bed', 'name': 'mattress'}, {'frequency': 'c', 'id': 692, 'synset': 'measuring_cup.n.01', 'synonyms': ['measuring_cup'], 'def': 'graduated cup used to measure liquid or granular ingredients', 'name': 'measuring_cup'}, {'frequency': 'c', 'id': 693, 'synset': 'measuring_stick.n.01', 'synonyms': ['measuring_stick', 'ruler_(measuring_stick)', 'measuring_rod'], 'def': 'measuring instrument having a sequence of marks at regular intervals', 'name': 'measuring_stick'}, {'frequency': 'c', 'id': 694, 'synset': 'meatball.n.01', 'synonyms': ['meatball'], 'def': 'ground meat formed into a ball and fried or simmered in broth', 'name': 'meatball'}, {'frequency': 'c', 'id': 695, 'synset': 'medicine.n.02', 'synonyms': ['medicine'], 'def': 'something that treats or prevents or alleviates the symptoms of disease', 'name': 'medicine'}, {'frequency': 'r', 'id': 696, 'synset': 'melon.n.01', 'synonyms': ['melon'], 'def': 'fruit of the gourd family having a hard rind and sweet juicy flesh', 'name': 'melon'}, {'frequency': 'f', 'id': 697, 'synset': 'microphone.n.01', 'synonyms': ['microphone'], 'def': 'device for converting sound waves into electrical energy', 'name': 'microphone'}, {'frequency': 'r', 'id': 698, 'synset': 'microscope.n.01', 'synonyms': ['microscope'], 'def': 'magnifier of the image of small objects', 'name': 'microscope'}, {'frequency': 'f', 'id': 699, 'synset': 'microwave.n.02', 'synonyms': ['microwave_oven'], 'def': 'kitchen appliance that cooks food by passing an electromagnetic wave through it', 'name': 'microwave_oven'}, {'frequency': 'r', 'id': 700, 'synset': 'milestone.n.01', 'synonyms': ['milestone', 'milepost'], 'def': 'stone post at side of a road to show distances', 'name': 'milestone'}, {'frequency': 'c', 'id': 701, 'synset': 'milk.n.01', 'synonyms': ['milk'], 'def': 'a white nutritious liquid secreted by mammals and used as food by human beings', 'name': 'milk'}, {'frequency': 'f', 'id': 702, 'synset': 'minivan.n.01', 'synonyms': ['minivan'], 'def': 'a small box-shaped passenger van', 'name': 'minivan'}, {'frequency': 'r', 'id': 703, 'synset': 'mint.n.05', 'synonyms': ['mint_candy'], 'def': 'a candy that is flavored with a mint oil', 'name': 'mint_candy'}, {'frequency': 'f', 'id': 704, 'synset': 'mirror.n.01', 'synonyms': ['mirror'], 'def': 'polished surface that forms images by reflecting light', 'name': 'mirror'}, {'frequency': 'c', 'id': 705, 'synset': 'mitten.n.01', 'synonyms': ['mitten'], 'def': 'glove that encases the thumb separately and the other four fingers together', 'name': 'mitten'}, {'frequency': 'c', 'id': 706, 'synset': 'mixer.n.04', 'synonyms': ['mixer_(kitchen_tool)', 'stand_mixer'], 'def': 'a kitchen utensil that is used for mixing foods', 'name': 'mixer_(kitchen_tool)'}, {'frequency': 'c', 'id': 707, 'synset': 'money.n.03', 'synonyms': ['money'], 'def': 'the official currency issued by a government or national bank', 'name': 'money'}, {'frequency': 'f', 'id': 708, 'synset': 'monitor.n.04', 'synonyms': ['monitor_(computer_equipment) computer_monitor'], 'def': 'a computer monitor', 'name': 'monitor_(computer_equipment) computer_monitor'}, {'frequency': 'c', 'id': 709, 'synset': 'monkey.n.01', 'synonyms': ['monkey'], 'def': 'any of various long-tailed primates', 'name': 'monkey'}, {'frequency': 'f', 'id': 710, 'synset': 'motor.n.01', 'synonyms': ['motor'], 'def': 'machine that converts other forms of energy into mechanical energy and so imparts motion', 'name': 'motor'}, {'frequency': 'f', 'id': 711, 'synset': 'motor_scooter.n.01', 'synonyms': ['motor_scooter', 'scooter'], 'def': 'a wheeled vehicle with small wheels and a low-powered engine', 'name': 'motor_scooter'}, {'frequency': 'r', 'id': 712, 'synset': 'motor_vehicle.n.01', 'synonyms': ['motor_vehicle', 'automotive_vehicle'], 'def': 'a self-propelled wheeled vehicle that does not run on rails', 'name': 'motor_vehicle'}, {'frequency': 'r', 'id': 713, 'synset': 'motorboat.n.01', 'synonyms': ['motorboat', 'powerboat'], 'def': 'a boat propelled by an internal-combustion engine', 'name': 'motorboat'}, {'frequency': 'f', 'id': 714, 'synset': 'motorcycle.n.01', 'synonyms': ['motorcycle'], 'def': 'a motor vehicle with two wheels and a strong frame', 'name': 'motorcycle'}, {'frequency': 'f', 'id': 715, 'synset': 'mound.n.01', 'synonyms': ['mound_(baseball)', "pitcher's_mound"], 'def': '(baseball) the slight elevation on which the pitcher stands', 'name': 'mound_(baseball)'}, {'frequency': 'r', 'id': 716, 'synset': 'mouse.n.01', 'synonyms': ['mouse_(animal_rodent)'], 'def': 'a small rodent with pointed snouts and small ears on elongated bodies with slender usually hairless tails', 'name': 'mouse_(animal_rodent)'}, {'frequency': 'f', 'id': 717, 'synset': 'mouse.n.04', 'synonyms': ['mouse_(computer_equipment)', 'computer_mouse'], 'def': 'a computer input device that controls an on-screen pointer', 'name': 'mouse_(computer_equipment)'}, {'frequency': 'f', 'id': 718, 'synset': 'mousepad.n.01', 'synonyms': ['mousepad'], 'def': 'a small portable pad that provides an operating surface for a computer mouse', 'name': 'mousepad'}, {'frequency': 'c', 'id': 719, 'synset': 'muffin.n.01', 'synonyms': ['muffin'], 'def': 'a sweet quick bread baked in a cup-shaped pan', 'name': 'muffin'}, {'frequency': 'f', 'id': 720, 'synset': 'mug.n.04', 'synonyms': ['mug'], 'def': 'with handle and usually cylindrical', 'name': 'mug'}, {'frequency': 'f', 'id': 721, 'synset': 'mushroom.n.02', 'synonyms': ['mushroom'], 'def': 'a common mushroom', 'name': 'mushroom'}, {'frequency': 'r', 'id': 722, 'synset': 'music_stool.n.01', 'synonyms': ['music_stool', 'piano_stool'], 'def': 'a stool for piano players; usually adjustable in height', 'name': 'music_stool'}, {'frequency': 'r', 'id': 723, 'synset': 'musical_instrument.n.01', 'synonyms': ['musical_instrument', 'instrument_(musical)'], 'def': 'any of various devices or contrivances that can be used to produce musical tones or sounds', 'name': 'musical_instrument'}, {'frequency': 'r', 'id': 724, 'synset': 'nailfile.n.01', 'synonyms': ['nailfile'], 'def': 'a small flat file for shaping the nails', 'name': 'nailfile'}, {'frequency': 'r', 'id': 725, 'synset': 'nameplate.n.01', 'synonyms': ['nameplate'], 'def': 'a plate bearing a name', 'name': 'nameplate'}, {'frequency': 'f', 'id': 726, 'synset': 'napkin.n.01', 'synonyms': ['napkin', 'table_napkin', 'serviette'], 'def': 'a small piece of table linen or paper that is used to wipe the mouth and to cover the lap in order to protect clothing', 'name': 'napkin'}, {'frequency': 'r', 'id': 727, 'synset': 'neckerchief.n.01', 'synonyms': ['neckerchief'], 'def': 'a kerchief worn around the neck', 'name': 'neckerchief'}, {'frequency': 'f', 'id': 728, 'synset': 'necklace.n.01', 'synonyms': ['necklace'], 'def': 'jewelry consisting of a cord or chain (often bearing gems) worn about the neck as an ornament', 'name': 'necklace'}, {'frequency': 'f', 'id': 729, 'synset': 'necktie.n.01', 'synonyms': ['necktie', 'tie_(necktie)'], 'def': 'neckwear consisting of a long narrow piece of material worn under a collar and tied in knot at the front', 'name': 'necktie'}, {'frequency': 'r', 'id': 730, 'synset': 'needle.n.03', 'synonyms': ['needle'], 'def': 'a sharp pointed implement (usually metal)', 'name': 'needle'}, {'frequency': 'c', 'id': 731, 'synset': 'nest.n.01', 'synonyms': ['nest'], 'def': 'a structure in which animals lay eggs or give birth to their young', 'name': 'nest'}, {'frequency': 'r', 'id': 732, 'synset': 'newsstand.n.01', 'synonyms': ['newsstand'], 'def': 'a stall where newspapers and other periodicals are sold', 'name': 'newsstand'}, {'frequency': 'c', 'id': 733, 'synset': 'nightwear.n.01', 'synonyms': ['nightshirt', 'nightwear', 'sleepwear', 'nightclothes'], 'def': 'garments designed to be worn in bed', 'name': 'nightshirt'}, {'frequency': 'r', 'id': 734, 'synset': 'nosebag.n.01', 'synonyms': ['nosebag_(for_animals)', 'feedbag'], 'def': 'a canvas bag that is used to feed an animal (such as a horse); covers the muzzle and fastens at the top of the head', 'name': 'nosebag_(for_animals)'}, {'frequency': 'r', 'id': 735, 'synset': 'noseband.n.01', 'synonyms': ['noseband_(for_animals)', 'nosepiece_(for_animals)'], 'def': "a strap that is the part of a bridle that goes over the animal's nose", 'name': 'noseband_(for_animals)'}, {'frequency': 'f', 'id': 736, 'synset': 'notebook.n.01', 'synonyms': ['notebook'], 'def': 'a book with blank pages for recording notes or memoranda', 'name': 'notebook'}, {'frequency': 'c', 'id': 737, 'synset': 'notepad.n.01', 'synonyms': ['notepad'], 'def': 'a pad of paper for keeping notes', 'name': 'notepad'}, {'frequency': 'c', 'id': 738, 'synset': 'nut.n.03', 'synonyms': ['nut'], 'def': 'a small metal block (usually square or hexagonal) with internal screw thread to be fitted onto a bolt', 'name': 'nut'}, {'frequency': 'r', 'id': 739, 'synset': 'nutcracker.n.01', 'synonyms': ['nutcracker'], 'def': 'a hand tool used to crack nuts open', 'name': 'nutcracker'}, {'frequency': 'c', 'id': 740, 'synset': 'oar.n.01', 'synonyms': ['oar'], 'def': 'an implement used to propel or steer a boat', 'name': 'oar'}, {'frequency': 'r', 'id': 741, 'synset': 'octopus.n.01', 'synonyms': ['octopus_(food)'], 'def': 'tentacles of octopus prepared as food', 'name': 'octopus_(food)'}, {'frequency': 'r', 'id': 742, 'synset': 'octopus.n.02', 'synonyms': ['octopus_(animal)'], 'def': 'bottom-living cephalopod having a soft oval body with eight long tentacles', 'name': 'octopus_(animal)'}, {'frequency': 'c', 'id': 743, 'synset': 'oil_lamp.n.01', 'synonyms': ['oil_lamp', 'kerosene_lamp', 'kerosine_lamp'], 'def': 'a lamp that burns oil (as kerosine) for light', 'name': 'oil_lamp'}, {'frequency': 'c', 'id': 744, 'synset': 'olive_oil.n.01', 'synonyms': ['olive_oil'], 'def': 'oil from olives', 'name': 'olive_oil'}, {'frequency': 'r', 'id': 745, 'synset': 'omelet.n.01', 'synonyms': ['omelet', 'omelette'], 'def': 'beaten eggs cooked until just set; may be folded around e.g. ham or cheese or jelly', 'name': 'omelet'}, {'frequency': 'f', 'id': 746, 'synset': 'onion.n.01', 'synonyms': ['onion'], 'def': 'the bulb of an onion plant', 'name': 'onion'}, {'frequency': 'f', 'id': 747, 'synset': 'orange.n.01', 'synonyms': ['orange_(fruit)'], 'def': 'orange (FRUIT of an orange tree)', 'name': 'orange_(fruit)'}, {'frequency': 'c', 'id': 748, 'synset': 'orange_juice.n.01', 'synonyms': ['orange_juice'], 'def': 'bottled or freshly squeezed juice of oranges', 'name': 'orange_juice'}, {'frequency': 'r', 'id': 749, 'synset': 'oregano.n.01', 'synonyms': ['oregano', 'marjoram'], 'def': 'aromatic Eurasian perennial herb used in cooking and baking', 'name': 'oregano'}, {'frequency': 'c', 'id': 750, 'synset': 'ostrich.n.02', 'synonyms': ['ostrich'], 'def': 'fast-running African flightless bird with two-toed feet; largest living bird', 'name': 'ostrich'}, {'frequency': 'c', 'id': 751, 'synset': 'ottoman.n.03', 'synonyms': ['ottoman', 'pouf', 'pouffe', 'hassock'], 'def': 'thick cushion used as a seat', 'name': 'ottoman'}, {'frequency': 'c', 'id': 752, 'synset': 'overall.n.01', 'synonyms': ['overalls_(clothing)'], 'def': 'work clothing consisting of denim trousers usually with a bib and shoulder straps', 'name': 'overalls_(clothing)'}, {'frequency': 'c', 'id': 753, 'synset': 'owl.n.01', 'synonyms': ['owl'], 'def': 'nocturnal bird of prey with hawk-like beak and claws and large head with front-facing eyes', 'name': 'owl'}, {'frequency': 'c', 'id': 754, 'synset': 'packet.n.03', 'synonyms': ['packet'], 'def': 'a small package or bundle', 'name': 'packet'}, {'frequency': 'r', 'id': 755, 'synset': 'pad.n.03', 'synonyms': ['inkpad', 'inking_pad', 'stamp_pad'], 'def': 'absorbent material saturated with ink used to transfer ink evenly to a rubber stamp', 'name': 'inkpad'}, {'frequency': 'c', 'id': 756, 'synset': 'pad.n.04', 'synonyms': ['pad'], 'def': 'a flat mass of soft material used for protection, stuffing, or comfort', 'name': 'pad'}, {'frequency': 'c', 'id': 757, 'synset': 'paddle.n.04', 'synonyms': ['paddle', 'boat_paddle'], 'def': 'a short light oar used without an oarlock to propel a canoe or small boat', 'name': 'paddle'}, {'frequency': 'c', 'id': 758, 'synset': 'padlock.n.01', 'synonyms': ['padlock'], 'def': 'a detachable, portable lock', 'name': 'padlock'}, {'frequency': 'r', 'id': 759, 'synset': 'paintbox.n.01', 'synonyms': ['paintbox'], 'def': "a box containing a collection of cubes or tubes of artists' paint", 'name': 'paintbox'}, {'frequency': 'c', 'id': 760, 'synset': 'paintbrush.n.01', 'synonyms': ['paintbrush'], 'def': 'a brush used as an applicator to apply paint', 'name': 'paintbrush'}, {'frequency': 'f', 'id': 761, 'synset': 'painting.n.01', 'synonyms': ['painting'], 'def': 'graphic art consisting of an artistic composition made by applying paints to a surface', 'name': 'painting'}, {'frequency': 'c', 'id': 762, 'synset': 'pajama.n.02', 'synonyms': ['pajamas', 'pyjamas'], 'def': 'loose-fitting nightclothes worn for sleeping or lounging', 'name': 'pajamas'}, {'frequency': 'c', 'id': 763, 'synset': 'palette.n.02', 'synonyms': ['palette', 'pallet'], 'def': 'board that provides a flat surface on which artists mix paints and the range of colors used', 'name': 'palette'}, {'frequency': 'f', 'id': 764, 'synset': 'pan.n.01', 'synonyms': ['pan_(for_cooking)', 'cooking_pan'], 'def': 'cooking utensil consisting of a wide metal vessel', 'name': 'pan_(for_cooking)'}, {'frequency': 'r', 'id': 765, 'synset': 'pan.n.03', 'synonyms': ['pan_(metal_container)'], 'def': 'shallow container made of metal', 'name': 'pan_(metal_container)'}, {'frequency': 'c', 'id': 766, 'synset': 'pancake.n.01', 'synonyms': ['pancake'], 'def': 'a flat cake of thin batter fried on both sides on a griddle', 'name': 'pancake'}, {'frequency': 'r', 'id': 767, 'synset': 'pantyhose.n.01', 'synonyms': ['pantyhose'], 'def': "a woman's tights consisting of underpants and stockings", 'name': 'pantyhose'}, {'frequency': 'r', 'id': 768, 'synset': 'papaya.n.02', 'synonyms': ['papaya'], 'def': 'large oval melon-like tropical fruit with yellowish flesh', 'name': 'papaya'}, {'frequency': 'r', 'id': 769, 'synset': 'paper_clip.n.01', 'synonyms': ['paperclip'], 'def': 'a wire or plastic clip for holding sheets of paper together', 'name': 'paperclip'}, {'frequency': 'f', 'id': 770, 'synset': 'paper_plate.n.01', 'synonyms': ['paper_plate'], 'def': 'a disposable plate made of cardboard', 'name': 'paper_plate'}, {'frequency': 'f', 'id': 771, 'synset': 'paper_towel.n.01', 'synonyms': ['paper_towel'], 'def': 'a disposable towel made of absorbent paper', 'name': 'paper_towel'}, {'frequency': 'r', 'id': 772, 'synset': 'paperback_book.n.01', 'synonyms': ['paperback_book', 'paper-back_book', 'softback_book', 'soft-cover_book'], 'def': 'a book with paper covers', 'name': 'paperback_book'}, {'frequency': 'r', 'id': 773, 'synset': 'paperweight.n.01', 'synonyms': ['paperweight'], 'def': 'a weight used to hold down a stack of papers', 'name': 'paperweight'}, {'frequency': 'c', 'id': 774, 'synset': 'parachute.n.01', 'synonyms': ['parachute'], 'def': 'rescue equipment consisting of a device that fills with air and retards your fall', 'name': 'parachute'}, {'frequency': 'r', 'id': 775, 'synset': 'parakeet.n.01', 'synonyms': ['parakeet', 'parrakeet', 'parroket', 'paraquet', 'paroquet', 'parroquet'], 'def': 'any of numerous small slender long-tailed parrots', 'name': 'parakeet'}, {'frequency': 'c', 'id': 776, 'synset': 'parasail.n.01', 'synonyms': ['parasail_(sports)'], 'def': 'parachute that will lift a person up into the air when it is towed by a motorboat or a car', 'name': 'parasail_(sports)'}, {'frequency': 'r', 'id': 777, 'synset': 'parchment.n.01', 'synonyms': ['parchment'], 'def': 'a superior paper resembling sheepskin', 'name': 'parchment'}, {'frequency': 'r', 'id': 778, 'synset': 'parka.n.01', 'synonyms': ['parka', 'anorak'], 'def': "a kind of heavy jacket (`windcheater' is a British term)", 'name': 'parka'}, {'frequency': 'f', 'id': 779, 'synset': 'parking_meter.n.01', 'synonyms': ['parking_meter'], 'def': 'a coin-operated timer located next to a parking space', 'name': 'parking_meter'}, {'frequency': 'c', 'id': 780, 'synset': 'parrot.n.01', 'synonyms': ['parrot'], 'def': 'usually brightly colored tropical birds with short hooked beaks and the ability to mimic sounds', 'name': 'parrot'}, {'frequency': 'c', 'id': 781, 'synset': 'passenger_car.n.01', 'synonyms': ['passenger_car_(part_of_a_train)', 'coach_(part_of_a_train)'], 'def': 'a railcar where passengers ride', 'name': 'passenger_car_(part_of_a_train)'}, {'frequency': 'r', 'id': 782, 'synset': 'passenger_ship.n.01', 'synonyms': ['passenger_ship'], 'def': 'a ship built to carry passengers', 'name': 'passenger_ship'}, {'frequency': 'r', 'id': 783, 'synset': 'passport.n.02', 'synonyms': ['passport'], 'def': 'a document issued by a country to a citizen allowing that person to travel abroad and re-enter the home country', 'name': 'passport'}, {'frequency': 'f', 'id': 784, 'synset': 'pastry.n.02', 'synonyms': ['pastry'], 'def': 'any of various baked foods made of dough or batter', 'name': 'pastry'}, {'frequency': 'r', 'id': 785, 'synset': 'patty.n.01', 'synonyms': ['patty_(food)'], 'def': 'small flat mass of chopped food', 'name': 'patty_(food)'}, {'frequency': 'c', 'id': 786, 'synset': 'pea.n.01', 'synonyms': ['pea_(food)'], 'def': 'seed of a pea plant used for food', 'name': 'pea_(food)'}, {'frequency': 'c', 'id': 787, 'synset': 'peach.n.03', 'synonyms': ['peach'], 'def': 'downy juicy fruit with sweet yellowish or whitish flesh', 'name': 'peach'}, {'frequency': 'c', 'id': 788, 'synset': 'peanut_butter.n.01', 'synonyms': ['peanut_butter'], 'def': 'a spread made from ground peanuts', 'name': 'peanut_butter'}, {'frequency': 'c', 'id': 789, 'synset': 'pear.n.01', 'synonyms': ['pear'], 'def': 'sweet juicy gritty-textured fruit available in many varieties', 'name': 'pear'}, {'frequency': 'r', 'id': 790, 'synset': 'peeler.n.03', 'synonyms': ['peeler_(tool_for_fruit_and_vegetables)'], 'def': 'a device for peeling vegetables or fruits', 'name': 'peeler_(tool_for_fruit_and_vegetables)'}, {'frequency': 'r', 'id': 791, 'synset': 'pegboard.n.01', 'synonyms': ['pegboard'], 'def': 'a board perforated with regularly spaced holes into which pegs can be fitted', 'name': 'pegboard'}, {'frequency': 'c', 'id': 792, 'synset': 'pelican.n.01', 'synonyms': ['pelican'], 'def': 'large long-winged warm-water seabird having a large bill with a distensible pouch for fish', 'name': 'pelican'}, {'frequency': 'f', 'id': 793, 'synset': 'pen.n.01', 'synonyms': ['pen'], 'def': 'a writing implement with a point from which ink flows', 'name': 'pen'}, {'frequency': 'c', 'id': 794, 'synset': 'pencil.n.01', 'synonyms': ['pencil'], 'def': 'a thin cylindrical pointed writing implement made of wood and graphite', 'name': 'pencil'}, {'frequency': 'r', 'id': 795, 'synset': 'pencil_box.n.01', 'synonyms': ['pencil_box', 'pencil_case'], 'def': 'a box for holding pencils', 'name': 'pencil_box'}, {'frequency': 'r', 'id': 796, 'synset': 'pencil_sharpener.n.01', 'synonyms': ['pencil_sharpener'], 'def': 'a rotary implement for sharpening the point on pencils', 'name': 'pencil_sharpener'}, {'frequency': 'r', 'id': 797, 'synset': 'pendulum.n.01', 'synonyms': ['pendulum'], 'def': 'an apparatus consisting of an object mounted so that it swings freely under the influence of gravity', 'name': 'pendulum'}, {'frequency': 'c', 'id': 798, 'synset': 'penguin.n.01', 'synonyms': ['penguin'], 'def': 'short-legged flightless birds of cold southern regions having webbed feet and wings modified as flippers', 'name': 'penguin'}, {'frequency': 'r', 'id': 799, 'synset': 'pennant.n.02', 'synonyms': ['pennant'], 'def': 'a flag longer than it is wide (and often tapering)', 'name': 'pennant'}, {'frequency': 'r', 'id': 800, 'synset': 'penny.n.02', 'synonyms': ['penny_(coin)'], 'def': 'a coin worth one-hundredth of the value of the basic unit', 'name': 'penny_(coin)'}, {'frequency': 'c', 'id': 801, 'synset': 'pepper.n.03', 'synonyms': ['pepper', 'peppercorn'], 'def': 'pungent seasoning from the berry of the common pepper plant; whole or ground', 'name': 'pepper'}, {'frequency': 'c', 'id': 802, 'synset': 'pepper_mill.n.01', 'synonyms': ['pepper_mill', 'pepper_grinder'], 'def': 'a mill for grinding pepper', 'name': 'pepper_mill'}, {'frequency': 'c', 'id': 803, 'synset': 'perfume.n.02', 'synonyms': ['perfume'], 'def': 'a toiletry that emits and diffuses a fragrant odor', 'name': 'perfume'}, {'frequency': 'r', 'id': 804, 'synset': 'persimmon.n.02', 'synonyms': ['persimmon'], 'def': 'orange fruit resembling a plum; edible when fully ripe', 'name': 'persimmon'}, {'frequency': 'f', 'id': 805, 'synset': 'person.n.01', 'synonyms': ['baby', 'child', 'boy', 'girl', 'man', 'woman', 'person', 'human'], 'def': 'a human being', 'name': 'baby'}, {'frequency': 'r', 'id': 806, 'synset': 'pet.n.01', 'synonyms': ['pet'], 'def': 'a domesticated animal kept for companionship or amusement', 'name': 'pet'}, {'frequency': 'r', 'id': 807, 'synset': 'petfood.n.01', 'synonyms': ['petfood', 'pet-food'], 'def': 'food prepared for animal pets', 'name': 'petfood'}, {'frequency': 'r', 'id': 808, 'synset': 'pew.n.01', 'synonyms': ['pew_(church_bench)', 'church_bench'], 'def': 'long bench with backs; used in church by the congregation', 'name': 'pew_(church_bench)'}, {'frequency': 'r', 'id': 809, 'synset': 'phonebook.n.01', 'synonyms': ['phonebook', 'telephone_book', 'telephone_directory'], 'def': 'a directory containing an alphabetical list of telephone subscribers and their telephone numbers', 'name': 'phonebook'}, {'frequency': 'c', 'id': 810, 'synset': 'phonograph_record.n.01', 'synonyms': ['phonograph_record', 'phonograph_recording', 'record_(phonograph_recording)'], 'def': 'sound recording consisting of a typically black disk with a continuous groove', 'name': 'phonograph_record'}, {'frequency': 'c', 'id': 811, 'synset': 'piano.n.01', 'synonyms': ['piano'], 'def': 'a keyboard instrument that is played by depressing keys that cause hammers to strike tuned strings and produce sounds', 'name': 'piano'}, {'frequency': 'f', 'id': 812, 'synset': 'pickle.n.01', 'synonyms': ['pickle'], 'def': 'vegetables (especially cucumbers) preserved in brine or vinegar', 'name': 'pickle'}, {'frequency': 'f', 'id': 813, 'synset': 'pickup.n.01', 'synonyms': ['pickup_truck'], 'def': 'a light truck with an open body and low sides and a tailboard', 'name': 'pickup_truck'}, {'frequency': 'c', 'id': 814, 'synset': 'pie.n.01', 'synonyms': ['pie'], 'def': 'dish baked in pastry-lined pan often with a pastry top', 'name': 'pie'}, {'frequency': 'c', 'id': 815, 'synset': 'pigeon.n.01', 'synonyms': ['pigeon'], 'def': 'wild and domesticated birds having a heavy body and short legs', 'name': 'pigeon'}, {'frequency': 'r', 'id': 816, 'synset': 'piggy_bank.n.01', 'synonyms': ['piggy_bank', 'penny_bank'], 'def': "a child's coin bank (often shaped like a pig)", 'name': 'piggy_bank'}, {'frequency': 'f', 'id': 817, 'synset': 'pillow.n.01', 'synonyms': ['pillow'], 'def': 'a cushion to support the head of a sleeping person', 'name': 'pillow'}, {'frequency': 'r', 'id': 818, 'synset': 'pin.n.09', 'synonyms': ['pin_(non_jewelry)'], 'def': 'a small slender (often pointed) piece of wood or metal used to support or fasten or attach things', 'name': 'pin_(non_jewelry)'}, {'frequency': 'f', 'id': 819, 'synset': 'pineapple.n.02', 'synonyms': ['pineapple'], 'def': 'large sweet fleshy tropical fruit with a tuft of stiff leaves', 'name': 'pineapple'}, {'frequency': 'c', 'id': 820, 'synset': 'pinecone.n.01', 'synonyms': ['pinecone'], 'def': 'the seed-producing cone of a pine tree', 'name': 'pinecone'}, {'frequency': 'r', 'id': 821, 'synset': 'ping-pong_ball.n.01', 'synonyms': ['ping-pong_ball'], 'def': 'light hollow ball used in playing table tennis', 'name': 'ping-pong_ball'}, {'frequency': 'r', 'id': 822, 'synset': 'pinwheel.n.03', 'synonyms': ['pinwheel'], 'def': 'a toy consisting of vanes of colored paper or plastic that is pinned to a stick and spins when it is pointed into the wind', 'name': 'pinwheel'}, {'frequency': 'r', 'id': 823, 'synset': 'pipe.n.01', 'synonyms': ['tobacco_pipe'], 'def': 'a tube with a small bowl at one end; used for smoking tobacco', 'name': 'tobacco_pipe'}, {'frequency': 'f', 'id': 824, 'synset': 'pipe.n.02', 'synonyms': ['pipe', 'piping'], 'def': 'a long tube made of metal or plastic that is used to carry water or oil or gas etc.', 'name': 'pipe'}, {'frequency': 'r', 'id': 825, 'synset': 'pistol.n.01', 'synonyms': ['pistol', 'handgun'], 'def': 'a firearm that is held and fired with one hand', 'name': 'pistol'}, {'frequency': 'r', 'id': 826, 'synset': 'pita.n.01', 'synonyms': ['pita_(bread)', 'pocket_bread'], 'def': 'usually small round bread that can open into a pocket for filling', 'name': 'pita_(bread)'}, {'frequency': 'f', 'id': 827, 'synset': 'pitcher.n.02', 'synonyms': ['pitcher_(vessel_for_liquid)', 'ewer'], 'def': 'an open vessel with a handle and a spout for pouring', 'name': 'pitcher_(vessel_for_liquid)'}, {'frequency': 'r', 'id': 828, 'synset': 'pitchfork.n.01', 'synonyms': ['pitchfork'], 'def': 'a long-handled hand tool with sharp widely spaced prongs for lifting and pitching hay', 'name': 'pitchfork'}, {'frequency': 'f', 'id': 829, 'synset': 'pizza.n.01', 'synonyms': ['pizza'], 'def': 'Italian open pie made of thin bread dough spread with a spiced mixture of e.g. tomato sauce and cheese', 'name': 'pizza'}, {'frequency': 'f', 'id': 830, 'synset': 'place_mat.n.01', 'synonyms': ['place_mat'], 'def': 'a mat placed on a table for an individual place setting', 'name': 'place_mat'}, {'frequency': 'f', 'id': 831, 'synset': 'plate.n.04', 'synonyms': ['plate'], 'def': 'dish on which food is served or from which food is eaten', 'name': 'plate'}, {'frequency': 'c', 'id': 832, 'synset': 'platter.n.01', 'synonyms': ['platter'], 'def': 'a large shallow dish used for serving food', 'name': 'platter'}, {'frequency': 'r', 'id': 833, 'synset': 'playing_card.n.01', 'synonyms': ['playing_card'], 'def': 'one of a pack of cards that are used to play card games', 'name': 'playing_card'}, {'frequency': 'r', 'id': 834, 'synset': 'playpen.n.01', 'synonyms': ['playpen'], 'def': 'a portable enclosure in which babies may be left to play', 'name': 'playpen'}, {'frequency': 'c', 'id': 835, 'synset': 'pliers.n.01', 'synonyms': ['pliers', 'plyers'], 'def': 'a gripping hand tool with two hinged arms and (usually) serrated jaws', 'name': 'pliers'}, {'frequency': 'r', 'id': 836, 'synset': 'plow.n.01', 'synonyms': ['plow_(farm_equipment)', 'plough_(farm_equipment)'], 'def': 'a farm tool having one or more heavy blades to break the soil and cut a furrow prior to sowing', 'name': 'plow_(farm_equipment)'}, {'frequency': 'r', 'id': 837, 'synset': 'pocket_watch.n.01', 'synonyms': ['pocket_watch'], 'def': 'a watch that is carried in a small watch pocket', 'name': 'pocket_watch'}, {'frequency': 'c', 'id': 838, 'synset': 'pocketknife.n.01', 'synonyms': ['pocketknife'], 'def': 'a knife with a blade that folds into the handle; suitable for carrying in the pocket', 'name': 'pocketknife'}, {'frequency': 'c', 'id': 839, 'synset': 'poker.n.01', 'synonyms': ['poker_(fire_stirring_tool)', 'stove_poker', 'fire_hook'], 'def': 'fire iron consisting of a metal rod with a handle; used to stir a fire', 'name': 'poker_(fire_stirring_tool)'}, {'frequency': 'f', 'id': 840, 'synset': 'pole.n.01', 'synonyms': ['pole', 'post'], 'def': 'a long (usually round) rod of wood or metal or plastic', 'name': 'pole'}, {'frequency': 'r', 'id': 841, 'synset': 'police_van.n.01', 'synonyms': ['police_van', 'police_wagon', 'paddy_wagon', 'patrol_wagon'], 'def': 'van used by police to transport prisoners', 'name': 'police_van'}, {'frequency': 'f', 'id': 842, 'synset': 'polo_shirt.n.01', 'synonyms': ['polo_shirt', 'sport_shirt'], 'def': 'a shirt with short sleeves designed for comfort and casual wear', 'name': 'polo_shirt'}, {'frequency': 'r', 'id': 843, 'synset': 'poncho.n.01', 'synonyms': ['poncho'], 'def': 'a blanket-like cloak with a hole in the center for the head', 'name': 'poncho'}, {'frequency': 'c', 'id': 844, 'synset': 'pony.n.05', 'synonyms': ['pony'], 'def': 'any of various breeds of small gentle horses usually less than five feet high at the shoulder', 'name': 'pony'}, {'frequency': 'r', 'id': 845, 'synset': 'pool_table.n.01', 'synonyms': ['pool_table', 'billiard_table', 'snooker_table'], 'def': 'game equipment consisting of a heavy table on which pool is played', 'name': 'pool_table'}, {'frequency': 'f', 'id': 846, 'synset': 'pop.n.02', 'synonyms': ['pop_(soda)', 'soda_(pop)', 'tonic', 'soft_drink'], 'def': 'a sweet drink containing carbonated water and flavoring', 'name': 'pop_(soda)'}, {'frequency': 'r', 'id': 847, 'synset': 'portrait.n.02', 'synonyms': ['portrait', 'portrayal'], 'def': 'any likeness of a person, in any medium', 'name': 'portrait'}, {'frequency': 'c', 'id': 848, 'synset': 'postbox.n.01', 'synonyms': ['postbox_(public)', 'mailbox_(public)'], 'def': 'public box for deposit of mail', 'name': 'postbox_(public)'}, {'frequency': 'c', 'id': 849, 'synset': 'postcard.n.01', 'synonyms': ['postcard', 'postal_card', 'mailing-card'], 'def': 'a card for sending messages by post without an envelope', 'name': 'postcard'}, {'frequency': 'f', 'id': 850, 'synset': 'poster.n.01', 'synonyms': ['poster', 'placard'], 'def': 'a sign posted in a public place as an advertisement', 'name': 'poster'}, {'frequency': 'f', 'id': 851, 'synset': 'pot.n.01', 'synonyms': ['pot'], 'def': 'metal or earthenware cooking vessel that is usually round and deep; often has a handle and lid', 'name': 'pot'}, {'frequency': 'f', 'id': 852, 'synset': 'pot.n.04', 'synonyms': ['flowerpot'], 'def': 'a container in which plants are cultivated', 'name': 'flowerpot'}, {'frequency': 'f', 'id': 853, 'synset': 'potato.n.01', 'synonyms': ['potato'], 'def': 'an edible tuber native to South America', 'name': 'potato'}, {'frequency': 'c', 'id': 854, 'synset': 'potholder.n.01', 'synonyms': ['potholder'], 'def': 'an insulated pad for holding hot pots', 'name': 'potholder'}, {'frequency': 'c', 'id': 855, 'synset': 'pottery.n.01', 'synonyms': ['pottery', 'clayware'], 'def': 'ceramic ware made from clay and baked in a kiln', 'name': 'pottery'}, {'frequency': 'c', 'id': 856, 'synset': 'pouch.n.01', 'synonyms': ['pouch'], 'def': 'a small or medium size container for holding or carrying things', 'name': 'pouch'}, {'frequency': 'r', 'id': 857, 'synset': 'power_shovel.n.01', 'synonyms': ['power_shovel', 'excavator', 'digger'], 'def': 'a machine for excavating', 'name': 'power_shovel'}, {'frequency': 'c', 'id': 858, 'synset': 'prawn.n.01', 'synonyms': ['prawn', 'shrimp'], 'def': 'any of various edible decapod crustaceans', 'name': 'prawn'}, {'frequency': 'f', 'id': 859, 'synset': 'printer.n.03', 'synonyms': ['printer', 'printing_machine'], 'def': 'a machine that prints', 'name': 'printer'}, {'frequency': 'c', 'id': 860, 'synset': 'projectile.n.01', 'synonyms': ['projectile_(weapon)', 'missile'], 'def': 'a weapon that is forcibly thrown or projected at a targets', 'name': 'projectile_(weapon)'}, {'frequency': 'c', 'id': 861, 'synset': 'projector.n.02', 'synonyms': ['projector'], 'def': 'an optical instrument that projects an enlarged image onto a screen', 'name': 'projector'}, {'frequency': 'f', 'id': 862, 'synset': 'propeller.n.01', 'synonyms': ['propeller', 'propellor'], 'def': 'a mechanical device that rotates to push against air or water', 'name': 'propeller'}, {'frequency': 'r', 'id': 863, 'synset': 'prune.n.01', 'synonyms': ['prune'], 'def': 'dried plum', 'name': 'prune'}, {'frequency': 'r', 'id': 864, 'synset': 'pudding.n.01', 'synonyms': ['pudding'], 'def': 'any of various soft thick unsweetened baked dishes', 'name': 'pudding'}, {'frequency': 'r', 'id': 865, 'synset': 'puffer.n.02', 'synonyms': ['puffer_(fish)', 'pufferfish', 'blowfish', 'globefish'], 'def': 'fishes whose elongated spiny body can inflate itself with water or air to form a globe', 'name': 'puffer_(fish)'}, {'frequency': 'r', 'id': 866, 'synset': 'puffin.n.01', 'synonyms': ['puffin'], 'def': 'seabirds having short necks and brightly colored compressed bills', 'name': 'puffin'}, {'frequency': 'r', 'id': 867, 'synset': 'pug.n.01', 'synonyms': ['pug-dog'], 'def': 'small compact smooth-coated breed of Asiatic origin having a tightly curled tail and broad flat wrinkled muzzle', 'name': 'pug-dog'}, {'frequency': 'c', 'id': 868, 'synset': 'pumpkin.n.02', 'synonyms': ['pumpkin'], 'def': 'usually large pulpy deep-yellow round fruit of the squash family maturing in late summer or early autumn', 'name': 'pumpkin'}, {'frequency': 'r', 'id': 869, 'synset': 'punch.n.03', 'synonyms': ['puncher'], 'def': 'a tool for making holes or indentations', 'name': 'puncher'}, {'frequency': 'r', 'id': 870, 'synset': 'puppet.n.01', 'synonyms': ['puppet', 'marionette'], 'def': 'a small figure of a person operated from above with strings by a puppeteer', 'name': 'puppet'}, {'frequency': 'r', 'id': 871, 'synset': 'puppy.n.01', 'synonyms': ['puppy'], 'def': 'a young dog', 'name': 'puppy'}, {'frequency': 'r', 'id': 872, 'synset': 'quesadilla.n.01', 'synonyms': ['quesadilla'], 'def': 'a tortilla that is filled with cheese and heated', 'name': 'quesadilla'}, {'frequency': 'r', 'id': 873, 'synset': 'quiche.n.02', 'synonyms': ['quiche'], 'def': 'a tart filled with rich unsweetened custard; often contains other ingredients (as cheese or ham or seafood or vegetables)', 'name': 'quiche'}, {'frequency': 'f', 'id': 874, 'synset': 'quilt.n.01', 'synonyms': ['quilt', 'comforter'], 'def': 'bedding made of two layers of cloth filled with stuffing and stitched together', 'name': 'quilt'}, {'frequency': 'c', 'id': 875, 'synset': 'rabbit.n.01', 'synonyms': ['rabbit'], 'def': 'any of various burrowing animals of the family Leporidae having long ears and short tails', 'name': 'rabbit'}, {'frequency': 'r', 'id': 876, 'synset': 'racer.n.02', 'synonyms': ['race_car', 'racing_car'], 'def': 'a fast car that competes in races', 'name': 'race_car'}, {'frequency': 'c', 'id': 877, 'synset': 'racket.n.04', 'synonyms': ['racket', 'racquet'], 'def': 'a sports implement used to strike a ball in various games', 'name': 'racket'}, {'frequency': 'r', 'id': 878, 'synset': 'radar.n.01', 'synonyms': ['radar'], 'def': 'measuring instrument in which the echo of a pulse of microwave radiation is used to detect and locate distant objects', 'name': 'radar'}, {'frequency': 'c', 'id': 879, 'synset': 'radiator.n.03', 'synonyms': ['radiator'], 'def': 'a mechanism consisting of a metal honeycomb through which hot fluids circulate', 'name': 'radiator'}, {'frequency': 'c', 'id': 880, 'synset': 'radio_receiver.n.01', 'synonyms': ['radio_receiver', 'radio_set', 'radio', 'tuner_(radio)'], 'def': 'an electronic receiver that detects and demodulates and amplifies transmitted radio signals', 'name': 'radio_receiver'}, {'frequency': 'c', 'id': 881, 'synset': 'radish.n.03', 'synonyms': ['radish', 'daikon'], 'def': 'pungent edible root of any of various cultivated radish plants', 'name': 'radish'}, {'frequency': 'c', 'id': 882, 'synset': 'raft.n.01', 'synonyms': ['raft'], 'def': 'a flat float (usually made of logs or planks) that can be used for transport or as a platform for swimmers', 'name': 'raft'}, {'frequency': 'r', 'id': 883, 'synset': 'rag_doll.n.01', 'synonyms': ['rag_doll'], 'def': 'a cloth doll that is stuffed and (usually) painted', 'name': 'rag_doll'}, {'frequency': 'c', 'id': 884, 'synset': 'raincoat.n.01', 'synonyms': ['raincoat', 'waterproof_jacket'], 'def': 'a water-resistant coat', 'name': 'raincoat'}, {'frequency': 'c', 'id': 885, 'synset': 'ram.n.05', 'synonyms': ['ram_(animal)'], 'def': 'uncastrated adult male sheep', 'name': 'ram_(animal)'}, {'frequency': 'c', 'id': 886, 'synset': 'raspberry.n.02', 'synonyms': ['raspberry'], 'def': 'red or black edible aggregate berries usually smaller than the related blackberries', 'name': 'raspberry'}, {'frequency': 'r', 'id': 887, 'synset': 'rat.n.01', 'synonyms': ['rat'], 'def': 'any of various long-tailed rodents similar to but larger than a mouse', 'name': 'rat'}, {'frequency': 'c', 'id': 888, 'synset': 'razorblade.n.01', 'synonyms': ['razorblade'], 'def': 'a blade that has very sharp edge', 'name': 'razorblade'}, {'frequency': 'c', 'id': 889, 'synset': 'reamer.n.01', 'synonyms': ['reamer_(juicer)', 'juicer', 'juice_reamer'], 'def': 'a squeezer with a conical ridged center that is used for squeezing juice from citrus fruit', 'name': 'reamer_(juicer)'}, {'frequency': 'f', 'id': 890, 'synset': 'rearview_mirror.n.01', 'synonyms': ['rearview_mirror'], 'def': 'car mirror that reflects the view out of the rear window', 'name': 'rearview_mirror'}, {'frequency': 'c', 'id': 891, 'synset': 'receipt.n.02', 'synonyms': ['receipt'], 'def': 'an acknowledgment (usually tangible) that payment has been made', 'name': 'receipt'}, {'frequency': 'c', 'id': 892, 'synset': 'recliner.n.01', 'synonyms': ['recliner', 'reclining_chair', 'lounger_(chair)'], 'def': 'an armchair whose back can be lowered and foot can be raised to allow the sitter to recline in it', 'name': 'recliner'}, {'frequency': 'r', 'id': 893, 'synset': 'record_player.n.01', 'synonyms': ['record_player', 'phonograph_(record_player)', 'turntable'], 'def': 'machine in which rotating records cause a stylus to vibrate and the vibrations are amplified acoustically or electronically', 'name': 'record_player'}, {'frequency': 'r', 'id': 894, 'synset': 'red_cabbage.n.02', 'synonyms': ['red_cabbage'], 'def': 'compact head of purplish-red leaves', 'name': 'red_cabbage'}, {'frequency': 'f', 'id': 895, 'synset': 'reflector.n.01', 'synonyms': ['reflector'], 'def': 'device that reflects light, radiation, etc.', 'name': 'reflector'}, {'frequency': 'f', 'id': 896, 'synset': 'remote_control.n.01', 'synonyms': ['remote_control'], 'def': 'a device that can be used to control a machine or apparatus from a distance', 'name': 'remote_control'}, {'frequency': 'c', 'id': 897, 'synset': 'rhinoceros.n.01', 'synonyms': ['rhinoceros'], 'def': 'massive powerful herbivorous odd-toed ungulate of southeast Asia and Africa having very thick skin and one or two horns on the snout', 'name': 'rhinoceros'}, {'frequency': 'r', 'id': 898, 'synset': 'rib.n.03', 'synonyms': ['rib_(food)'], 'def': 'cut of meat including one or more ribs', 'name': 'rib_(food)'}, {'frequency': 'r', 'id': 899, 'synset': 'rifle.n.01', 'synonyms': ['rifle'], 'def': 'a shoulder firearm with a long barrel', 'name': 'rifle'}, {'frequency': 'f', 'id': 900, 'synset': 'ring.n.08', 'synonyms': ['ring'], 'def': 'jewelry consisting of a circlet of precious metal (often set with jewels) worn on the finger', 'name': 'ring'}, {'frequency': 'r', 'id': 901, 'synset': 'river_boat.n.01', 'synonyms': ['river_boat'], 'def': 'a boat used on rivers or to ply a river', 'name': 'river_boat'}, {'frequency': 'r', 'id': 902, 'synset': 'road_map.n.02', 'synonyms': ['road_map'], 'def': '(NOT A ROAD) a MAP showing roads (for automobile travel)', 'name': 'road_map'}, {'frequency': 'c', 'id': 903, 'synset': 'robe.n.01', 'synonyms': ['robe'], 'def': 'any loose flowing garment', 'name': 'robe'}, {'frequency': 'c', 'id': 904, 'synset': 'rocking_chair.n.01', 'synonyms': ['rocking_chair'], 'def': 'a chair mounted on rockers', 'name': 'rocking_chair'}, {'frequency': 'r', 'id': 905, 'synset': 'roller_skate.n.01', 'synonyms': ['roller_skate'], 'def': 'a shoe with pairs of rollers (small hard wheels) fixed to the sole', 'name': 'roller_skate'}, {'frequency': 'r', 'id': 906, 'synset': 'rollerblade.n.01', 'synonyms': ['Rollerblade'], 'def': 'an in-line variant of a roller skate', 'name': 'Rollerblade'}, {'frequency': 'c', 'id': 907, 'synset': 'rolling_pin.n.01', 'synonyms': ['rolling_pin'], 'def': 'utensil consisting of a cylinder (usually of wood) with a handle at each end; used to roll out dough', 'name': 'rolling_pin'}, {'frequency': 'r', 'id': 908, 'synset': 'root_beer.n.01', 'synonyms': ['root_beer'], 'def': 'carbonated drink containing extracts of roots and herbs', 'name': 'root_beer'}, {'frequency': 'c', 'id': 909, 'synset': 'router.n.02', 'synonyms': ['router_(computer_equipment)'], 'def': 'a device that forwards data packets between computer networks', 'name': 'router_(computer_equipment)'}, {'frequency': 'f', 'id': 910, 'synset': 'rubber_band.n.01', 'synonyms': ['rubber_band', 'elastic_band'], 'def': 'a narrow band of elastic rubber used to hold things (such as papers) together', 'name': 'rubber_band'}, {'frequency': 'c', 'id': 911, 'synset': 'runner.n.08', 'synonyms': ['runner_(carpet)'], 'def': 'a long narrow carpet', 'name': 'runner_(carpet)'}, {'frequency': 'f', 'id': 912, 'synset': 'sack.n.01', 'synonyms': ['plastic_bag', 'paper_bag'], 'def': "a bag made of paper or plastic for holding customer's purchases", 'name': 'plastic_bag'}, {'frequency': 'f', 'id': 913, 'synset': 'saddle.n.01', 'synonyms': ['saddle_(on_an_animal)'], 'def': 'a seat for the rider of a horse or camel', 'name': 'saddle_(on_an_animal)'}, {'frequency': 'f', 'id': 914, 'synset': 'saddle_blanket.n.01', 'synonyms': ['saddle_blanket', 'saddlecloth', 'horse_blanket'], 'def': 'stable gear consisting of a blanket placed under the saddle', 'name': 'saddle_blanket'}, {'frequency': 'c', 'id': 915, 'synset': 'saddlebag.n.01', 'synonyms': ['saddlebag'], 'def': 'a large bag (or pair of bags) hung over a saddle', 'name': 'saddlebag'}, {'frequency': 'r', 'id': 916, 'synset': 'safety_pin.n.01', 'synonyms': ['safety_pin'], 'def': 'a pin in the form of a clasp; has a guard so the point of the pin will not stick the user', 'name': 'safety_pin'}, {'frequency': 'c', 'id': 917, 'synset': 'sail.n.01', 'synonyms': ['sail'], 'def': 'a large piece of fabric by means of which wind is used to propel a sailing vessel', 'name': 'sail'}, {'frequency': 'c', 'id': 918, 'synset': 'salad.n.01', 'synonyms': ['salad'], 'def': 'food mixtures either arranged on a plate or tossed and served with a moist dressing; usually consisting of or including greens', 'name': 'salad'}, {'frequency': 'r', 'id': 919, 'synset': 'salad_plate.n.01', 'synonyms': ['salad_plate', 'salad_bowl'], 'def': 'a plate or bowl for individual servings of salad', 'name': 'salad_plate'}, {'frequency': 'r', 'id': 920, 'synset': 'salami.n.01', 'synonyms': ['salami'], 'def': 'highly seasoned fatty sausage of pork and beef usually dried', 'name': 'salami'}, {'frequency': 'r', 'id': 921, 'synset': 'salmon.n.01', 'synonyms': ['salmon_(fish)'], 'def': 'any of various large food and game fishes of northern waters', 'name': 'salmon_(fish)'}, {'frequency': 'r', 'id': 922, 'synset': 'salmon.n.03', 'synonyms': ['salmon_(food)'], 'def': 'flesh of any of various marine or freshwater fish of the family Salmonidae', 'name': 'salmon_(food)'}, {'frequency': 'r', 'id': 923, 'synset': 'salsa.n.01', 'synonyms': ['salsa'], 'def': 'spicy sauce of tomatoes and onions and chili peppers to accompany Mexican foods', 'name': 'salsa'}, {'frequency': 'f', 'id': 924, 'synset': 'saltshaker.n.01', 'synonyms': ['saltshaker'], 'def': 'a shaker with a perforated top for sprinkling salt', 'name': 'saltshaker'}, {'frequency': 'f', 'id': 925, 'synset': 'sandal.n.01', 'synonyms': ['sandal_(type_of_shoe)'], 'def': 'a shoe consisting of a sole fastened by straps to the foot', 'name': 'sandal_(type_of_shoe)'}, {'frequency': 'f', 'id': 926, 'synset': 'sandwich.n.01', 'synonyms': ['sandwich'], 'def': 'two (or more) slices of bread with a filling between them', 'name': 'sandwich'}, {'frequency': 'r', 'id': 927, 'synset': 'satchel.n.01', 'synonyms': ['satchel'], 'def': 'luggage consisting of a small case with a flat bottom and (usually) a shoulder strap', 'name': 'satchel'}, {'frequency': 'r', 'id': 928, 'synset': 'saucepan.n.01', 'synonyms': ['saucepan'], 'def': 'a deep pan with a handle; used for stewing or boiling', 'name': 'saucepan'}, {'frequency': 'f', 'id': 929, 'synset': 'saucer.n.02', 'synonyms': ['saucer'], 'def': 'a small shallow dish for holding a cup at the table', 'name': 'saucer'}, {'frequency': 'f', 'id': 930, 'synset': 'sausage.n.01', 'synonyms': ['sausage'], 'def': 'highly seasoned minced meat stuffed in casings', 'name': 'sausage'}, {'frequency': 'r', 'id': 931, 'synset': 'sawhorse.n.01', 'synonyms': ['sawhorse', 'sawbuck'], 'def': 'a framework for holding wood that is being sawed', 'name': 'sawhorse'}, {'frequency': 'r', 'id': 932, 'synset': 'sax.n.02', 'synonyms': ['saxophone'], 'def': "a wind instrument with a `J'-shaped form typically made of brass", 'name': 'saxophone'}, {'frequency': 'f', 'id': 933, 'synset': 'scale.n.07', 'synonyms': ['scale_(measuring_instrument)'], 'def': 'a measuring instrument for weighing; shows amount of mass', 'name': 'scale_(measuring_instrument)'}, {'frequency': 'r', 'id': 934, 'synset': 'scarecrow.n.01', 'synonyms': ['scarecrow', 'strawman'], 'def': 'an effigy in the shape of a man to frighten birds away from seeds', 'name': 'scarecrow'}, {'frequency': 'f', 'id': 935, 'synset': 'scarf.n.01', 'synonyms': ['scarf'], 'def': 'a garment worn around the head or neck or shoulders for warmth or decoration', 'name': 'scarf'}, {'frequency': 'c', 'id': 936, 'synset': 'school_bus.n.01', 'synonyms': ['school_bus'], 'def': 'a bus used to transport children to or from school', 'name': 'school_bus'}, {'frequency': 'f', 'id': 937, 'synset': 'scissors.n.01', 'synonyms': ['scissors'], 'def': 'a tool having two crossed pivoting blades with looped handles', 'name': 'scissors'}, {'frequency': 'c', 'id': 938, 'synset': 'scoreboard.n.01', 'synonyms': ['scoreboard'], 'def': 'a large board for displaying the score of a contest (and some other information)', 'name': 'scoreboard'}, {'frequency': 'c', 'id': 939, 'synset': 'scrambled_eggs.n.01', 'synonyms': ['scrambled_eggs'], 'def': 'eggs beaten and cooked to a soft firm consistency while stirring', 'name': 'scrambled_eggs'}, {'frequency': 'r', 'id': 940, 'synset': 'scraper.n.01', 'synonyms': ['scraper'], 'def': 'any of various hand tools for scraping', 'name': 'scraper'}, {'frequency': 'r', 'id': 941, 'synset': 'scratcher.n.03', 'synonyms': ['scratcher'], 'def': 'a device used for scratching', 'name': 'scratcher'}, {'frequency': 'c', 'id': 942, 'synset': 'screwdriver.n.01', 'synonyms': ['screwdriver'], 'def': 'a hand tool for driving screws; has a tip that fits into the head of a screw', 'name': 'screwdriver'}, {'frequency': 'c', 'id': 943, 'synset': 'scrub_brush.n.01', 'synonyms': ['scrubbing_brush'], 'def': 'a brush with short stiff bristles for heavy cleaning', 'name': 'scrubbing_brush'}, {'frequency': 'c', 'id': 944, 'synset': 'sculpture.n.01', 'synonyms': ['sculpture'], 'def': 'a three-dimensional work of art', 'name': 'sculpture'}, {'frequency': 'r', 'id': 945, 'synset': 'seabird.n.01', 'synonyms': ['seabird', 'seafowl'], 'def': 'a bird that frequents coastal waters and the open ocean: gulls; pelicans; gannets; cormorants; albatrosses; petrels; etc.', 'name': 'seabird'}, {'frequency': 'r', 'id': 946, 'synset': 'seahorse.n.02', 'synonyms': ['seahorse'], 'def': 'small fish with horse-like heads bent sharply downward and curled tails', 'name': 'seahorse'}, {'frequency': 'r', 'id': 947, 'synset': 'seaplane.n.01', 'synonyms': ['seaplane', 'hydroplane'], 'def': 'an airplane that can land on or take off from water', 'name': 'seaplane'}, {'frequency': 'c', 'id': 948, 'synset': 'seashell.n.01', 'synonyms': ['seashell'], 'def': 'the shell of a marine organism', 'name': 'seashell'}, {'frequency': 'r', 'id': 949, 'synset': 'seedling.n.01', 'synonyms': ['seedling'], 'def': 'young plant or tree grown from a seed', 'name': 'seedling'}, {'frequency': 'c', 'id': 950, 'synset': 'serving_dish.n.01', 'synonyms': ['serving_dish'], 'def': 'a dish used for serving food', 'name': 'serving_dish'}, {'frequency': 'r', 'id': 951, 'synset': 'sewing_machine.n.01', 'synonyms': ['sewing_machine'], 'def': 'a textile machine used as a home appliance for sewing', 'name': 'sewing_machine'}, {'frequency': 'r', 'id': 952, 'synset': 'shaker.n.03', 'synonyms': ['shaker'], 'def': 'a container in which something can be shaken', 'name': 'shaker'}, {'frequency': 'c', 'id': 953, 'synset': 'shampoo.n.01', 'synonyms': ['shampoo'], 'def': 'cleansing agent consisting of soaps or detergents used for washing the hair', 'name': 'shampoo'}, {'frequency': 'r', 'id': 954, 'synset': 'shark.n.01', 'synonyms': ['shark'], 'def': 'typically large carnivorous fishes with sharpe teeth', 'name': 'shark'}, {'frequency': 'r', 'id': 955, 'synset': 'sharpener.n.01', 'synonyms': ['sharpener'], 'def': 'any implement that is used to make something (an edge or a point) sharper', 'name': 'sharpener'}, {'frequency': 'r', 'id': 956, 'synset': 'sharpie.n.03', 'synonyms': ['Sharpie'], 'def': 'a pen with indelible ink that will write on any surface', 'name': 'Sharpie'}, {'frequency': 'r', 'id': 957, 'synset': 'shaver.n.03', 'synonyms': ['shaver_(electric)', 'electric_shaver', 'electric_razor'], 'def': 'a razor powered by an electric motor', 'name': 'shaver_(electric)'}, {'frequency': 'c', 'id': 958, 'synset': 'shaving_cream.n.01', 'synonyms': ['shaving_cream', 'shaving_soap'], 'def': 'toiletry consisting that forms a rich lather for softening the beard before shaving', 'name': 'shaving_cream'}, {'frequency': 'r', 'id': 959, 'synset': 'shawl.n.01', 'synonyms': ['shawl'], 'def': 'cloak consisting of an oblong piece of cloth used to cover the head and shoulders', 'name': 'shawl'}, {'frequency': 'r', 'id': 960, 'synset': 'shears.n.01', 'synonyms': ['shears'], 'def': 'large scissors with strong blades', 'name': 'shears'}, {'frequency': 'f', 'id': 961, 'synset': 'sheep.n.01', 'synonyms': ['sheep'], 'def': 'woolly usually horned ruminant mammal related to the goat', 'name': 'sheep'}, {'frequency': 'r', 'id': 962, 'synset': 'shepherd_dog.n.01', 'synonyms': ['shepherd_dog', 'sheepdog'], 'def': 'any of various usually long-haired breeds of dog reared to herd and guard sheep', 'name': 'shepherd_dog'}, {'frequency': 'r', 'id': 963, 'synset': 'sherbert.n.01', 'synonyms': ['sherbert', 'sherbet'], 'def': 'a frozen dessert made primarily of fruit juice and sugar', 'name': 'sherbert'}, {'frequency': 'r', 'id': 964, 'synset': 'shield.n.02', 'synonyms': ['shield'], 'def': 'armor carried on the arm to intercept blows', 'name': 'shield'}, {'frequency': 'f', 'id': 965, 'synset': 'shirt.n.01', 'synonyms': ['shirt'], 'def': 'a garment worn on the upper half of the body', 'name': 'shirt'}, {'frequency': 'f', 'id': 966, 'synset': 'shoe.n.01', 'synonyms': ['shoe', 'sneaker_(type_of_shoe)', 'tennis_shoe'], 'def': 'common footwear covering the foot', 'name': 'shoe'}, {'frequency': 'c', 'id': 967, 'synset': 'shopping_bag.n.01', 'synonyms': ['shopping_bag'], 'def': 'a bag made of plastic or strong paper (often with handles); used to transport goods after shopping', 'name': 'shopping_bag'}, {'frequency': 'c', 'id': 968, 'synset': 'shopping_cart.n.01', 'synonyms': ['shopping_cart'], 'def': 'a handcart that holds groceries or other goods while shopping', 'name': 'shopping_cart'}, {'frequency': 'f', 'id': 969, 'synset': 'short_pants.n.01', 'synonyms': ['short_pants', 'shorts_(clothing)', 'trunks_(clothing)'], 'def': 'trousers that end at or above the knee', 'name': 'short_pants'}, {'frequency': 'r', 'id': 970, 'synset': 'shot_glass.n.01', 'synonyms': ['shot_glass'], 'def': 'a small glass adequate to hold a single swallow of whiskey', 'name': 'shot_glass'}, {'frequency': 'c', 'id': 971, 'synset': 'shoulder_bag.n.01', 'synonyms': ['shoulder_bag'], 'def': 'a large handbag that can be carried by a strap looped over the shoulder', 'name': 'shoulder_bag'}, {'frequency': 'c', 'id': 972, 'synset': 'shovel.n.01', 'synonyms': ['shovel'], 'def': 'a hand tool for lifting loose material such as snow, dirt, etc.', 'name': 'shovel'}, {'frequency': 'f', 'id': 973, 'synset': 'shower.n.01', 'synonyms': ['shower_head'], 'def': 'a plumbing fixture that sprays water over you', 'name': 'shower_head'}, {'frequency': 'f', 'id': 974, 'synset': 'shower_curtain.n.01', 'synonyms': ['shower_curtain'], 'def': 'a curtain that keeps water from splashing out of the shower area', 'name': 'shower_curtain'}, {'frequency': 'r', 'id': 975, 'synset': 'shredder.n.01', 'synonyms': ['shredder_(for_paper)'], 'def': 'a device that shreds documents', 'name': 'shredder_(for_paper)'}, {'frequency': 'r', 'id': 976, 'synset': 'sieve.n.01', 'synonyms': ['sieve', 'screen_(sieve)'], 'def': 'a strainer for separating lumps from powdered material or grading particles', 'name': 'sieve'}, {'frequency': 'f', 'id': 977, 'synset': 'signboard.n.01', 'synonyms': ['signboard'], 'def': 'structure displaying a board on which advertisements can be posted', 'name': 'signboard'}, {'frequency': 'c', 'id': 978, 'synset': 'silo.n.01', 'synonyms': ['silo'], 'def': 'a cylindrical tower used for storing goods', 'name': 'silo'}, {'frequency': 'f', 'id': 979, 'synset': 'sink.n.01', 'synonyms': ['sink'], 'def': 'plumbing fixture consisting of a water basin fixed to a wall or floor and having a drainpipe', 'name': 'sink'}, {'frequency': 'f', 'id': 980, 'synset': 'skateboard.n.01', 'synonyms': ['skateboard'], 'def': 'a board with wheels that is ridden in a standing or crouching position and propelled by foot', 'name': 'skateboard'}, {'frequency': 'c', 'id': 981, 'synset': 'skewer.n.01', 'synonyms': ['skewer'], 'def': 'a long pin for holding meat in position while it is being roasted', 'name': 'skewer'}, {'frequency': 'f', 'id': 982, 'synset': 'ski.n.01', 'synonyms': ['ski'], 'def': 'sports equipment for skiing on snow', 'name': 'ski'}, {'frequency': 'f', 'id': 983, 'synset': 'ski_boot.n.01', 'synonyms': ['ski_boot'], 'def': 'a stiff boot that is fastened to a ski with a ski binding', 'name': 'ski_boot'}, {'frequency': 'f', 'id': 984, 'synset': 'ski_parka.n.01', 'synonyms': ['ski_parka', 'ski_jacket'], 'def': 'a parka to be worn while skiing', 'name': 'ski_parka'}, {'frequency': 'f', 'id': 985, 'synset': 'ski_pole.n.01', 'synonyms': ['ski_pole'], 'def': 'a pole with metal points used as an aid in skiing', 'name': 'ski_pole'}, {'frequency': 'f', 'id': 986, 'synset': 'skirt.n.02', 'synonyms': ['skirt'], 'def': 'a garment hanging from the waist; worn mainly by girls and women', 'name': 'skirt'}, {'frequency': 'c', 'id': 987, 'synset': 'sled.n.01', 'synonyms': ['sled', 'sledge', 'sleigh'], 'def': 'a vehicle or flat object for transportation over snow by sliding or pulled by dogs, etc.', 'name': 'sled'}, {'frequency': 'c', 'id': 988, 'synset': 'sleeping_bag.n.01', 'synonyms': ['sleeping_bag'], 'def': 'large padded bag designed to be slept in outdoors', 'name': 'sleeping_bag'}, {'frequency': 'r', 'id': 989, 'synset': 'sling.n.05', 'synonyms': ['sling_(bandage)', 'triangular_bandage'], 'def': 'bandage to support an injured forearm; slung over the shoulder or neck', 'name': 'sling_(bandage)'}, {'frequency': 'c', 'id': 990, 'synset': 'slipper.n.01', 'synonyms': ['slipper_(footwear)', 'carpet_slipper_(footwear)'], 'def': 'low footwear that can be slipped on and off easily; usually worn indoors', 'name': 'slipper_(footwear)'}, {'frequency': 'r', 'id': 991, 'synset': 'smoothie.n.02', 'synonyms': ['smoothie'], 'def': 'a thick smooth drink consisting of fresh fruit pureed with ice cream or yoghurt or milk', 'name': 'smoothie'}, {'frequency': 'r', 'id': 992, 'synset': 'snake.n.01', 'synonyms': ['snake', 'serpent'], 'def': 'limbless scaly elongate reptile; some are venomous', 'name': 'snake'}, {'frequency': 'f', 'id': 993, 'synset': 'snowboard.n.01', 'synonyms': ['snowboard'], 'def': 'a board that resembles a broad ski or a small surfboard; used in a standing position to slide down snow-covered slopes', 'name': 'snowboard'}, {'frequency': 'c', 'id': 994, 'synset': 'snowman.n.01', 'synonyms': ['snowman'], 'def': 'a figure of a person made of packed snow', 'name': 'snowman'}, {'frequency': 'c', 'id': 995, 'synset': 'snowmobile.n.01', 'synonyms': ['snowmobile'], 'def': 'tracked vehicle for travel on snow having skis in front', 'name': 'snowmobile'}, {'frequency': 'f', 'id': 996, 'synset': 'soap.n.01', 'synonyms': ['soap'], 'def': 'a cleansing agent made from the salts of vegetable or animal fats', 'name': 'soap'}, {'frequency': 'f', 'id': 997, 'synset': 'soccer_ball.n.01', 'synonyms': ['soccer_ball'], 'def': "an inflated ball used in playing soccer (called `football' outside of the United States)", 'name': 'soccer_ball'}, {'frequency': 'f', 'id': 998, 'synset': 'sock.n.01', 'synonyms': ['sock'], 'def': 'cloth covering for the foot; worn inside the shoe; reaches to between the ankle and the knee', 'name': 'sock'}, {'frequency': 'r', 'id': 999, 'synset': 'soda_fountain.n.02', 'synonyms': ['soda_fountain'], 'def': 'an apparatus for dispensing soda water', 'name': 'soda_fountain'}, {'frequency': 'r', 'id': 1000, 'synset': 'soda_water.n.01', 'synonyms': ['carbonated_water', 'club_soda', 'seltzer', 'sparkling_water'], 'def': 'effervescent beverage artificially charged with carbon dioxide', 'name': 'carbonated_water'}, {'frequency': 'f', 'id': 1001, 'synset': 'sofa.n.01', 'synonyms': ['sofa', 'couch', 'lounge'], 'def': 'an upholstered seat for more than one person', 'name': 'sofa'}, {'frequency': 'r', 'id': 1002, 'synset': 'softball.n.01', 'synonyms': ['softball'], 'def': 'ball used in playing softball', 'name': 'softball'}, {'frequency': 'c', 'id': 1003, 'synset': 'solar_array.n.01', 'synonyms': ['solar_array', 'solar_battery', 'solar_panel'], 'def': 'electrical device consisting of a large array of connected solar cells', 'name': 'solar_array'}, {'frequency': 'r', 'id': 1004, 'synset': 'sombrero.n.02', 'synonyms': ['sombrero'], 'def': 'a straw hat with a tall crown and broad brim; worn in American southwest and in Mexico', 'name': 'sombrero'}, {'frequency': 'c', 'id': 1005, 'synset': 'soup.n.01', 'synonyms': ['soup'], 'def': 'liquid food especially of meat or fish or vegetable stock often containing pieces of solid food', 'name': 'soup'}, {'frequency': 'r', 'id': 1006, 'synset': 'soup_bowl.n.01', 'synonyms': ['soup_bowl'], 'def': 'a bowl for serving soup', 'name': 'soup_bowl'}, {'frequency': 'c', 'id': 1007, 'synset': 'soupspoon.n.01', 'synonyms': ['soupspoon'], 'def': 'a spoon with a rounded bowl for eating soup', 'name': 'soupspoon'}, {'frequency': 'c', 'id': 1008, 'synset': 'sour_cream.n.01', 'synonyms': ['sour_cream', 'soured_cream'], 'def': 'soured light cream', 'name': 'sour_cream'}, {'frequency': 'r', 'id': 1009, 'synset': 'soya_milk.n.01', 'synonyms': ['soya_milk', 'soybean_milk', 'soymilk'], 'def': 'a milk substitute containing soybean flour and water; used in some infant formulas and in making tofu', 'name': 'soya_milk'}, {'frequency': 'r', 'id': 1010, 'synset': 'space_shuttle.n.01', 'synonyms': ['space_shuttle'], 'def': "a reusable spacecraft with wings for a controlled descent through the Earth's atmosphere", 'name': 'space_shuttle'}, {'frequency': 'r', 'id': 1011, 'synset': 'sparkler.n.02', 'synonyms': ['sparkler_(fireworks)'], 'def': 'a firework that burns slowly and throws out a shower of sparks', 'name': 'sparkler_(fireworks)'}, {'frequency': 'f', 'id': 1012, 'synset': 'spatula.n.02', 'synonyms': ['spatula'], 'def': 'a hand tool with a thin flexible blade used to mix or spread soft substances', 'name': 'spatula'}, {'frequency': 'r', 'id': 1013, 'synset': 'spear.n.01', 'synonyms': ['spear', 'lance'], 'def': 'a long pointed rod used as a tool or weapon', 'name': 'spear'}, {'frequency': 'f', 'id': 1014, 'synset': 'spectacles.n.01', 'synonyms': ['spectacles', 'specs', 'eyeglasses', 'glasses'], 'def': 'optical instrument consisting of a frame that holds a pair of lenses for correcting defective vision', 'name': 'spectacles'}, {'frequency': 'c', 'id': 1015, 'synset': 'spice_rack.n.01', 'synonyms': ['spice_rack'], 'def': 'a rack for displaying containers filled with spices', 'name': 'spice_rack'}, {'frequency': 'r', 'id': 1016, 'synset': 'spider.n.01', 'synonyms': ['spider'], 'def': 'predatory arachnid with eight legs, two poison fangs, two feelers, and usually two silk-spinning organs at the back end of the body', 'name': 'spider'}, {'frequency': 'c', 'id': 1017, 'synset': 'sponge.n.01', 'synonyms': ['sponge'], 'def': 'a porous mass usable to absorb water typically used for cleaning', 'name': 'sponge'}, {'frequency': 'f', 'id': 1018, 'synset': 'spoon.n.01', 'synonyms': ['spoon'], 'def': 'a piece of cutlery with a shallow bowl-shaped container and a handle', 'name': 'spoon'}, {'frequency': 'c', 'id': 1019, 'synset': 'sportswear.n.01', 'synonyms': ['sportswear', 'athletic_wear', 'activewear'], 'def': 'attire worn for sport or for casual wear', 'name': 'sportswear'}, {'frequency': 'c', 'id': 1020, 'synset': 'spotlight.n.02', 'synonyms': ['spotlight'], 'def': 'a lamp that produces a strong beam of light to illuminate a restricted area; used to focus attention of a stage performer', 'name': 'spotlight'}, {'frequency': 'r', 'id': 1021, 'synset': 'squirrel.n.01', 'synonyms': ['squirrel'], 'def': 'a kind of arboreal rodent having a long bushy tail', 'name': 'squirrel'}, {'frequency': 'c', 'id': 1022, 'synset': 'stapler.n.01', 'synonyms': ['stapler_(stapling_machine)'], 'def': 'a machine that inserts staples into sheets of paper in order to fasten them together', 'name': 'stapler_(stapling_machine)'}, {'frequency': 'r', 'id': 1023, 'synset': 'starfish.n.01', 'synonyms': ['starfish', 'sea_star'], 'def': 'echinoderms characterized by five arms extending from a central disk', 'name': 'starfish'}, {'frequency': 'f', 'id': 1024, 'synset': 'statue.n.01', 'synonyms': ['statue_(sculpture)'], 'def': 'a sculpture representing a human or animal', 'name': 'statue_(sculpture)'}, {'frequency': 'c', 'id': 1025, 'synset': 'steak.n.01', 'synonyms': ['steak_(food)'], 'def': 'a slice of meat cut from the fleshy part of an animal or large fish', 'name': 'steak_(food)'}, {'frequency': 'r', 'id': 1026, 'synset': 'steak_knife.n.01', 'synonyms': ['steak_knife'], 'def': 'a sharp table knife used in eating steak', 'name': 'steak_knife'}, {'frequency': 'r', 'id': 1027, 'synset': 'steamer.n.02', 'synonyms': ['steamer_(kitchen_appliance)'], 'def': 'a cooking utensil that can be used to cook food by steaming it', 'name': 'steamer_(kitchen_appliance)'}, {'frequency': 'f', 'id': 1028, 'synset': 'steering_wheel.n.01', 'synonyms': ['steering_wheel'], 'def': 'a handwheel that is used for steering', 'name': 'steering_wheel'}, {'frequency': 'r', 'id': 1029, 'synset': 'stencil.n.01', 'synonyms': ['stencil'], 'def': 'a sheet of material (metal, plastic, etc.) that has been perforated with a pattern; ink or paint can pass through the perforations to create the printed pattern on the surface below', 'name': 'stencil'}, {'frequency': 'r', 'id': 1030, 'synset': 'step_ladder.n.01', 'synonyms': ['stepladder'], 'def': 'a folding portable ladder hinged at the top', 'name': 'stepladder'}, {'frequency': 'c', 'id': 1031, 'synset': 'step_stool.n.01', 'synonyms': ['step_stool'], 'def': 'a stool that has one or two steps that fold under the seat', 'name': 'step_stool'}, {'frequency': 'c', 'id': 1032, 'synset': 'stereo.n.01', 'synonyms': ['stereo_(sound_system)'], 'def': 'electronic device for playing audio', 'name': 'stereo_(sound_system)'}, {'frequency': 'r', 'id': 1033, 'synset': 'stew.n.02', 'synonyms': ['stew'], 'def': 'food prepared by stewing especially meat or fish with vegetables', 'name': 'stew'}, {'frequency': 'r', 'id': 1034, 'synset': 'stirrer.n.02', 'synonyms': ['stirrer'], 'def': 'an implement used for stirring', 'name': 'stirrer'}, {'frequency': 'f', 'id': 1035, 'synset': 'stirrup.n.01', 'synonyms': ['stirrup'], 'def': "support consisting of metal loops into which rider's feet go", 'name': 'stirrup'}, {'frequency': 'c', 'id': 1036, 'synset': 'stocking.n.01', 'synonyms': ['stockings_(leg_wear)'], 'def': 'close-fitting hosiery to cover the foot and leg; come in matched pairs', 'name': 'stockings_(leg_wear)'}, {'frequency': 'f', 'id': 1037, 'synset': 'stool.n.01', 'synonyms': ['stool'], 'def': 'a simple seat without a back or arms', 'name': 'stool'}, {'frequency': 'f', 'id': 1038, 'synset': 'stop_sign.n.01', 'synonyms': ['stop_sign'], 'def': 'a traffic sign to notify drivers that they must come to a complete stop', 'name': 'stop_sign'}, {'frequency': 'f', 'id': 1039, 'synset': 'stoplight.n.01', 'synonyms': ['brake_light'], 'def': 'a red light on the rear of a motor vehicle that signals when the brakes are applied', 'name': 'brake_light'}, {'frequency': 'f', 'id': 1040, 'synset': 'stove.n.01', 'synonyms': ['stove', 'kitchen_stove', 'range_(kitchen_appliance)', 'kitchen_range', 'cooking_stove'], 'def': 'a kitchen appliance used for cooking food', 'name': 'stove'}, {'frequency': 'c', 'id': 1041, 'synset': 'strainer.n.01', 'synonyms': ['strainer'], 'def': 'a filter to retain larger pieces while smaller pieces and liquids pass through', 'name': 'strainer'}, {'frequency': 'f', 'id': 1042, 'synset': 'strap.n.01', 'synonyms': ['strap'], 'def': 'an elongated strip of material for binding things together or holding', 'name': 'strap'}, {'frequency': 'f', 'id': 1043, 'synset': 'straw.n.04', 'synonyms': ['straw_(for_drinking)', 'drinking_straw'], 'def': 'a thin paper or plastic tube used to suck liquids into the mouth', 'name': 'straw_(for_drinking)'}, {'frequency': 'f', 'id': 1044, 'synset': 'strawberry.n.01', 'synonyms': ['strawberry'], 'def': 'sweet fleshy red fruit', 'name': 'strawberry'}, {'frequency': 'f', 'id': 1045, 'synset': 'street_sign.n.01', 'synonyms': ['street_sign'], 'def': 'a sign visible from the street', 'name': 'street_sign'}, {'frequency': 'f', 'id': 1046, 'synset': 'streetlight.n.01', 'synonyms': ['streetlight', 'street_lamp'], 'def': 'a lamp supported on a lamppost; for illuminating a street', 'name': 'streetlight'}, {'frequency': 'r', 'id': 1047, 'synset': 'string_cheese.n.01', 'synonyms': ['string_cheese'], 'def': 'cheese formed in long strings twisted together', 'name': 'string_cheese'}, {'frequency': 'r', 'id': 1048, 'synset': 'stylus.n.02', 'synonyms': ['stylus'], 'def': 'a pointed tool for writing or drawing or engraving', 'name': 'stylus'}, {'frequency': 'r', 'id': 1049, 'synset': 'subwoofer.n.01', 'synonyms': ['subwoofer'], 'def': 'a loudspeaker that is designed to reproduce very low bass frequencies', 'name': 'subwoofer'}, {'frequency': 'r', 'id': 1050, 'synset': 'sugar_bowl.n.01', 'synonyms': ['sugar_bowl'], 'def': 'a dish in which sugar is served', 'name': 'sugar_bowl'}, {'frequency': 'r', 'id': 1051, 'synset': 'sugarcane.n.01', 'synonyms': ['sugarcane_(plant)'], 'def': 'juicy canes whose sap is a source of molasses and commercial sugar; fresh canes are sometimes chewed for the juice', 'name': 'sugarcane_(plant)'}, {'frequency': 'c', 'id': 1052, 'synset': 'suit.n.01', 'synonyms': ['suit_(clothing)'], 'def': 'a set of garments (usually including a jacket and trousers or skirt) for outerwear all of the same fabric and color', 'name': 'suit_(clothing)'}, {'frequency': 'c', 'id': 1053, 'synset': 'sunflower.n.01', 'synonyms': ['sunflower'], 'def': 'any plant of the genus Helianthus having large flower heads with dark disk florets and showy yellow rays', 'name': 'sunflower'}, {'frequency': 'f', 'id': 1054, 'synset': 'sunglasses.n.01', 'synonyms': ['sunglasses'], 'def': 'spectacles that are darkened or polarized to protect the eyes from the glare of the sun', 'name': 'sunglasses'}, {'frequency': 'c', 'id': 1055, 'synset': 'sunhat.n.01', 'synonyms': ['sunhat'], 'def': 'a hat with a broad brim that protects the face from direct exposure to the sun', 'name': 'sunhat'}, {'frequency': 'r', 'id': 1056, 'synset': 'sunscreen.n.01', 'synonyms': ['sunscreen', 'sunblock'], 'def': 'a cream spread on the skin; contains a chemical to filter out ultraviolet light and so protect from sunburn', 'name': 'sunscreen'}, {'frequency': 'f', 'id': 1057, 'synset': 'surfboard.n.01', 'synonyms': ['surfboard'], 'def': 'a narrow buoyant board for riding surf', 'name': 'surfboard'}, {'frequency': 'c', 'id': 1058, 'synset': 'sushi.n.01', 'synonyms': ['sushi'], 'def': 'rice (with raw fish) wrapped in seaweed', 'name': 'sushi'}, {'frequency': 'c', 'id': 1059, 'synset': 'swab.n.02', 'synonyms': ['mop'], 'def': 'cleaning implement consisting of absorbent material fastened to a handle; for cleaning floors', 'name': 'mop'}, {'frequency': 'c', 'id': 1060, 'synset': 'sweat_pants.n.01', 'synonyms': ['sweat_pants'], 'def': 'loose-fitting trousers with elastic cuffs; worn by athletes', 'name': 'sweat_pants'}, {'frequency': 'c', 'id': 1061, 'synset': 'sweatband.n.02', 'synonyms': ['sweatband'], 'def': 'a band of material tied around the forehead or wrist to absorb sweat', 'name': 'sweatband'}, {'frequency': 'f', 'id': 1062, 'synset': 'sweater.n.01', 'synonyms': ['sweater'], 'def': 'a crocheted or knitted garment covering the upper part of the body', 'name': 'sweater'}, {'frequency': 'f', 'id': 1063, 'synset': 'sweatshirt.n.01', 'synonyms': ['sweatshirt'], 'def': 'cotton knit pullover with long sleeves worn during athletic activity', 'name': 'sweatshirt'}, {'frequency': 'c', 'id': 1064, 'synset': 'sweet_potato.n.02', 'synonyms': ['sweet_potato'], 'def': 'the edible tuberous root of the sweet potato vine', 'name': 'sweet_potato'}, {'frequency': 'f', 'id': 1065, 'synset': 'swimsuit.n.01', 'synonyms': ['swimsuit', 'swimwear', 'bathing_suit', 'swimming_costume', 'bathing_costume', 'swimming_trunks', 'bathing_trunks'], 'def': 'garment worn for swimming', 'name': 'swimsuit'}, {'frequency': 'c', 'id': 1066, 'synset': 'sword.n.01', 'synonyms': ['sword'], 'def': 'a cutting or thrusting weapon that has a long metal blade', 'name': 'sword'}, {'frequency': 'r', 'id': 1067, 'synset': 'syringe.n.01', 'synonyms': ['syringe'], 'def': 'a medical instrument used to inject or withdraw fluids', 'name': 'syringe'}, {'frequency': 'r', 'id': 1068, 'synset': 'tabasco.n.02', 'synonyms': ['Tabasco_sauce'], 'def': 'very spicy sauce (trade name Tabasco) made from fully-aged red peppers', 'name': 'Tabasco_sauce'}, {'frequency': 'r', 'id': 1069, 'synset': 'table-tennis_table.n.01', 'synonyms': ['table-tennis_table', 'ping-pong_table'], 'def': 'a table used for playing table tennis', 'name': 'table-tennis_table'}, {'frequency': 'f', 'id': 1070, 'synset': 'table.n.02', 'synonyms': ['table'], 'def': 'a piece of furniture having a smooth flat top that is usually supported by one or more vertical legs', 'name': 'table'}, {'frequency': 'c', 'id': 1071, 'synset': 'table_lamp.n.01', 'synonyms': ['table_lamp'], 'def': 'a lamp that sits on a table', 'name': 'table_lamp'}, {'frequency': 'f', 'id': 1072, 'synset': 'tablecloth.n.01', 'synonyms': ['tablecloth'], 'def': 'a covering spread over a dining table', 'name': 'tablecloth'}, {'frequency': 'r', 'id': 1073, 'synset': 'tachometer.n.01', 'synonyms': ['tachometer'], 'def': 'measuring instrument for indicating speed of rotation', 'name': 'tachometer'}, {'frequency': 'r', 'id': 1074, 'synset': 'taco.n.02', 'synonyms': ['taco'], 'def': 'a small tortilla cupped around a filling', 'name': 'taco'}, {'frequency': 'f', 'id': 1075, 'synset': 'tag.n.02', 'synonyms': ['tag'], 'def': 'a label associated with something for the purpose of identification or information', 'name': 'tag'}, {'frequency': 'f', 'id': 1076, 'synset': 'taillight.n.01', 'synonyms': ['taillight', 'rear_light'], 'def': 'lamp (usually red) mounted at the rear of a motor vehicle', 'name': 'taillight'}, {'frequency': 'r', 'id': 1077, 'synset': 'tambourine.n.01', 'synonyms': ['tambourine'], 'def': 'a shallow drum with a single drumhead and with metallic disks in the sides', 'name': 'tambourine'}, {'frequency': 'r', 'id': 1078, 'synset': 'tank.n.01', 'synonyms': ['army_tank', 'armored_combat_vehicle', 'armoured_combat_vehicle'], 'def': 'an enclosed armored military vehicle; has a cannon and moves on caterpillar treads', 'name': 'army_tank'}, {'frequency': 'c', 'id': 1079, 'synset': 'tank.n.02', 'synonyms': ['tank_(storage_vessel)', 'storage_tank'], 'def': 'a large (usually metallic) vessel for holding gases or liquids', 'name': 'tank_(storage_vessel)'}, {'frequency': 'f', 'id': 1080, 'synset': 'tank_top.n.01', 'synonyms': ['tank_top_(clothing)'], 'def': 'a tight-fitting sleeveless shirt with wide shoulder straps and low neck and no front opening', 'name': 'tank_top_(clothing)'}, {'frequency': 'c', 'id': 1081, 'synset': 'tape.n.01', 'synonyms': ['tape_(sticky_cloth_or_paper)'], 'def': 'a long thin piece of cloth or paper as used for binding or fastening', 'name': 'tape_(sticky_cloth_or_paper)'}, {'frequency': 'c', 'id': 1082, 'synset': 'tape.n.04', 'synonyms': ['tape_measure', 'measuring_tape'], 'def': 'measuring instrument consisting of a narrow strip (cloth or metal) marked in inches or centimeters and used for measuring lengths', 'name': 'tape_measure'}, {'frequency': 'c', 'id': 1083, 'synset': 'tapestry.n.02', 'synonyms': ['tapestry'], 'def': 'a heavy textile with a woven design; used for curtains and upholstery', 'name': 'tapestry'}, {'frequency': 'f', 'id': 1084, 'synset': 'tarpaulin.n.01', 'synonyms': ['tarp'], 'def': 'waterproofed canvas', 'name': 'tarp'}, {'frequency': 'c', 'id': 1085, 'synset': 'tartan.n.01', 'synonyms': ['tartan', 'plaid'], 'def': 'a cloth having a crisscross design', 'name': 'tartan'}, {'frequency': 'c', 'id': 1086, 'synset': 'tassel.n.01', 'synonyms': ['tassel'], 'def': 'adornment consisting of a bunch of cords fastened at one end', 'name': 'tassel'}, {'frequency': 'r', 'id': 1087, 'synset': 'tea_bag.n.01', 'synonyms': ['tea_bag'], 'def': 'a measured amount of tea in a bag for an individual serving of tea', 'name': 'tea_bag'}, {'frequency': 'c', 'id': 1088, 'synset': 'teacup.n.02', 'synonyms': ['teacup'], 'def': 'a cup from which tea is drunk', 'name': 'teacup'}, {'frequency': 'c', 'id': 1089, 'synset': 'teakettle.n.01', 'synonyms': ['teakettle'], 'def': 'kettle for boiling water to make tea', 'name': 'teakettle'}, {'frequency': 'c', 'id': 1090, 'synset': 'teapot.n.01', 'synonyms': ['teapot'], 'def': 'pot for brewing tea; usually has a spout and handle', 'name': 'teapot'}, {'frequency': 'f', 'id': 1091, 'synset': 'teddy.n.01', 'synonyms': ['teddy_bear'], 'def': "plaything consisting of a child's toy bear (usually plush and stuffed with soft materials)", 'name': 'teddy_bear'}, {'frequency': 'f', 'id': 1092, 'synset': 'telephone.n.01', 'synonyms': ['telephone', 'phone', 'telephone_set'], 'def': 'electronic device for communicating by voice over long distances', 'name': 'telephone'}, {'frequency': 'c', 'id': 1093, 'synset': 'telephone_booth.n.01', 'synonyms': ['telephone_booth', 'phone_booth', 'call_box', 'telephone_box', 'telephone_kiosk'], 'def': 'booth for using a telephone', 'name': 'telephone_booth'}, {'frequency': 'f', 'id': 1094, 'synset': 'telephone_pole.n.01', 'synonyms': ['telephone_pole', 'telegraph_pole', 'telegraph_post'], 'def': 'tall pole supporting telephone wires', 'name': 'telephone_pole'}, {'frequency': 'r', 'id': 1095, 'synset': 'telephoto_lens.n.01', 'synonyms': ['telephoto_lens', 'zoom_lens'], 'def': 'a camera lens that magnifies the image', 'name': 'telephoto_lens'}, {'frequency': 'c', 'id': 1096, 'synset': 'television_camera.n.01', 'synonyms': ['television_camera', 'tv_camera'], 'def': 'television equipment for capturing and recording video', 'name': 'television_camera'}, {'frequency': 'f', 'id': 1097, 'synset': 'television_receiver.n.01', 'synonyms': ['television_set', 'tv', 'tv_set'], 'def': 'an electronic device that receives television signals and displays them on a screen', 'name': 'television_set'}, {'frequency': 'f', 'id': 1098, 'synset': 'tennis_ball.n.01', 'synonyms': ['tennis_ball'], 'def': 'ball about the size of a fist used in playing tennis', 'name': 'tennis_ball'}, {'frequency': 'f', 'id': 1099, 'synset': 'tennis_racket.n.01', 'synonyms': ['tennis_racket'], 'def': 'a racket used to play tennis', 'name': 'tennis_racket'}, {'frequency': 'r', 'id': 1100, 'synset': 'tequila.n.01', 'synonyms': ['tequila'], 'def': 'Mexican liquor made from fermented juices of an agave plant', 'name': 'tequila'}, {'frequency': 'c', 'id': 1101, 'synset': 'thermometer.n.01', 'synonyms': ['thermometer'], 'def': 'measuring instrument for measuring temperature', 'name': 'thermometer'}, {'frequency': 'c', 'id': 1102, 'synset': 'thermos.n.01', 'synonyms': ['thermos_bottle'], 'def': 'vacuum flask that preserves temperature of hot or cold drinks', 'name': 'thermos_bottle'}, {'frequency': 'c', 'id': 1103, 'synset': 'thermostat.n.01', 'synonyms': ['thermostat'], 'def': 'a regulator for automatically regulating temperature by starting or stopping the supply of heat', 'name': 'thermostat'}, {'frequency': 'r', 'id': 1104, 'synset': 'thimble.n.02', 'synonyms': ['thimble'], 'def': 'a small metal cap to protect the finger while sewing; can be used as a small container', 'name': 'thimble'}, {'frequency': 'c', 'id': 1105, 'synset': 'thread.n.01', 'synonyms': ['thread', 'yarn'], 'def': 'a fine cord of twisted fibers (of cotton or silk or wool or nylon etc.) used in sewing and weaving', 'name': 'thread'}, {'frequency': 'c', 'id': 1106, 'synset': 'thumbtack.n.01', 'synonyms': ['thumbtack', 'drawing_pin', 'pushpin'], 'def': 'a tack for attaching papers to a bulletin board or drawing board', 'name': 'thumbtack'}, {'frequency': 'c', 'id': 1107, 'synset': 'tiara.n.01', 'synonyms': ['tiara'], 'def': 'a jeweled headdress worn by women on formal occasions', 'name': 'tiara'}, {'frequency': 'c', 'id': 1108, 'synset': 'tiger.n.02', 'synonyms': ['tiger'], 'def': 'large feline of forests in most of Asia having a tawny coat with black stripes', 'name': 'tiger'}, {'frequency': 'c', 'id': 1109, 'synset': 'tights.n.01', 'synonyms': ['tights_(clothing)', 'leotards'], 'def': 'skintight knit hose covering the body from the waist to the feet worn by acrobats and dancers and as stockings by women and girls', 'name': 'tights_(clothing)'}, {'frequency': 'c', 'id': 1110, 'synset': 'timer.n.01', 'synonyms': ['timer', 'stopwatch'], 'def': 'a timepiece that measures a time interval and signals its end', 'name': 'timer'}, {'frequency': 'f', 'id': 1111, 'synset': 'tinfoil.n.01', 'synonyms': ['tinfoil'], 'def': 'foil made of tin or an alloy of tin and lead', 'name': 'tinfoil'}, {'frequency': 'r', 'id': 1112, 'synset': 'tinsel.n.01', 'synonyms': ['tinsel'], 'def': 'a showy decoration that is basically valueless', 'name': 'tinsel'}, {'frequency': 'f', 'id': 1113, 'synset': 'tissue.n.02', 'synonyms': ['tissue_paper'], 'def': 'a soft thin (usually translucent) paper', 'name': 'tissue_paper'}, {'frequency': 'c', 'id': 1114, 'synset': 'toast.n.01', 'synonyms': ['toast_(food)'], 'def': 'slice of bread that has been toasted', 'name': 'toast_(food)'}, {'frequency': 'f', 'id': 1115, 'synset': 'toaster.n.02', 'synonyms': ['toaster'], 'def': 'a kitchen appliance (usually electric) for toasting bread', 'name': 'toaster'}, {'frequency': 'c', 'id': 1116, 'synset': 'toaster_oven.n.01', 'synonyms': ['toaster_oven'], 'def': 'kitchen appliance consisting of a small electric oven for toasting or warming food', 'name': 'toaster_oven'}, {'frequency': 'f', 'id': 1117, 'synset': 'toilet.n.02', 'synonyms': ['toilet'], 'def': 'a plumbing fixture for defecation and urination', 'name': 'toilet'}, {'frequency': 'f', 'id': 1118, 'synset': 'toilet_tissue.n.01', 'synonyms': ['toilet_tissue', 'toilet_paper', 'bathroom_tissue'], 'def': 'a soft thin absorbent paper for use in toilets', 'name': 'toilet_tissue'}, {'frequency': 'f', 'id': 1119, 'synset': 'tomato.n.01', 'synonyms': ['tomato'], 'def': 'mildly acid red or yellow pulpy fruit eaten as a vegetable', 'name': 'tomato'}, {'frequency': 'c', 'id': 1120, 'synset': 'tongs.n.01', 'synonyms': ['tongs'], 'def': 'any of various devices for taking hold of objects; usually have two hinged legs with handles above and pointed hooks below', 'name': 'tongs'}, {'frequency': 'c', 'id': 1121, 'synset': 'toolbox.n.01', 'synonyms': ['toolbox'], 'def': 'a box or chest or cabinet for holding hand tools', 'name': 'toolbox'}, {'frequency': 'f', 'id': 1122, 'synset': 'toothbrush.n.01', 'synonyms': ['toothbrush'], 'def': 'small brush; has long handle; used to clean teeth', 'name': 'toothbrush'}, {'frequency': 'f', 'id': 1123, 'synset': 'toothpaste.n.01', 'synonyms': ['toothpaste'], 'def': 'a dentifrice in the form of a paste', 'name': 'toothpaste'}, {'frequency': 'c', 'id': 1124, 'synset': 'toothpick.n.01', 'synonyms': ['toothpick'], 'def': 'pick consisting of a small strip of wood or plastic; used to pick food from between the teeth', 'name': 'toothpick'}, {'frequency': 'c', 'id': 1125, 'synset': 'top.n.09', 'synonyms': ['cover'], 'def': 'covering for a hole (especially a hole in the top of a container)', 'name': 'cover'}, {'frequency': 'c', 'id': 1126, 'synset': 'tortilla.n.01', 'synonyms': ['tortilla'], 'def': 'thin unleavened pancake made from cornmeal or wheat flour', 'name': 'tortilla'}, {'frequency': 'c', 'id': 1127, 'synset': 'tow_truck.n.01', 'synonyms': ['tow_truck'], 'def': 'a truck equipped to hoist and pull wrecked cars (or to remove cars from no-parking zones)', 'name': 'tow_truck'}, {'frequency': 'f', 'id': 1128, 'synset': 'towel.n.01', 'synonyms': ['towel'], 'def': 'a rectangular piece of absorbent cloth (or paper) for drying or wiping', 'name': 'towel'}, {'frequency': 'f', 'id': 1129, 'synset': 'towel_rack.n.01', 'synonyms': ['towel_rack', 'towel_rail', 'towel_bar'], 'def': 'a rack consisting of one or more bars on which towels can be hung', 'name': 'towel_rack'}, {'frequency': 'f', 'id': 1130, 'synset': 'toy.n.03', 'synonyms': ['toy'], 'def': 'a device regarded as providing amusement', 'name': 'toy'}, {'frequency': 'c', 'id': 1131, 'synset': 'tractor.n.01', 'synonyms': ['tractor_(farm_equipment)'], 'def': 'a wheeled vehicle with large wheels; used in farming and other applications', 'name': 'tractor_(farm_equipment)'}, {'frequency': 'f', 'id': 1132, 'synset': 'traffic_light.n.01', 'synonyms': ['traffic_light'], 'def': 'a device to control vehicle traffic often consisting of three or more lights', 'name': 'traffic_light'}, {'frequency': 'r', 'id': 1133, 'synset': 'trail_bike.n.01', 'synonyms': ['dirt_bike'], 'def': 'a lightweight motorcycle equipped with rugged tires and suspension for off-road use', 'name': 'dirt_bike'}, {'frequency': 'c', 'id': 1134, 'synset': 'trailer_truck.n.01', 'synonyms': ['trailer_truck', 'tractor_trailer', 'trucking_rig', 'articulated_lorry', 'semi_truck'], 'def': 'a truck consisting of a tractor and trailer together', 'name': 'trailer_truck'}, {'frequency': 'f', 'id': 1135, 'synset': 'train.n.01', 'synonyms': ['train_(railroad_vehicle)', 'railroad_train'], 'def': 'public or private transport provided by a line of railway cars coupled together and drawn by a locomotive', 'name': 'train_(railroad_vehicle)'}, {'frequency': 'r', 'id': 1136, 'synset': 'trampoline.n.01', 'synonyms': ['trampoline'], 'def': 'gymnastic apparatus consisting of a strong canvas sheet attached with springs to a metal frame', 'name': 'trampoline'}, {'frequency': 'f', 'id': 1137, 'synset': 'tray.n.01', 'synonyms': ['tray'], 'def': 'an open receptacle for holding or displaying or serving articles or food', 'name': 'tray'}, {'frequency': 'r', 'id': 1138, 'synset': 'tree_house.n.01', 'synonyms': ['tree_house'], 'def': '(NOT A TREE) a PLAYHOUSE built in the branches of a tree', 'name': 'tree_house'}, {'frequency': 'r', 'id': 1139, 'synset': 'trench_coat.n.01', 'synonyms': ['trench_coat'], 'def': 'a military style raincoat; belted with deep pockets', 'name': 'trench_coat'}, {'frequency': 'r', 'id': 1140, 'synset': 'triangle.n.05', 'synonyms': ['triangle_(musical_instrument)'], 'def': 'a percussion instrument consisting of a metal bar bent in the shape of an open triangle', 'name': 'triangle_(musical_instrument)'}, {'frequency': 'r', 'id': 1141, 'synset': 'tricycle.n.01', 'synonyms': ['tricycle'], 'def': 'a vehicle with three wheels that is moved by foot pedals', 'name': 'tricycle'}, {'frequency': 'c', 'id': 1142, 'synset': 'tripod.n.01', 'synonyms': ['tripod'], 'def': 'a three-legged rack used for support', 'name': 'tripod'}, {'frequency': 'f', 'id': 1143, 'synset': 'trouser.n.01', 'synonyms': ['trousers', 'pants_(clothing)'], 'def': 'a garment extending from the waist to the knee or ankle, covering each leg separately', 'name': 'trousers'}, {'frequency': 'f', 'id': 1144, 'synset': 'truck.n.01', 'synonyms': ['truck'], 'def': 'an automotive vehicle suitable for hauling', 'name': 'truck'}, {'frequency': 'r', 'id': 1145, 'synset': 'truffle.n.03', 'synonyms': ['truffle_(chocolate)', 'chocolate_truffle'], 'def': 'creamy chocolate candy', 'name': 'truffle_(chocolate)'}, {'frequency': 'c', 'id': 1146, 'synset': 'trunk.n.02', 'synonyms': ['trunk'], 'def': 'luggage consisting of a large strong case used when traveling or for storage', 'name': 'trunk'}, {'frequency': 'r', 'id': 1147, 'synset': 'tub.n.02', 'synonyms': ['vat'], 'def': 'a large open vessel for holding or storing liquids', 'name': 'vat'}, {'frequency': 'c', 'id': 1148, 'synset': 'turban.n.01', 'synonyms': ['turban'], 'def': 'a traditional headdress consisting of a long scarf wrapped around the head', 'name': 'turban'}, {'frequency': 'r', 'id': 1149, 'synset': 'turkey.n.01', 'synonyms': ['turkey_(bird)'], 'def': 'large gallinaceous bird with fan-shaped tail; widely domesticated for food', 'name': 'turkey_(bird)'}, {'frequency': 'c', 'id': 1150, 'synset': 'turkey.n.04', 'synonyms': ['turkey_(food)'], 'def': 'flesh of large domesticated fowl usually roasted', 'name': 'turkey_(food)'}, {'frequency': 'r', 'id': 1151, 'synset': 'turnip.n.01', 'synonyms': ['turnip'], 'def': 'widely cultivated plant having a large fleshy edible white or yellow root', 'name': 'turnip'}, {'frequency': 'c', 'id': 1152, 'synset': 'turtle.n.02', 'synonyms': ['turtle'], 'def': 'any of various aquatic and land reptiles having a bony shell and flipper-like limbs for swimming', 'name': 'turtle'}, {'frequency': 'r', 'id': 1153, 'synset': 'turtleneck.n.01', 'synonyms': ['turtleneck_(clothing)', 'polo-neck'], 'def': 'a sweater or jersey with a high close-fitting collar', 'name': 'turtleneck_(clothing)'}, {'frequency': 'r', 'id': 1154, 'synset': 'typewriter.n.01', 'synonyms': ['typewriter'], 'def': 'hand-operated character printer for printing written messages one character at a time', 'name': 'typewriter'}, {'frequency': 'f', 'id': 1155, 'synset': 'umbrella.n.01', 'synonyms': ['umbrella'], 'def': 'a lightweight handheld collapsible canopy', 'name': 'umbrella'}, {'frequency': 'c', 'id': 1156, 'synset': 'underwear.n.01', 'synonyms': ['underwear', 'underclothes', 'underclothing', 'underpants'], 'def': 'undergarment worn next to the skin and under the outer garments', 'name': 'underwear'}, {'frequency': 'r', 'id': 1157, 'synset': 'unicycle.n.01', 'synonyms': ['unicycle'], 'def': 'a vehicle with a single wheel that is driven by pedals', 'name': 'unicycle'}, {'frequency': 'c', 'id': 1158, 'synset': 'urinal.n.01', 'synonyms': ['urinal'], 'def': 'a plumbing fixture (usually attached to the wall) used by men to urinate', 'name': 'urinal'}, {'frequency': 'r', 'id': 1159, 'synset': 'urn.n.01', 'synonyms': ['urn'], 'def': 'a large vase that usually has a pedestal or feet', 'name': 'urn'}, {'frequency': 'c', 'id': 1160, 'synset': 'vacuum.n.04', 'synonyms': ['vacuum_cleaner'], 'def': 'an electrical home appliance that cleans by suction', 'name': 'vacuum_cleaner'}, {'frequency': 'c', 'id': 1161, 'synset': 'valve.n.03', 'synonyms': ['valve'], 'def': 'control consisting of a mechanical device for controlling the flow of a fluid', 'name': 'valve'}, {'frequency': 'f', 'id': 1162, 'synset': 'vase.n.01', 'synonyms': ['vase'], 'def': 'an open jar of glass or porcelain used as an ornament or to hold flowers', 'name': 'vase'}, {'frequency': 'c', 'id': 1163, 'synset': 'vending_machine.n.01', 'synonyms': ['vending_machine'], 'def': 'a slot machine for selling goods', 'name': 'vending_machine'}, {'frequency': 'f', 'id': 1164, 'synset': 'vent.n.01', 'synonyms': ['vent', 'blowhole', 'air_vent'], 'def': 'a hole for the escape of gas or air', 'name': 'vent'}, {'frequency': 'c', 'id': 1165, 'synset': 'videotape.n.01', 'synonyms': ['videotape'], 'def': 'a video recording made on magnetic tape', 'name': 'videotape'}, {'frequency': 'r', 'id': 1166, 'synset': 'vinegar.n.01', 'synonyms': ['vinegar'], 'def': 'sour-tasting liquid produced usually by oxidation of the alcohol in wine or cider and used as a condiment or food preservative', 'name': 'vinegar'}, {'frequency': 'r', 'id': 1167, 'synset': 'violin.n.01', 'synonyms': ['violin', 'fiddle'], 'def': 'bowed stringed instrument that is the highest member of the violin family', 'name': 'violin'}, {'frequency': 'r', 'id': 1168, 'synset': 'vodka.n.01', 'synonyms': ['vodka'], 'def': 'unaged colorless liquor originating in Russia', 'name': 'vodka'}, {'frequency': 'r', 'id': 1169, 'synset': 'volleyball.n.02', 'synonyms': ['volleyball'], 'def': 'an inflated ball used in playing volleyball', 'name': 'volleyball'}, {'frequency': 'r', 'id': 1170, 'synset': 'vulture.n.01', 'synonyms': ['vulture'], 'def': 'any of various large birds of prey having naked heads and weak claws and feeding chiefly on carrion', 'name': 'vulture'}, {'frequency': 'c', 'id': 1171, 'synset': 'waffle.n.01', 'synonyms': ['waffle'], 'def': 'pancake batter baked in a waffle iron', 'name': 'waffle'}, {'frequency': 'r', 'id': 1172, 'synset': 'waffle_iron.n.01', 'synonyms': ['waffle_iron'], 'def': 'a kitchen appliance for baking waffles', 'name': 'waffle_iron'}, {'frequency': 'c', 'id': 1173, 'synset': 'wagon.n.01', 'synonyms': ['wagon'], 'def': 'any of various kinds of wheeled vehicles drawn by an animal or a tractor', 'name': 'wagon'}, {'frequency': 'c', 'id': 1174, 'synset': 'wagon_wheel.n.01', 'synonyms': ['wagon_wheel'], 'def': 'a wheel of a wagon', 'name': 'wagon_wheel'}, {'frequency': 'c', 'id': 1175, 'synset': 'walking_stick.n.01', 'synonyms': ['walking_stick'], 'def': 'a stick carried in the hand for support in walking', 'name': 'walking_stick'}, {'frequency': 'c', 'id': 1176, 'synset': 'wall_clock.n.01', 'synonyms': ['wall_clock'], 'def': 'a clock mounted on a wall', 'name': 'wall_clock'}, {'frequency': 'f', 'id': 1177, 'synset': 'wall_socket.n.01', 'synonyms': ['wall_socket', 'wall_plug', 'electric_outlet', 'electrical_outlet', 'outlet', 'electric_receptacle'], 'def': 'receptacle providing a place in a wiring system where current can be taken to run electrical devices', 'name': 'wall_socket'}, {'frequency': 'c', 'id': 1178, 'synset': 'wallet.n.01', 'synonyms': ['wallet', 'billfold'], 'def': 'a pocket-size case for holding papers and paper money', 'name': 'wallet'}, {'frequency': 'r', 'id': 1179, 'synset': 'walrus.n.01', 'synonyms': ['walrus'], 'def': 'either of two large northern marine mammals having ivory tusks and tough hide over thick blubber', 'name': 'walrus'}, {'frequency': 'r', 'id': 1180, 'synset': 'wardrobe.n.01', 'synonyms': ['wardrobe'], 'def': 'a tall piece of furniture that provides storage space for clothes; has a door and rails or hooks for hanging clothes', 'name': 'wardrobe'}, {'frequency': 'r', 'id': 1181, 'synset': 'wasabi.n.02', 'synonyms': ['wasabi'], 'def': 'the thick green root of the wasabi plant that the Japanese use in cooking and that tastes like strong horseradish', 'name': 'wasabi'}, {'frequency': 'c', 'id': 1182, 'synset': 'washer.n.03', 'synonyms': ['automatic_washer', 'washing_machine'], 'def': 'a home appliance for washing clothes and linens automatically', 'name': 'automatic_washer'}, {'frequency': 'f', 'id': 1183, 'synset': 'watch.n.01', 'synonyms': ['watch', 'wristwatch'], 'def': 'a small, portable timepiece', 'name': 'watch'}, {'frequency': 'f', 'id': 1184, 'synset': 'water_bottle.n.01', 'synonyms': ['water_bottle'], 'def': 'a bottle for holding water', 'name': 'water_bottle'}, {'frequency': 'c', 'id': 1185, 'synset': 'water_cooler.n.01', 'synonyms': ['water_cooler'], 'def': 'a device for cooling and dispensing drinking water', 'name': 'water_cooler'}, {'frequency': 'c', 'id': 1186, 'synset': 'water_faucet.n.01', 'synonyms': ['water_faucet', 'water_tap', 'tap_(water_faucet)'], 'def': 'a faucet for drawing water from a pipe or cask', 'name': 'water_faucet'}, {'frequency': 'r', 'id': 1187, 'synset': 'water_filter.n.01', 'synonyms': ['water_filter'], 'def': 'a filter to remove impurities from the water supply', 'name': 'water_filter'}, {'frequency': 'r', 'id': 1188, 'synset': 'water_heater.n.01', 'synonyms': ['water_heater', 'hot-water_heater'], 'def': 'a heater and storage tank to supply heated water', 'name': 'water_heater'}, {'frequency': 'r', 'id': 1189, 'synset': 'water_jug.n.01', 'synonyms': ['water_jug'], 'def': 'a jug that holds water', 'name': 'water_jug'}, {'frequency': 'r', 'id': 1190, 'synset': 'water_pistol.n.01', 'synonyms': ['water_gun', 'squirt_gun'], 'def': 'plaything consisting of a toy pistol that squirts water', 'name': 'water_gun'}, {'frequency': 'c', 'id': 1191, 'synset': 'water_scooter.n.01', 'synonyms': ['water_scooter', 'sea_scooter', 'jet_ski'], 'def': 'a motorboat resembling a motor scooter (NOT A SURFBOARD OR WATER SKI)', 'name': 'water_scooter'}, {'frequency': 'c', 'id': 1192, 'synset': 'water_ski.n.01', 'synonyms': ['water_ski'], 'def': 'broad ski for skimming over water towed by a speedboat (DO NOT MARK WATER)', 'name': 'water_ski'}, {'frequency': 'c', 'id': 1193, 'synset': 'water_tower.n.01', 'synonyms': ['water_tower'], 'def': 'a large reservoir for water', 'name': 'water_tower'}, {'frequency': 'c', 'id': 1194, 'synset': 'watering_can.n.01', 'synonyms': ['watering_can'], 'def': 'a container with a handle and a spout with a perforated nozzle; used to sprinkle water over plants', 'name': 'watering_can'}, {'frequency': 'c', 'id': 1195, 'synset': 'watermelon.n.02', 'synonyms': ['watermelon'], 'def': 'large oblong or roundish melon with a hard green rind and sweet watery red or occasionally yellowish pulp', 'name': 'watermelon'}, {'frequency': 'f', 'id': 1196, 'synset': 'weathervane.n.01', 'synonyms': ['weathervane', 'vane_(weathervane)', 'wind_vane'], 'def': 'mechanical device attached to an elevated structure; rotates freely to show the direction of the wind', 'name': 'weathervane'}, {'frequency': 'c', 'id': 1197, 'synset': 'webcam.n.01', 'synonyms': ['webcam'], 'def': 'a digital camera designed to take digital photographs and transmit them over the internet', 'name': 'webcam'}, {'frequency': 'c', 'id': 1198, 'synset': 'wedding_cake.n.01', 'synonyms': ['wedding_cake', 'bridecake'], 'def': 'a rich cake with two or more tiers and covered with frosting and decorations; served at a wedding reception', 'name': 'wedding_cake'}, {'frequency': 'c', 'id': 1199, 'synset': 'wedding_ring.n.01', 'synonyms': ['wedding_ring', 'wedding_band'], 'def': 'a ring given to the bride and/or groom at the wedding', 'name': 'wedding_ring'}, {'frequency': 'f', 'id': 1200, 'synset': 'wet_suit.n.01', 'synonyms': ['wet_suit'], 'def': 'a close-fitting garment made of a permeable material; worn in cold water to retain body heat', 'name': 'wet_suit'}, {'frequency': 'f', 'id': 1201, 'synset': 'wheel.n.01', 'synonyms': ['wheel'], 'def': 'a circular frame with spokes (or a solid disc) that can rotate on a shaft or axle', 'name': 'wheel'}, {'frequency': 'c', 'id': 1202, 'synset': 'wheelchair.n.01', 'synonyms': ['wheelchair'], 'def': 'a movable chair mounted on large wheels', 'name': 'wheelchair'}, {'frequency': 'c', 'id': 1203, 'synset': 'whipped_cream.n.01', 'synonyms': ['whipped_cream'], 'def': 'cream that has been beaten until light and fluffy', 'name': 'whipped_cream'}, {'frequency': 'r', 'id': 1204, 'synset': 'whiskey.n.01', 'synonyms': ['whiskey'], 'def': 'a liquor made from fermented mash of grain', 'name': 'whiskey'}, {'frequency': 'r', 'id': 1205, 'synset': 'whistle.n.03', 'synonyms': ['whistle'], 'def': 'a small wind instrument that produces a whistling sound by blowing into it', 'name': 'whistle'}, {'frequency': 'r', 'id': 1206, 'synset': 'wick.n.02', 'synonyms': ['wick'], 'def': 'a loosely woven cord in a candle or oil lamp that is lit on fire', 'name': 'wick'}, {'frequency': 'c', 'id': 1207, 'synset': 'wig.n.01', 'synonyms': ['wig'], 'def': 'hairpiece covering the head and made of real or synthetic hair', 'name': 'wig'}, {'frequency': 'c', 'id': 1208, 'synset': 'wind_chime.n.01', 'synonyms': ['wind_chime'], 'def': 'a decorative arrangement of pieces of metal or glass or pottery that hang together loosely so the wind can cause them to tinkle', 'name': 'wind_chime'}, {'frequency': 'c', 'id': 1209, 'synset': 'windmill.n.01', 'synonyms': ['windmill'], 'def': 'a mill that is powered by the wind', 'name': 'windmill'}, {'frequency': 'c', 'id': 1210, 'synset': 'window_box.n.01', 'synonyms': ['window_box_(for_plants)'], 'def': 'a container for growing plants on a windowsill', 'name': 'window_box_(for_plants)'}, {'frequency': 'f', 'id': 1211, 'synset': 'windshield_wiper.n.01', 'synonyms': ['windshield_wiper', 'windscreen_wiper', 'wiper_(for_windshield/screen)'], 'def': 'a mechanical device that cleans the windshield', 'name': 'windshield_wiper'}, {'frequency': 'c', 'id': 1212, 'synset': 'windsock.n.01', 'synonyms': ['windsock', 'air_sock', 'air-sleeve', 'wind_sleeve', 'wind_cone'], 'def': 'a truncated cloth cone mounted on a mast/pole; shows wind direction', 'name': 'windsock'}, {'frequency': 'f', 'id': 1213, 'synset': 'wine_bottle.n.01', 'synonyms': ['wine_bottle'], 'def': 'a bottle for holding wine', 'name': 'wine_bottle'}, {'frequency': 'r', 'id': 1214, 'synset': 'wine_bucket.n.01', 'synonyms': ['wine_bucket', 'wine_cooler'], 'def': 'a bucket of ice used to chill a bottle of wine', 'name': 'wine_bucket'}, {'frequency': 'f', 'id': 1215, 'synset': 'wineglass.n.01', 'synonyms': ['wineglass'], 'def': 'a glass that has a stem and in which wine is served', 'name': 'wineglass'}, {'frequency': 'r', 'id': 1216, 'synset': 'wing_chair.n.01', 'synonyms': ['wing_chair'], 'def': 'easy chair having wings on each side of a high back', 'name': 'wing_chair'}, {'frequency': 'c', 'id': 1217, 'synset': 'winker.n.02', 'synonyms': ['blinder_(for_horses)'], 'def': 'blinds that prevent a horse from seeing something on either side', 'name': 'blinder_(for_horses)'}, {'frequency': 'c', 'id': 1218, 'synset': 'wok.n.01', 'synonyms': ['wok'], 'def': 'pan with a convex bottom; used for frying in Chinese cooking', 'name': 'wok'}, {'frequency': 'r', 'id': 1219, 'synset': 'wolf.n.01', 'synonyms': ['wolf'], 'def': 'a wild carnivorous mammal of the dog family, living and hunting in packs', 'name': 'wolf'}, {'frequency': 'c', 'id': 1220, 'synset': 'wooden_spoon.n.02', 'synonyms': ['wooden_spoon'], 'def': 'a spoon made of wood', 'name': 'wooden_spoon'}, {'frequency': 'c', 'id': 1221, 'synset': 'wreath.n.01', 'synonyms': ['wreath'], 'def': 'an arrangement of flowers, leaves, or stems fastened in a ring', 'name': 'wreath'}, {'frequency': 'c', 'id': 1222, 'synset': 'wrench.n.03', 'synonyms': ['wrench', 'spanner'], 'def': 'a hand tool that is used to hold or twist a nut or bolt', 'name': 'wrench'}, {'frequency': 'c', 'id': 1223, 'synset': 'wristband.n.01', 'synonyms': ['wristband'], 'def': 'band consisting of a part of a sleeve that covers the wrist', 'name': 'wristband'}, {'frequency': 'f', 'id': 1224, 'synset': 'wristlet.n.01', 'synonyms': ['wristlet', 'wrist_band'], 'def': 'a band or bracelet worn around the wrist', 'name': 'wristlet'}, {'frequency': 'r', 'id': 1225, 'synset': 'yacht.n.01', 'synonyms': ['yacht'], 'def': 'an expensive vessel propelled by sail or power and used for cruising or racing', 'name': 'yacht'}, {'frequency': 'r', 'id': 1226, 'synset': 'yak.n.02', 'synonyms': ['yak'], 'def': 'large long-haired wild ox of Tibet often domesticated', 'name': 'yak'}, {'frequency': 'c', 'id': 1227, 'synset': 'yogurt.n.01', 'synonyms': ['yogurt', 'yoghurt', 'yoghourt'], 'def': 'a custard-like food made from curdled milk', 'name': 'yogurt'}, {'frequency': 'r', 'id': 1228, 'synset': 'yoke.n.07', 'synonyms': ['yoke_(animal_equipment)'], 'def': 'gear joining two animals at the neck; NOT egg yolk', 'name': 'yoke_(animal_equipment)'}, {'frequency': 'f', 'id': 1229, 'synset': 'zebra.n.01', 'synonyms': ['zebra'], 'def': 'any of several fleet black-and-white striped African equines', 'name': 'zebra'}, {'frequency': 'c', 'id': 1230, 'synset': 'zucchini.n.02', 'synonyms': ['zucchini', 'courgette'], 'def': 'small cucumber-shaped vegetable marrow; typically dark green', 'name': 'zucchini'}] # noqa
+# fmt: on
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/lvis_v1_categories.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/lvis_v1_categories.py
new file mode 100644
index 0000000000000000000000000000000000000000..7374e6968bb006f5d8c49e75d9d3b31ea3d77d05
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/lvis_v1_categories.py
@@ -0,0 +1,16 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# Autogen with
+# with open("lvis_v1_val.json", "r") as f:
+# a = json.load(f)
+# c = a["categories"]
+# for x in c:
+# del x["image_count"]
+# del x["instance_count"]
+# LVIS_CATEGORIES = repr(c) + " # noqa"
+# with open("/tmp/lvis_categories.py", "wt") as f:
+# f.write(f"LVIS_CATEGORIES = {LVIS_CATEGORIES}")
+# Then paste the contents of that file below
+
+# fmt: off
+LVIS_CATEGORIES = [{'frequency': 'c', 'synset': 'aerosol.n.02', 'synonyms': ['aerosol_can', 'spray_can'], 'id': 1, 'def': 'a dispenser that holds a substance under pressure', 'name': 'aerosol_can'}, {'frequency': 'f', 'synset': 'air_conditioner.n.01', 'synonyms': ['air_conditioner'], 'id': 2, 'def': 'a machine that keeps air cool and dry', 'name': 'air_conditioner'}, {'frequency': 'f', 'synset': 'airplane.n.01', 'synonyms': ['airplane', 'aeroplane'], 'id': 3, 'def': 'an aircraft that has a fixed wing and is powered by propellers or jets', 'name': 'airplane'}, {'frequency': 'f', 'synset': 'alarm_clock.n.01', 'synonyms': ['alarm_clock'], 'id': 4, 'def': 'a clock that wakes a sleeper at some preset time', 'name': 'alarm_clock'}, {'frequency': 'c', 'synset': 'alcohol.n.01', 'synonyms': ['alcohol', 'alcoholic_beverage'], 'id': 5, 'def': 'a liquor or brew containing alcohol as the active agent', 'name': 'alcohol'}, {'frequency': 'c', 'synset': 'alligator.n.02', 'synonyms': ['alligator', 'gator'], 'id': 6, 'def': 'amphibious reptiles related to crocodiles but with shorter broader snouts', 'name': 'alligator'}, {'frequency': 'c', 'synset': 'almond.n.02', 'synonyms': ['almond'], 'id': 7, 'def': 'oval-shaped edible seed of the almond tree', 'name': 'almond'}, {'frequency': 'c', 'synset': 'ambulance.n.01', 'synonyms': ['ambulance'], 'id': 8, 'def': 'a vehicle that takes people to and from hospitals', 'name': 'ambulance'}, {'frequency': 'c', 'synset': 'amplifier.n.01', 'synonyms': ['amplifier'], 'id': 9, 'def': 'electronic equipment that increases strength of signals', 'name': 'amplifier'}, {'frequency': 'c', 'synset': 'anklet.n.03', 'synonyms': ['anklet', 'ankle_bracelet'], 'id': 10, 'def': 'an ornament worn around the ankle', 'name': 'anklet'}, {'frequency': 'f', 'synset': 'antenna.n.01', 'synonyms': ['antenna', 'aerial', 'transmitting_aerial'], 'id': 11, 'def': 'an electrical device that sends or receives radio or television signals', 'name': 'antenna'}, {'frequency': 'f', 'synset': 'apple.n.01', 'synonyms': ['apple'], 'id': 12, 'def': 'fruit with red or yellow or green skin and sweet to tart crisp whitish flesh', 'name': 'apple'}, {'frequency': 'r', 'synset': 'applesauce.n.01', 'synonyms': ['applesauce'], 'id': 13, 'def': 'puree of stewed apples usually sweetened and spiced', 'name': 'applesauce'}, {'frequency': 'r', 'synset': 'apricot.n.02', 'synonyms': ['apricot'], 'id': 14, 'def': 'downy yellow to rosy-colored fruit resembling a small peach', 'name': 'apricot'}, {'frequency': 'f', 'synset': 'apron.n.01', 'synonyms': ['apron'], 'id': 15, 'def': 'a garment of cloth that is tied about the waist and worn to protect clothing', 'name': 'apron'}, {'frequency': 'c', 'synset': 'aquarium.n.01', 'synonyms': ['aquarium', 'fish_tank'], 'id': 16, 'def': 'a tank/pool/bowl filled with water for keeping live fish and underwater animals', 'name': 'aquarium'}, {'frequency': 'r', 'synset': 'arctic.n.02', 'synonyms': ['arctic_(type_of_shoe)', 'galosh', 'golosh', 'rubber_(type_of_shoe)', 'gumshoe'], 'id': 17, 'def': 'a waterproof overshoe that protects shoes from water or snow', 'name': 'arctic_(type_of_shoe)'}, {'frequency': 'c', 'synset': 'armband.n.02', 'synonyms': ['armband'], 'id': 18, 'def': 'a band worn around the upper arm', 'name': 'armband'}, {'frequency': 'f', 'synset': 'armchair.n.01', 'synonyms': ['armchair'], 'id': 19, 'def': 'chair with a support on each side for arms', 'name': 'armchair'}, {'frequency': 'r', 'synset': 'armoire.n.01', 'synonyms': ['armoire'], 'id': 20, 'def': 'a large wardrobe or cabinet', 'name': 'armoire'}, {'frequency': 'r', 'synset': 'armor.n.01', 'synonyms': ['armor', 'armour'], 'id': 21, 'def': 'protective covering made of metal and used in combat', 'name': 'armor'}, {'frequency': 'c', 'synset': 'artichoke.n.02', 'synonyms': ['artichoke'], 'id': 22, 'def': 'a thistlelike flower head with edible fleshy leaves and heart', 'name': 'artichoke'}, {'frequency': 'f', 'synset': 'ashcan.n.01', 'synonyms': ['trash_can', 'garbage_can', 'wastebin', 'dustbin', 'trash_barrel', 'trash_bin'], 'id': 23, 'def': 'a bin that holds rubbish until it is collected', 'name': 'trash_can'}, {'frequency': 'c', 'synset': 'ashtray.n.01', 'synonyms': ['ashtray'], 'id': 24, 'def': "a receptacle for the ash from smokers' cigars or cigarettes", 'name': 'ashtray'}, {'frequency': 'c', 'synset': 'asparagus.n.02', 'synonyms': ['asparagus'], 'id': 25, 'def': 'edible young shoots of the asparagus plant', 'name': 'asparagus'}, {'frequency': 'c', 'synset': 'atomizer.n.01', 'synonyms': ['atomizer', 'atomiser', 'spray', 'sprayer', 'nebulizer', 'nebuliser'], 'id': 26, 'def': 'a dispenser that turns a liquid (such as perfume) into a fine mist', 'name': 'atomizer'}, {'frequency': 'f', 'synset': 'avocado.n.01', 'synonyms': ['avocado'], 'id': 27, 'def': 'a pear-shaped fruit with green or blackish skin and rich yellowish pulp enclosing a single large seed', 'name': 'avocado'}, {'frequency': 'c', 'synset': 'award.n.02', 'synonyms': ['award', 'accolade'], 'id': 28, 'def': 'a tangible symbol signifying approval or distinction', 'name': 'award'}, {'frequency': 'f', 'synset': 'awning.n.01', 'synonyms': ['awning'], 'id': 29, 'def': 'a canopy made of canvas to shelter people or things from rain or sun', 'name': 'awning'}, {'frequency': 'r', 'synset': 'ax.n.01', 'synonyms': ['ax', 'axe'], 'id': 30, 'def': 'an edge tool with a heavy bladed head mounted across a handle', 'name': 'ax'}, {'frequency': 'r', 'synset': 'baboon.n.01', 'synonyms': ['baboon'], 'id': 31, 'def': 'large terrestrial monkeys having doglike muzzles', 'name': 'baboon'}, {'frequency': 'f', 'synset': 'baby_buggy.n.01', 'synonyms': ['baby_buggy', 'baby_carriage', 'perambulator', 'pram', 'stroller'], 'id': 32, 'def': 'a small vehicle with four wheels in which a baby or child is pushed around', 'name': 'baby_buggy'}, {'frequency': 'c', 'synset': 'backboard.n.01', 'synonyms': ['basketball_backboard'], 'id': 33, 'def': 'a raised vertical board with basket attached; used to play basketball', 'name': 'basketball_backboard'}, {'frequency': 'f', 'synset': 'backpack.n.01', 'synonyms': ['backpack', 'knapsack', 'packsack', 'rucksack', 'haversack'], 'id': 34, 'def': 'a bag carried by a strap on your back or shoulder', 'name': 'backpack'}, {'frequency': 'f', 'synset': 'bag.n.04', 'synonyms': ['handbag', 'purse', 'pocketbook'], 'id': 35, 'def': 'a container used for carrying money and small personal items or accessories', 'name': 'handbag'}, {'frequency': 'f', 'synset': 'bag.n.06', 'synonyms': ['suitcase', 'baggage', 'luggage'], 'id': 36, 'def': 'cases used to carry belongings when traveling', 'name': 'suitcase'}, {'frequency': 'c', 'synset': 'bagel.n.01', 'synonyms': ['bagel', 'beigel'], 'id': 37, 'def': 'glazed yeast-raised doughnut-shaped roll with hard crust', 'name': 'bagel'}, {'frequency': 'r', 'synset': 'bagpipe.n.01', 'synonyms': ['bagpipe'], 'id': 38, 'def': 'a tubular wind instrument; the player blows air into a bag and squeezes it out', 'name': 'bagpipe'}, {'frequency': 'r', 'synset': 'baguet.n.01', 'synonyms': ['baguet', 'baguette'], 'id': 39, 'def': 'narrow French stick loaf', 'name': 'baguet'}, {'frequency': 'r', 'synset': 'bait.n.02', 'synonyms': ['bait', 'lure'], 'id': 40, 'def': 'something used to lure fish or other animals into danger so they can be trapped or killed', 'name': 'bait'}, {'frequency': 'f', 'synset': 'ball.n.06', 'synonyms': ['ball'], 'id': 41, 'def': 'a spherical object used as a plaything', 'name': 'ball'}, {'frequency': 'r', 'synset': 'ballet_skirt.n.01', 'synonyms': ['ballet_skirt', 'tutu'], 'id': 42, 'def': 'very short skirt worn by ballerinas', 'name': 'ballet_skirt'}, {'frequency': 'f', 'synset': 'balloon.n.01', 'synonyms': ['balloon'], 'id': 43, 'def': 'large tough nonrigid bag filled with gas or heated air', 'name': 'balloon'}, {'frequency': 'c', 'synset': 'bamboo.n.02', 'synonyms': ['bamboo'], 'id': 44, 'def': 'woody tropical grass having hollow woody stems', 'name': 'bamboo'}, {'frequency': 'f', 'synset': 'banana.n.02', 'synonyms': ['banana'], 'id': 45, 'def': 'elongated crescent-shaped yellow fruit with soft sweet flesh', 'name': 'banana'}, {'frequency': 'c', 'synset': 'band_aid.n.01', 'synonyms': ['Band_Aid'], 'id': 46, 'def': 'trade name for an adhesive bandage to cover small cuts or blisters', 'name': 'Band_Aid'}, {'frequency': 'c', 'synset': 'bandage.n.01', 'synonyms': ['bandage'], 'id': 47, 'def': 'a piece of soft material that covers and protects an injured part of the body', 'name': 'bandage'}, {'frequency': 'f', 'synset': 'bandanna.n.01', 'synonyms': ['bandanna', 'bandana'], 'id': 48, 'def': 'large and brightly colored handkerchief; often used as a neckerchief', 'name': 'bandanna'}, {'frequency': 'r', 'synset': 'banjo.n.01', 'synonyms': ['banjo'], 'id': 49, 'def': 'a stringed instrument of the guitar family with a long neck and circular body', 'name': 'banjo'}, {'frequency': 'f', 'synset': 'banner.n.01', 'synonyms': ['banner', 'streamer'], 'id': 50, 'def': 'long strip of cloth or paper used for decoration or advertising', 'name': 'banner'}, {'frequency': 'r', 'synset': 'barbell.n.01', 'synonyms': ['barbell'], 'id': 51, 'def': 'a bar to which heavy discs are attached at each end; used in weightlifting', 'name': 'barbell'}, {'frequency': 'r', 'synset': 'barge.n.01', 'synonyms': ['barge'], 'id': 52, 'def': 'a flatbottom boat for carrying heavy loads (especially on canals)', 'name': 'barge'}, {'frequency': 'f', 'synset': 'barrel.n.02', 'synonyms': ['barrel', 'cask'], 'id': 53, 'def': 'a cylindrical container that holds liquids', 'name': 'barrel'}, {'frequency': 'c', 'synset': 'barrette.n.01', 'synonyms': ['barrette'], 'id': 54, 'def': "a pin for holding women's hair in place", 'name': 'barrette'}, {'frequency': 'c', 'synset': 'barrow.n.03', 'synonyms': ['barrow', 'garden_cart', 'lawn_cart', 'wheelbarrow'], 'id': 55, 'def': 'a cart for carrying small loads; has handles and one or more wheels', 'name': 'barrow'}, {'frequency': 'f', 'synset': 'base.n.03', 'synonyms': ['baseball_base'], 'id': 56, 'def': 'a place that the runner must touch before scoring', 'name': 'baseball_base'}, {'frequency': 'f', 'synset': 'baseball.n.02', 'synonyms': ['baseball'], 'id': 57, 'def': 'a ball used in playing baseball', 'name': 'baseball'}, {'frequency': 'f', 'synset': 'baseball_bat.n.01', 'synonyms': ['baseball_bat'], 'id': 58, 'def': 'an implement used in baseball by the batter', 'name': 'baseball_bat'}, {'frequency': 'f', 'synset': 'baseball_cap.n.01', 'synonyms': ['baseball_cap', 'jockey_cap', 'golf_cap'], 'id': 59, 'def': 'a cap with a bill', 'name': 'baseball_cap'}, {'frequency': 'f', 'synset': 'baseball_glove.n.01', 'synonyms': ['baseball_glove', 'baseball_mitt'], 'id': 60, 'def': 'the handwear used by fielders in playing baseball', 'name': 'baseball_glove'}, {'frequency': 'f', 'synset': 'basket.n.01', 'synonyms': ['basket', 'handbasket'], 'id': 61, 'def': 'a container that is usually woven and has handles', 'name': 'basket'}, {'frequency': 'c', 'synset': 'basketball.n.02', 'synonyms': ['basketball'], 'id': 62, 'def': 'an inflated ball used in playing basketball', 'name': 'basketball'}, {'frequency': 'r', 'synset': 'bass_horn.n.01', 'synonyms': ['bass_horn', 'sousaphone', 'tuba'], 'id': 63, 'def': 'the lowest brass wind instrument', 'name': 'bass_horn'}, {'frequency': 'c', 'synset': 'bat.n.01', 'synonyms': ['bat_(animal)'], 'id': 64, 'def': 'nocturnal mouselike mammal with forelimbs modified to form membranous wings', 'name': 'bat_(animal)'}, {'frequency': 'f', 'synset': 'bath_mat.n.01', 'synonyms': ['bath_mat'], 'id': 65, 'def': 'a heavy towel or mat to stand on while drying yourself after a bath', 'name': 'bath_mat'}, {'frequency': 'f', 'synset': 'bath_towel.n.01', 'synonyms': ['bath_towel'], 'id': 66, 'def': 'a large towel; to dry yourself after a bath', 'name': 'bath_towel'}, {'frequency': 'c', 'synset': 'bathrobe.n.01', 'synonyms': ['bathrobe'], 'id': 67, 'def': 'a loose-fitting robe of towelling; worn after a bath or swim', 'name': 'bathrobe'}, {'frequency': 'f', 'synset': 'bathtub.n.01', 'synonyms': ['bathtub', 'bathing_tub'], 'id': 68, 'def': 'a large open container that you fill with water and use to wash the body', 'name': 'bathtub'}, {'frequency': 'r', 'synset': 'batter.n.02', 'synonyms': ['batter_(food)'], 'id': 69, 'def': 'a liquid or semiliquid mixture, as of flour, eggs, and milk, used in cooking', 'name': 'batter_(food)'}, {'frequency': 'c', 'synset': 'battery.n.02', 'synonyms': ['battery'], 'id': 70, 'def': 'a portable device that produces electricity', 'name': 'battery'}, {'frequency': 'r', 'synset': 'beach_ball.n.01', 'synonyms': ['beachball'], 'id': 71, 'def': 'large and light ball; for play at the seaside', 'name': 'beachball'}, {'frequency': 'c', 'synset': 'bead.n.01', 'synonyms': ['bead'], 'id': 72, 'def': 'a small ball with a hole through the middle used for ornamentation, jewellery, etc.', 'name': 'bead'}, {'frequency': 'c', 'synset': 'bean_curd.n.01', 'synonyms': ['bean_curd', 'tofu'], 'id': 73, 'def': 'cheeselike food made of curdled soybean milk', 'name': 'bean_curd'}, {'frequency': 'c', 'synset': 'beanbag.n.01', 'synonyms': ['beanbag'], 'id': 74, 'def': 'a bag filled with dried beans or similar items; used in games or to sit on', 'name': 'beanbag'}, {'frequency': 'f', 'synset': 'beanie.n.01', 'synonyms': ['beanie', 'beany'], 'id': 75, 'def': 'a small skullcap; formerly worn by schoolboys and college freshmen', 'name': 'beanie'}, {'frequency': 'f', 'synset': 'bear.n.01', 'synonyms': ['bear'], 'id': 76, 'def': 'large carnivorous or omnivorous mammals with shaggy coats and claws', 'name': 'bear'}, {'frequency': 'f', 'synset': 'bed.n.01', 'synonyms': ['bed'], 'id': 77, 'def': 'a piece of furniture that provides a place to sleep', 'name': 'bed'}, {'frequency': 'r', 'synset': 'bedpan.n.01', 'synonyms': ['bedpan'], 'id': 78, 'def': 'a shallow vessel used by a bedridden patient for defecation and urination', 'name': 'bedpan'}, {'frequency': 'f', 'synset': 'bedspread.n.01', 'synonyms': ['bedspread', 'bedcover', 'bed_covering', 'counterpane', 'spread'], 'id': 79, 'def': 'decorative cover for a bed', 'name': 'bedspread'}, {'frequency': 'f', 'synset': 'beef.n.01', 'synonyms': ['cow'], 'id': 80, 'def': 'cattle/cow', 'name': 'cow'}, {'frequency': 'f', 'synset': 'beef.n.02', 'synonyms': ['beef_(food)', 'boeuf_(food)'], 'id': 81, 'def': 'meat from an adult domestic bovine', 'name': 'beef_(food)'}, {'frequency': 'r', 'synset': 'beeper.n.01', 'synonyms': ['beeper', 'pager'], 'id': 82, 'def': 'an device that beeps when the person carrying it is being paged', 'name': 'beeper'}, {'frequency': 'f', 'synset': 'beer_bottle.n.01', 'synonyms': ['beer_bottle'], 'id': 83, 'def': 'a bottle that holds beer', 'name': 'beer_bottle'}, {'frequency': 'c', 'synset': 'beer_can.n.01', 'synonyms': ['beer_can'], 'id': 84, 'def': 'a can that holds beer', 'name': 'beer_can'}, {'frequency': 'r', 'synset': 'beetle.n.01', 'synonyms': ['beetle'], 'id': 85, 'def': 'insect with hard wing covers', 'name': 'beetle'}, {'frequency': 'f', 'synset': 'bell.n.01', 'synonyms': ['bell'], 'id': 86, 'def': 'a hollow device made of metal that makes a ringing sound when struck', 'name': 'bell'}, {'frequency': 'f', 'synset': 'bell_pepper.n.02', 'synonyms': ['bell_pepper', 'capsicum'], 'id': 87, 'def': 'large bell-shaped sweet pepper in green or red or yellow or orange or black varieties', 'name': 'bell_pepper'}, {'frequency': 'f', 'synset': 'belt.n.02', 'synonyms': ['belt'], 'id': 88, 'def': 'a band to tie or buckle around the body (usually at the waist)', 'name': 'belt'}, {'frequency': 'f', 'synset': 'belt_buckle.n.01', 'synonyms': ['belt_buckle'], 'id': 89, 'def': 'the buckle used to fasten a belt', 'name': 'belt_buckle'}, {'frequency': 'f', 'synset': 'bench.n.01', 'synonyms': ['bench'], 'id': 90, 'def': 'a long seat for more than one person', 'name': 'bench'}, {'frequency': 'c', 'synset': 'beret.n.01', 'synonyms': ['beret'], 'id': 91, 'def': 'a cap with no brim or bill; made of soft cloth', 'name': 'beret'}, {'frequency': 'c', 'synset': 'bib.n.02', 'synonyms': ['bib'], 'id': 92, 'def': 'a napkin tied under the chin of a child while eating', 'name': 'bib'}, {'frequency': 'r', 'synset': 'bible.n.01', 'synonyms': ['Bible'], 'id': 93, 'def': 'the sacred writings of the Christian religions', 'name': 'Bible'}, {'frequency': 'f', 'synset': 'bicycle.n.01', 'synonyms': ['bicycle', 'bike_(bicycle)'], 'id': 94, 'def': 'a wheeled vehicle that has two wheels and is moved by foot pedals', 'name': 'bicycle'}, {'frequency': 'f', 'synset': 'bill.n.09', 'synonyms': ['visor', 'vizor'], 'id': 95, 'def': 'a brim that projects to the front to shade the eyes', 'name': 'visor'}, {'frequency': 'f', 'synset': 'billboard.n.01', 'synonyms': ['billboard'], 'id': 96, 'def': 'large outdoor signboard', 'name': 'billboard'}, {'frequency': 'c', 'synset': 'binder.n.03', 'synonyms': ['binder', 'ring-binder'], 'id': 97, 'def': 'holds loose papers or magazines', 'name': 'binder'}, {'frequency': 'c', 'synset': 'binoculars.n.01', 'synonyms': ['binoculars', 'field_glasses', 'opera_glasses'], 'id': 98, 'def': 'an optical instrument designed for simultaneous use by both eyes', 'name': 'binoculars'}, {'frequency': 'f', 'synset': 'bird.n.01', 'synonyms': ['bird'], 'id': 99, 'def': 'animal characterized by feathers and wings', 'name': 'bird'}, {'frequency': 'c', 'synset': 'bird_feeder.n.01', 'synonyms': ['birdfeeder'], 'id': 100, 'def': 'an outdoor device that supplies food for wild birds', 'name': 'birdfeeder'}, {'frequency': 'c', 'synset': 'birdbath.n.01', 'synonyms': ['birdbath'], 'id': 101, 'def': 'an ornamental basin (usually in a garden) for birds to bathe in', 'name': 'birdbath'}, {'frequency': 'c', 'synset': 'birdcage.n.01', 'synonyms': ['birdcage'], 'id': 102, 'def': 'a cage in which a bird can be kept', 'name': 'birdcage'}, {'frequency': 'c', 'synset': 'birdhouse.n.01', 'synonyms': ['birdhouse'], 'id': 103, 'def': 'a shelter for birds', 'name': 'birdhouse'}, {'frequency': 'f', 'synset': 'birthday_cake.n.01', 'synonyms': ['birthday_cake'], 'id': 104, 'def': 'decorated cake served at a birthday party', 'name': 'birthday_cake'}, {'frequency': 'r', 'synset': 'birthday_card.n.01', 'synonyms': ['birthday_card'], 'id': 105, 'def': 'a card expressing a birthday greeting', 'name': 'birthday_card'}, {'frequency': 'r', 'synset': 'black_flag.n.01', 'synonyms': ['pirate_flag'], 'id': 106, 'def': 'a flag usually bearing a white skull and crossbones on a black background', 'name': 'pirate_flag'}, {'frequency': 'c', 'synset': 'black_sheep.n.02', 'synonyms': ['black_sheep'], 'id': 107, 'def': 'sheep with a black coat', 'name': 'black_sheep'}, {'frequency': 'c', 'synset': 'blackberry.n.01', 'synonyms': ['blackberry'], 'id': 108, 'def': 'large sweet black or very dark purple edible aggregate fruit', 'name': 'blackberry'}, {'frequency': 'f', 'synset': 'blackboard.n.01', 'synonyms': ['blackboard', 'chalkboard'], 'id': 109, 'def': 'sheet of slate; for writing with chalk', 'name': 'blackboard'}, {'frequency': 'f', 'synset': 'blanket.n.01', 'synonyms': ['blanket'], 'id': 110, 'def': 'bedding that keeps a person warm in bed', 'name': 'blanket'}, {'frequency': 'c', 'synset': 'blazer.n.01', 'synonyms': ['blazer', 'sport_jacket', 'sport_coat', 'sports_jacket', 'sports_coat'], 'id': 111, 'def': 'lightweight jacket; often striped in the colors of a club or school', 'name': 'blazer'}, {'frequency': 'f', 'synset': 'blender.n.01', 'synonyms': ['blender', 'liquidizer', 'liquidiser'], 'id': 112, 'def': 'an electrically powered mixer that mix or chop or liquefy foods', 'name': 'blender'}, {'frequency': 'r', 'synset': 'blimp.n.02', 'synonyms': ['blimp'], 'id': 113, 'def': 'a small nonrigid airship used for observation or as a barrage balloon', 'name': 'blimp'}, {'frequency': 'f', 'synset': 'blinker.n.01', 'synonyms': ['blinker', 'flasher'], 'id': 114, 'def': 'a light that flashes on and off; used as a signal or to send messages', 'name': 'blinker'}, {'frequency': 'f', 'synset': 'blouse.n.01', 'synonyms': ['blouse'], 'id': 115, 'def': 'a top worn by women', 'name': 'blouse'}, {'frequency': 'f', 'synset': 'blueberry.n.02', 'synonyms': ['blueberry'], 'id': 116, 'def': 'sweet edible dark-blue berries of blueberry plants', 'name': 'blueberry'}, {'frequency': 'r', 'synset': 'board.n.09', 'synonyms': ['gameboard'], 'id': 117, 'def': 'a flat portable surface (usually rectangular) designed for board games', 'name': 'gameboard'}, {'frequency': 'f', 'synset': 'boat.n.01', 'synonyms': ['boat', 'ship_(boat)'], 'id': 118, 'def': 'a vessel for travel on water', 'name': 'boat'}, {'frequency': 'r', 'synset': 'bob.n.05', 'synonyms': ['bob', 'bobber', 'bobfloat'], 'id': 119, 'def': 'a small float usually made of cork; attached to a fishing line', 'name': 'bob'}, {'frequency': 'c', 'synset': 'bobbin.n.01', 'synonyms': ['bobbin', 'spool', 'reel'], 'id': 120, 'def': 'a thing around which thread/tape/film or other flexible materials can be wound', 'name': 'bobbin'}, {'frequency': 'c', 'synset': 'bobby_pin.n.01', 'synonyms': ['bobby_pin', 'hairgrip'], 'id': 121, 'def': 'a flat wire hairpin used to hold bobbed hair in place', 'name': 'bobby_pin'}, {'frequency': 'c', 'synset': 'boiled_egg.n.01', 'synonyms': ['boiled_egg', 'coddled_egg'], 'id': 122, 'def': 'egg cooked briefly in the shell in gently boiling water', 'name': 'boiled_egg'}, {'frequency': 'r', 'synset': 'bolo_tie.n.01', 'synonyms': ['bolo_tie', 'bolo', 'bola_tie', 'bola'], 'id': 123, 'def': 'a cord fastened around the neck with an ornamental clasp and worn as a necktie', 'name': 'bolo_tie'}, {'frequency': 'c', 'synset': 'bolt.n.03', 'synonyms': ['deadbolt'], 'id': 124, 'def': 'the part of a lock that is engaged or withdrawn with a key', 'name': 'deadbolt'}, {'frequency': 'f', 'synset': 'bolt.n.06', 'synonyms': ['bolt'], 'id': 125, 'def': 'a screw that screws into a nut to form a fastener', 'name': 'bolt'}, {'frequency': 'r', 'synset': 'bonnet.n.01', 'synonyms': ['bonnet'], 'id': 126, 'def': 'a hat tied under the chin', 'name': 'bonnet'}, {'frequency': 'f', 'synset': 'book.n.01', 'synonyms': ['book'], 'id': 127, 'def': 'a written work or composition that has been published', 'name': 'book'}, {'frequency': 'c', 'synset': 'bookcase.n.01', 'synonyms': ['bookcase'], 'id': 128, 'def': 'a piece of furniture with shelves for storing books', 'name': 'bookcase'}, {'frequency': 'c', 'synset': 'booklet.n.01', 'synonyms': ['booklet', 'brochure', 'leaflet', 'pamphlet'], 'id': 129, 'def': 'a small book usually having a paper cover', 'name': 'booklet'}, {'frequency': 'r', 'synset': 'bookmark.n.01', 'synonyms': ['bookmark', 'bookmarker'], 'id': 130, 'def': 'a marker (a piece of paper or ribbon) placed between the pages of a book', 'name': 'bookmark'}, {'frequency': 'r', 'synset': 'boom.n.04', 'synonyms': ['boom_microphone', 'microphone_boom'], 'id': 131, 'def': 'a pole carrying an overhead microphone projected over a film or tv set', 'name': 'boom_microphone'}, {'frequency': 'f', 'synset': 'boot.n.01', 'synonyms': ['boot'], 'id': 132, 'def': 'footwear that covers the whole foot and lower leg', 'name': 'boot'}, {'frequency': 'f', 'synset': 'bottle.n.01', 'synonyms': ['bottle'], 'id': 133, 'def': 'a glass or plastic vessel used for storing drinks or other liquids', 'name': 'bottle'}, {'frequency': 'c', 'synset': 'bottle_opener.n.01', 'synonyms': ['bottle_opener'], 'id': 134, 'def': 'an opener for removing caps or corks from bottles', 'name': 'bottle_opener'}, {'frequency': 'c', 'synset': 'bouquet.n.01', 'synonyms': ['bouquet'], 'id': 135, 'def': 'an arrangement of flowers that is usually given as a present', 'name': 'bouquet'}, {'frequency': 'r', 'synset': 'bow.n.04', 'synonyms': ['bow_(weapon)'], 'id': 136, 'def': 'a weapon for shooting arrows', 'name': 'bow_(weapon)'}, {'frequency': 'f', 'synset': 'bow.n.08', 'synonyms': ['bow_(decorative_ribbons)'], 'id': 137, 'def': 'a decorative interlacing of ribbons', 'name': 'bow_(decorative_ribbons)'}, {'frequency': 'f', 'synset': 'bow_tie.n.01', 'synonyms': ['bow-tie', 'bowtie'], 'id': 138, 'def': "a man's tie that ties in a bow", 'name': 'bow-tie'}, {'frequency': 'f', 'synset': 'bowl.n.03', 'synonyms': ['bowl'], 'id': 139, 'def': 'a dish that is round and open at the top for serving foods', 'name': 'bowl'}, {'frequency': 'r', 'synset': 'bowl.n.08', 'synonyms': ['pipe_bowl'], 'id': 140, 'def': 'a small round container that is open at the top for holding tobacco', 'name': 'pipe_bowl'}, {'frequency': 'c', 'synset': 'bowler_hat.n.01', 'synonyms': ['bowler_hat', 'bowler', 'derby_hat', 'derby', 'plug_hat'], 'id': 141, 'def': 'a felt hat that is round and hard with a narrow brim', 'name': 'bowler_hat'}, {'frequency': 'r', 'synset': 'bowling_ball.n.01', 'synonyms': ['bowling_ball'], 'id': 142, 'def': 'a large ball with finger holes used in the sport of bowling', 'name': 'bowling_ball'}, {'frequency': 'f', 'synset': 'box.n.01', 'synonyms': ['box'], 'id': 143, 'def': 'a (usually rectangular) container; may have a lid', 'name': 'box'}, {'frequency': 'r', 'synset': 'boxing_glove.n.01', 'synonyms': ['boxing_glove'], 'id': 144, 'def': 'large glove coverings the fists of a fighter worn for the sport of boxing', 'name': 'boxing_glove'}, {'frequency': 'c', 'synset': 'brace.n.06', 'synonyms': ['suspenders'], 'id': 145, 'def': 'elastic straps that hold trousers up (usually used in the plural)', 'name': 'suspenders'}, {'frequency': 'f', 'synset': 'bracelet.n.02', 'synonyms': ['bracelet', 'bangle'], 'id': 146, 'def': 'jewelry worn around the wrist for decoration', 'name': 'bracelet'}, {'frequency': 'r', 'synset': 'brass.n.07', 'synonyms': ['brass_plaque'], 'id': 147, 'def': 'a memorial made of brass', 'name': 'brass_plaque'}, {'frequency': 'c', 'synset': 'brassiere.n.01', 'synonyms': ['brassiere', 'bra', 'bandeau'], 'id': 148, 'def': 'an undergarment worn by women to support their breasts', 'name': 'brassiere'}, {'frequency': 'c', 'synset': 'bread-bin.n.01', 'synonyms': ['bread-bin', 'breadbox'], 'id': 149, 'def': 'a container used to keep bread or cake in', 'name': 'bread-bin'}, {'frequency': 'f', 'synset': 'bread.n.01', 'synonyms': ['bread'], 'id': 150, 'def': 'food made from dough of flour or meal and usually raised with yeast or baking powder and then baked', 'name': 'bread'}, {'frequency': 'r', 'synset': 'breechcloth.n.01', 'synonyms': ['breechcloth', 'breechclout', 'loincloth'], 'id': 151, 'def': 'a garment that provides covering for the loins', 'name': 'breechcloth'}, {'frequency': 'f', 'synset': 'bridal_gown.n.01', 'synonyms': ['bridal_gown', 'wedding_gown', 'wedding_dress'], 'id': 152, 'def': 'a gown worn by the bride at a wedding', 'name': 'bridal_gown'}, {'frequency': 'c', 'synset': 'briefcase.n.01', 'synonyms': ['briefcase'], 'id': 153, 'def': 'a case with a handle; for carrying papers or files or books', 'name': 'briefcase'}, {'frequency': 'f', 'synset': 'broccoli.n.01', 'synonyms': ['broccoli'], 'id': 154, 'def': 'plant with dense clusters of tight green flower buds', 'name': 'broccoli'}, {'frequency': 'r', 'synset': 'brooch.n.01', 'synonyms': ['broach'], 'id': 155, 'def': 'a decorative pin worn by women', 'name': 'broach'}, {'frequency': 'c', 'synset': 'broom.n.01', 'synonyms': ['broom'], 'id': 156, 'def': 'bundle of straws or twigs attached to a long handle; used for cleaning', 'name': 'broom'}, {'frequency': 'c', 'synset': 'brownie.n.03', 'synonyms': ['brownie'], 'id': 157, 'def': 'square or bar of very rich chocolate cake usually with nuts', 'name': 'brownie'}, {'frequency': 'c', 'synset': 'brussels_sprouts.n.01', 'synonyms': ['brussels_sprouts'], 'id': 158, 'def': 'the small edible cabbage-like buds growing along a stalk', 'name': 'brussels_sprouts'}, {'frequency': 'r', 'synset': 'bubble_gum.n.01', 'synonyms': ['bubble_gum'], 'id': 159, 'def': 'a kind of chewing gum that can be blown into bubbles', 'name': 'bubble_gum'}, {'frequency': 'f', 'synset': 'bucket.n.01', 'synonyms': ['bucket', 'pail'], 'id': 160, 'def': 'a roughly cylindrical vessel that is open at the top', 'name': 'bucket'}, {'frequency': 'r', 'synset': 'buggy.n.01', 'synonyms': ['horse_buggy'], 'id': 161, 'def': 'a small lightweight carriage; drawn by a single horse', 'name': 'horse_buggy'}, {'frequency': 'c', 'synset': 'bull.n.11', 'synonyms': ['horned_cow'], 'id': 162, 'def': 'a cow with horns', 'name': 'bull'}, {'frequency': 'c', 'synset': 'bulldog.n.01', 'synonyms': ['bulldog'], 'id': 163, 'def': 'a thickset short-haired dog with a large head and strong undershot lower jaw', 'name': 'bulldog'}, {'frequency': 'r', 'synset': 'bulldozer.n.01', 'synonyms': ['bulldozer', 'dozer'], 'id': 164, 'def': 'large powerful tractor; a large blade in front flattens areas of ground', 'name': 'bulldozer'}, {'frequency': 'c', 'synset': 'bullet_train.n.01', 'synonyms': ['bullet_train'], 'id': 165, 'def': 'a high-speed passenger train', 'name': 'bullet_train'}, {'frequency': 'c', 'synset': 'bulletin_board.n.02', 'synonyms': ['bulletin_board', 'notice_board'], 'id': 166, 'def': 'a board that hangs on a wall; displays announcements', 'name': 'bulletin_board'}, {'frequency': 'r', 'synset': 'bulletproof_vest.n.01', 'synonyms': ['bulletproof_vest'], 'id': 167, 'def': 'a vest capable of resisting the impact of a bullet', 'name': 'bulletproof_vest'}, {'frequency': 'c', 'synset': 'bullhorn.n.01', 'synonyms': ['bullhorn', 'megaphone'], 'id': 168, 'def': 'a portable loudspeaker with built-in microphone and amplifier', 'name': 'bullhorn'}, {'frequency': 'f', 'synset': 'bun.n.01', 'synonyms': ['bun', 'roll'], 'id': 169, 'def': 'small rounded bread either plain or sweet', 'name': 'bun'}, {'frequency': 'c', 'synset': 'bunk_bed.n.01', 'synonyms': ['bunk_bed'], 'id': 170, 'def': 'beds built one above the other', 'name': 'bunk_bed'}, {'frequency': 'f', 'synset': 'buoy.n.01', 'synonyms': ['buoy'], 'id': 171, 'def': 'a float attached by rope to the seabed to mark channels in a harbor or underwater hazards', 'name': 'buoy'}, {'frequency': 'r', 'synset': 'burrito.n.01', 'synonyms': ['burrito'], 'id': 172, 'def': 'a flour tortilla folded around a filling', 'name': 'burrito'}, {'frequency': 'f', 'synset': 'bus.n.01', 'synonyms': ['bus_(vehicle)', 'autobus', 'charabanc', 'double-decker', 'motorbus', 'motorcoach'], 'id': 173, 'def': 'a vehicle carrying many passengers; used for public transport', 'name': 'bus_(vehicle)'}, {'frequency': 'c', 'synset': 'business_card.n.01', 'synonyms': ['business_card'], 'id': 174, 'def': "a card on which are printed the person's name and business affiliation", 'name': 'business_card'}, {'frequency': 'f', 'synset': 'butter.n.01', 'synonyms': ['butter'], 'id': 175, 'def': 'an edible emulsion of fat globules made by churning milk or cream; for cooking and table use', 'name': 'butter'}, {'frequency': 'c', 'synset': 'butterfly.n.01', 'synonyms': ['butterfly'], 'id': 176, 'def': 'insect typically having a slender body with knobbed antennae and broad colorful wings', 'name': 'butterfly'}, {'frequency': 'f', 'synset': 'button.n.01', 'synonyms': ['button'], 'id': 177, 'def': 'a round fastener sewn to shirts and coats etc to fit through buttonholes', 'name': 'button'}, {'frequency': 'f', 'synset': 'cab.n.03', 'synonyms': ['cab_(taxi)', 'taxi', 'taxicab'], 'id': 178, 'def': 'a car that takes passengers where they want to go in exchange for money', 'name': 'cab_(taxi)'}, {'frequency': 'r', 'synset': 'cabana.n.01', 'synonyms': ['cabana'], 'id': 179, 'def': 'a small tent used as a dressing room beside the sea or a swimming pool', 'name': 'cabana'}, {'frequency': 'c', 'synset': 'cabin_car.n.01', 'synonyms': ['cabin_car', 'caboose'], 'id': 180, 'def': 'a car on a freight train for use of the train crew; usually the last car on the train', 'name': 'cabin_car'}, {'frequency': 'f', 'synset': 'cabinet.n.01', 'synonyms': ['cabinet'], 'id': 181, 'def': 'a piece of furniture resembling a cupboard with doors and shelves and drawers', 'name': 'cabinet'}, {'frequency': 'r', 'synset': 'cabinet.n.03', 'synonyms': ['locker', 'storage_locker'], 'id': 182, 'def': 'a storage compartment for clothes and valuables; usually it has a lock', 'name': 'locker'}, {'frequency': 'f', 'synset': 'cake.n.03', 'synonyms': ['cake'], 'id': 183, 'def': 'baked goods made from or based on a mixture of flour, sugar, eggs, and fat', 'name': 'cake'}, {'frequency': 'c', 'synset': 'calculator.n.02', 'synonyms': ['calculator'], 'id': 184, 'def': 'a small machine that is used for mathematical calculations', 'name': 'calculator'}, {'frequency': 'f', 'synset': 'calendar.n.02', 'synonyms': ['calendar'], 'id': 185, 'def': 'a list or register of events (appointments/social events/court cases, etc)', 'name': 'calendar'}, {'frequency': 'c', 'synset': 'calf.n.01', 'synonyms': ['calf'], 'id': 186, 'def': 'young of domestic cattle', 'name': 'calf'}, {'frequency': 'c', 'synset': 'camcorder.n.01', 'synonyms': ['camcorder'], 'id': 187, 'def': 'a portable television camera and videocassette recorder', 'name': 'camcorder'}, {'frequency': 'c', 'synset': 'camel.n.01', 'synonyms': ['camel'], 'id': 188, 'def': 'cud-chewing mammal used as a draft or saddle animal in desert regions', 'name': 'camel'}, {'frequency': 'f', 'synset': 'camera.n.01', 'synonyms': ['camera'], 'id': 189, 'def': 'equipment for taking photographs', 'name': 'camera'}, {'frequency': 'c', 'synset': 'camera_lens.n.01', 'synonyms': ['camera_lens'], 'id': 190, 'def': 'a lens that focuses the image in a camera', 'name': 'camera_lens'}, {'frequency': 'c', 'synset': 'camper.n.02', 'synonyms': ['camper_(vehicle)', 'camping_bus', 'motor_home'], 'id': 191, 'def': 'a recreational vehicle equipped for camping out while traveling', 'name': 'camper_(vehicle)'}, {'frequency': 'f', 'synset': 'can.n.01', 'synonyms': ['can', 'tin_can'], 'id': 192, 'def': 'airtight sealed metal container for food or drink or paint etc.', 'name': 'can'}, {'frequency': 'c', 'synset': 'can_opener.n.01', 'synonyms': ['can_opener', 'tin_opener'], 'id': 193, 'def': 'a device for cutting cans open', 'name': 'can_opener'}, {'frequency': 'f', 'synset': 'candle.n.01', 'synonyms': ['candle', 'candlestick'], 'id': 194, 'def': 'stick of wax with a wick in the middle', 'name': 'candle'}, {'frequency': 'f', 'synset': 'candlestick.n.01', 'synonyms': ['candle_holder'], 'id': 195, 'def': 'a holder with sockets for candles', 'name': 'candle_holder'}, {'frequency': 'r', 'synset': 'candy_bar.n.01', 'synonyms': ['candy_bar'], 'id': 196, 'def': 'a candy shaped as a bar', 'name': 'candy_bar'}, {'frequency': 'c', 'synset': 'candy_cane.n.01', 'synonyms': ['candy_cane'], 'id': 197, 'def': 'a hard candy in the shape of a rod (usually with stripes)', 'name': 'candy_cane'}, {'frequency': 'c', 'synset': 'cane.n.01', 'synonyms': ['walking_cane'], 'id': 198, 'def': 'a stick that people can lean on to help them walk', 'name': 'walking_cane'}, {'frequency': 'c', 'synset': 'canister.n.02', 'synonyms': ['canister', 'cannister'], 'id': 199, 'def': 'metal container for storing dry foods such as tea or flour', 'name': 'canister'}, {'frequency': 'c', 'synset': 'canoe.n.01', 'synonyms': ['canoe'], 'id': 200, 'def': 'small and light boat; pointed at both ends; propelled with a paddle', 'name': 'canoe'}, {'frequency': 'c', 'synset': 'cantaloup.n.02', 'synonyms': ['cantaloup', 'cantaloupe'], 'id': 201, 'def': 'the fruit of a cantaloup vine; small to medium-sized melon with yellowish flesh', 'name': 'cantaloup'}, {'frequency': 'r', 'synset': 'canteen.n.01', 'synonyms': ['canteen'], 'id': 202, 'def': 'a flask for carrying water; used by soldiers or travelers', 'name': 'canteen'}, {'frequency': 'f', 'synset': 'cap.n.01', 'synonyms': ['cap_(headwear)'], 'id': 203, 'def': 'a tight-fitting headwear', 'name': 'cap_(headwear)'}, {'frequency': 'f', 'synset': 'cap.n.02', 'synonyms': ['bottle_cap', 'cap_(container_lid)'], 'id': 204, 'def': 'a top (as for a bottle)', 'name': 'bottle_cap'}, {'frequency': 'c', 'synset': 'cape.n.02', 'synonyms': ['cape'], 'id': 205, 'def': 'a sleeveless garment like a cloak but shorter', 'name': 'cape'}, {'frequency': 'c', 'synset': 'cappuccino.n.01', 'synonyms': ['cappuccino', 'coffee_cappuccino'], 'id': 206, 'def': 'equal parts of espresso and steamed milk', 'name': 'cappuccino'}, {'frequency': 'f', 'synset': 'car.n.01', 'synonyms': ['car_(automobile)', 'auto_(automobile)', 'automobile'], 'id': 207, 'def': 'a motor vehicle with four wheels', 'name': 'car_(automobile)'}, {'frequency': 'f', 'synset': 'car.n.02', 'synonyms': ['railcar_(part_of_a_train)', 'railway_car_(part_of_a_train)', 'railroad_car_(part_of_a_train)'], 'id': 208, 'def': 'a wheeled vehicle adapted to the rails of railroad (mark each individual railcar separately)', 'name': 'railcar_(part_of_a_train)'}, {'frequency': 'r', 'synset': 'car.n.04', 'synonyms': ['elevator_car'], 'id': 209, 'def': 'where passengers ride up and down', 'name': 'elevator_car'}, {'frequency': 'r', 'synset': 'car_battery.n.01', 'synonyms': ['car_battery', 'automobile_battery'], 'id': 210, 'def': 'a battery in a motor vehicle', 'name': 'car_battery'}, {'frequency': 'c', 'synset': 'card.n.02', 'synonyms': ['identity_card'], 'id': 211, 'def': 'a card certifying the identity of the bearer', 'name': 'identity_card'}, {'frequency': 'c', 'synset': 'card.n.03', 'synonyms': ['card'], 'id': 212, 'def': 'a rectangular piece of paper used to send messages (e.g. greetings or pictures)', 'name': 'card'}, {'frequency': 'c', 'synset': 'cardigan.n.01', 'synonyms': ['cardigan'], 'id': 213, 'def': 'knitted jacket that is fastened up the front with buttons or a zipper', 'name': 'cardigan'}, {'frequency': 'r', 'synset': 'cargo_ship.n.01', 'synonyms': ['cargo_ship', 'cargo_vessel'], 'id': 214, 'def': 'a ship designed to carry cargo', 'name': 'cargo_ship'}, {'frequency': 'r', 'synset': 'carnation.n.01', 'synonyms': ['carnation'], 'id': 215, 'def': 'plant with pink to purple-red spice-scented usually double flowers', 'name': 'carnation'}, {'frequency': 'c', 'synset': 'carriage.n.02', 'synonyms': ['horse_carriage'], 'id': 216, 'def': 'a vehicle with wheels drawn by one or more horses', 'name': 'horse_carriage'}, {'frequency': 'f', 'synset': 'carrot.n.01', 'synonyms': ['carrot'], 'id': 217, 'def': 'deep orange edible root of the cultivated carrot plant', 'name': 'carrot'}, {'frequency': 'f', 'synset': 'carryall.n.01', 'synonyms': ['tote_bag'], 'id': 218, 'def': 'a capacious bag or basket', 'name': 'tote_bag'}, {'frequency': 'c', 'synset': 'cart.n.01', 'synonyms': ['cart'], 'id': 219, 'def': 'a heavy open wagon usually having two wheels and drawn by an animal', 'name': 'cart'}, {'frequency': 'c', 'synset': 'carton.n.02', 'synonyms': ['carton'], 'id': 220, 'def': 'a container made of cardboard for holding food or drink', 'name': 'carton'}, {'frequency': 'c', 'synset': 'cash_register.n.01', 'synonyms': ['cash_register', 'register_(for_cash_transactions)'], 'id': 221, 'def': 'a cashbox with an adding machine to register transactions', 'name': 'cash_register'}, {'frequency': 'r', 'synset': 'casserole.n.01', 'synonyms': ['casserole'], 'id': 222, 'def': 'food cooked and served in a casserole', 'name': 'casserole'}, {'frequency': 'r', 'synset': 'cassette.n.01', 'synonyms': ['cassette'], 'id': 223, 'def': 'a container that holds a magnetic tape used for recording or playing sound or video', 'name': 'cassette'}, {'frequency': 'c', 'synset': 'cast.n.05', 'synonyms': ['cast', 'plaster_cast', 'plaster_bandage'], 'id': 224, 'def': 'bandage consisting of a firm covering that immobilizes broken bones while they heal', 'name': 'cast'}, {'frequency': 'f', 'synset': 'cat.n.01', 'synonyms': ['cat'], 'id': 225, 'def': 'a domestic house cat', 'name': 'cat'}, {'frequency': 'f', 'synset': 'cauliflower.n.02', 'synonyms': ['cauliflower'], 'id': 226, 'def': 'edible compact head of white undeveloped flowers', 'name': 'cauliflower'}, {'frequency': 'c', 'synset': 'cayenne.n.02', 'synonyms': ['cayenne_(spice)', 'cayenne_pepper_(spice)', 'red_pepper_(spice)'], 'id': 227, 'def': 'ground pods and seeds of pungent red peppers of the genus Capsicum', 'name': 'cayenne_(spice)'}, {'frequency': 'c', 'synset': 'cd_player.n.01', 'synonyms': ['CD_player'], 'id': 228, 'def': 'electronic equipment for playing compact discs (CDs)', 'name': 'CD_player'}, {'frequency': 'f', 'synset': 'celery.n.01', 'synonyms': ['celery'], 'id': 229, 'def': 'widely cultivated herb with aromatic leaf stalks that are eaten raw or cooked', 'name': 'celery'}, {'frequency': 'f', 'synset': 'cellular_telephone.n.01', 'synonyms': ['cellular_telephone', 'cellular_phone', 'cellphone', 'mobile_phone', 'smart_phone'], 'id': 230, 'def': 'a hand-held mobile telephone', 'name': 'cellular_telephone'}, {'frequency': 'r', 'synset': 'chain_mail.n.01', 'synonyms': ['chain_mail', 'ring_mail', 'chain_armor', 'chain_armour', 'ring_armor', 'ring_armour'], 'id': 231, 'def': '(Middle Ages) flexible armor made of interlinked metal rings', 'name': 'chain_mail'}, {'frequency': 'f', 'synset': 'chair.n.01', 'synonyms': ['chair'], 'id': 232, 'def': 'a seat for one person, with a support for the back', 'name': 'chair'}, {'frequency': 'r', 'synset': 'chaise_longue.n.01', 'synonyms': ['chaise_longue', 'chaise', 'daybed'], 'id': 233, 'def': 'a long chair; for reclining', 'name': 'chaise_longue'}, {'frequency': 'r', 'synset': 'chalice.n.01', 'synonyms': ['chalice'], 'id': 234, 'def': 'a bowl-shaped drinking vessel; especially the Eucharistic cup', 'name': 'chalice'}, {'frequency': 'f', 'synset': 'chandelier.n.01', 'synonyms': ['chandelier'], 'id': 235, 'def': 'branched lighting fixture; often ornate; hangs from the ceiling', 'name': 'chandelier'}, {'frequency': 'r', 'synset': 'chap.n.04', 'synonyms': ['chap'], 'id': 236, 'def': 'leather leggings without a seat; worn over trousers by cowboys to protect their legs', 'name': 'chap'}, {'frequency': 'r', 'synset': 'checkbook.n.01', 'synonyms': ['checkbook', 'chequebook'], 'id': 237, 'def': 'a book issued to holders of checking accounts', 'name': 'checkbook'}, {'frequency': 'r', 'synset': 'checkerboard.n.01', 'synonyms': ['checkerboard'], 'id': 238, 'def': 'a board having 64 squares of two alternating colors', 'name': 'checkerboard'}, {'frequency': 'c', 'synset': 'cherry.n.03', 'synonyms': ['cherry'], 'id': 239, 'def': 'a red fruit with a single hard stone', 'name': 'cherry'}, {'frequency': 'r', 'synset': 'chessboard.n.01', 'synonyms': ['chessboard'], 'id': 240, 'def': 'a checkerboard used to play chess', 'name': 'chessboard'}, {'frequency': 'c', 'synset': 'chicken.n.02', 'synonyms': ['chicken_(animal)'], 'id': 241, 'def': 'a domestic fowl bred for flesh or eggs', 'name': 'chicken_(animal)'}, {'frequency': 'c', 'synset': 'chickpea.n.01', 'synonyms': ['chickpea', 'garbanzo'], 'id': 242, 'def': 'the seed of the chickpea plant; usually dried', 'name': 'chickpea'}, {'frequency': 'c', 'synset': 'chili.n.02', 'synonyms': ['chili_(vegetable)', 'chili_pepper_(vegetable)', 'chilli_(vegetable)', 'chilly_(vegetable)', 'chile_(vegetable)'], 'id': 243, 'def': 'very hot and finely tapering pepper of special pungency', 'name': 'chili_(vegetable)'}, {'frequency': 'r', 'synset': 'chime.n.01', 'synonyms': ['chime', 'gong'], 'id': 244, 'def': 'an instrument consisting of a set of bells that are struck with a hammer', 'name': 'chime'}, {'frequency': 'r', 'synset': 'chinaware.n.01', 'synonyms': ['chinaware'], 'id': 245, 'def': 'dishware made of high quality porcelain', 'name': 'chinaware'}, {'frequency': 'c', 'synset': 'chip.n.04', 'synonyms': ['crisp_(potato_chip)', 'potato_chip'], 'id': 246, 'def': 'a thin crisp slice of potato fried in deep fat', 'name': 'crisp_(potato_chip)'}, {'frequency': 'r', 'synset': 'chip.n.06', 'synonyms': ['poker_chip'], 'id': 247, 'def': 'a small disk-shaped counter used to represent money when gambling', 'name': 'poker_chip'}, {'frequency': 'c', 'synset': 'chocolate_bar.n.01', 'synonyms': ['chocolate_bar'], 'id': 248, 'def': 'a bar of chocolate candy', 'name': 'chocolate_bar'}, {'frequency': 'c', 'synset': 'chocolate_cake.n.01', 'synonyms': ['chocolate_cake'], 'id': 249, 'def': 'cake containing chocolate', 'name': 'chocolate_cake'}, {'frequency': 'r', 'synset': 'chocolate_milk.n.01', 'synonyms': ['chocolate_milk'], 'id': 250, 'def': 'milk flavored with chocolate syrup', 'name': 'chocolate_milk'}, {'frequency': 'r', 'synset': 'chocolate_mousse.n.01', 'synonyms': ['chocolate_mousse'], 'id': 251, 'def': 'dessert mousse made with chocolate', 'name': 'chocolate_mousse'}, {'frequency': 'f', 'synset': 'choker.n.03', 'synonyms': ['choker', 'collar', 'neckband'], 'id': 252, 'def': 'shirt collar, animal collar, or tight-fitting necklace', 'name': 'choker'}, {'frequency': 'f', 'synset': 'chopping_board.n.01', 'synonyms': ['chopping_board', 'cutting_board', 'chopping_block'], 'id': 253, 'def': 'a wooden board where meats or vegetables can be cut', 'name': 'chopping_board'}, {'frequency': 'f', 'synset': 'chopstick.n.01', 'synonyms': ['chopstick'], 'id': 254, 'def': 'one of a pair of slender sticks used as oriental tableware to eat food with', 'name': 'chopstick'}, {'frequency': 'f', 'synset': 'christmas_tree.n.05', 'synonyms': ['Christmas_tree'], 'id': 255, 'def': 'an ornamented evergreen used as a Christmas decoration', 'name': 'Christmas_tree'}, {'frequency': 'c', 'synset': 'chute.n.02', 'synonyms': ['slide'], 'id': 256, 'def': 'sloping channel through which things can descend', 'name': 'slide'}, {'frequency': 'r', 'synset': 'cider.n.01', 'synonyms': ['cider', 'cyder'], 'id': 257, 'def': 'a beverage made from juice pressed from apples', 'name': 'cider'}, {'frequency': 'r', 'synset': 'cigar_box.n.01', 'synonyms': ['cigar_box'], 'id': 258, 'def': 'a box for holding cigars', 'name': 'cigar_box'}, {'frequency': 'f', 'synset': 'cigarette.n.01', 'synonyms': ['cigarette'], 'id': 259, 'def': 'finely ground tobacco wrapped in paper; for smoking', 'name': 'cigarette'}, {'frequency': 'c', 'synset': 'cigarette_case.n.01', 'synonyms': ['cigarette_case', 'cigarette_pack'], 'id': 260, 'def': 'a small flat case for holding cigarettes', 'name': 'cigarette_case'}, {'frequency': 'f', 'synset': 'cistern.n.02', 'synonyms': ['cistern', 'water_tank'], 'id': 261, 'def': 'a tank that holds the water used to flush a toilet', 'name': 'cistern'}, {'frequency': 'r', 'synset': 'clarinet.n.01', 'synonyms': ['clarinet'], 'id': 262, 'def': 'a single-reed instrument with a straight tube', 'name': 'clarinet'}, {'frequency': 'c', 'synset': 'clasp.n.01', 'synonyms': ['clasp'], 'id': 263, 'def': 'a fastener (as a buckle or hook) that is used to hold two things together', 'name': 'clasp'}, {'frequency': 'c', 'synset': 'cleansing_agent.n.01', 'synonyms': ['cleansing_agent', 'cleanser', 'cleaner'], 'id': 264, 'def': 'a preparation used in cleaning something', 'name': 'cleansing_agent'}, {'frequency': 'r', 'synset': 'cleat.n.02', 'synonyms': ['cleat_(for_securing_rope)'], 'id': 265, 'def': 'a fastener (usually with two projecting horns) around which a rope can be secured', 'name': 'cleat_(for_securing_rope)'}, {'frequency': 'r', 'synset': 'clementine.n.01', 'synonyms': ['clementine'], 'id': 266, 'def': 'a variety of mandarin orange', 'name': 'clementine'}, {'frequency': 'c', 'synset': 'clip.n.03', 'synonyms': ['clip'], 'id': 267, 'def': 'any of various small fasteners used to hold loose articles together', 'name': 'clip'}, {'frequency': 'c', 'synset': 'clipboard.n.01', 'synonyms': ['clipboard'], 'id': 268, 'def': 'a small writing board with a clip at the top for holding papers', 'name': 'clipboard'}, {'frequency': 'r', 'synset': 'clipper.n.03', 'synonyms': ['clippers_(for_plants)'], 'id': 269, 'def': 'shears for cutting grass or shrubbery (often used in the plural)', 'name': 'clippers_(for_plants)'}, {'frequency': 'r', 'synset': 'cloak.n.02', 'synonyms': ['cloak'], 'id': 270, 'def': 'a loose outer garment', 'name': 'cloak'}, {'frequency': 'f', 'synset': 'clock.n.01', 'synonyms': ['clock', 'timepiece', 'timekeeper'], 'id': 271, 'def': 'a timepiece that shows the time of day', 'name': 'clock'}, {'frequency': 'f', 'synset': 'clock_tower.n.01', 'synonyms': ['clock_tower'], 'id': 272, 'def': 'a tower with a large clock visible high up on an outside face', 'name': 'clock_tower'}, {'frequency': 'c', 'synset': 'clothes_hamper.n.01', 'synonyms': ['clothes_hamper', 'laundry_basket', 'clothes_basket'], 'id': 273, 'def': 'a hamper that holds dirty clothes to be washed or wet clothes to be dried', 'name': 'clothes_hamper'}, {'frequency': 'c', 'synset': 'clothespin.n.01', 'synonyms': ['clothespin', 'clothes_peg'], 'id': 274, 'def': 'wood or plastic fastener; for holding clothes on a clothesline', 'name': 'clothespin'}, {'frequency': 'r', 'synset': 'clutch_bag.n.01', 'synonyms': ['clutch_bag'], 'id': 275, 'def': "a woman's strapless purse that is carried in the hand", 'name': 'clutch_bag'}, {'frequency': 'f', 'synset': 'coaster.n.03', 'synonyms': ['coaster'], 'id': 276, 'def': 'a covering (plate or mat) that protects the surface of a table', 'name': 'coaster'}, {'frequency': 'f', 'synset': 'coat.n.01', 'synonyms': ['coat'], 'id': 277, 'def': 'an outer garment that has sleeves and covers the body from shoulder down', 'name': 'coat'}, {'frequency': 'c', 'synset': 'coat_hanger.n.01', 'synonyms': ['coat_hanger', 'clothes_hanger', 'dress_hanger'], 'id': 278, 'def': "a hanger that is shaped like a person's shoulders", 'name': 'coat_hanger'}, {'frequency': 'c', 'synset': 'coatrack.n.01', 'synonyms': ['coatrack', 'hatrack'], 'id': 279, 'def': 'a rack with hooks for temporarily holding coats and hats', 'name': 'coatrack'}, {'frequency': 'c', 'synset': 'cock.n.04', 'synonyms': ['cock', 'rooster'], 'id': 280, 'def': 'adult male chicken', 'name': 'cock'}, {'frequency': 'r', 'synset': 'cockroach.n.01', 'synonyms': ['cockroach'], 'id': 281, 'def': 'any of numerous chiefly nocturnal insects; some are domestic pests', 'name': 'cockroach'}, {'frequency': 'r', 'synset': 'cocoa.n.01', 'synonyms': ['cocoa_(beverage)', 'hot_chocolate_(beverage)', 'drinking_chocolate'], 'id': 282, 'def': 'a beverage made from cocoa powder and milk and sugar; usually drunk hot', 'name': 'cocoa_(beverage)'}, {'frequency': 'c', 'synset': 'coconut.n.02', 'synonyms': ['coconut', 'cocoanut'], 'id': 283, 'def': 'large hard-shelled brown oval nut with a fibrous husk', 'name': 'coconut'}, {'frequency': 'f', 'synset': 'coffee_maker.n.01', 'synonyms': ['coffee_maker', 'coffee_machine'], 'id': 284, 'def': 'a kitchen appliance for brewing coffee automatically', 'name': 'coffee_maker'}, {'frequency': 'f', 'synset': 'coffee_table.n.01', 'synonyms': ['coffee_table', 'cocktail_table'], 'id': 285, 'def': 'low table where magazines can be placed and coffee or cocktails are served', 'name': 'coffee_table'}, {'frequency': 'c', 'synset': 'coffeepot.n.01', 'synonyms': ['coffeepot'], 'id': 286, 'def': 'tall pot in which coffee is brewed', 'name': 'coffeepot'}, {'frequency': 'r', 'synset': 'coil.n.05', 'synonyms': ['coil'], 'id': 287, 'def': 'tubing that is wound in a spiral', 'name': 'coil'}, {'frequency': 'c', 'synset': 'coin.n.01', 'synonyms': ['coin'], 'id': 288, 'def': 'a flat metal piece (usually a disc) used as money', 'name': 'coin'}, {'frequency': 'c', 'synset': 'colander.n.01', 'synonyms': ['colander', 'cullender'], 'id': 289, 'def': 'bowl-shaped strainer; used to wash or drain foods', 'name': 'colander'}, {'frequency': 'c', 'synset': 'coleslaw.n.01', 'synonyms': ['coleslaw', 'slaw'], 'id': 290, 'def': 'basically shredded cabbage', 'name': 'coleslaw'}, {'frequency': 'r', 'synset': 'coloring_material.n.01', 'synonyms': ['coloring_material', 'colouring_material'], 'id': 291, 'def': 'any material used for its color', 'name': 'coloring_material'}, {'frequency': 'r', 'synset': 'combination_lock.n.01', 'synonyms': ['combination_lock'], 'id': 292, 'def': 'lock that can be opened only by turning dials in a special sequence', 'name': 'combination_lock'}, {'frequency': 'c', 'synset': 'comforter.n.04', 'synonyms': ['pacifier', 'teething_ring'], 'id': 293, 'def': 'device used for an infant to suck or bite on', 'name': 'pacifier'}, {'frequency': 'r', 'synset': 'comic_book.n.01', 'synonyms': ['comic_book'], 'id': 294, 'def': 'a magazine devoted to comic strips', 'name': 'comic_book'}, {'frequency': 'r', 'synset': 'compass.n.01', 'synonyms': ['compass'], 'id': 295, 'def': 'navigational instrument for finding directions', 'name': 'compass'}, {'frequency': 'f', 'synset': 'computer_keyboard.n.01', 'synonyms': ['computer_keyboard', 'keyboard_(computer)'], 'id': 296, 'def': 'a keyboard that is a data input device for computers', 'name': 'computer_keyboard'}, {'frequency': 'f', 'synset': 'condiment.n.01', 'synonyms': ['condiment'], 'id': 297, 'def': 'a preparation (a sauce or relish or spice) to enhance flavor or enjoyment', 'name': 'condiment'}, {'frequency': 'f', 'synset': 'cone.n.01', 'synonyms': ['cone', 'traffic_cone'], 'id': 298, 'def': 'a cone-shaped object used to direct traffic', 'name': 'cone'}, {'frequency': 'f', 'synset': 'control.n.09', 'synonyms': ['control', 'controller'], 'id': 299, 'def': 'a mechanism that controls the operation of a machine', 'name': 'control'}, {'frequency': 'r', 'synset': 'convertible.n.01', 'synonyms': ['convertible_(automobile)'], 'id': 300, 'def': 'a car that has top that can be folded or removed', 'name': 'convertible_(automobile)'}, {'frequency': 'r', 'synset': 'convertible.n.03', 'synonyms': ['sofa_bed'], 'id': 301, 'def': 'a sofa that can be converted into a bed', 'name': 'sofa_bed'}, {'frequency': 'r', 'synset': 'cooker.n.01', 'synonyms': ['cooker'], 'id': 302, 'def': 'a utensil for cooking', 'name': 'cooker'}, {'frequency': 'f', 'synset': 'cookie.n.01', 'synonyms': ['cookie', 'cooky', 'biscuit_(cookie)'], 'id': 303, 'def': "any of various small flat sweet cakes (`biscuit' is the British term)", 'name': 'cookie'}, {'frequency': 'r', 'synset': 'cooking_utensil.n.01', 'synonyms': ['cooking_utensil'], 'id': 304, 'def': 'a kitchen utensil made of material that does not melt easily; used for cooking', 'name': 'cooking_utensil'}, {'frequency': 'f', 'synset': 'cooler.n.01', 'synonyms': ['cooler_(for_food)', 'ice_chest'], 'id': 305, 'def': 'an insulated box for storing food often with ice', 'name': 'cooler_(for_food)'}, {'frequency': 'f', 'synset': 'cork.n.04', 'synonyms': ['cork_(bottle_plug)', 'bottle_cork'], 'id': 306, 'def': 'the plug in the mouth of a bottle (especially a wine bottle)', 'name': 'cork_(bottle_plug)'}, {'frequency': 'r', 'synset': 'corkboard.n.01', 'synonyms': ['corkboard'], 'id': 307, 'def': 'a sheet consisting of cork granules', 'name': 'corkboard'}, {'frequency': 'c', 'synset': 'corkscrew.n.01', 'synonyms': ['corkscrew', 'bottle_screw'], 'id': 308, 'def': 'a bottle opener that pulls corks', 'name': 'corkscrew'}, {'frequency': 'f', 'synset': 'corn.n.03', 'synonyms': ['edible_corn', 'corn', 'maize'], 'id': 309, 'def': 'ears or kernels of corn that can be prepared and served for human food (only mark individual ears or kernels)', 'name': 'edible_corn'}, {'frequency': 'r', 'synset': 'cornbread.n.01', 'synonyms': ['cornbread'], 'id': 310, 'def': 'bread made primarily of cornmeal', 'name': 'cornbread'}, {'frequency': 'c', 'synset': 'cornet.n.01', 'synonyms': ['cornet', 'horn', 'trumpet'], 'id': 311, 'def': 'a brass musical instrument with a narrow tube and a flared bell and many valves', 'name': 'cornet'}, {'frequency': 'c', 'synset': 'cornice.n.01', 'synonyms': ['cornice', 'valance', 'valance_board', 'pelmet'], 'id': 312, 'def': 'a decorative framework to conceal curtain fixtures at the top of a window casing', 'name': 'cornice'}, {'frequency': 'r', 'synset': 'cornmeal.n.01', 'synonyms': ['cornmeal'], 'id': 313, 'def': 'coarsely ground corn', 'name': 'cornmeal'}, {'frequency': 'c', 'synset': 'corset.n.01', 'synonyms': ['corset', 'girdle'], 'id': 314, 'def': "a woman's close-fitting foundation garment", 'name': 'corset'}, {'frequency': 'c', 'synset': 'costume.n.04', 'synonyms': ['costume'], 'id': 315, 'def': 'the attire characteristic of a country or a time or a social class', 'name': 'costume'}, {'frequency': 'r', 'synset': 'cougar.n.01', 'synonyms': ['cougar', 'puma', 'catamount', 'mountain_lion', 'panther'], 'id': 316, 'def': 'large American feline resembling a lion', 'name': 'cougar'}, {'frequency': 'r', 'synset': 'coverall.n.01', 'synonyms': ['coverall'], 'id': 317, 'def': 'a loose-fitting protective garment that is worn over other clothing', 'name': 'coverall'}, {'frequency': 'c', 'synset': 'cowbell.n.01', 'synonyms': ['cowbell'], 'id': 318, 'def': 'a bell hung around the neck of cow so that the cow can be easily located', 'name': 'cowbell'}, {'frequency': 'f', 'synset': 'cowboy_hat.n.01', 'synonyms': ['cowboy_hat', 'ten-gallon_hat'], 'id': 319, 'def': 'a hat with a wide brim and a soft crown; worn by American ranch hands', 'name': 'cowboy_hat'}, {'frequency': 'c', 'synset': 'crab.n.01', 'synonyms': ['crab_(animal)'], 'id': 320, 'def': 'decapod having eyes on short stalks and a broad flattened shell and pincers', 'name': 'crab_(animal)'}, {'frequency': 'r', 'synset': 'crab.n.05', 'synonyms': ['crabmeat'], 'id': 321, 'def': 'the edible flesh of any of various crabs', 'name': 'crabmeat'}, {'frequency': 'c', 'synset': 'cracker.n.01', 'synonyms': ['cracker'], 'id': 322, 'def': 'a thin crisp wafer', 'name': 'cracker'}, {'frequency': 'r', 'synset': 'crape.n.01', 'synonyms': ['crape', 'crepe', 'French_pancake'], 'id': 323, 'def': 'small very thin pancake', 'name': 'crape'}, {'frequency': 'f', 'synset': 'crate.n.01', 'synonyms': ['crate'], 'id': 324, 'def': 'a rugged box (usually made of wood); used for shipping', 'name': 'crate'}, {'frequency': 'c', 'synset': 'crayon.n.01', 'synonyms': ['crayon', 'wax_crayon'], 'id': 325, 'def': 'writing or drawing implement made of a colored stick of composition wax', 'name': 'crayon'}, {'frequency': 'r', 'synset': 'cream_pitcher.n.01', 'synonyms': ['cream_pitcher'], 'id': 326, 'def': 'a small pitcher for serving cream', 'name': 'cream_pitcher'}, {'frequency': 'c', 'synset': 'crescent_roll.n.01', 'synonyms': ['crescent_roll', 'croissant'], 'id': 327, 'def': 'very rich flaky crescent-shaped roll', 'name': 'crescent_roll'}, {'frequency': 'c', 'synset': 'crib.n.01', 'synonyms': ['crib', 'cot'], 'id': 328, 'def': 'baby bed with high sides made of slats', 'name': 'crib'}, {'frequency': 'c', 'synset': 'crock.n.03', 'synonyms': ['crock_pot', 'earthenware_jar'], 'id': 329, 'def': 'an earthen jar (made of baked clay) or a modern electric crockpot', 'name': 'crock_pot'}, {'frequency': 'f', 'synset': 'crossbar.n.01', 'synonyms': ['crossbar'], 'id': 330, 'def': 'a horizontal bar that goes across something', 'name': 'crossbar'}, {'frequency': 'r', 'synset': 'crouton.n.01', 'synonyms': ['crouton'], 'id': 331, 'def': 'a small piece of toasted or fried bread; served in soup or salads', 'name': 'crouton'}, {'frequency': 'c', 'synset': 'crow.n.01', 'synonyms': ['crow'], 'id': 332, 'def': 'black birds having a raucous call', 'name': 'crow'}, {'frequency': 'r', 'synset': 'crowbar.n.01', 'synonyms': ['crowbar', 'wrecking_bar', 'pry_bar'], 'id': 333, 'def': 'a heavy iron lever with one end forged into a wedge', 'name': 'crowbar'}, {'frequency': 'c', 'synset': 'crown.n.04', 'synonyms': ['crown'], 'id': 334, 'def': 'an ornamental jeweled headdress signifying sovereignty', 'name': 'crown'}, {'frequency': 'c', 'synset': 'crucifix.n.01', 'synonyms': ['crucifix'], 'id': 335, 'def': 'representation of the cross on which Jesus died', 'name': 'crucifix'}, {'frequency': 'c', 'synset': 'cruise_ship.n.01', 'synonyms': ['cruise_ship', 'cruise_liner'], 'id': 336, 'def': 'a passenger ship used commercially for pleasure cruises', 'name': 'cruise_ship'}, {'frequency': 'c', 'synset': 'cruiser.n.01', 'synonyms': ['police_cruiser', 'patrol_car', 'police_car', 'squad_car'], 'id': 337, 'def': 'a car in which policemen cruise the streets', 'name': 'police_cruiser'}, {'frequency': 'f', 'synset': 'crumb.n.03', 'synonyms': ['crumb'], 'id': 338, 'def': 'small piece of e.g. bread or cake', 'name': 'crumb'}, {'frequency': 'c', 'synset': 'crutch.n.01', 'synonyms': ['crutch'], 'id': 339, 'def': 'a wooden or metal staff that fits under the armpit and reaches to the ground', 'name': 'crutch'}, {'frequency': 'c', 'synset': 'cub.n.03', 'synonyms': ['cub_(animal)'], 'id': 340, 'def': 'the young of certain carnivorous mammals such as the bear or wolf or lion', 'name': 'cub_(animal)'}, {'frequency': 'c', 'synset': 'cube.n.05', 'synonyms': ['cube', 'square_block'], 'id': 341, 'def': 'a block in the (approximate) shape of a cube', 'name': 'cube'}, {'frequency': 'f', 'synset': 'cucumber.n.02', 'synonyms': ['cucumber', 'cuke'], 'id': 342, 'def': 'cylindrical green fruit with thin green rind and white flesh eaten as a vegetable', 'name': 'cucumber'}, {'frequency': 'c', 'synset': 'cufflink.n.01', 'synonyms': ['cufflink'], 'id': 343, 'def': 'jewelry consisting of linked buttons used to fasten the cuffs of a shirt', 'name': 'cufflink'}, {'frequency': 'f', 'synset': 'cup.n.01', 'synonyms': ['cup'], 'id': 344, 'def': 'a small open container usually used for drinking; usually has a handle', 'name': 'cup'}, {'frequency': 'c', 'synset': 'cup.n.08', 'synonyms': ['trophy_cup'], 'id': 345, 'def': 'a metal award or cup-shaped vessel with handles that is awarded as a trophy to a competition winner', 'name': 'trophy_cup'}, {'frequency': 'f', 'synset': 'cupboard.n.01', 'synonyms': ['cupboard', 'closet'], 'id': 346, 'def': 'a small room (or recess) or cabinet used for storage space', 'name': 'cupboard'}, {'frequency': 'f', 'synset': 'cupcake.n.01', 'synonyms': ['cupcake'], 'id': 347, 'def': 'small cake baked in a muffin tin', 'name': 'cupcake'}, {'frequency': 'r', 'synset': 'curler.n.01', 'synonyms': ['hair_curler', 'hair_roller', 'hair_crimper'], 'id': 348, 'def': 'a cylindrical tube around which the hair is wound to curl it', 'name': 'hair_curler'}, {'frequency': 'r', 'synset': 'curling_iron.n.01', 'synonyms': ['curling_iron'], 'id': 349, 'def': 'a cylindrical home appliance that heats hair that has been curled around it', 'name': 'curling_iron'}, {'frequency': 'f', 'synset': 'curtain.n.01', 'synonyms': ['curtain', 'drapery'], 'id': 350, 'def': 'hanging cloth used as a blind (especially for a window)', 'name': 'curtain'}, {'frequency': 'f', 'synset': 'cushion.n.03', 'synonyms': ['cushion'], 'id': 351, 'def': 'a soft bag filled with air or padding such as feathers or foam rubber', 'name': 'cushion'}, {'frequency': 'r', 'synset': 'cylinder.n.04', 'synonyms': ['cylinder'], 'id': 352, 'def': 'a cylindrical container', 'name': 'cylinder'}, {'frequency': 'r', 'synset': 'cymbal.n.01', 'synonyms': ['cymbal'], 'id': 353, 'def': 'a percussion instrument consisting of a concave brass disk', 'name': 'cymbal'}, {'frequency': 'r', 'synset': 'dagger.n.01', 'synonyms': ['dagger'], 'id': 354, 'def': 'a short knife with a pointed blade used for piercing or stabbing', 'name': 'dagger'}, {'frequency': 'r', 'synset': 'dalmatian.n.02', 'synonyms': ['dalmatian'], 'id': 355, 'def': 'a large breed having a smooth white coat with black or brown spots', 'name': 'dalmatian'}, {'frequency': 'c', 'synset': 'dartboard.n.01', 'synonyms': ['dartboard'], 'id': 356, 'def': 'a circular board of wood or cork used as the target in the game of darts', 'name': 'dartboard'}, {'frequency': 'r', 'synset': 'date.n.08', 'synonyms': ['date_(fruit)'], 'id': 357, 'def': 'sweet edible fruit of the date palm with a single long woody seed', 'name': 'date_(fruit)'}, {'frequency': 'f', 'synset': 'deck_chair.n.01', 'synonyms': ['deck_chair', 'beach_chair'], 'id': 358, 'def': 'a folding chair for use outdoors; a wooden frame supports a length of canvas', 'name': 'deck_chair'}, {'frequency': 'c', 'synset': 'deer.n.01', 'synonyms': ['deer', 'cervid'], 'id': 359, 'def': "distinguished from Bovidae by the male's having solid deciduous antlers", 'name': 'deer'}, {'frequency': 'c', 'synset': 'dental_floss.n.01', 'synonyms': ['dental_floss', 'floss'], 'id': 360, 'def': 'a soft thread for cleaning the spaces between the teeth', 'name': 'dental_floss'}, {'frequency': 'f', 'synset': 'desk.n.01', 'synonyms': ['desk'], 'id': 361, 'def': 'a piece of furniture with a writing surface and usually drawers or other compartments', 'name': 'desk'}, {'frequency': 'r', 'synset': 'detergent.n.01', 'synonyms': ['detergent'], 'id': 362, 'def': 'a surface-active chemical widely used in industry and laundering', 'name': 'detergent'}, {'frequency': 'c', 'synset': 'diaper.n.01', 'synonyms': ['diaper'], 'id': 363, 'def': 'garment consisting of a folded cloth drawn up between the legs and fastened at the waist', 'name': 'diaper'}, {'frequency': 'r', 'synset': 'diary.n.01', 'synonyms': ['diary', 'journal'], 'id': 364, 'def': 'yearly planner book', 'name': 'diary'}, {'frequency': 'r', 'synset': 'die.n.01', 'synonyms': ['die', 'dice'], 'id': 365, 'def': 'a small cube with 1 to 6 spots on the six faces; used in gambling', 'name': 'die'}, {'frequency': 'r', 'synset': 'dinghy.n.01', 'synonyms': ['dinghy', 'dory', 'rowboat'], 'id': 366, 'def': 'a small boat of shallow draft with seats and oars with which it is propelled', 'name': 'dinghy'}, {'frequency': 'f', 'synset': 'dining_table.n.01', 'synonyms': ['dining_table'], 'id': 367, 'def': 'a table at which meals are served', 'name': 'dining_table'}, {'frequency': 'r', 'synset': 'dinner_jacket.n.01', 'synonyms': ['tux', 'tuxedo'], 'id': 368, 'def': 'semiformal evening dress for men', 'name': 'tux'}, {'frequency': 'f', 'synset': 'dish.n.01', 'synonyms': ['dish'], 'id': 369, 'def': 'a piece of dishware normally used as a container for holding or serving food', 'name': 'dish'}, {'frequency': 'c', 'synset': 'dish.n.05', 'synonyms': ['dish_antenna'], 'id': 370, 'def': 'directional antenna consisting of a parabolic reflector', 'name': 'dish_antenna'}, {'frequency': 'c', 'synset': 'dishrag.n.01', 'synonyms': ['dishrag', 'dishcloth'], 'id': 371, 'def': 'a cloth for washing dishes or cleaning in general', 'name': 'dishrag'}, {'frequency': 'f', 'synset': 'dishtowel.n.01', 'synonyms': ['dishtowel', 'tea_towel'], 'id': 372, 'def': 'a towel for drying dishes', 'name': 'dishtowel'}, {'frequency': 'f', 'synset': 'dishwasher.n.01', 'synonyms': ['dishwasher', 'dishwashing_machine'], 'id': 373, 'def': 'a machine for washing dishes', 'name': 'dishwasher'}, {'frequency': 'r', 'synset': 'dishwasher_detergent.n.01', 'synonyms': ['dishwasher_detergent', 'dishwashing_detergent', 'dishwashing_liquid', 'dishsoap'], 'id': 374, 'def': 'dishsoap or dish detergent designed for use in dishwashers', 'name': 'dishwasher_detergent'}, {'frequency': 'f', 'synset': 'dispenser.n.01', 'synonyms': ['dispenser'], 'id': 375, 'def': 'a container so designed that the contents can be used in prescribed amounts', 'name': 'dispenser'}, {'frequency': 'r', 'synset': 'diving_board.n.01', 'synonyms': ['diving_board'], 'id': 376, 'def': 'a springboard from which swimmers can dive', 'name': 'diving_board'}, {'frequency': 'f', 'synset': 'dixie_cup.n.01', 'synonyms': ['Dixie_cup', 'paper_cup'], 'id': 377, 'def': 'a disposable cup made of paper; for holding drinks', 'name': 'Dixie_cup'}, {'frequency': 'f', 'synset': 'dog.n.01', 'synonyms': ['dog'], 'id': 378, 'def': 'a common domesticated dog', 'name': 'dog'}, {'frequency': 'f', 'synset': 'dog_collar.n.01', 'synonyms': ['dog_collar'], 'id': 379, 'def': 'a collar for a dog', 'name': 'dog_collar'}, {'frequency': 'f', 'synset': 'doll.n.01', 'synonyms': ['doll'], 'id': 380, 'def': 'a toy replica of a HUMAN (NOT AN ANIMAL)', 'name': 'doll'}, {'frequency': 'r', 'synset': 'dollar.n.02', 'synonyms': ['dollar', 'dollar_bill', 'one_dollar_bill'], 'id': 381, 'def': 'a piece of paper money worth one dollar', 'name': 'dollar'}, {'frequency': 'r', 'synset': 'dollhouse.n.01', 'synonyms': ['dollhouse', "doll's_house"], 'id': 382, 'def': "a house so small that it is likened to a child's plaything", 'name': 'dollhouse'}, {'frequency': 'c', 'synset': 'dolphin.n.02', 'synonyms': ['dolphin'], 'id': 383, 'def': 'any of various small toothed whales with a beaklike snout; larger than porpoises', 'name': 'dolphin'}, {'frequency': 'c', 'synset': 'domestic_ass.n.01', 'synonyms': ['domestic_ass', 'donkey'], 'id': 384, 'def': 'domestic beast of burden descended from the African wild ass; patient but stubborn', 'name': 'domestic_ass'}, {'frequency': 'f', 'synset': 'doorknob.n.01', 'synonyms': ['doorknob', 'doorhandle'], 'id': 385, 'def': "a knob used to open a door (often called `doorhandle' in Great Britain)", 'name': 'doorknob'}, {'frequency': 'c', 'synset': 'doormat.n.02', 'synonyms': ['doormat', 'welcome_mat'], 'id': 386, 'def': 'a mat placed outside an exterior door for wiping the shoes before entering', 'name': 'doormat'}, {'frequency': 'f', 'synset': 'doughnut.n.02', 'synonyms': ['doughnut', 'donut'], 'id': 387, 'def': 'a small ring-shaped friedcake', 'name': 'doughnut'}, {'frequency': 'r', 'synset': 'dove.n.01', 'synonyms': ['dove'], 'id': 388, 'def': 'any of numerous small pigeons', 'name': 'dove'}, {'frequency': 'r', 'synset': 'dragonfly.n.01', 'synonyms': ['dragonfly'], 'id': 389, 'def': 'slender-bodied non-stinging insect having iridescent wings that are outspread at rest', 'name': 'dragonfly'}, {'frequency': 'f', 'synset': 'drawer.n.01', 'synonyms': ['drawer'], 'id': 390, 'def': 'a boxlike container in a piece of furniture; made so as to slide in and out', 'name': 'drawer'}, {'frequency': 'c', 'synset': 'drawers.n.01', 'synonyms': ['underdrawers', 'boxers', 'boxershorts'], 'id': 391, 'def': 'underpants worn by men', 'name': 'underdrawers'}, {'frequency': 'f', 'synset': 'dress.n.01', 'synonyms': ['dress', 'frock'], 'id': 392, 'def': 'a one-piece garment for a woman; has skirt and bodice', 'name': 'dress'}, {'frequency': 'c', 'synset': 'dress_hat.n.01', 'synonyms': ['dress_hat', 'high_hat', 'opera_hat', 'silk_hat', 'top_hat'], 'id': 393, 'def': "a man's hat with a tall crown; usually covered with silk or with beaver fur", 'name': 'dress_hat'}, {'frequency': 'f', 'synset': 'dress_suit.n.01', 'synonyms': ['dress_suit'], 'id': 394, 'def': 'formalwear consisting of full evening dress for men', 'name': 'dress_suit'}, {'frequency': 'f', 'synset': 'dresser.n.05', 'synonyms': ['dresser'], 'id': 395, 'def': 'a cabinet with shelves', 'name': 'dresser'}, {'frequency': 'c', 'synset': 'drill.n.01', 'synonyms': ['drill'], 'id': 396, 'def': 'a tool with a sharp rotating point for making holes in hard materials', 'name': 'drill'}, {'frequency': 'r', 'synset': 'drone.n.04', 'synonyms': ['drone'], 'id': 397, 'def': 'an aircraft without a pilot that is operated by remote control', 'name': 'drone'}, {'frequency': 'r', 'synset': 'dropper.n.01', 'synonyms': ['dropper', 'eye_dropper'], 'id': 398, 'def': 'pipet consisting of a small tube with a vacuum bulb at one end for drawing liquid in and releasing it a drop at a time', 'name': 'dropper'}, {'frequency': 'c', 'synset': 'drum.n.01', 'synonyms': ['drum_(musical_instrument)'], 'id': 399, 'def': 'a musical percussion instrument; usually consists of a hollow cylinder with a membrane stretched across each end', 'name': 'drum_(musical_instrument)'}, {'frequency': 'r', 'synset': 'drumstick.n.02', 'synonyms': ['drumstick'], 'id': 400, 'def': 'a stick used for playing a drum', 'name': 'drumstick'}, {'frequency': 'f', 'synset': 'duck.n.01', 'synonyms': ['duck'], 'id': 401, 'def': 'small web-footed broad-billed swimming bird', 'name': 'duck'}, {'frequency': 'c', 'synset': 'duckling.n.02', 'synonyms': ['duckling'], 'id': 402, 'def': 'young duck', 'name': 'duckling'}, {'frequency': 'c', 'synset': 'duct_tape.n.01', 'synonyms': ['duct_tape'], 'id': 403, 'def': 'a wide silvery adhesive tape', 'name': 'duct_tape'}, {'frequency': 'f', 'synset': 'duffel_bag.n.01', 'synonyms': ['duffel_bag', 'duffle_bag', 'duffel', 'duffle'], 'id': 404, 'def': 'a large cylindrical bag of heavy cloth (does not include suitcases)', 'name': 'duffel_bag'}, {'frequency': 'r', 'synset': 'dumbbell.n.01', 'synonyms': ['dumbbell'], 'id': 405, 'def': 'an exercising weight with two ball-like ends connected by a short handle', 'name': 'dumbbell'}, {'frequency': 'c', 'synset': 'dumpster.n.01', 'synonyms': ['dumpster'], 'id': 406, 'def': 'a container designed to receive and transport and dump waste', 'name': 'dumpster'}, {'frequency': 'r', 'synset': 'dustpan.n.02', 'synonyms': ['dustpan'], 'id': 407, 'def': 'a short-handled receptacle into which dust can be swept', 'name': 'dustpan'}, {'frequency': 'c', 'synset': 'eagle.n.01', 'synonyms': ['eagle'], 'id': 408, 'def': 'large birds of prey noted for their broad wings and strong soaring flight', 'name': 'eagle'}, {'frequency': 'f', 'synset': 'earphone.n.01', 'synonyms': ['earphone', 'earpiece', 'headphone'], 'id': 409, 'def': 'device for listening to audio that is held over or inserted into the ear', 'name': 'earphone'}, {'frequency': 'r', 'synset': 'earplug.n.01', 'synonyms': ['earplug'], 'id': 410, 'def': 'a soft plug that is inserted into the ear canal to block sound', 'name': 'earplug'}, {'frequency': 'f', 'synset': 'earring.n.01', 'synonyms': ['earring'], 'id': 411, 'def': 'jewelry to ornament the ear', 'name': 'earring'}, {'frequency': 'c', 'synset': 'easel.n.01', 'synonyms': ['easel'], 'id': 412, 'def': "an upright tripod for displaying something (usually an artist's canvas)", 'name': 'easel'}, {'frequency': 'r', 'synset': 'eclair.n.01', 'synonyms': ['eclair'], 'id': 413, 'def': 'oblong cream puff', 'name': 'eclair'}, {'frequency': 'r', 'synset': 'eel.n.01', 'synonyms': ['eel'], 'id': 414, 'def': 'an elongate fish with fatty flesh', 'name': 'eel'}, {'frequency': 'f', 'synset': 'egg.n.02', 'synonyms': ['egg', 'eggs'], 'id': 415, 'def': 'oval reproductive body of a fowl (especially a hen) used as food', 'name': 'egg'}, {'frequency': 'r', 'synset': 'egg_roll.n.01', 'synonyms': ['egg_roll', 'spring_roll'], 'id': 416, 'def': 'minced vegetables and meat wrapped in a pancake and fried', 'name': 'egg_roll'}, {'frequency': 'c', 'synset': 'egg_yolk.n.01', 'synonyms': ['egg_yolk', 'yolk_(egg)'], 'id': 417, 'def': 'the yellow spherical part of an egg', 'name': 'egg_yolk'}, {'frequency': 'c', 'synset': 'eggbeater.n.02', 'synonyms': ['eggbeater', 'eggwhisk'], 'id': 418, 'def': 'a mixer for beating eggs or whipping cream', 'name': 'eggbeater'}, {'frequency': 'c', 'synset': 'eggplant.n.01', 'synonyms': ['eggplant', 'aubergine'], 'id': 419, 'def': 'egg-shaped vegetable having a shiny skin typically dark purple', 'name': 'eggplant'}, {'frequency': 'r', 'synset': 'electric_chair.n.01', 'synonyms': ['electric_chair'], 'id': 420, 'def': 'a chair-shaped instrument of execution by electrocution', 'name': 'electric_chair'}, {'frequency': 'f', 'synset': 'electric_refrigerator.n.01', 'synonyms': ['refrigerator'], 'id': 421, 'def': 'a refrigerator in which the coolant is pumped around by an electric motor', 'name': 'refrigerator'}, {'frequency': 'f', 'synset': 'elephant.n.01', 'synonyms': ['elephant'], 'id': 422, 'def': 'a common elephant', 'name': 'elephant'}, {'frequency': 'c', 'synset': 'elk.n.01', 'synonyms': ['elk', 'moose'], 'id': 423, 'def': 'large northern deer with enormous flattened antlers in the male', 'name': 'elk'}, {'frequency': 'c', 'synset': 'envelope.n.01', 'synonyms': ['envelope'], 'id': 424, 'def': 'a flat (usually rectangular) container for a letter, thin package, etc.', 'name': 'envelope'}, {'frequency': 'c', 'synset': 'eraser.n.01', 'synonyms': ['eraser'], 'id': 425, 'def': 'an implement used to erase something', 'name': 'eraser'}, {'frequency': 'r', 'synset': 'escargot.n.01', 'synonyms': ['escargot'], 'id': 426, 'def': 'edible snail usually served in the shell with a sauce of melted butter and garlic', 'name': 'escargot'}, {'frequency': 'r', 'synset': 'eyepatch.n.01', 'synonyms': ['eyepatch'], 'id': 427, 'def': 'a protective cloth covering for an injured eye', 'name': 'eyepatch'}, {'frequency': 'r', 'synset': 'falcon.n.01', 'synonyms': ['falcon'], 'id': 428, 'def': 'birds of prey having long pointed powerful wings adapted for swift flight', 'name': 'falcon'}, {'frequency': 'f', 'synset': 'fan.n.01', 'synonyms': ['fan'], 'id': 429, 'def': 'a device for creating a current of air by movement of a surface or surfaces', 'name': 'fan'}, {'frequency': 'f', 'synset': 'faucet.n.01', 'synonyms': ['faucet', 'spigot', 'tap'], 'id': 430, 'def': 'a regulator for controlling the flow of a liquid from a reservoir', 'name': 'faucet'}, {'frequency': 'r', 'synset': 'fedora.n.01', 'synonyms': ['fedora'], 'id': 431, 'def': 'a hat made of felt with a creased crown', 'name': 'fedora'}, {'frequency': 'r', 'synset': 'ferret.n.02', 'synonyms': ['ferret'], 'id': 432, 'def': 'domesticated albino variety of the European polecat bred for hunting rats and rabbits', 'name': 'ferret'}, {'frequency': 'c', 'synset': 'ferris_wheel.n.01', 'synonyms': ['Ferris_wheel'], 'id': 433, 'def': 'a large wheel with suspended seats that remain upright as the wheel rotates', 'name': 'Ferris_wheel'}, {'frequency': 'c', 'synset': 'ferry.n.01', 'synonyms': ['ferry', 'ferryboat'], 'id': 434, 'def': 'a boat that transports people or vehicles across a body of water and operates on a regular schedule', 'name': 'ferry'}, {'frequency': 'r', 'synset': 'fig.n.04', 'synonyms': ['fig_(fruit)'], 'id': 435, 'def': 'fleshy sweet pear-shaped yellowish or purple fruit eaten fresh or preserved or dried', 'name': 'fig_(fruit)'}, {'frequency': 'c', 'synset': 'fighter.n.02', 'synonyms': ['fighter_jet', 'fighter_aircraft', 'attack_aircraft'], 'id': 436, 'def': 'a high-speed military or naval airplane designed to destroy enemy targets', 'name': 'fighter_jet'}, {'frequency': 'f', 'synset': 'figurine.n.01', 'synonyms': ['figurine'], 'id': 437, 'def': 'a small carved or molded figure', 'name': 'figurine'}, {'frequency': 'c', 'synset': 'file.n.03', 'synonyms': ['file_cabinet', 'filing_cabinet'], 'id': 438, 'def': 'office furniture consisting of a container for keeping papers in order', 'name': 'file_cabinet'}, {'frequency': 'r', 'synset': 'file.n.04', 'synonyms': ['file_(tool)'], 'id': 439, 'def': 'a steel hand tool with small sharp teeth on some or all of its surfaces; used for smoothing wood or metal', 'name': 'file_(tool)'}, {'frequency': 'f', 'synset': 'fire_alarm.n.02', 'synonyms': ['fire_alarm', 'smoke_alarm'], 'id': 440, 'def': 'an alarm that is tripped off by fire or smoke', 'name': 'fire_alarm'}, {'frequency': 'f', 'synset': 'fire_engine.n.01', 'synonyms': ['fire_engine', 'fire_truck'], 'id': 441, 'def': 'large trucks that carry firefighters and equipment to the site of a fire', 'name': 'fire_engine'}, {'frequency': 'f', 'synset': 'fire_extinguisher.n.01', 'synonyms': ['fire_extinguisher', 'extinguisher'], 'id': 442, 'def': 'a manually operated device for extinguishing small fires', 'name': 'fire_extinguisher'}, {'frequency': 'c', 'synset': 'fire_hose.n.01', 'synonyms': ['fire_hose'], 'id': 443, 'def': 'a large hose that carries water from a fire hydrant to the site of the fire', 'name': 'fire_hose'}, {'frequency': 'f', 'synset': 'fireplace.n.01', 'synonyms': ['fireplace'], 'id': 444, 'def': 'an open recess in a wall at the base of a chimney where a fire can be built', 'name': 'fireplace'}, {'frequency': 'f', 'synset': 'fireplug.n.01', 'synonyms': ['fireplug', 'fire_hydrant', 'hydrant'], 'id': 445, 'def': 'an upright hydrant for drawing water to use in fighting a fire', 'name': 'fireplug'}, {'frequency': 'r', 'synset': 'first-aid_kit.n.01', 'synonyms': ['first-aid_kit'], 'id': 446, 'def': 'kit consisting of a set of bandages and medicines for giving first aid', 'name': 'first-aid_kit'}, {'frequency': 'f', 'synset': 'fish.n.01', 'synonyms': ['fish'], 'id': 447, 'def': 'any of various mostly cold-blooded aquatic vertebrates usually having scales and breathing through gills', 'name': 'fish'}, {'frequency': 'c', 'synset': 'fish.n.02', 'synonyms': ['fish_(food)'], 'id': 448, 'def': 'the flesh of fish used as food', 'name': 'fish_(food)'}, {'frequency': 'r', 'synset': 'fishbowl.n.02', 'synonyms': ['fishbowl', 'goldfish_bowl'], 'id': 449, 'def': 'a transparent bowl in which small fish are kept', 'name': 'fishbowl'}, {'frequency': 'c', 'synset': 'fishing_rod.n.01', 'synonyms': ['fishing_rod', 'fishing_pole'], 'id': 450, 'def': 'a rod that is used in fishing to extend the fishing line', 'name': 'fishing_rod'}, {'frequency': 'f', 'synset': 'flag.n.01', 'synonyms': ['flag'], 'id': 451, 'def': 'emblem usually consisting of a rectangular piece of cloth of distinctive design (do not include pole)', 'name': 'flag'}, {'frequency': 'f', 'synset': 'flagpole.n.02', 'synonyms': ['flagpole', 'flagstaff'], 'id': 452, 'def': 'a tall staff or pole on which a flag is raised', 'name': 'flagpole'}, {'frequency': 'c', 'synset': 'flamingo.n.01', 'synonyms': ['flamingo'], 'id': 453, 'def': 'large pink web-footed bird with down-bent bill', 'name': 'flamingo'}, {'frequency': 'c', 'synset': 'flannel.n.01', 'synonyms': ['flannel'], 'id': 454, 'def': 'a soft light woolen fabric; used for clothing', 'name': 'flannel'}, {'frequency': 'c', 'synset': 'flap.n.01', 'synonyms': ['flap'], 'id': 455, 'def': 'any broad thin covering attached at one edge, such as a mud flap next to a wheel or a flap on an airplane wing', 'name': 'flap'}, {'frequency': 'r', 'synset': 'flash.n.10', 'synonyms': ['flash', 'flashbulb'], 'id': 456, 'def': 'a lamp for providing momentary light to take a photograph', 'name': 'flash'}, {'frequency': 'c', 'synset': 'flashlight.n.01', 'synonyms': ['flashlight', 'torch'], 'id': 457, 'def': 'a small portable battery-powered electric lamp', 'name': 'flashlight'}, {'frequency': 'r', 'synset': 'fleece.n.03', 'synonyms': ['fleece'], 'id': 458, 'def': 'a soft bulky fabric with deep pile; used chiefly for clothing', 'name': 'fleece'}, {'frequency': 'f', 'synset': 'flip-flop.n.02', 'synonyms': ['flip-flop_(sandal)'], 'id': 459, 'def': 'a backless sandal held to the foot by a thong between two toes', 'name': 'flip-flop_(sandal)'}, {'frequency': 'c', 'synset': 'flipper.n.01', 'synonyms': ['flipper_(footwear)', 'fin_(footwear)'], 'id': 460, 'def': 'a shoe to aid a person in swimming', 'name': 'flipper_(footwear)'}, {'frequency': 'f', 'synset': 'flower_arrangement.n.01', 'synonyms': ['flower_arrangement', 'floral_arrangement'], 'id': 461, 'def': 'a decorative arrangement of flowers', 'name': 'flower_arrangement'}, {'frequency': 'c', 'synset': 'flute.n.02', 'synonyms': ['flute_glass', 'champagne_flute'], 'id': 462, 'def': 'a tall narrow wineglass', 'name': 'flute_glass'}, {'frequency': 'c', 'synset': 'foal.n.01', 'synonyms': ['foal'], 'id': 463, 'def': 'a young horse', 'name': 'foal'}, {'frequency': 'c', 'synset': 'folding_chair.n.01', 'synonyms': ['folding_chair'], 'id': 464, 'def': 'a chair that can be folded flat for storage', 'name': 'folding_chair'}, {'frequency': 'c', 'synset': 'food_processor.n.01', 'synonyms': ['food_processor'], 'id': 465, 'def': 'a kitchen appliance for shredding, blending, chopping, or slicing food', 'name': 'food_processor'}, {'frequency': 'c', 'synset': 'football.n.02', 'synonyms': ['football_(American)'], 'id': 466, 'def': 'the inflated oblong ball used in playing American football', 'name': 'football_(American)'}, {'frequency': 'r', 'synset': 'football_helmet.n.01', 'synonyms': ['football_helmet'], 'id': 467, 'def': 'a padded helmet with a face mask to protect the head of football players', 'name': 'football_helmet'}, {'frequency': 'c', 'synset': 'footstool.n.01', 'synonyms': ['footstool', 'footrest'], 'id': 468, 'def': 'a low seat or a stool to rest the feet of a seated person', 'name': 'footstool'}, {'frequency': 'f', 'synset': 'fork.n.01', 'synonyms': ['fork'], 'id': 469, 'def': 'cutlery used for serving and eating food', 'name': 'fork'}, {'frequency': 'c', 'synset': 'forklift.n.01', 'synonyms': ['forklift'], 'id': 470, 'def': 'an industrial vehicle with a power operated fork in front that can be inserted under loads to lift and move them', 'name': 'forklift'}, {'frequency': 'c', 'synset': 'freight_car.n.01', 'synonyms': ['freight_car'], 'id': 471, 'def': 'a railway car that carries freight', 'name': 'freight_car'}, {'frequency': 'c', 'synset': 'french_toast.n.01', 'synonyms': ['French_toast'], 'id': 472, 'def': 'bread slice dipped in egg and milk and fried', 'name': 'French_toast'}, {'frequency': 'c', 'synset': 'freshener.n.01', 'synonyms': ['freshener', 'air_freshener'], 'id': 473, 'def': 'anything that freshens air by removing or covering odor', 'name': 'freshener'}, {'frequency': 'f', 'synset': 'frisbee.n.01', 'synonyms': ['frisbee'], 'id': 474, 'def': 'a light, plastic disk propelled with a flip of the wrist for recreation or competition', 'name': 'frisbee'}, {'frequency': 'c', 'synset': 'frog.n.01', 'synonyms': ['frog', 'toad', 'toad_frog'], 'id': 475, 'def': 'a tailless stout-bodied amphibians with long hind limbs for leaping', 'name': 'frog'}, {'frequency': 'c', 'synset': 'fruit_juice.n.01', 'synonyms': ['fruit_juice'], 'id': 476, 'def': 'drink produced by squeezing or crushing fruit', 'name': 'fruit_juice'}, {'frequency': 'f', 'synset': 'frying_pan.n.01', 'synonyms': ['frying_pan', 'frypan', 'skillet'], 'id': 477, 'def': 'a pan used for frying foods', 'name': 'frying_pan'}, {'frequency': 'r', 'synset': 'fudge.n.01', 'synonyms': ['fudge'], 'id': 478, 'def': 'soft creamy candy', 'name': 'fudge'}, {'frequency': 'r', 'synset': 'funnel.n.02', 'synonyms': ['funnel'], 'id': 479, 'def': 'a cone-shaped utensil used to channel a substance into a container with a small mouth', 'name': 'funnel'}, {'frequency': 'r', 'synset': 'futon.n.01', 'synonyms': ['futon'], 'id': 480, 'def': 'a pad that is used for sleeping on the floor or on a raised frame', 'name': 'futon'}, {'frequency': 'r', 'synset': 'gag.n.02', 'synonyms': ['gag', 'muzzle'], 'id': 481, 'def': "restraint put into a person's mouth to prevent speaking or shouting", 'name': 'gag'}, {'frequency': 'r', 'synset': 'garbage.n.03', 'synonyms': ['garbage'], 'id': 482, 'def': 'a receptacle where waste can be discarded', 'name': 'garbage'}, {'frequency': 'c', 'synset': 'garbage_truck.n.01', 'synonyms': ['garbage_truck'], 'id': 483, 'def': 'a truck for collecting domestic refuse', 'name': 'garbage_truck'}, {'frequency': 'c', 'synset': 'garden_hose.n.01', 'synonyms': ['garden_hose'], 'id': 484, 'def': 'a hose used for watering a lawn or garden', 'name': 'garden_hose'}, {'frequency': 'c', 'synset': 'gargle.n.01', 'synonyms': ['gargle', 'mouthwash'], 'id': 485, 'def': 'a medicated solution used for gargling and rinsing the mouth', 'name': 'gargle'}, {'frequency': 'r', 'synset': 'gargoyle.n.02', 'synonyms': ['gargoyle'], 'id': 486, 'def': 'an ornament consisting of a grotesquely carved figure of a person or animal', 'name': 'gargoyle'}, {'frequency': 'c', 'synset': 'garlic.n.02', 'synonyms': ['garlic', 'ail'], 'id': 487, 'def': 'aromatic bulb used as seasoning', 'name': 'garlic'}, {'frequency': 'r', 'synset': 'gasmask.n.01', 'synonyms': ['gasmask', 'respirator', 'gas_helmet'], 'id': 488, 'def': 'a protective face mask with a filter', 'name': 'gasmask'}, {'frequency': 'c', 'synset': 'gazelle.n.01', 'synonyms': ['gazelle'], 'id': 489, 'def': 'small swift graceful antelope of Africa and Asia having lustrous eyes', 'name': 'gazelle'}, {'frequency': 'c', 'synset': 'gelatin.n.02', 'synonyms': ['gelatin', 'jelly'], 'id': 490, 'def': 'an edible jelly made with gelatin and used as a dessert or salad base or a coating for foods', 'name': 'gelatin'}, {'frequency': 'r', 'synset': 'gem.n.02', 'synonyms': ['gemstone'], 'id': 491, 'def': 'a crystalline rock that can be cut and polished for jewelry', 'name': 'gemstone'}, {'frequency': 'r', 'synset': 'generator.n.02', 'synonyms': ['generator'], 'id': 492, 'def': 'engine that converts mechanical energy into electrical energy by electromagnetic induction', 'name': 'generator'}, {'frequency': 'c', 'synset': 'giant_panda.n.01', 'synonyms': ['giant_panda', 'panda', 'panda_bear'], 'id': 493, 'def': 'large black-and-white herbivorous mammal of bamboo forests of China and Tibet', 'name': 'giant_panda'}, {'frequency': 'c', 'synset': 'gift_wrap.n.01', 'synonyms': ['gift_wrap'], 'id': 494, 'def': 'attractive wrapping paper suitable for wrapping gifts', 'name': 'gift_wrap'}, {'frequency': 'c', 'synset': 'ginger.n.03', 'synonyms': ['ginger', 'gingerroot'], 'id': 495, 'def': 'the root of the common ginger plant; used fresh as a seasoning', 'name': 'ginger'}, {'frequency': 'f', 'synset': 'giraffe.n.01', 'synonyms': ['giraffe'], 'id': 496, 'def': 'tall animal having a spotted coat and small horns and very long neck and legs', 'name': 'giraffe'}, {'frequency': 'c', 'synset': 'girdle.n.02', 'synonyms': ['cincture', 'sash', 'waistband', 'waistcloth'], 'id': 497, 'def': 'a band of material around the waist that strengthens a skirt or trousers', 'name': 'cincture'}, {'frequency': 'f', 'synset': 'glass.n.02', 'synonyms': ['glass_(drink_container)', 'drinking_glass'], 'id': 498, 'def': 'a container for holding liquids while drinking', 'name': 'glass_(drink_container)'}, {'frequency': 'c', 'synset': 'globe.n.03', 'synonyms': ['globe'], 'id': 499, 'def': 'a sphere on which a map (especially of the earth) is represented', 'name': 'globe'}, {'frequency': 'f', 'synset': 'glove.n.02', 'synonyms': ['glove'], 'id': 500, 'def': 'handwear covering the hand', 'name': 'glove'}, {'frequency': 'c', 'synset': 'goat.n.01', 'synonyms': ['goat'], 'id': 501, 'def': 'a common goat', 'name': 'goat'}, {'frequency': 'f', 'synset': 'goggles.n.01', 'synonyms': ['goggles'], 'id': 502, 'def': 'tight-fitting spectacles worn to protect the eyes', 'name': 'goggles'}, {'frequency': 'r', 'synset': 'goldfish.n.01', 'synonyms': ['goldfish'], 'id': 503, 'def': 'small golden or orange-red freshwater fishes used as pond or aquarium pets', 'name': 'goldfish'}, {'frequency': 'c', 'synset': 'golf_club.n.02', 'synonyms': ['golf_club', 'golf-club'], 'id': 504, 'def': 'golf equipment used by a golfer to hit a golf ball', 'name': 'golf_club'}, {'frequency': 'c', 'synset': 'golfcart.n.01', 'synonyms': ['golfcart'], 'id': 505, 'def': 'a small motor vehicle in which golfers can ride between shots', 'name': 'golfcart'}, {'frequency': 'r', 'synset': 'gondola.n.02', 'synonyms': ['gondola_(boat)'], 'id': 506, 'def': 'long narrow flat-bottomed boat propelled by sculling; traditionally used on canals of Venice', 'name': 'gondola_(boat)'}, {'frequency': 'c', 'synset': 'goose.n.01', 'synonyms': ['goose'], 'id': 507, 'def': 'loud, web-footed long-necked aquatic birds usually larger than ducks', 'name': 'goose'}, {'frequency': 'r', 'synset': 'gorilla.n.01', 'synonyms': ['gorilla'], 'id': 508, 'def': 'largest ape', 'name': 'gorilla'}, {'frequency': 'r', 'synset': 'gourd.n.02', 'synonyms': ['gourd'], 'id': 509, 'def': 'any of numerous inedible fruits with hard rinds', 'name': 'gourd'}, {'frequency': 'f', 'synset': 'grape.n.01', 'synonyms': ['grape'], 'id': 510, 'def': 'any of various juicy fruit with green or purple skins; grow in clusters', 'name': 'grape'}, {'frequency': 'c', 'synset': 'grater.n.01', 'synonyms': ['grater'], 'id': 511, 'def': 'utensil with sharp perforations for shredding foods (as vegetables or cheese)', 'name': 'grater'}, {'frequency': 'c', 'synset': 'gravestone.n.01', 'synonyms': ['gravestone', 'headstone', 'tombstone'], 'id': 512, 'def': 'a stone that is used to mark a grave', 'name': 'gravestone'}, {'frequency': 'r', 'synset': 'gravy_boat.n.01', 'synonyms': ['gravy_boat', 'gravy_holder'], 'id': 513, 'def': 'a dish (often boat-shaped) for serving gravy or sauce', 'name': 'gravy_boat'}, {'frequency': 'f', 'synset': 'green_bean.n.02', 'synonyms': ['green_bean'], 'id': 514, 'def': 'a common bean plant cultivated for its slender green edible pods', 'name': 'green_bean'}, {'frequency': 'f', 'synset': 'green_onion.n.01', 'synonyms': ['green_onion', 'spring_onion', 'scallion'], 'id': 515, 'def': 'a young onion before the bulb has enlarged', 'name': 'green_onion'}, {'frequency': 'r', 'synset': 'griddle.n.01', 'synonyms': ['griddle'], 'id': 516, 'def': 'cooking utensil consisting of a flat heated surface on which food is cooked', 'name': 'griddle'}, {'frequency': 'f', 'synset': 'grill.n.02', 'synonyms': ['grill', 'grille', 'grillwork', 'radiator_grille'], 'id': 517, 'def': 'a framework of metal bars used as a partition or a grate', 'name': 'grill'}, {'frequency': 'r', 'synset': 'grits.n.01', 'synonyms': ['grits', 'hominy_grits'], 'id': 518, 'def': 'coarsely ground corn boiled as a breakfast dish', 'name': 'grits'}, {'frequency': 'c', 'synset': 'grizzly.n.01', 'synonyms': ['grizzly', 'grizzly_bear'], 'id': 519, 'def': 'powerful brownish-yellow bear of the uplands of western North America', 'name': 'grizzly'}, {'frequency': 'c', 'synset': 'grocery_bag.n.01', 'synonyms': ['grocery_bag'], 'id': 520, 'def': "a sack for holding customer's groceries", 'name': 'grocery_bag'}, {'frequency': 'f', 'synset': 'guitar.n.01', 'synonyms': ['guitar'], 'id': 521, 'def': 'a stringed instrument usually having six strings; played by strumming or plucking', 'name': 'guitar'}, {'frequency': 'c', 'synset': 'gull.n.02', 'synonyms': ['gull', 'seagull'], 'id': 522, 'def': 'mostly white aquatic bird having long pointed wings and short legs', 'name': 'gull'}, {'frequency': 'c', 'synset': 'gun.n.01', 'synonyms': ['gun'], 'id': 523, 'def': 'a weapon that discharges a bullet at high velocity from a metal tube', 'name': 'gun'}, {'frequency': 'f', 'synset': 'hairbrush.n.01', 'synonyms': ['hairbrush'], 'id': 524, 'def': "a brush used to groom a person's hair", 'name': 'hairbrush'}, {'frequency': 'c', 'synset': 'hairnet.n.01', 'synonyms': ['hairnet'], 'id': 525, 'def': 'a small net that someone wears over their hair to keep it in place', 'name': 'hairnet'}, {'frequency': 'c', 'synset': 'hairpin.n.01', 'synonyms': ['hairpin'], 'id': 526, 'def': "a double pronged pin used to hold women's hair in place", 'name': 'hairpin'}, {'frequency': 'r', 'synset': 'halter.n.03', 'synonyms': ['halter_top'], 'id': 527, 'def': "a woman's top that fastens behind the back and neck leaving the back and arms uncovered", 'name': 'halter_top'}, {'frequency': 'f', 'synset': 'ham.n.01', 'synonyms': ['ham', 'jambon', 'gammon'], 'id': 528, 'def': 'meat cut from the thigh of a hog (usually smoked)', 'name': 'ham'}, {'frequency': 'c', 'synset': 'hamburger.n.01', 'synonyms': ['hamburger', 'beefburger', 'burger'], 'id': 529, 'def': 'a sandwich consisting of a patty of minced beef served on a bun', 'name': 'hamburger'}, {'frequency': 'c', 'synset': 'hammer.n.02', 'synonyms': ['hammer'], 'id': 530, 'def': 'a hand tool with a heavy head and a handle; used to deliver an impulsive force by striking', 'name': 'hammer'}, {'frequency': 'c', 'synset': 'hammock.n.02', 'synonyms': ['hammock'], 'id': 531, 'def': 'a hanging bed of canvas or rope netting (usually suspended between two trees)', 'name': 'hammock'}, {'frequency': 'r', 'synset': 'hamper.n.02', 'synonyms': ['hamper'], 'id': 532, 'def': 'a basket usually with a cover', 'name': 'hamper'}, {'frequency': 'c', 'synset': 'hamster.n.01', 'synonyms': ['hamster'], 'id': 533, 'def': 'short-tailed burrowing rodent with large cheek pouches', 'name': 'hamster'}, {'frequency': 'f', 'synset': 'hand_blower.n.01', 'synonyms': ['hair_dryer'], 'id': 534, 'def': 'a hand-held electric blower that can blow warm air onto the hair', 'name': 'hair_dryer'}, {'frequency': 'r', 'synset': 'hand_glass.n.01', 'synonyms': ['hand_glass', 'hand_mirror'], 'id': 535, 'def': 'a mirror intended to be held in the hand', 'name': 'hand_glass'}, {'frequency': 'f', 'synset': 'hand_towel.n.01', 'synonyms': ['hand_towel', 'face_towel'], 'id': 536, 'def': 'a small towel used to dry the hands or face', 'name': 'hand_towel'}, {'frequency': 'c', 'synset': 'handcart.n.01', 'synonyms': ['handcart', 'pushcart', 'hand_truck'], 'id': 537, 'def': 'wheeled vehicle that can be pushed by a person', 'name': 'handcart'}, {'frequency': 'r', 'synset': 'handcuff.n.01', 'synonyms': ['handcuff'], 'id': 538, 'def': 'shackle that consists of a metal loop that can be locked around the wrist', 'name': 'handcuff'}, {'frequency': 'c', 'synset': 'handkerchief.n.01', 'synonyms': ['handkerchief'], 'id': 539, 'def': 'a square piece of cloth used for wiping the eyes or nose or as a costume accessory', 'name': 'handkerchief'}, {'frequency': 'f', 'synset': 'handle.n.01', 'synonyms': ['handle', 'grip', 'handgrip'], 'id': 540, 'def': 'the appendage to an object that is designed to be held in order to use or move it', 'name': 'handle'}, {'frequency': 'r', 'synset': 'handsaw.n.01', 'synonyms': ['handsaw', "carpenter's_saw"], 'id': 541, 'def': 'a saw used with one hand for cutting wood', 'name': 'handsaw'}, {'frequency': 'r', 'synset': 'hardback.n.01', 'synonyms': ['hardback_book', 'hardcover_book'], 'id': 542, 'def': 'a book with cardboard or cloth or leather covers', 'name': 'hardback_book'}, {'frequency': 'r', 'synset': 'harmonium.n.01', 'synonyms': ['harmonium', 'organ_(musical_instrument)', 'reed_organ_(musical_instrument)'], 'id': 543, 'def': 'a free-reed instrument in which air is forced through the reeds by bellows', 'name': 'harmonium'}, {'frequency': 'f', 'synset': 'hat.n.01', 'synonyms': ['hat'], 'id': 544, 'def': 'headwear that protects the head from bad weather, sun, or worn for fashion', 'name': 'hat'}, {'frequency': 'r', 'synset': 'hatbox.n.01', 'synonyms': ['hatbox'], 'id': 545, 'def': 'a round piece of luggage for carrying hats', 'name': 'hatbox'}, {'frequency': 'c', 'synset': 'head_covering.n.01', 'synonyms': ['veil'], 'id': 546, 'def': 'a garment that covers the head OR face', 'name': 'veil'}, {'frequency': 'f', 'synset': 'headband.n.01', 'synonyms': ['headband'], 'id': 547, 'def': 'a band worn around or over the head', 'name': 'headband'}, {'frequency': 'f', 'synset': 'headboard.n.01', 'synonyms': ['headboard'], 'id': 548, 'def': 'a vertical board or panel forming the head of a bedstead', 'name': 'headboard'}, {'frequency': 'f', 'synset': 'headlight.n.01', 'synonyms': ['headlight', 'headlamp'], 'id': 549, 'def': 'a powerful light with reflector; attached to the front of an automobile or locomotive', 'name': 'headlight'}, {'frequency': 'c', 'synset': 'headscarf.n.01', 'synonyms': ['headscarf'], 'id': 550, 'def': 'a kerchief worn over the head and tied under the chin', 'name': 'headscarf'}, {'frequency': 'r', 'synset': 'headset.n.01', 'synonyms': ['headset'], 'id': 551, 'def': 'receiver consisting of a pair of headphones', 'name': 'headset'}, {'frequency': 'c', 'synset': 'headstall.n.01', 'synonyms': ['headstall_(for_horses)', 'headpiece_(for_horses)'], 'id': 552, 'def': "the band that is the part of a bridle that fits around a horse's head", 'name': 'headstall_(for_horses)'}, {'frequency': 'c', 'synset': 'heart.n.02', 'synonyms': ['heart'], 'id': 553, 'def': 'a muscular organ; its contractions move the blood through the body', 'name': 'heart'}, {'frequency': 'c', 'synset': 'heater.n.01', 'synonyms': ['heater', 'warmer'], 'id': 554, 'def': 'device that heats water or supplies warmth to a room', 'name': 'heater'}, {'frequency': 'c', 'synset': 'helicopter.n.01', 'synonyms': ['helicopter'], 'id': 555, 'def': 'an aircraft without wings that obtains its lift from the rotation of overhead blades', 'name': 'helicopter'}, {'frequency': 'f', 'synset': 'helmet.n.02', 'synonyms': ['helmet'], 'id': 556, 'def': 'a protective headgear made of hard material to resist blows', 'name': 'helmet'}, {'frequency': 'r', 'synset': 'heron.n.02', 'synonyms': ['heron'], 'id': 557, 'def': 'grey or white wading bird with long neck and long legs and (usually) long bill', 'name': 'heron'}, {'frequency': 'c', 'synset': 'highchair.n.01', 'synonyms': ['highchair', 'feeding_chair'], 'id': 558, 'def': 'a chair for feeding a very young child', 'name': 'highchair'}, {'frequency': 'f', 'synset': 'hinge.n.01', 'synonyms': ['hinge'], 'id': 559, 'def': 'a joint that holds two parts together so that one can swing relative to the other', 'name': 'hinge'}, {'frequency': 'r', 'synset': 'hippopotamus.n.01', 'synonyms': ['hippopotamus'], 'id': 560, 'def': 'massive thick-skinned animal living in or around rivers of tropical Africa', 'name': 'hippopotamus'}, {'frequency': 'r', 'synset': 'hockey_stick.n.01', 'synonyms': ['hockey_stick'], 'id': 561, 'def': 'sports implement consisting of a stick used by hockey players to move the puck', 'name': 'hockey_stick'}, {'frequency': 'c', 'synset': 'hog.n.03', 'synonyms': ['hog', 'pig'], 'id': 562, 'def': 'domestic swine', 'name': 'hog'}, {'frequency': 'f', 'synset': 'home_plate.n.01', 'synonyms': ['home_plate_(baseball)', 'home_base_(baseball)'], 'id': 563, 'def': '(baseball) a rubber slab where the batter stands; it must be touched by a base runner in order to score', 'name': 'home_plate_(baseball)'}, {'frequency': 'c', 'synset': 'honey.n.01', 'synonyms': ['honey'], 'id': 564, 'def': 'a sweet yellow liquid produced by bees', 'name': 'honey'}, {'frequency': 'f', 'synset': 'hood.n.06', 'synonyms': ['fume_hood', 'exhaust_hood'], 'id': 565, 'def': 'metal covering leading to a vent that exhausts smoke or fumes', 'name': 'fume_hood'}, {'frequency': 'f', 'synset': 'hook.n.05', 'synonyms': ['hook'], 'id': 566, 'def': 'a curved or bent implement for suspending or pulling something', 'name': 'hook'}, {'frequency': 'r', 'synset': 'hookah.n.01', 'synonyms': ['hookah', 'narghile', 'nargileh', 'sheesha', 'shisha', 'water_pipe'], 'id': 567, 'def': 'a tobacco pipe with a long flexible tube connected to a container where the smoke is cooled by passing through water', 'name': 'hookah'}, {'frequency': 'r', 'synset': 'hornet.n.01', 'synonyms': ['hornet'], 'id': 568, 'def': 'large stinging wasp', 'name': 'hornet'}, {'frequency': 'f', 'synset': 'horse.n.01', 'synonyms': ['horse'], 'id': 569, 'def': 'a common horse', 'name': 'horse'}, {'frequency': 'f', 'synset': 'hose.n.03', 'synonyms': ['hose', 'hosepipe'], 'id': 570, 'def': 'a flexible pipe for conveying a liquid or gas', 'name': 'hose'}, {'frequency': 'r', 'synset': 'hot-air_balloon.n.01', 'synonyms': ['hot-air_balloon'], 'id': 571, 'def': 'balloon for travel through the air in a basket suspended below a large bag of heated air', 'name': 'hot-air_balloon'}, {'frequency': 'r', 'synset': 'hot_plate.n.01', 'synonyms': ['hotplate'], 'id': 572, 'def': 'a portable electric appliance for heating or cooking or keeping food warm', 'name': 'hotplate'}, {'frequency': 'c', 'synset': 'hot_sauce.n.01', 'synonyms': ['hot_sauce'], 'id': 573, 'def': 'a pungent peppery sauce', 'name': 'hot_sauce'}, {'frequency': 'r', 'synset': 'hourglass.n.01', 'synonyms': ['hourglass'], 'id': 574, 'def': 'a sandglass timer that runs for sixty minutes', 'name': 'hourglass'}, {'frequency': 'r', 'synset': 'houseboat.n.01', 'synonyms': ['houseboat'], 'id': 575, 'def': 'a barge that is designed and equipped for use as a dwelling', 'name': 'houseboat'}, {'frequency': 'c', 'synset': 'hummingbird.n.01', 'synonyms': ['hummingbird'], 'id': 576, 'def': 'tiny American bird having brilliant iridescent plumage and long slender bills', 'name': 'hummingbird'}, {'frequency': 'r', 'synset': 'hummus.n.01', 'synonyms': ['hummus', 'humus', 'hommos', 'hoummos', 'humous'], 'id': 577, 'def': 'a thick spread made from mashed chickpeas', 'name': 'hummus'}, {'frequency': 'f', 'synset': 'ice_bear.n.01', 'synonyms': ['polar_bear'], 'id': 578, 'def': 'white bear of Arctic regions', 'name': 'polar_bear'}, {'frequency': 'c', 'synset': 'ice_cream.n.01', 'synonyms': ['icecream'], 'id': 579, 'def': 'frozen dessert containing cream and sugar and flavoring', 'name': 'icecream'}, {'frequency': 'r', 'synset': 'ice_lolly.n.01', 'synonyms': ['popsicle'], 'id': 580, 'def': 'ice cream or water ice on a small wooden stick', 'name': 'popsicle'}, {'frequency': 'c', 'synset': 'ice_maker.n.01', 'synonyms': ['ice_maker'], 'id': 581, 'def': 'an appliance included in some electric refrigerators for making ice cubes', 'name': 'ice_maker'}, {'frequency': 'r', 'synset': 'ice_pack.n.01', 'synonyms': ['ice_pack', 'ice_bag'], 'id': 582, 'def': 'a waterproof bag filled with ice: applied to the body (especially the head) to cool or reduce swelling', 'name': 'ice_pack'}, {'frequency': 'r', 'synset': 'ice_skate.n.01', 'synonyms': ['ice_skate'], 'id': 583, 'def': 'skate consisting of a boot with a steel blade fitted to the sole', 'name': 'ice_skate'}, {'frequency': 'c', 'synset': 'igniter.n.01', 'synonyms': ['igniter', 'ignitor', 'lighter'], 'id': 584, 'def': 'a substance or device used to start a fire', 'name': 'igniter'}, {'frequency': 'r', 'synset': 'inhaler.n.01', 'synonyms': ['inhaler', 'inhalator'], 'id': 585, 'def': 'a dispenser that produces a chemical vapor to be inhaled through mouth or nose', 'name': 'inhaler'}, {'frequency': 'f', 'synset': 'ipod.n.01', 'synonyms': ['iPod'], 'id': 586, 'def': 'a pocket-sized device used to play music files', 'name': 'iPod'}, {'frequency': 'c', 'synset': 'iron.n.04', 'synonyms': ['iron_(for_clothing)', 'smoothing_iron_(for_clothing)'], 'id': 587, 'def': 'home appliance consisting of a flat metal base that is heated and used to smooth cloth', 'name': 'iron_(for_clothing)'}, {'frequency': 'c', 'synset': 'ironing_board.n.01', 'synonyms': ['ironing_board'], 'id': 588, 'def': 'narrow padded board on collapsible supports; used for ironing clothes', 'name': 'ironing_board'}, {'frequency': 'f', 'synset': 'jacket.n.01', 'synonyms': ['jacket'], 'id': 589, 'def': 'a waist-length coat', 'name': 'jacket'}, {'frequency': 'c', 'synset': 'jam.n.01', 'synonyms': ['jam'], 'id': 590, 'def': 'preserve of crushed fruit', 'name': 'jam'}, {'frequency': 'f', 'synset': 'jar.n.01', 'synonyms': ['jar'], 'id': 591, 'def': 'a vessel (usually cylindrical) with a wide mouth and without handles', 'name': 'jar'}, {'frequency': 'f', 'synset': 'jean.n.01', 'synonyms': ['jean', 'blue_jean', 'denim'], 'id': 592, 'def': '(usually plural) close-fitting trousers of heavy denim for manual work or casual wear', 'name': 'jean'}, {'frequency': 'c', 'synset': 'jeep.n.01', 'synonyms': ['jeep', 'landrover'], 'id': 593, 'def': 'a car suitable for traveling over rough terrain', 'name': 'jeep'}, {'frequency': 'r', 'synset': 'jelly_bean.n.01', 'synonyms': ['jelly_bean', 'jelly_egg'], 'id': 594, 'def': 'sugar-glazed jellied candy', 'name': 'jelly_bean'}, {'frequency': 'f', 'synset': 'jersey.n.03', 'synonyms': ['jersey', 'T-shirt', 'tee_shirt'], 'id': 595, 'def': 'a close-fitting pullover shirt', 'name': 'jersey'}, {'frequency': 'c', 'synset': 'jet.n.01', 'synonyms': ['jet_plane', 'jet-propelled_plane'], 'id': 596, 'def': 'an airplane powered by one or more jet engines', 'name': 'jet_plane'}, {'frequency': 'r', 'synset': 'jewel.n.01', 'synonyms': ['jewel', 'gem', 'precious_stone'], 'id': 597, 'def': 'a precious or semiprecious stone incorporated into a piece of jewelry', 'name': 'jewel'}, {'frequency': 'c', 'synset': 'jewelry.n.01', 'synonyms': ['jewelry', 'jewellery'], 'id': 598, 'def': 'an adornment (as a bracelet or ring or necklace) made of precious metals and set with gems (or imitation gems)', 'name': 'jewelry'}, {'frequency': 'r', 'synset': 'joystick.n.02', 'synonyms': ['joystick'], 'id': 599, 'def': 'a control device for computers consisting of a vertical handle that can move freely in two directions', 'name': 'joystick'}, {'frequency': 'c', 'synset': 'jump_suit.n.01', 'synonyms': ['jumpsuit'], 'id': 600, 'def': "one-piece garment fashioned after a parachutist's uniform", 'name': 'jumpsuit'}, {'frequency': 'c', 'synset': 'kayak.n.01', 'synonyms': ['kayak'], 'id': 601, 'def': 'a small canoe consisting of a light frame made watertight with animal skins', 'name': 'kayak'}, {'frequency': 'r', 'synset': 'keg.n.02', 'synonyms': ['keg'], 'id': 602, 'def': 'small cask or barrel', 'name': 'keg'}, {'frequency': 'r', 'synset': 'kennel.n.01', 'synonyms': ['kennel', 'doghouse'], 'id': 603, 'def': 'outbuilding that serves as a shelter for a dog', 'name': 'kennel'}, {'frequency': 'c', 'synset': 'kettle.n.01', 'synonyms': ['kettle', 'boiler'], 'id': 604, 'def': 'a metal pot for stewing or boiling; usually has a lid', 'name': 'kettle'}, {'frequency': 'f', 'synset': 'key.n.01', 'synonyms': ['key'], 'id': 605, 'def': 'metal instrument used to unlock a lock', 'name': 'key'}, {'frequency': 'r', 'synset': 'keycard.n.01', 'synonyms': ['keycard'], 'id': 606, 'def': 'a plastic card used to gain access typically to a door', 'name': 'keycard'}, {'frequency': 'c', 'synset': 'kilt.n.01', 'synonyms': ['kilt'], 'id': 607, 'def': 'a knee-length pleated tartan skirt worn by men as part of the traditional dress in the Highlands of northern Scotland', 'name': 'kilt'}, {'frequency': 'c', 'synset': 'kimono.n.01', 'synonyms': ['kimono'], 'id': 608, 'def': 'a loose robe; imitated from robes originally worn by Japanese', 'name': 'kimono'}, {'frequency': 'f', 'synset': 'kitchen_sink.n.01', 'synonyms': ['kitchen_sink'], 'id': 609, 'def': 'a sink in a kitchen', 'name': 'kitchen_sink'}, {'frequency': 'r', 'synset': 'kitchen_table.n.01', 'synonyms': ['kitchen_table'], 'id': 610, 'def': 'a table in the kitchen', 'name': 'kitchen_table'}, {'frequency': 'f', 'synset': 'kite.n.03', 'synonyms': ['kite'], 'id': 611, 'def': 'plaything consisting of a light frame covered with tissue paper; flown in wind at end of a string', 'name': 'kite'}, {'frequency': 'c', 'synset': 'kitten.n.01', 'synonyms': ['kitten', 'kitty'], 'id': 612, 'def': 'young domestic cat', 'name': 'kitten'}, {'frequency': 'c', 'synset': 'kiwi.n.03', 'synonyms': ['kiwi_fruit'], 'id': 613, 'def': 'fuzzy brown egg-shaped fruit with slightly tart green flesh', 'name': 'kiwi_fruit'}, {'frequency': 'f', 'synset': 'knee_pad.n.01', 'synonyms': ['knee_pad'], 'id': 614, 'def': 'protective garment consisting of a pad worn by football or baseball or hockey players', 'name': 'knee_pad'}, {'frequency': 'f', 'synset': 'knife.n.01', 'synonyms': ['knife'], 'id': 615, 'def': 'tool with a blade and point used as a cutting instrument', 'name': 'knife'}, {'frequency': 'r', 'synset': 'knitting_needle.n.01', 'synonyms': ['knitting_needle'], 'id': 616, 'def': 'needle consisting of a slender rod with pointed ends; usually used in pairs', 'name': 'knitting_needle'}, {'frequency': 'f', 'synset': 'knob.n.02', 'synonyms': ['knob'], 'id': 617, 'def': 'a round handle often found on a door', 'name': 'knob'}, {'frequency': 'r', 'synset': 'knocker.n.05', 'synonyms': ['knocker_(on_a_door)', 'doorknocker'], 'id': 618, 'def': 'a device (usually metal and ornamental) attached by a hinge to a door', 'name': 'knocker_(on_a_door)'}, {'frequency': 'r', 'synset': 'koala.n.01', 'synonyms': ['koala', 'koala_bear'], 'id': 619, 'def': 'sluggish tailless Australian marsupial with grey furry ears and coat', 'name': 'koala'}, {'frequency': 'r', 'synset': 'lab_coat.n.01', 'synonyms': ['lab_coat', 'laboratory_coat'], 'id': 620, 'def': 'a light coat worn to protect clothing from substances used while working in a laboratory', 'name': 'lab_coat'}, {'frequency': 'f', 'synset': 'ladder.n.01', 'synonyms': ['ladder'], 'id': 621, 'def': 'steps consisting of two parallel members connected by rungs', 'name': 'ladder'}, {'frequency': 'c', 'synset': 'ladle.n.01', 'synonyms': ['ladle'], 'id': 622, 'def': 'a spoon-shaped vessel with a long handle frequently used to transfer liquids', 'name': 'ladle'}, {'frequency': 'c', 'synset': 'ladybug.n.01', 'synonyms': ['ladybug', 'ladybeetle', 'ladybird_beetle'], 'id': 623, 'def': 'small round bright-colored and spotted beetle, typically red and black', 'name': 'ladybug'}, {'frequency': 'f', 'synset': 'lamb.n.01', 'synonyms': ['lamb_(animal)'], 'id': 624, 'def': 'young sheep', 'name': 'lamb_(animal)'}, {'frequency': 'r', 'synset': 'lamb_chop.n.01', 'synonyms': ['lamb-chop', 'lambchop'], 'id': 625, 'def': 'chop cut from a lamb', 'name': 'lamb-chop'}, {'frequency': 'f', 'synset': 'lamp.n.02', 'synonyms': ['lamp'], 'id': 626, 'def': 'a piece of furniture holding one or more electric light bulbs', 'name': 'lamp'}, {'frequency': 'f', 'synset': 'lamppost.n.01', 'synonyms': ['lamppost'], 'id': 627, 'def': 'a metal post supporting an outdoor lamp (such as a streetlight)', 'name': 'lamppost'}, {'frequency': 'f', 'synset': 'lampshade.n.01', 'synonyms': ['lampshade'], 'id': 628, 'def': 'a protective ornamental shade used to screen a light bulb from direct view', 'name': 'lampshade'}, {'frequency': 'c', 'synset': 'lantern.n.01', 'synonyms': ['lantern'], 'id': 629, 'def': 'light in a transparent protective case', 'name': 'lantern'}, {'frequency': 'f', 'synset': 'lanyard.n.02', 'synonyms': ['lanyard', 'laniard'], 'id': 630, 'def': 'a cord worn around the neck to hold a knife or whistle, etc.', 'name': 'lanyard'}, {'frequency': 'f', 'synset': 'laptop.n.01', 'synonyms': ['laptop_computer', 'notebook_computer'], 'id': 631, 'def': 'a portable computer small enough to use in your lap', 'name': 'laptop_computer'}, {'frequency': 'r', 'synset': 'lasagna.n.01', 'synonyms': ['lasagna', 'lasagne'], 'id': 632, 'def': 'baked dish of layers of lasagna pasta with sauce and cheese and meat or vegetables', 'name': 'lasagna'}, {'frequency': 'f', 'synset': 'latch.n.02', 'synonyms': ['latch'], 'id': 633, 'def': 'a bar that can be lowered or slid into a groove to fasten a door or gate', 'name': 'latch'}, {'frequency': 'r', 'synset': 'lawn_mower.n.01', 'synonyms': ['lawn_mower'], 'id': 634, 'def': 'garden tool for mowing grass on lawns', 'name': 'lawn_mower'}, {'frequency': 'r', 'synset': 'leather.n.01', 'synonyms': ['leather'], 'id': 635, 'def': 'an animal skin made smooth and flexible by removing the hair and then tanning', 'name': 'leather'}, {'frequency': 'c', 'synset': 'legging.n.01', 'synonyms': ['legging_(clothing)', 'leging_(clothing)', 'leg_covering'], 'id': 636, 'def': 'a garment covering the leg (usually extending from the knee to the ankle)', 'name': 'legging_(clothing)'}, {'frequency': 'c', 'synset': 'lego.n.01', 'synonyms': ['Lego', 'Lego_set'], 'id': 637, 'def': "a child's plastic construction set for making models from blocks", 'name': 'Lego'}, {'frequency': 'r', 'synset': 'legume.n.02', 'synonyms': ['legume'], 'id': 638, 'def': 'the fruit or seed of bean or pea plants', 'name': 'legume'}, {'frequency': 'f', 'synset': 'lemon.n.01', 'synonyms': ['lemon'], 'id': 639, 'def': 'yellow oval fruit with juicy acidic flesh', 'name': 'lemon'}, {'frequency': 'r', 'synset': 'lemonade.n.01', 'synonyms': ['lemonade'], 'id': 640, 'def': 'sweetened beverage of diluted lemon juice', 'name': 'lemonade'}, {'frequency': 'f', 'synset': 'lettuce.n.02', 'synonyms': ['lettuce'], 'id': 641, 'def': 'leafy plant commonly eaten in salad or on sandwiches', 'name': 'lettuce'}, {'frequency': 'f', 'synset': 'license_plate.n.01', 'synonyms': ['license_plate', 'numberplate'], 'id': 642, 'def': "a plate mounted on the front and back of car and bearing the car's registration number", 'name': 'license_plate'}, {'frequency': 'f', 'synset': 'life_buoy.n.01', 'synonyms': ['life_buoy', 'lifesaver', 'life_belt', 'life_ring'], 'id': 643, 'def': 'a ring-shaped life preserver used to prevent drowning (NOT a life-jacket or vest)', 'name': 'life_buoy'}, {'frequency': 'f', 'synset': 'life_jacket.n.01', 'synonyms': ['life_jacket', 'life_vest'], 'id': 644, 'def': 'life preserver consisting of a sleeveless jacket of buoyant or inflatable design', 'name': 'life_jacket'}, {'frequency': 'f', 'synset': 'light_bulb.n.01', 'synonyms': ['lightbulb'], 'id': 645, 'def': 'lightblub/source of light', 'name': 'lightbulb'}, {'frequency': 'r', 'synset': 'lightning_rod.n.02', 'synonyms': ['lightning_rod', 'lightning_conductor'], 'id': 646, 'def': 'a metallic conductor that is attached to a high point and leads to the ground', 'name': 'lightning_rod'}, {'frequency': 'f', 'synset': 'lime.n.06', 'synonyms': ['lime'], 'id': 647, 'def': 'the green acidic fruit of any of various lime trees', 'name': 'lime'}, {'frequency': 'r', 'synset': 'limousine.n.01', 'synonyms': ['limousine'], 'id': 648, 'def': 'long luxurious car; usually driven by a chauffeur', 'name': 'limousine'}, {'frequency': 'c', 'synset': 'lion.n.01', 'synonyms': ['lion'], 'id': 649, 'def': 'large gregarious predatory cat of Africa and India', 'name': 'lion'}, {'frequency': 'c', 'synset': 'lip_balm.n.01', 'synonyms': ['lip_balm'], 'id': 650, 'def': 'a balm applied to the lips', 'name': 'lip_balm'}, {'frequency': 'r', 'synset': 'liquor.n.01', 'synonyms': ['liquor', 'spirits', 'hard_liquor', 'liqueur', 'cordial'], 'id': 651, 'def': 'liquor or beer', 'name': 'liquor'}, {'frequency': 'c', 'synset': 'lizard.n.01', 'synonyms': ['lizard'], 'id': 652, 'def': 'a reptile with usually two pairs of legs and a tapering tail', 'name': 'lizard'}, {'frequency': 'f', 'synset': 'log.n.01', 'synonyms': ['log'], 'id': 653, 'def': 'a segment of the trunk of a tree when stripped of branches', 'name': 'log'}, {'frequency': 'c', 'synset': 'lollipop.n.02', 'synonyms': ['lollipop'], 'id': 654, 'def': 'hard candy on a stick', 'name': 'lollipop'}, {'frequency': 'f', 'synset': 'loudspeaker.n.01', 'synonyms': ['speaker_(stero_equipment)'], 'id': 655, 'def': 'electronic device that produces sound often as part of a stereo system', 'name': 'speaker_(stero_equipment)'}, {'frequency': 'c', 'synset': 'love_seat.n.01', 'synonyms': ['loveseat'], 'id': 656, 'def': 'small sofa that seats two people', 'name': 'loveseat'}, {'frequency': 'r', 'synset': 'machine_gun.n.01', 'synonyms': ['machine_gun'], 'id': 657, 'def': 'a rapidly firing automatic gun', 'name': 'machine_gun'}, {'frequency': 'f', 'synset': 'magazine.n.02', 'synonyms': ['magazine'], 'id': 658, 'def': 'a paperback periodic publication', 'name': 'magazine'}, {'frequency': 'f', 'synset': 'magnet.n.01', 'synonyms': ['magnet'], 'id': 659, 'def': 'a device that attracts iron and produces a magnetic field', 'name': 'magnet'}, {'frequency': 'c', 'synset': 'mail_slot.n.01', 'synonyms': ['mail_slot'], 'id': 660, 'def': 'a slot (usually in a door) through which mail can be delivered', 'name': 'mail_slot'}, {'frequency': 'f', 'synset': 'mailbox.n.01', 'synonyms': ['mailbox_(at_home)', 'letter_box_(at_home)'], 'id': 661, 'def': 'a private box for delivery of mail', 'name': 'mailbox_(at_home)'}, {'frequency': 'r', 'synset': 'mallard.n.01', 'synonyms': ['mallard'], 'id': 662, 'def': 'wild dabbling duck from which domestic ducks are descended', 'name': 'mallard'}, {'frequency': 'r', 'synset': 'mallet.n.01', 'synonyms': ['mallet'], 'id': 663, 'def': 'a sports implement with a long handle and a hammer-like head used to hit a ball', 'name': 'mallet'}, {'frequency': 'r', 'synset': 'mammoth.n.01', 'synonyms': ['mammoth'], 'id': 664, 'def': 'any of numerous extinct elephants widely distributed in the Pleistocene', 'name': 'mammoth'}, {'frequency': 'r', 'synset': 'manatee.n.01', 'synonyms': ['manatee'], 'id': 665, 'def': 'sirenian mammal of tropical coastal waters of America', 'name': 'manatee'}, {'frequency': 'c', 'synset': 'mandarin.n.05', 'synonyms': ['mandarin_orange'], 'id': 666, 'def': 'a somewhat flat reddish-orange loose skinned citrus of China', 'name': 'mandarin_orange'}, {'frequency': 'c', 'synset': 'manger.n.01', 'synonyms': ['manger', 'trough'], 'id': 667, 'def': 'a container (usually in a barn or stable) from which cattle or horses feed', 'name': 'manger'}, {'frequency': 'f', 'synset': 'manhole.n.01', 'synonyms': ['manhole'], 'id': 668, 'def': 'a hole (usually with a flush cover) through which a person can gain access to an underground structure', 'name': 'manhole'}, {'frequency': 'f', 'synset': 'map.n.01', 'synonyms': ['map'], 'id': 669, 'def': "a diagrammatic representation of the earth's surface (or part of it)", 'name': 'map'}, {'frequency': 'f', 'synset': 'marker.n.03', 'synonyms': ['marker'], 'id': 670, 'def': 'a writing implement for making a mark', 'name': 'marker'}, {'frequency': 'r', 'synset': 'martini.n.01', 'synonyms': ['martini'], 'id': 671, 'def': 'a cocktail made of gin (or vodka) with dry vermouth', 'name': 'martini'}, {'frequency': 'r', 'synset': 'mascot.n.01', 'synonyms': ['mascot'], 'id': 672, 'def': 'a person or animal that is adopted by a team or other group as a symbolic figure', 'name': 'mascot'}, {'frequency': 'c', 'synset': 'mashed_potato.n.01', 'synonyms': ['mashed_potato'], 'id': 673, 'def': 'potato that has been peeled and boiled and then mashed', 'name': 'mashed_potato'}, {'frequency': 'r', 'synset': 'masher.n.02', 'synonyms': ['masher'], 'id': 674, 'def': 'a kitchen utensil used for mashing (e.g. potatoes)', 'name': 'masher'}, {'frequency': 'f', 'synset': 'mask.n.04', 'synonyms': ['mask', 'facemask'], 'id': 675, 'def': 'a protective covering worn over the face', 'name': 'mask'}, {'frequency': 'f', 'synset': 'mast.n.01', 'synonyms': ['mast'], 'id': 676, 'def': 'a vertical spar for supporting sails', 'name': 'mast'}, {'frequency': 'c', 'synset': 'mat.n.03', 'synonyms': ['mat_(gym_equipment)', 'gym_mat'], 'id': 677, 'def': 'sports equipment consisting of a piece of thick padding on the floor for gymnastics', 'name': 'mat_(gym_equipment)'}, {'frequency': 'r', 'synset': 'matchbox.n.01', 'synonyms': ['matchbox'], 'id': 678, 'def': 'a box for holding matches', 'name': 'matchbox'}, {'frequency': 'f', 'synset': 'mattress.n.01', 'synonyms': ['mattress'], 'id': 679, 'def': 'a thick pad filled with resilient material used as a bed or part of a bed', 'name': 'mattress'}, {'frequency': 'c', 'synset': 'measuring_cup.n.01', 'synonyms': ['measuring_cup'], 'id': 680, 'def': 'graduated cup used to measure liquid or granular ingredients', 'name': 'measuring_cup'}, {'frequency': 'c', 'synset': 'measuring_stick.n.01', 'synonyms': ['measuring_stick', 'ruler_(measuring_stick)', 'measuring_rod'], 'id': 681, 'def': 'measuring instrument having a sequence of marks at regular intervals', 'name': 'measuring_stick'}, {'frequency': 'c', 'synset': 'meatball.n.01', 'synonyms': ['meatball'], 'id': 682, 'def': 'ground meat formed into a ball and fried or simmered in broth', 'name': 'meatball'}, {'frequency': 'c', 'synset': 'medicine.n.02', 'synonyms': ['medicine'], 'id': 683, 'def': 'something that treats or prevents or alleviates the symptoms of disease', 'name': 'medicine'}, {'frequency': 'c', 'synset': 'melon.n.01', 'synonyms': ['melon'], 'id': 684, 'def': 'fruit of the gourd family having a hard rind and sweet juicy flesh', 'name': 'melon'}, {'frequency': 'f', 'synset': 'microphone.n.01', 'synonyms': ['microphone'], 'id': 685, 'def': 'device for converting sound waves into electrical energy', 'name': 'microphone'}, {'frequency': 'r', 'synset': 'microscope.n.01', 'synonyms': ['microscope'], 'id': 686, 'def': 'magnifier of the image of small objects', 'name': 'microscope'}, {'frequency': 'f', 'synset': 'microwave.n.02', 'synonyms': ['microwave_oven'], 'id': 687, 'def': 'kitchen appliance that cooks food by passing an electromagnetic wave through it', 'name': 'microwave_oven'}, {'frequency': 'r', 'synset': 'milestone.n.01', 'synonyms': ['milestone', 'milepost'], 'id': 688, 'def': 'stone post at side of a road to show distances', 'name': 'milestone'}, {'frequency': 'f', 'synset': 'milk.n.01', 'synonyms': ['milk'], 'id': 689, 'def': 'a white nutritious liquid secreted by mammals and used as food by human beings', 'name': 'milk'}, {'frequency': 'r', 'synset': 'milk_can.n.01', 'synonyms': ['milk_can'], 'id': 690, 'def': 'can for transporting milk', 'name': 'milk_can'}, {'frequency': 'r', 'synset': 'milkshake.n.01', 'synonyms': ['milkshake'], 'id': 691, 'def': 'frothy drink of milk and flavoring and sometimes fruit or ice cream', 'name': 'milkshake'}, {'frequency': 'f', 'synset': 'minivan.n.01', 'synonyms': ['minivan'], 'id': 692, 'def': 'a small box-shaped passenger van', 'name': 'minivan'}, {'frequency': 'r', 'synset': 'mint.n.05', 'synonyms': ['mint_candy'], 'id': 693, 'def': 'a candy that is flavored with a mint oil', 'name': 'mint_candy'}, {'frequency': 'f', 'synset': 'mirror.n.01', 'synonyms': ['mirror'], 'id': 694, 'def': 'polished surface that forms images by reflecting light', 'name': 'mirror'}, {'frequency': 'c', 'synset': 'mitten.n.01', 'synonyms': ['mitten'], 'id': 695, 'def': 'glove that encases the thumb separately and the other four fingers together', 'name': 'mitten'}, {'frequency': 'c', 'synset': 'mixer.n.04', 'synonyms': ['mixer_(kitchen_tool)', 'stand_mixer'], 'id': 696, 'def': 'a kitchen utensil that is used for mixing foods', 'name': 'mixer_(kitchen_tool)'}, {'frequency': 'c', 'synset': 'money.n.03', 'synonyms': ['money'], 'id': 697, 'def': 'the official currency issued by a government or national bank', 'name': 'money'}, {'frequency': 'f', 'synset': 'monitor.n.04', 'synonyms': ['monitor_(computer_equipment) computer_monitor'], 'id': 698, 'def': 'a computer monitor', 'name': 'monitor_(computer_equipment) computer_monitor'}, {'frequency': 'c', 'synset': 'monkey.n.01', 'synonyms': ['monkey'], 'id': 699, 'def': 'any of various long-tailed primates', 'name': 'monkey'}, {'frequency': 'f', 'synset': 'motor.n.01', 'synonyms': ['motor'], 'id': 700, 'def': 'machine that converts other forms of energy into mechanical energy and so imparts motion', 'name': 'motor'}, {'frequency': 'f', 'synset': 'motor_scooter.n.01', 'synonyms': ['motor_scooter', 'scooter'], 'id': 701, 'def': 'a wheeled vehicle with small wheels and a low-powered engine', 'name': 'motor_scooter'}, {'frequency': 'r', 'synset': 'motor_vehicle.n.01', 'synonyms': ['motor_vehicle', 'automotive_vehicle'], 'id': 702, 'def': 'a self-propelled wheeled vehicle that does not run on rails', 'name': 'motor_vehicle'}, {'frequency': 'f', 'synset': 'motorcycle.n.01', 'synonyms': ['motorcycle'], 'id': 703, 'def': 'a motor vehicle with two wheels and a strong frame', 'name': 'motorcycle'}, {'frequency': 'f', 'synset': 'mound.n.01', 'synonyms': ['mound_(baseball)', "pitcher's_mound"], 'id': 704, 'def': '(baseball) the slight elevation on which the pitcher stands', 'name': 'mound_(baseball)'}, {'frequency': 'f', 'synset': 'mouse.n.04', 'synonyms': ['mouse_(computer_equipment)', 'computer_mouse'], 'id': 705, 'def': 'a computer input device that controls an on-screen pointer (does not include trackpads / touchpads)', 'name': 'mouse_(computer_equipment)'}, {'frequency': 'f', 'synset': 'mousepad.n.01', 'synonyms': ['mousepad'], 'id': 706, 'def': 'a small portable pad that provides an operating surface for a computer mouse', 'name': 'mousepad'}, {'frequency': 'c', 'synset': 'muffin.n.01', 'synonyms': ['muffin'], 'id': 707, 'def': 'a sweet quick bread baked in a cup-shaped pan', 'name': 'muffin'}, {'frequency': 'f', 'synset': 'mug.n.04', 'synonyms': ['mug'], 'id': 708, 'def': 'with handle and usually cylindrical', 'name': 'mug'}, {'frequency': 'f', 'synset': 'mushroom.n.02', 'synonyms': ['mushroom'], 'id': 709, 'def': 'a common mushroom', 'name': 'mushroom'}, {'frequency': 'r', 'synset': 'music_stool.n.01', 'synonyms': ['music_stool', 'piano_stool'], 'id': 710, 'def': 'a stool for piano players; usually adjustable in height', 'name': 'music_stool'}, {'frequency': 'c', 'synset': 'musical_instrument.n.01', 'synonyms': ['musical_instrument', 'instrument_(musical)'], 'id': 711, 'def': 'any of various devices or contrivances that can be used to produce musical tones or sounds', 'name': 'musical_instrument'}, {'frequency': 'r', 'synset': 'nailfile.n.01', 'synonyms': ['nailfile'], 'id': 712, 'def': 'a small flat file for shaping the nails', 'name': 'nailfile'}, {'frequency': 'f', 'synset': 'napkin.n.01', 'synonyms': ['napkin', 'table_napkin', 'serviette'], 'id': 713, 'def': 'a small piece of table linen or paper that is used to wipe the mouth and to cover the lap in order to protect clothing', 'name': 'napkin'}, {'frequency': 'r', 'synset': 'neckerchief.n.01', 'synonyms': ['neckerchief'], 'id': 714, 'def': 'a kerchief worn around the neck', 'name': 'neckerchief'}, {'frequency': 'f', 'synset': 'necklace.n.01', 'synonyms': ['necklace'], 'id': 715, 'def': 'jewelry consisting of a cord or chain (often bearing gems) worn about the neck as an ornament', 'name': 'necklace'}, {'frequency': 'f', 'synset': 'necktie.n.01', 'synonyms': ['necktie', 'tie_(necktie)'], 'id': 716, 'def': 'neckwear consisting of a long narrow piece of material worn under a collar and tied in knot at the front', 'name': 'necktie'}, {'frequency': 'c', 'synset': 'needle.n.03', 'synonyms': ['needle'], 'id': 717, 'def': 'a sharp pointed implement (usually metal)', 'name': 'needle'}, {'frequency': 'c', 'synset': 'nest.n.01', 'synonyms': ['nest'], 'id': 718, 'def': 'a structure in which animals lay eggs or give birth to their young', 'name': 'nest'}, {'frequency': 'f', 'synset': 'newspaper.n.01', 'synonyms': ['newspaper', 'paper_(newspaper)'], 'id': 719, 'def': 'a daily or weekly publication on folded sheets containing news, articles, and advertisements', 'name': 'newspaper'}, {'frequency': 'c', 'synset': 'newsstand.n.01', 'synonyms': ['newsstand'], 'id': 720, 'def': 'a stall where newspapers and other periodicals are sold', 'name': 'newsstand'}, {'frequency': 'c', 'synset': 'nightwear.n.01', 'synonyms': ['nightshirt', 'nightwear', 'sleepwear', 'nightclothes'], 'id': 721, 'def': 'garments designed to be worn in bed', 'name': 'nightshirt'}, {'frequency': 'r', 'synset': 'nosebag.n.01', 'synonyms': ['nosebag_(for_animals)', 'feedbag'], 'id': 722, 'def': 'a canvas bag that is used to feed an animal (such as a horse); covers the muzzle and fastens at the top of the head', 'name': 'nosebag_(for_animals)'}, {'frequency': 'c', 'synset': 'noseband.n.01', 'synonyms': ['noseband_(for_animals)', 'nosepiece_(for_animals)'], 'id': 723, 'def': "a strap that is the part of a bridle that goes over the animal's nose", 'name': 'noseband_(for_animals)'}, {'frequency': 'f', 'synset': 'notebook.n.01', 'synonyms': ['notebook'], 'id': 724, 'def': 'a book with blank pages for recording notes or memoranda', 'name': 'notebook'}, {'frequency': 'c', 'synset': 'notepad.n.01', 'synonyms': ['notepad'], 'id': 725, 'def': 'a pad of paper for keeping notes', 'name': 'notepad'}, {'frequency': 'f', 'synset': 'nut.n.03', 'synonyms': ['nut'], 'id': 726, 'def': 'a small metal block (usually square or hexagonal) with internal screw thread to be fitted onto a bolt', 'name': 'nut'}, {'frequency': 'r', 'synset': 'nutcracker.n.01', 'synonyms': ['nutcracker'], 'id': 727, 'def': 'a hand tool used to crack nuts open', 'name': 'nutcracker'}, {'frequency': 'f', 'synset': 'oar.n.01', 'synonyms': ['oar'], 'id': 728, 'def': 'an implement used to propel or steer a boat', 'name': 'oar'}, {'frequency': 'r', 'synset': 'octopus.n.01', 'synonyms': ['octopus_(food)'], 'id': 729, 'def': 'tentacles of octopus prepared as food', 'name': 'octopus_(food)'}, {'frequency': 'r', 'synset': 'octopus.n.02', 'synonyms': ['octopus_(animal)'], 'id': 730, 'def': 'bottom-living cephalopod having a soft oval body with eight long tentacles', 'name': 'octopus_(animal)'}, {'frequency': 'c', 'synset': 'oil_lamp.n.01', 'synonyms': ['oil_lamp', 'kerosene_lamp', 'kerosine_lamp'], 'id': 731, 'def': 'a lamp that burns oil (as kerosine) for light', 'name': 'oil_lamp'}, {'frequency': 'c', 'synset': 'olive_oil.n.01', 'synonyms': ['olive_oil'], 'id': 732, 'def': 'oil from olives', 'name': 'olive_oil'}, {'frequency': 'r', 'synset': 'omelet.n.01', 'synonyms': ['omelet', 'omelette'], 'id': 733, 'def': 'beaten eggs cooked until just set; may be folded around e.g. ham or cheese or jelly', 'name': 'omelet'}, {'frequency': 'f', 'synset': 'onion.n.01', 'synonyms': ['onion'], 'id': 734, 'def': 'the bulb of an onion plant', 'name': 'onion'}, {'frequency': 'f', 'synset': 'orange.n.01', 'synonyms': ['orange_(fruit)'], 'id': 735, 'def': 'orange (FRUIT of an orange tree)', 'name': 'orange_(fruit)'}, {'frequency': 'c', 'synset': 'orange_juice.n.01', 'synonyms': ['orange_juice'], 'id': 736, 'def': 'bottled or freshly squeezed juice of oranges', 'name': 'orange_juice'}, {'frequency': 'c', 'synset': 'ostrich.n.02', 'synonyms': ['ostrich'], 'id': 737, 'def': 'fast-running African flightless bird with two-toed feet; largest living bird', 'name': 'ostrich'}, {'frequency': 'f', 'synset': 'ottoman.n.03', 'synonyms': ['ottoman', 'pouf', 'pouffe', 'hassock'], 'id': 738, 'def': 'a thick standalone cushion used as a seat or footrest, often next to a chair', 'name': 'ottoman'}, {'frequency': 'f', 'synset': 'oven.n.01', 'synonyms': ['oven'], 'id': 739, 'def': 'kitchen appliance used for baking or roasting', 'name': 'oven'}, {'frequency': 'c', 'synset': 'overall.n.01', 'synonyms': ['overalls_(clothing)'], 'id': 740, 'def': 'work clothing consisting of denim trousers usually with a bib and shoulder straps', 'name': 'overalls_(clothing)'}, {'frequency': 'c', 'synset': 'owl.n.01', 'synonyms': ['owl'], 'id': 741, 'def': 'nocturnal bird of prey with hawk-like beak and claws and large head with front-facing eyes', 'name': 'owl'}, {'frequency': 'c', 'synset': 'packet.n.03', 'synonyms': ['packet'], 'id': 742, 'def': 'a small package or bundle', 'name': 'packet'}, {'frequency': 'r', 'synset': 'pad.n.03', 'synonyms': ['inkpad', 'inking_pad', 'stamp_pad'], 'id': 743, 'def': 'absorbent material saturated with ink used to transfer ink evenly to a rubber stamp', 'name': 'inkpad'}, {'frequency': 'c', 'synset': 'pad.n.04', 'synonyms': ['pad'], 'id': 744, 'def': 'mostly arm/knee pads labeled', 'name': 'pad'}, {'frequency': 'f', 'synset': 'paddle.n.04', 'synonyms': ['paddle', 'boat_paddle'], 'id': 745, 'def': 'a short light oar used without an oarlock to propel a canoe or small boat', 'name': 'paddle'}, {'frequency': 'c', 'synset': 'padlock.n.01', 'synonyms': ['padlock'], 'id': 746, 'def': 'a detachable, portable lock', 'name': 'padlock'}, {'frequency': 'c', 'synset': 'paintbrush.n.01', 'synonyms': ['paintbrush'], 'id': 747, 'def': 'a brush used as an applicator to apply paint', 'name': 'paintbrush'}, {'frequency': 'f', 'synset': 'painting.n.01', 'synonyms': ['painting'], 'id': 748, 'def': 'graphic art consisting of an artistic composition made by applying paints to a surface', 'name': 'painting'}, {'frequency': 'f', 'synset': 'pajama.n.02', 'synonyms': ['pajamas', 'pyjamas'], 'id': 749, 'def': 'loose-fitting nightclothes worn for sleeping or lounging', 'name': 'pajamas'}, {'frequency': 'c', 'synset': 'palette.n.02', 'synonyms': ['palette', 'pallet'], 'id': 750, 'def': 'board that provides a flat surface on which artists mix paints and the range of colors used', 'name': 'palette'}, {'frequency': 'f', 'synset': 'pan.n.01', 'synonyms': ['pan_(for_cooking)', 'cooking_pan'], 'id': 751, 'def': 'cooking utensil consisting of a wide metal vessel', 'name': 'pan_(for_cooking)'}, {'frequency': 'r', 'synset': 'pan.n.03', 'synonyms': ['pan_(metal_container)'], 'id': 752, 'def': 'shallow container made of metal', 'name': 'pan_(metal_container)'}, {'frequency': 'c', 'synset': 'pancake.n.01', 'synonyms': ['pancake'], 'id': 753, 'def': 'a flat cake of thin batter fried on both sides on a griddle', 'name': 'pancake'}, {'frequency': 'r', 'synset': 'pantyhose.n.01', 'synonyms': ['pantyhose'], 'id': 754, 'def': "a woman's tights consisting of underpants and stockings", 'name': 'pantyhose'}, {'frequency': 'r', 'synset': 'papaya.n.02', 'synonyms': ['papaya'], 'id': 755, 'def': 'large oval melon-like tropical fruit with yellowish flesh', 'name': 'papaya'}, {'frequency': 'f', 'synset': 'paper_plate.n.01', 'synonyms': ['paper_plate'], 'id': 756, 'def': 'a disposable plate made of cardboard', 'name': 'paper_plate'}, {'frequency': 'f', 'synset': 'paper_towel.n.01', 'synonyms': ['paper_towel'], 'id': 757, 'def': 'a disposable towel made of absorbent paper', 'name': 'paper_towel'}, {'frequency': 'r', 'synset': 'paperback_book.n.01', 'synonyms': ['paperback_book', 'paper-back_book', 'softback_book', 'soft-cover_book'], 'id': 758, 'def': 'a book with paper covers', 'name': 'paperback_book'}, {'frequency': 'r', 'synset': 'paperweight.n.01', 'synonyms': ['paperweight'], 'id': 759, 'def': 'a weight used to hold down a stack of papers', 'name': 'paperweight'}, {'frequency': 'c', 'synset': 'parachute.n.01', 'synonyms': ['parachute'], 'id': 760, 'def': 'rescue equipment consisting of a device that fills with air and retards your fall', 'name': 'parachute'}, {'frequency': 'c', 'synset': 'parakeet.n.01', 'synonyms': ['parakeet', 'parrakeet', 'parroket', 'paraquet', 'paroquet', 'parroquet'], 'id': 761, 'def': 'any of numerous small slender long-tailed parrots', 'name': 'parakeet'}, {'frequency': 'c', 'synset': 'parasail.n.01', 'synonyms': ['parasail_(sports)'], 'id': 762, 'def': 'parachute that will lift a person up into the air when it is towed by a motorboat or a car', 'name': 'parasail_(sports)'}, {'frequency': 'c', 'synset': 'parasol.n.01', 'synonyms': ['parasol', 'sunshade'], 'id': 763, 'def': 'a handheld collapsible source of shade', 'name': 'parasol'}, {'frequency': 'r', 'synset': 'parchment.n.01', 'synonyms': ['parchment'], 'id': 764, 'def': 'a superior paper resembling sheepskin', 'name': 'parchment'}, {'frequency': 'c', 'synset': 'parka.n.01', 'synonyms': ['parka', 'anorak'], 'id': 765, 'def': "a kind of heavy jacket (`windcheater' is a British term)", 'name': 'parka'}, {'frequency': 'f', 'synset': 'parking_meter.n.01', 'synonyms': ['parking_meter'], 'id': 766, 'def': 'a coin-operated timer located next to a parking space', 'name': 'parking_meter'}, {'frequency': 'c', 'synset': 'parrot.n.01', 'synonyms': ['parrot'], 'id': 767, 'def': 'usually brightly colored tropical birds with short hooked beaks and the ability to mimic sounds', 'name': 'parrot'}, {'frequency': 'c', 'synset': 'passenger_car.n.01', 'synonyms': ['passenger_car_(part_of_a_train)', 'coach_(part_of_a_train)'], 'id': 768, 'def': 'a railcar where passengers ride', 'name': 'passenger_car_(part_of_a_train)'}, {'frequency': 'r', 'synset': 'passenger_ship.n.01', 'synonyms': ['passenger_ship'], 'id': 769, 'def': 'a ship built to carry passengers', 'name': 'passenger_ship'}, {'frequency': 'c', 'synset': 'passport.n.02', 'synonyms': ['passport'], 'id': 770, 'def': 'a document issued by a country to a citizen allowing that person to travel abroad and re-enter the home country', 'name': 'passport'}, {'frequency': 'f', 'synset': 'pastry.n.02', 'synonyms': ['pastry'], 'id': 771, 'def': 'any of various baked foods made of dough or batter', 'name': 'pastry'}, {'frequency': 'r', 'synset': 'patty.n.01', 'synonyms': ['patty_(food)'], 'id': 772, 'def': 'small flat mass of chopped food', 'name': 'patty_(food)'}, {'frequency': 'c', 'synset': 'pea.n.01', 'synonyms': ['pea_(food)'], 'id': 773, 'def': 'seed of a pea plant used for food', 'name': 'pea_(food)'}, {'frequency': 'c', 'synset': 'peach.n.03', 'synonyms': ['peach'], 'id': 774, 'def': 'downy juicy fruit with sweet yellowish or whitish flesh', 'name': 'peach'}, {'frequency': 'c', 'synset': 'peanut_butter.n.01', 'synonyms': ['peanut_butter'], 'id': 775, 'def': 'a spread made from ground peanuts', 'name': 'peanut_butter'}, {'frequency': 'f', 'synset': 'pear.n.01', 'synonyms': ['pear'], 'id': 776, 'def': 'sweet juicy gritty-textured fruit available in many varieties', 'name': 'pear'}, {'frequency': 'c', 'synset': 'peeler.n.03', 'synonyms': ['peeler_(tool_for_fruit_and_vegetables)'], 'id': 777, 'def': 'a device for peeling vegetables or fruits', 'name': 'peeler_(tool_for_fruit_and_vegetables)'}, {'frequency': 'r', 'synset': 'peg.n.04', 'synonyms': ['wooden_leg', 'pegleg'], 'id': 778, 'def': 'a prosthesis that replaces a missing leg', 'name': 'wooden_leg'}, {'frequency': 'r', 'synset': 'pegboard.n.01', 'synonyms': ['pegboard'], 'id': 779, 'def': 'a board perforated with regularly spaced holes into which pegs can be fitted', 'name': 'pegboard'}, {'frequency': 'c', 'synset': 'pelican.n.01', 'synonyms': ['pelican'], 'id': 780, 'def': 'large long-winged warm-water seabird having a large bill with a distensible pouch for fish', 'name': 'pelican'}, {'frequency': 'f', 'synset': 'pen.n.01', 'synonyms': ['pen'], 'id': 781, 'def': 'a writing implement with a point from which ink flows', 'name': 'pen'}, {'frequency': 'f', 'synset': 'pencil.n.01', 'synonyms': ['pencil'], 'id': 782, 'def': 'a thin cylindrical pointed writing implement made of wood and graphite', 'name': 'pencil'}, {'frequency': 'r', 'synset': 'pencil_box.n.01', 'synonyms': ['pencil_box', 'pencil_case'], 'id': 783, 'def': 'a box for holding pencils', 'name': 'pencil_box'}, {'frequency': 'r', 'synset': 'pencil_sharpener.n.01', 'synonyms': ['pencil_sharpener'], 'id': 784, 'def': 'a rotary implement for sharpening the point on pencils', 'name': 'pencil_sharpener'}, {'frequency': 'r', 'synset': 'pendulum.n.01', 'synonyms': ['pendulum'], 'id': 785, 'def': 'an apparatus consisting of an object mounted so that it swings freely under the influence of gravity', 'name': 'pendulum'}, {'frequency': 'c', 'synset': 'penguin.n.01', 'synonyms': ['penguin'], 'id': 786, 'def': 'short-legged flightless birds of cold southern regions having webbed feet and wings modified as flippers', 'name': 'penguin'}, {'frequency': 'r', 'synset': 'pennant.n.02', 'synonyms': ['pennant'], 'id': 787, 'def': 'a flag longer than it is wide (and often tapering)', 'name': 'pennant'}, {'frequency': 'r', 'synset': 'penny.n.02', 'synonyms': ['penny_(coin)'], 'id': 788, 'def': 'a coin worth one-hundredth of the value of the basic unit', 'name': 'penny_(coin)'}, {'frequency': 'f', 'synset': 'pepper.n.03', 'synonyms': ['pepper', 'peppercorn'], 'id': 789, 'def': 'pungent seasoning from the berry of the common pepper plant; whole or ground', 'name': 'pepper'}, {'frequency': 'c', 'synset': 'pepper_mill.n.01', 'synonyms': ['pepper_mill', 'pepper_grinder'], 'id': 790, 'def': 'a mill for grinding pepper', 'name': 'pepper_mill'}, {'frequency': 'c', 'synset': 'perfume.n.02', 'synonyms': ['perfume'], 'id': 791, 'def': 'a toiletry that emits and diffuses a fragrant odor', 'name': 'perfume'}, {'frequency': 'r', 'synset': 'persimmon.n.02', 'synonyms': ['persimmon'], 'id': 792, 'def': 'orange fruit resembling a plum; edible when fully ripe', 'name': 'persimmon'}, {'frequency': 'f', 'synset': 'person.n.01', 'synonyms': ['person', 'baby', 'child', 'boy', 'girl', 'man', 'woman', 'human'], 'id': 793, 'def': 'a human being', 'name': 'person'}, {'frequency': 'c', 'synset': 'pet.n.01', 'synonyms': ['pet'], 'id': 794, 'def': 'a domesticated animal kept for companionship or amusement', 'name': 'pet'}, {'frequency': 'c', 'synset': 'pew.n.01', 'synonyms': ['pew_(church_bench)', 'church_bench'], 'id': 795, 'def': 'long bench with backs; used in church by the congregation', 'name': 'pew_(church_bench)'}, {'frequency': 'r', 'synset': 'phonebook.n.01', 'synonyms': ['phonebook', 'telephone_book', 'telephone_directory'], 'id': 796, 'def': 'a directory containing an alphabetical list of telephone subscribers and their telephone numbers', 'name': 'phonebook'}, {'frequency': 'c', 'synset': 'phonograph_record.n.01', 'synonyms': ['phonograph_record', 'phonograph_recording', 'record_(phonograph_recording)'], 'id': 797, 'def': 'sound recording consisting of a typically black disk with a continuous groove', 'name': 'phonograph_record'}, {'frequency': 'f', 'synset': 'piano.n.01', 'synonyms': ['piano'], 'id': 798, 'def': 'a keyboard instrument that is played by depressing keys that cause hammers to strike tuned strings and produce sounds', 'name': 'piano'}, {'frequency': 'f', 'synset': 'pickle.n.01', 'synonyms': ['pickle'], 'id': 799, 'def': 'vegetables (especially cucumbers) preserved in brine or vinegar', 'name': 'pickle'}, {'frequency': 'f', 'synset': 'pickup.n.01', 'synonyms': ['pickup_truck'], 'id': 800, 'def': 'a light truck with an open body and low sides and a tailboard', 'name': 'pickup_truck'}, {'frequency': 'c', 'synset': 'pie.n.01', 'synonyms': ['pie'], 'id': 801, 'def': 'dish baked in pastry-lined pan often with a pastry top', 'name': 'pie'}, {'frequency': 'c', 'synset': 'pigeon.n.01', 'synonyms': ['pigeon'], 'id': 802, 'def': 'wild and domesticated birds having a heavy body and short legs', 'name': 'pigeon'}, {'frequency': 'r', 'synset': 'piggy_bank.n.01', 'synonyms': ['piggy_bank', 'penny_bank'], 'id': 803, 'def': "a child's coin bank (often shaped like a pig)", 'name': 'piggy_bank'}, {'frequency': 'f', 'synset': 'pillow.n.01', 'synonyms': ['pillow'], 'id': 804, 'def': 'a cushion to support the head of a sleeping person', 'name': 'pillow'}, {'frequency': 'r', 'synset': 'pin.n.09', 'synonyms': ['pin_(non_jewelry)'], 'id': 805, 'def': 'a small slender (often pointed) piece of wood or metal used to support or fasten or attach things', 'name': 'pin_(non_jewelry)'}, {'frequency': 'f', 'synset': 'pineapple.n.02', 'synonyms': ['pineapple'], 'id': 806, 'def': 'large sweet fleshy tropical fruit with a tuft of stiff leaves', 'name': 'pineapple'}, {'frequency': 'c', 'synset': 'pinecone.n.01', 'synonyms': ['pinecone'], 'id': 807, 'def': 'the seed-producing cone of a pine tree', 'name': 'pinecone'}, {'frequency': 'r', 'synset': 'ping-pong_ball.n.01', 'synonyms': ['ping-pong_ball'], 'id': 808, 'def': 'light hollow ball used in playing table tennis', 'name': 'ping-pong_ball'}, {'frequency': 'r', 'synset': 'pinwheel.n.03', 'synonyms': ['pinwheel'], 'id': 809, 'def': 'a toy consisting of vanes of colored paper or plastic that is pinned to a stick and spins when it is pointed into the wind', 'name': 'pinwheel'}, {'frequency': 'r', 'synset': 'pipe.n.01', 'synonyms': ['tobacco_pipe'], 'id': 810, 'def': 'a tube with a small bowl at one end; used for smoking tobacco', 'name': 'tobacco_pipe'}, {'frequency': 'f', 'synset': 'pipe.n.02', 'synonyms': ['pipe', 'piping'], 'id': 811, 'def': 'a long tube made of metal or plastic that is used to carry water or oil or gas etc.', 'name': 'pipe'}, {'frequency': 'r', 'synset': 'pistol.n.01', 'synonyms': ['pistol', 'handgun'], 'id': 812, 'def': 'a firearm that is held and fired with one hand', 'name': 'pistol'}, {'frequency': 'c', 'synset': 'pita.n.01', 'synonyms': ['pita_(bread)', 'pocket_bread'], 'id': 813, 'def': 'usually small round bread that can open into a pocket for filling', 'name': 'pita_(bread)'}, {'frequency': 'f', 'synset': 'pitcher.n.02', 'synonyms': ['pitcher_(vessel_for_liquid)', 'ewer'], 'id': 814, 'def': 'an open vessel with a handle and a spout for pouring', 'name': 'pitcher_(vessel_for_liquid)'}, {'frequency': 'r', 'synset': 'pitchfork.n.01', 'synonyms': ['pitchfork'], 'id': 815, 'def': 'a long-handled hand tool with sharp widely spaced prongs for lifting and pitching hay', 'name': 'pitchfork'}, {'frequency': 'f', 'synset': 'pizza.n.01', 'synonyms': ['pizza'], 'id': 816, 'def': 'Italian open pie made of thin bread dough spread with a spiced mixture of e.g. tomato sauce and cheese', 'name': 'pizza'}, {'frequency': 'f', 'synset': 'place_mat.n.01', 'synonyms': ['place_mat'], 'id': 817, 'def': 'a mat placed on a table for an individual place setting', 'name': 'place_mat'}, {'frequency': 'f', 'synset': 'plate.n.04', 'synonyms': ['plate'], 'id': 818, 'def': 'dish on which food is served or from which food is eaten', 'name': 'plate'}, {'frequency': 'c', 'synset': 'platter.n.01', 'synonyms': ['platter'], 'id': 819, 'def': 'a large shallow dish used for serving food', 'name': 'platter'}, {'frequency': 'r', 'synset': 'playpen.n.01', 'synonyms': ['playpen'], 'id': 820, 'def': 'a portable enclosure in which babies may be left to play', 'name': 'playpen'}, {'frequency': 'c', 'synset': 'pliers.n.01', 'synonyms': ['pliers', 'plyers'], 'id': 821, 'def': 'a gripping hand tool with two hinged arms and (usually) serrated jaws', 'name': 'pliers'}, {'frequency': 'r', 'synset': 'plow.n.01', 'synonyms': ['plow_(farm_equipment)', 'plough_(farm_equipment)'], 'id': 822, 'def': 'a farm tool having one or more heavy blades to break the soil and cut a furrow prior to sowing', 'name': 'plow_(farm_equipment)'}, {'frequency': 'r', 'synset': 'plume.n.02', 'synonyms': ['plume'], 'id': 823, 'def': 'a feather or cluster of feathers worn as an ornament', 'name': 'plume'}, {'frequency': 'r', 'synset': 'pocket_watch.n.01', 'synonyms': ['pocket_watch'], 'id': 824, 'def': 'a watch that is carried in a small watch pocket', 'name': 'pocket_watch'}, {'frequency': 'c', 'synset': 'pocketknife.n.01', 'synonyms': ['pocketknife'], 'id': 825, 'def': 'a knife with a blade that folds into the handle; suitable for carrying in the pocket', 'name': 'pocketknife'}, {'frequency': 'c', 'synset': 'poker.n.01', 'synonyms': ['poker_(fire_stirring_tool)', 'stove_poker', 'fire_hook'], 'id': 826, 'def': 'fire iron consisting of a metal rod with a handle; used to stir a fire', 'name': 'poker_(fire_stirring_tool)'}, {'frequency': 'f', 'synset': 'pole.n.01', 'synonyms': ['pole', 'post'], 'id': 827, 'def': 'a long (usually round) rod of wood or metal or plastic', 'name': 'pole'}, {'frequency': 'f', 'synset': 'polo_shirt.n.01', 'synonyms': ['polo_shirt', 'sport_shirt'], 'id': 828, 'def': 'a shirt with short sleeves designed for comfort and casual wear', 'name': 'polo_shirt'}, {'frequency': 'r', 'synset': 'poncho.n.01', 'synonyms': ['poncho'], 'id': 829, 'def': 'a blanket-like cloak with a hole in the center for the head', 'name': 'poncho'}, {'frequency': 'c', 'synset': 'pony.n.05', 'synonyms': ['pony'], 'id': 830, 'def': 'any of various breeds of small gentle horses usually less than five feet high at the shoulder', 'name': 'pony'}, {'frequency': 'r', 'synset': 'pool_table.n.01', 'synonyms': ['pool_table', 'billiard_table', 'snooker_table'], 'id': 831, 'def': 'game equipment consisting of a heavy table on which pool is played', 'name': 'pool_table'}, {'frequency': 'f', 'synset': 'pop.n.02', 'synonyms': ['pop_(soda)', 'soda_(pop)', 'tonic', 'soft_drink'], 'id': 832, 'def': 'a sweet drink containing carbonated water and flavoring', 'name': 'pop_(soda)'}, {'frequency': 'c', 'synset': 'postbox.n.01', 'synonyms': ['postbox_(public)', 'mailbox_(public)'], 'id': 833, 'def': 'public box for deposit of mail', 'name': 'postbox_(public)'}, {'frequency': 'c', 'synset': 'postcard.n.01', 'synonyms': ['postcard', 'postal_card', 'mailing-card'], 'id': 834, 'def': 'a card for sending messages by post without an envelope', 'name': 'postcard'}, {'frequency': 'f', 'synset': 'poster.n.01', 'synonyms': ['poster', 'placard'], 'id': 835, 'def': 'a sign posted in a public place as an advertisement', 'name': 'poster'}, {'frequency': 'f', 'synset': 'pot.n.01', 'synonyms': ['pot'], 'id': 836, 'def': 'metal or earthenware cooking vessel that is usually round and deep; often has a handle and lid', 'name': 'pot'}, {'frequency': 'f', 'synset': 'pot.n.04', 'synonyms': ['flowerpot'], 'id': 837, 'def': 'a container in which plants are cultivated', 'name': 'flowerpot'}, {'frequency': 'f', 'synset': 'potato.n.01', 'synonyms': ['potato'], 'id': 838, 'def': 'an edible tuber native to South America', 'name': 'potato'}, {'frequency': 'c', 'synset': 'potholder.n.01', 'synonyms': ['potholder'], 'id': 839, 'def': 'an insulated pad for holding hot pots', 'name': 'potholder'}, {'frequency': 'c', 'synset': 'pottery.n.01', 'synonyms': ['pottery', 'clayware'], 'id': 840, 'def': 'ceramic ware made from clay and baked in a kiln', 'name': 'pottery'}, {'frequency': 'c', 'synset': 'pouch.n.01', 'synonyms': ['pouch'], 'id': 841, 'def': 'a small or medium size container for holding or carrying things', 'name': 'pouch'}, {'frequency': 'c', 'synset': 'power_shovel.n.01', 'synonyms': ['power_shovel', 'excavator', 'digger'], 'id': 842, 'def': 'a machine for excavating', 'name': 'power_shovel'}, {'frequency': 'c', 'synset': 'prawn.n.01', 'synonyms': ['prawn', 'shrimp'], 'id': 843, 'def': 'any of various edible decapod crustaceans', 'name': 'prawn'}, {'frequency': 'c', 'synset': 'pretzel.n.01', 'synonyms': ['pretzel'], 'id': 844, 'def': 'glazed and salted cracker typically in the shape of a loose knot', 'name': 'pretzel'}, {'frequency': 'f', 'synset': 'printer.n.03', 'synonyms': ['printer', 'printing_machine'], 'id': 845, 'def': 'a machine that prints', 'name': 'printer'}, {'frequency': 'c', 'synset': 'projectile.n.01', 'synonyms': ['projectile_(weapon)', 'missile'], 'id': 846, 'def': 'a weapon that is forcibly thrown or projected at a targets', 'name': 'projectile_(weapon)'}, {'frequency': 'c', 'synset': 'projector.n.02', 'synonyms': ['projector'], 'id': 847, 'def': 'an optical instrument that projects an enlarged image onto a screen', 'name': 'projector'}, {'frequency': 'f', 'synset': 'propeller.n.01', 'synonyms': ['propeller', 'propellor'], 'id': 848, 'def': 'a mechanical device that rotates to push against air or water', 'name': 'propeller'}, {'frequency': 'r', 'synset': 'prune.n.01', 'synonyms': ['prune'], 'id': 849, 'def': 'dried plum', 'name': 'prune'}, {'frequency': 'r', 'synset': 'pudding.n.01', 'synonyms': ['pudding'], 'id': 850, 'def': 'any of various soft thick unsweetened baked dishes', 'name': 'pudding'}, {'frequency': 'r', 'synset': 'puffer.n.02', 'synonyms': ['puffer_(fish)', 'pufferfish', 'blowfish', 'globefish'], 'id': 851, 'def': 'fishes whose elongated spiny body can inflate itself with water or air to form a globe', 'name': 'puffer_(fish)'}, {'frequency': 'r', 'synset': 'puffin.n.01', 'synonyms': ['puffin'], 'id': 852, 'def': 'seabirds having short necks and brightly colored compressed bills', 'name': 'puffin'}, {'frequency': 'r', 'synset': 'pug.n.01', 'synonyms': ['pug-dog'], 'id': 853, 'def': 'small compact smooth-coated breed of Asiatic origin having a tightly curled tail and broad flat wrinkled muzzle', 'name': 'pug-dog'}, {'frequency': 'c', 'synset': 'pumpkin.n.02', 'synonyms': ['pumpkin'], 'id': 854, 'def': 'usually large pulpy deep-yellow round fruit of the squash family maturing in late summer or early autumn', 'name': 'pumpkin'}, {'frequency': 'r', 'synset': 'punch.n.03', 'synonyms': ['puncher'], 'id': 855, 'def': 'a tool for making holes or indentations', 'name': 'puncher'}, {'frequency': 'r', 'synset': 'puppet.n.01', 'synonyms': ['puppet', 'marionette'], 'id': 856, 'def': 'a small figure of a person operated from above with strings by a puppeteer', 'name': 'puppet'}, {'frequency': 'c', 'synset': 'puppy.n.01', 'synonyms': ['puppy'], 'id': 857, 'def': 'a young dog', 'name': 'puppy'}, {'frequency': 'r', 'synset': 'quesadilla.n.01', 'synonyms': ['quesadilla'], 'id': 858, 'def': 'a tortilla that is filled with cheese and heated', 'name': 'quesadilla'}, {'frequency': 'r', 'synset': 'quiche.n.02', 'synonyms': ['quiche'], 'id': 859, 'def': 'a tart filled with rich unsweetened custard; often contains other ingredients (as cheese or ham or seafood or vegetables)', 'name': 'quiche'}, {'frequency': 'f', 'synset': 'quilt.n.01', 'synonyms': ['quilt', 'comforter'], 'id': 860, 'def': 'bedding made of two layers of cloth filled with stuffing and stitched together', 'name': 'quilt'}, {'frequency': 'c', 'synset': 'rabbit.n.01', 'synonyms': ['rabbit'], 'id': 861, 'def': 'any of various burrowing animals of the family Leporidae having long ears and short tails', 'name': 'rabbit'}, {'frequency': 'r', 'synset': 'racer.n.02', 'synonyms': ['race_car', 'racing_car'], 'id': 862, 'def': 'a fast car that competes in races', 'name': 'race_car'}, {'frequency': 'c', 'synset': 'racket.n.04', 'synonyms': ['racket', 'racquet'], 'id': 863, 'def': 'a sports implement used to strike a ball in various games', 'name': 'racket'}, {'frequency': 'r', 'synset': 'radar.n.01', 'synonyms': ['radar'], 'id': 864, 'def': 'measuring instrument in which the echo of a pulse of microwave radiation is used to detect and locate distant objects', 'name': 'radar'}, {'frequency': 'f', 'synset': 'radiator.n.03', 'synonyms': ['radiator'], 'id': 865, 'def': 'a mechanism consisting of a metal honeycomb through which hot fluids circulate', 'name': 'radiator'}, {'frequency': 'c', 'synset': 'radio_receiver.n.01', 'synonyms': ['radio_receiver', 'radio_set', 'radio', 'tuner_(radio)'], 'id': 866, 'def': 'an electronic receiver that detects and demodulates and amplifies transmitted radio signals', 'name': 'radio_receiver'}, {'frequency': 'c', 'synset': 'radish.n.03', 'synonyms': ['radish', 'daikon'], 'id': 867, 'def': 'pungent edible root of any of various cultivated radish plants', 'name': 'radish'}, {'frequency': 'c', 'synset': 'raft.n.01', 'synonyms': ['raft'], 'id': 868, 'def': 'a flat float (usually made of logs or planks) that can be used for transport or as a platform for swimmers', 'name': 'raft'}, {'frequency': 'r', 'synset': 'rag_doll.n.01', 'synonyms': ['rag_doll'], 'id': 869, 'def': 'a cloth doll that is stuffed and (usually) painted', 'name': 'rag_doll'}, {'frequency': 'c', 'synset': 'raincoat.n.01', 'synonyms': ['raincoat', 'waterproof_jacket'], 'id': 870, 'def': 'a water-resistant coat', 'name': 'raincoat'}, {'frequency': 'c', 'synset': 'ram.n.05', 'synonyms': ['ram_(animal)'], 'id': 871, 'def': 'uncastrated adult male sheep', 'name': 'ram_(animal)'}, {'frequency': 'c', 'synset': 'raspberry.n.02', 'synonyms': ['raspberry'], 'id': 872, 'def': 'red or black edible aggregate berries usually smaller than the related blackberries', 'name': 'raspberry'}, {'frequency': 'r', 'synset': 'rat.n.01', 'synonyms': ['rat'], 'id': 873, 'def': 'any of various long-tailed rodents similar to but larger than a mouse', 'name': 'rat'}, {'frequency': 'c', 'synset': 'razorblade.n.01', 'synonyms': ['razorblade'], 'id': 874, 'def': 'a blade that has very sharp edge', 'name': 'razorblade'}, {'frequency': 'c', 'synset': 'reamer.n.01', 'synonyms': ['reamer_(juicer)', 'juicer', 'juice_reamer'], 'id': 875, 'def': 'a squeezer with a conical ridged center that is used for squeezing juice from citrus fruit', 'name': 'reamer_(juicer)'}, {'frequency': 'f', 'synset': 'rearview_mirror.n.01', 'synonyms': ['rearview_mirror'], 'id': 876, 'def': 'vehicle mirror (side or rearview)', 'name': 'rearview_mirror'}, {'frequency': 'c', 'synset': 'receipt.n.02', 'synonyms': ['receipt'], 'id': 877, 'def': 'an acknowledgment (usually tangible) that payment has been made', 'name': 'receipt'}, {'frequency': 'c', 'synset': 'recliner.n.01', 'synonyms': ['recliner', 'reclining_chair', 'lounger_(chair)'], 'id': 878, 'def': 'an armchair whose back can be lowered and foot can be raised to allow the sitter to recline in it', 'name': 'recliner'}, {'frequency': 'c', 'synset': 'record_player.n.01', 'synonyms': ['record_player', 'phonograph_(record_player)', 'turntable'], 'id': 879, 'def': 'machine in which rotating records cause a stylus to vibrate and the vibrations are amplified acoustically or electronically', 'name': 'record_player'}, {'frequency': 'f', 'synset': 'reflector.n.01', 'synonyms': ['reflector'], 'id': 880, 'def': 'device that reflects light, radiation, etc.', 'name': 'reflector'}, {'frequency': 'f', 'synset': 'remote_control.n.01', 'synonyms': ['remote_control'], 'id': 881, 'def': 'a device that can be used to control a machine or apparatus from a distance', 'name': 'remote_control'}, {'frequency': 'c', 'synset': 'rhinoceros.n.01', 'synonyms': ['rhinoceros'], 'id': 882, 'def': 'massive powerful herbivorous odd-toed ungulate of southeast Asia and Africa having very thick skin and one or two horns on the snout', 'name': 'rhinoceros'}, {'frequency': 'r', 'synset': 'rib.n.03', 'synonyms': ['rib_(food)'], 'id': 883, 'def': 'cut of meat including one or more ribs', 'name': 'rib_(food)'}, {'frequency': 'c', 'synset': 'rifle.n.01', 'synonyms': ['rifle'], 'id': 884, 'def': 'a shoulder firearm with a long barrel', 'name': 'rifle'}, {'frequency': 'f', 'synset': 'ring.n.08', 'synonyms': ['ring'], 'id': 885, 'def': 'jewelry consisting of a circlet of precious metal (often set with jewels) worn on the finger', 'name': 'ring'}, {'frequency': 'r', 'synset': 'river_boat.n.01', 'synonyms': ['river_boat'], 'id': 886, 'def': 'a boat used on rivers or to ply a river', 'name': 'river_boat'}, {'frequency': 'r', 'synset': 'road_map.n.02', 'synonyms': ['road_map'], 'id': 887, 'def': '(NOT A ROAD) a MAP showing roads (for automobile travel)', 'name': 'road_map'}, {'frequency': 'c', 'synset': 'robe.n.01', 'synonyms': ['robe'], 'id': 888, 'def': 'any loose flowing garment', 'name': 'robe'}, {'frequency': 'c', 'synset': 'rocking_chair.n.01', 'synonyms': ['rocking_chair'], 'id': 889, 'def': 'a chair mounted on rockers', 'name': 'rocking_chair'}, {'frequency': 'r', 'synset': 'rodent.n.01', 'synonyms': ['rodent'], 'id': 890, 'def': 'relatively small placental mammals having a single pair of constantly growing incisor teeth specialized for gnawing', 'name': 'rodent'}, {'frequency': 'r', 'synset': 'roller_skate.n.01', 'synonyms': ['roller_skate'], 'id': 891, 'def': 'a shoe with pairs of rollers (small hard wheels) fixed to the sole', 'name': 'roller_skate'}, {'frequency': 'r', 'synset': 'rollerblade.n.01', 'synonyms': ['Rollerblade'], 'id': 892, 'def': 'an in-line variant of a roller skate', 'name': 'Rollerblade'}, {'frequency': 'c', 'synset': 'rolling_pin.n.01', 'synonyms': ['rolling_pin'], 'id': 893, 'def': 'utensil consisting of a cylinder (usually of wood) with a handle at each end; used to roll out dough', 'name': 'rolling_pin'}, {'frequency': 'r', 'synset': 'root_beer.n.01', 'synonyms': ['root_beer'], 'id': 894, 'def': 'carbonated drink containing extracts of roots and herbs', 'name': 'root_beer'}, {'frequency': 'c', 'synset': 'router.n.02', 'synonyms': ['router_(computer_equipment)'], 'id': 895, 'def': 'a device that forwards data packets between computer networks', 'name': 'router_(computer_equipment)'}, {'frequency': 'f', 'synset': 'rubber_band.n.01', 'synonyms': ['rubber_band', 'elastic_band'], 'id': 896, 'def': 'a narrow band of elastic rubber used to hold things (such as papers) together', 'name': 'rubber_band'}, {'frequency': 'c', 'synset': 'runner.n.08', 'synonyms': ['runner_(carpet)'], 'id': 897, 'def': 'a long narrow carpet', 'name': 'runner_(carpet)'}, {'frequency': 'f', 'synset': 'sack.n.01', 'synonyms': ['plastic_bag', 'paper_bag'], 'id': 898, 'def': "a bag made of paper or plastic for holding customer's purchases", 'name': 'plastic_bag'}, {'frequency': 'f', 'synset': 'saddle.n.01', 'synonyms': ['saddle_(on_an_animal)'], 'id': 899, 'def': 'a seat for the rider of a horse or camel', 'name': 'saddle_(on_an_animal)'}, {'frequency': 'f', 'synset': 'saddle_blanket.n.01', 'synonyms': ['saddle_blanket', 'saddlecloth', 'horse_blanket'], 'id': 900, 'def': 'stable gear consisting of a blanket placed under the saddle', 'name': 'saddle_blanket'}, {'frequency': 'c', 'synset': 'saddlebag.n.01', 'synonyms': ['saddlebag'], 'id': 901, 'def': 'a large bag (or pair of bags) hung over a saddle', 'name': 'saddlebag'}, {'frequency': 'r', 'synset': 'safety_pin.n.01', 'synonyms': ['safety_pin'], 'id': 902, 'def': 'a pin in the form of a clasp; has a guard so the point of the pin will not stick the user', 'name': 'safety_pin'}, {'frequency': 'f', 'synset': 'sail.n.01', 'synonyms': ['sail'], 'id': 903, 'def': 'a large piece of fabric by means of which wind is used to propel a sailing vessel', 'name': 'sail'}, {'frequency': 'f', 'synset': 'salad.n.01', 'synonyms': ['salad'], 'id': 904, 'def': 'food mixtures either arranged on a plate or tossed and served with a moist dressing; usually consisting of or including greens', 'name': 'salad'}, {'frequency': 'r', 'synset': 'salad_plate.n.01', 'synonyms': ['salad_plate', 'salad_bowl'], 'id': 905, 'def': 'a plate or bowl for individual servings of salad', 'name': 'salad_plate'}, {'frequency': 'c', 'synset': 'salami.n.01', 'synonyms': ['salami'], 'id': 906, 'def': 'highly seasoned fatty sausage of pork and beef usually dried', 'name': 'salami'}, {'frequency': 'c', 'synset': 'salmon.n.01', 'synonyms': ['salmon_(fish)'], 'id': 907, 'def': 'any of various large food and game fishes of northern waters', 'name': 'salmon_(fish)'}, {'frequency': 'r', 'synset': 'salmon.n.03', 'synonyms': ['salmon_(food)'], 'id': 908, 'def': 'flesh of any of various marine or freshwater fish of the family Salmonidae', 'name': 'salmon_(food)'}, {'frequency': 'c', 'synset': 'salsa.n.01', 'synonyms': ['salsa'], 'id': 909, 'def': 'spicy sauce of tomatoes and onions and chili peppers to accompany Mexican foods', 'name': 'salsa'}, {'frequency': 'f', 'synset': 'saltshaker.n.01', 'synonyms': ['saltshaker'], 'id': 910, 'def': 'a shaker with a perforated top for sprinkling salt', 'name': 'saltshaker'}, {'frequency': 'f', 'synset': 'sandal.n.01', 'synonyms': ['sandal_(type_of_shoe)'], 'id': 911, 'def': 'a shoe consisting of a sole fastened by straps to the foot', 'name': 'sandal_(type_of_shoe)'}, {'frequency': 'f', 'synset': 'sandwich.n.01', 'synonyms': ['sandwich'], 'id': 912, 'def': 'two (or more) slices of bread with a filling between them', 'name': 'sandwich'}, {'frequency': 'r', 'synset': 'satchel.n.01', 'synonyms': ['satchel'], 'id': 913, 'def': 'luggage consisting of a small case with a flat bottom and (usually) a shoulder strap', 'name': 'satchel'}, {'frequency': 'r', 'synset': 'saucepan.n.01', 'synonyms': ['saucepan'], 'id': 914, 'def': 'a deep pan with a handle; used for stewing or boiling', 'name': 'saucepan'}, {'frequency': 'f', 'synset': 'saucer.n.02', 'synonyms': ['saucer'], 'id': 915, 'def': 'a small shallow dish for holding a cup at the table', 'name': 'saucer'}, {'frequency': 'f', 'synset': 'sausage.n.01', 'synonyms': ['sausage'], 'id': 916, 'def': 'highly seasoned minced meat stuffed in casings', 'name': 'sausage'}, {'frequency': 'r', 'synset': 'sawhorse.n.01', 'synonyms': ['sawhorse', 'sawbuck'], 'id': 917, 'def': 'a framework for holding wood that is being sawed', 'name': 'sawhorse'}, {'frequency': 'r', 'synset': 'sax.n.02', 'synonyms': ['saxophone'], 'id': 918, 'def': "a wind instrument with a `J'-shaped form typically made of brass", 'name': 'saxophone'}, {'frequency': 'f', 'synset': 'scale.n.07', 'synonyms': ['scale_(measuring_instrument)'], 'id': 919, 'def': 'a measuring instrument for weighing; shows amount of mass', 'name': 'scale_(measuring_instrument)'}, {'frequency': 'r', 'synset': 'scarecrow.n.01', 'synonyms': ['scarecrow', 'strawman'], 'id': 920, 'def': 'an effigy in the shape of a man to frighten birds away from seeds', 'name': 'scarecrow'}, {'frequency': 'f', 'synset': 'scarf.n.01', 'synonyms': ['scarf'], 'id': 921, 'def': 'a garment worn around the head or neck or shoulders for warmth or decoration', 'name': 'scarf'}, {'frequency': 'c', 'synset': 'school_bus.n.01', 'synonyms': ['school_bus'], 'id': 922, 'def': 'a bus used to transport children to or from school', 'name': 'school_bus'}, {'frequency': 'f', 'synset': 'scissors.n.01', 'synonyms': ['scissors'], 'id': 923, 'def': 'a tool having two crossed pivoting blades with looped handles', 'name': 'scissors'}, {'frequency': 'f', 'synset': 'scoreboard.n.01', 'synonyms': ['scoreboard'], 'id': 924, 'def': 'a large board for displaying the score of a contest (and some other information)', 'name': 'scoreboard'}, {'frequency': 'r', 'synset': 'scraper.n.01', 'synonyms': ['scraper'], 'id': 925, 'def': 'any of various hand tools for scraping', 'name': 'scraper'}, {'frequency': 'c', 'synset': 'screwdriver.n.01', 'synonyms': ['screwdriver'], 'id': 926, 'def': 'a hand tool for driving screws; has a tip that fits into the head of a screw', 'name': 'screwdriver'}, {'frequency': 'f', 'synset': 'scrub_brush.n.01', 'synonyms': ['scrubbing_brush'], 'id': 927, 'def': 'a brush with short stiff bristles for heavy cleaning', 'name': 'scrubbing_brush'}, {'frequency': 'c', 'synset': 'sculpture.n.01', 'synonyms': ['sculpture'], 'id': 928, 'def': 'a three-dimensional work of art', 'name': 'sculpture'}, {'frequency': 'c', 'synset': 'seabird.n.01', 'synonyms': ['seabird', 'seafowl'], 'id': 929, 'def': 'a bird that frequents coastal waters and the open ocean: gulls; pelicans; gannets; cormorants; albatrosses; petrels; etc.', 'name': 'seabird'}, {'frequency': 'c', 'synset': 'seahorse.n.02', 'synonyms': ['seahorse'], 'id': 930, 'def': 'small fish with horse-like heads bent sharply downward and curled tails', 'name': 'seahorse'}, {'frequency': 'r', 'synset': 'seaplane.n.01', 'synonyms': ['seaplane', 'hydroplane'], 'id': 931, 'def': 'an airplane that can land on or take off from water', 'name': 'seaplane'}, {'frequency': 'c', 'synset': 'seashell.n.01', 'synonyms': ['seashell'], 'id': 932, 'def': 'the shell of a marine organism', 'name': 'seashell'}, {'frequency': 'c', 'synset': 'sewing_machine.n.01', 'synonyms': ['sewing_machine'], 'id': 933, 'def': 'a textile machine used as a home appliance for sewing', 'name': 'sewing_machine'}, {'frequency': 'c', 'synset': 'shaker.n.03', 'synonyms': ['shaker'], 'id': 934, 'def': 'a container in which something can be shaken', 'name': 'shaker'}, {'frequency': 'c', 'synset': 'shampoo.n.01', 'synonyms': ['shampoo'], 'id': 935, 'def': 'cleansing agent consisting of soaps or detergents used for washing the hair', 'name': 'shampoo'}, {'frequency': 'c', 'synset': 'shark.n.01', 'synonyms': ['shark'], 'id': 936, 'def': 'typically large carnivorous fishes with sharpe teeth', 'name': 'shark'}, {'frequency': 'r', 'synset': 'sharpener.n.01', 'synonyms': ['sharpener'], 'id': 937, 'def': 'any implement that is used to make something (an edge or a point) sharper', 'name': 'sharpener'}, {'frequency': 'r', 'synset': 'sharpie.n.03', 'synonyms': ['Sharpie'], 'id': 938, 'def': 'a pen with indelible ink that will write on any surface', 'name': 'Sharpie'}, {'frequency': 'r', 'synset': 'shaver.n.03', 'synonyms': ['shaver_(electric)', 'electric_shaver', 'electric_razor'], 'id': 939, 'def': 'a razor powered by an electric motor', 'name': 'shaver_(electric)'}, {'frequency': 'c', 'synset': 'shaving_cream.n.01', 'synonyms': ['shaving_cream', 'shaving_soap'], 'id': 940, 'def': 'toiletry consisting that forms a rich lather for softening the beard before shaving', 'name': 'shaving_cream'}, {'frequency': 'r', 'synset': 'shawl.n.01', 'synonyms': ['shawl'], 'id': 941, 'def': 'cloak consisting of an oblong piece of cloth used to cover the head and shoulders', 'name': 'shawl'}, {'frequency': 'r', 'synset': 'shears.n.01', 'synonyms': ['shears'], 'id': 942, 'def': 'large scissors with strong blades', 'name': 'shears'}, {'frequency': 'f', 'synset': 'sheep.n.01', 'synonyms': ['sheep'], 'id': 943, 'def': 'woolly usually horned ruminant mammal related to the goat', 'name': 'sheep'}, {'frequency': 'r', 'synset': 'shepherd_dog.n.01', 'synonyms': ['shepherd_dog', 'sheepdog'], 'id': 944, 'def': 'any of various usually long-haired breeds of dog reared to herd and guard sheep', 'name': 'shepherd_dog'}, {'frequency': 'r', 'synset': 'sherbert.n.01', 'synonyms': ['sherbert', 'sherbet'], 'id': 945, 'def': 'a frozen dessert made primarily of fruit juice and sugar', 'name': 'sherbert'}, {'frequency': 'c', 'synset': 'shield.n.02', 'synonyms': ['shield'], 'id': 946, 'def': 'armor carried on the arm to intercept blows', 'name': 'shield'}, {'frequency': 'f', 'synset': 'shirt.n.01', 'synonyms': ['shirt'], 'id': 947, 'def': 'a garment worn on the upper half of the body', 'name': 'shirt'}, {'frequency': 'f', 'synset': 'shoe.n.01', 'synonyms': ['shoe', 'sneaker_(type_of_shoe)', 'tennis_shoe'], 'id': 948, 'def': 'common footwear covering the foot', 'name': 'shoe'}, {'frequency': 'f', 'synset': 'shopping_bag.n.01', 'synonyms': ['shopping_bag'], 'id': 949, 'def': 'a bag made of plastic or strong paper (often with handles); used to transport goods after shopping', 'name': 'shopping_bag'}, {'frequency': 'c', 'synset': 'shopping_cart.n.01', 'synonyms': ['shopping_cart'], 'id': 950, 'def': 'a handcart that holds groceries or other goods while shopping', 'name': 'shopping_cart'}, {'frequency': 'f', 'synset': 'short_pants.n.01', 'synonyms': ['short_pants', 'shorts_(clothing)', 'trunks_(clothing)'], 'id': 951, 'def': 'trousers that end at or above the knee', 'name': 'short_pants'}, {'frequency': 'r', 'synset': 'shot_glass.n.01', 'synonyms': ['shot_glass'], 'id': 952, 'def': 'a small glass adequate to hold a single swallow of whiskey', 'name': 'shot_glass'}, {'frequency': 'f', 'synset': 'shoulder_bag.n.01', 'synonyms': ['shoulder_bag'], 'id': 953, 'def': 'a large handbag that can be carried by a strap looped over the shoulder', 'name': 'shoulder_bag'}, {'frequency': 'c', 'synset': 'shovel.n.01', 'synonyms': ['shovel'], 'id': 954, 'def': 'a hand tool for lifting loose material such as snow, dirt, etc.', 'name': 'shovel'}, {'frequency': 'f', 'synset': 'shower.n.01', 'synonyms': ['shower_head'], 'id': 955, 'def': 'a plumbing fixture that sprays water over you', 'name': 'shower_head'}, {'frequency': 'r', 'synset': 'shower_cap.n.01', 'synonyms': ['shower_cap'], 'id': 956, 'def': 'a tight cap worn to keep hair dry while showering', 'name': 'shower_cap'}, {'frequency': 'f', 'synset': 'shower_curtain.n.01', 'synonyms': ['shower_curtain'], 'id': 957, 'def': 'a curtain that keeps water from splashing out of the shower area', 'name': 'shower_curtain'}, {'frequency': 'r', 'synset': 'shredder.n.01', 'synonyms': ['shredder_(for_paper)'], 'id': 958, 'def': 'a device that shreds documents', 'name': 'shredder_(for_paper)'}, {'frequency': 'f', 'synset': 'signboard.n.01', 'synonyms': ['signboard'], 'id': 959, 'def': 'structure displaying a board on which advertisements can be posted', 'name': 'signboard'}, {'frequency': 'c', 'synset': 'silo.n.01', 'synonyms': ['silo'], 'id': 960, 'def': 'a cylindrical tower used for storing goods', 'name': 'silo'}, {'frequency': 'f', 'synset': 'sink.n.01', 'synonyms': ['sink'], 'id': 961, 'def': 'plumbing fixture consisting of a water basin fixed to a wall or floor and having a drainpipe', 'name': 'sink'}, {'frequency': 'f', 'synset': 'skateboard.n.01', 'synonyms': ['skateboard'], 'id': 962, 'def': 'a board with wheels that is ridden in a standing or crouching position and propelled by foot', 'name': 'skateboard'}, {'frequency': 'c', 'synset': 'skewer.n.01', 'synonyms': ['skewer'], 'id': 963, 'def': 'a long pin for holding meat in position while it is being roasted', 'name': 'skewer'}, {'frequency': 'f', 'synset': 'ski.n.01', 'synonyms': ['ski'], 'id': 964, 'def': 'sports equipment for skiing on snow', 'name': 'ski'}, {'frequency': 'f', 'synset': 'ski_boot.n.01', 'synonyms': ['ski_boot'], 'id': 965, 'def': 'a stiff boot that is fastened to a ski with a ski binding', 'name': 'ski_boot'}, {'frequency': 'f', 'synset': 'ski_parka.n.01', 'synonyms': ['ski_parka', 'ski_jacket'], 'id': 966, 'def': 'a parka to be worn while skiing', 'name': 'ski_parka'}, {'frequency': 'f', 'synset': 'ski_pole.n.01', 'synonyms': ['ski_pole'], 'id': 967, 'def': 'a pole with metal points used as an aid in skiing', 'name': 'ski_pole'}, {'frequency': 'f', 'synset': 'skirt.n.02', 'synonyms': ['skirt'], 'id': 968, 'def': 'a garment hanging from the waist; worn mainly by girls and women', 'name': 'skirt'}, {'frequency': 'r', 'synset': 'skullcap.n.01', 'synonyms': ['skullcap'], 'id': 969, 'def': 'rounded brimless cap fitting the crown of the head', 'name': 'skullcap'}, {'frequency': 'c', 'synset': 'sled.n.01', 'synonyms': ['sled', 'sledge', 'sleigh'], 'id': 970, 'def': 'a vehicle or flat object for transportation over snow by sliding or pulled by dogs, etc.', 'name': 'sled'}, {'frequency': 'c', 'synset': 'sleeping_bag.n.01', 'synonyms': ['sleeping_bag'], 'id': 971, 'def': 'large padded bag designed to be slept in outdoors', 'name': 'sleeping_bag'}, {'frequency': 'r', 'synset': 'sling.n.05', 'synonyms': ['sling_(bandage)', 'triangular_bandage'], 'id': 972, 'def': 'bandage to support an injured forearm; slung over the shoulder or neck', 'name': 'sling_(bandage)'}, {'frequency': 'c', 'synset': 'slipper.n.01', 'synonyms': ['slipper_(footwear)', 'carpet_slipper_(footwear)'], 'id': 973, 'def': 'low footwear that can be slipped on and off easily; usually worn indoors', 'name': 'slipper_(footwear)'}, {'frequency': 'r', 'synset': 'smoothie.n.02', 'synonyms': ['smoothie'], 'id': 974, 'def': 'a thick smooth drink consisting of fresh fruit pureed with ice cream or yoghurt or milk', 'name': 'smoothie'}, {'frequency': 'r', 'synset': 'snake.n.01', 'synonyms': ['snake', 'serpent'], 'id': 975, 'def': 'limbless scaly elongate reptile; some are venomous', 'name': 'snake'}, {'frequency': 'f', 'synset': 'snowboard.n.01', 'synonyms': ['snowboard'], 'id': 976, 'def': 'a board that resembles a broad ski or a small surfboard; used in a standing position to slide down snow-covered slopes', 'name': 'snowboard'}, {'frequency': 'c', 'synset': 'snowman.n.01', 'synonyms': ['snowman'], 'id': 977, 'def': 'a figure of a person made of packed snow', 'name': 'snowman'}, {'frequency': 'c', 'synset': 'snowmobile.n.01', 'synonyms': ['snowmobile'], 'id': 978, 'def': 'tracked vehicle for travel on snow having skis in front', 'name': 'snowmobile'}, {'frequency': 'f', 'synset': 'soap.n.01', 'synonyms': ['soap'], 'id': 979, 'def': 'a cleansing agent made from the salts of vegetable or animal fats', 'name': 'soap'}, {'frequency': 'f', 'synset': 'soccer_ball.n.01', 'synonyms': ['soccer_ball'], 'id': 980, 'def': "an inflated ball used in playing soccer (called `football' outside of the United States)", 'name': 'soccer_ball'}, {'frequency': 'f', 'synset': 'sock.n.01', 'synonyms': ['sock'], 'id': 981, 'def': 'cloth covering for the foot; worn inside the shoe; reaches to between the ankle and the knee', 'name': 'sock'}, {'frequency': 'f', 'synset': 'sofa.n.01', 'synonyms': ['sofa', 'couch', 'lounge'], 'id': 982, 'def': 'an upholstered seat for more than one person', 'name': 'sofa'}, {'frequency': 'r', 'synset': 'softball.n.01', 'synonyms': ['softball'], 'id': 983, 'def': 'ball used in playing softball', 'name': 'softball'}, {'frequency': 'c', 'synset': 'solar_array.n.01', 'synonyms': ['solar_array', 'solar_battery', 'solar_panel'], 'id': 984, 'def': 'electrical device consisting of a large array of connected solar cells', 'name': 'solar_array'}, {'frequency': 'r', 'synset': 'sombrero.n.02', 'synonyms': ['sombrero'], 'id': 985, 'def': 'a straw hat with a tall crown and broad brim; worn in American southwest and in Mexico', 'name': 'sombrero'}, {'frequency': 'f', 'synset': 'soup.n.01', 'synonyms': ['soup'], 'id': 986, 'def': 'liquid food especially of meat or fish or vegetable stock often containing pieces of solid food', 'name': 'soup'}, {'frequency': 'r', 'synset': 'soup_bowl.n.01', 'synonyms': ['soup_bowl'], 'id': 987, 'def': 'a bowl for serving soup', 'name': 'soup_bowl'}, {'frequency': 'c', 'synset': 'soupspoon.n.01', 'synonyms': ['soupspoon'], 'id': 988, 'def': 'a spoon with a rounded bowl for eating soup', 'name': 'soupspoon'}, {'frequency': 'c', 'synset': 'sour_cream.n.01', 'synonyms': ['sour_cream', 'soured_cream'], 'id': 989, 'def': 'soured light cream', 'name': 'sour_cream'}, {'frequency': 'r', 'synset': 'soya_milk.n.01', 'synonyms': ['soya_milk', 'soybean_milk', 'soymilk'], 'id': 990, 'def': 'a milk substitute containing soybean flour and water; used in some infant formulas and in making tofu', 'name': 'soya_milk'}, {'frequency': 'r', 'synset': 'space_shuttle.n.01', 'synonyms': ['space_shuttle'], 'id': 991, 'def': "a reusable spacecraft with wings for a controlled descent through the Earth's atmosphere", 'name': 'space_shuttle'}, {'frequency': 'r', 'synset': 'sparkler.n.02', 'synonyms': ['sparkler_(fireworks)'], 'id': 992, 'def': 'a firework that burns slowly and throws out a shower of sparks', 'name': 'sparkler_(fireworks)'}, {'frequency': 'f', 'synset': 'spatula.n.02', 'synonyms': ['spatula'], 'id': 993, 'def': 'a hand tool with a thin flexible blade used to mix or spread soft substances', 'name': 'spatula'}, {'frequency': 'r', 'synset': 'spear.n.01', 'synonyms': ['spear', 'lance'], 'id': 994, 'def': 'a long pointed rod used as a tool or weapon', 'name': 'spear'}, {'frequency': 'f', 'synset': 'spectacles.n.01', 'synonyms': ['spectacles', 'specs', 'eyeglasses', 'glasses'], 'id': 995, 'def': 'optical instrument consisting of a frame that holds a pair of lenses for correcting defective vision', 'name': 'spectacles'}, {'frequency': 'c', 'synset': 'spice_rack.n.01', 'synonyms': ['spice_rack'], 'id': 996, 'def': 'a rack for displaying containers filled with spices', 'name': 'spice_rack'}, {'frequency': 'c', 'synset': 'spider.n.01', 'synonyms': ['spider'], 'id': 997, 'def': 'predatory arachnid with eight legs, two poison fangs, two feelers, and usually two silk-spinning organs at the back end of the body', 'name': 'spider'}, {'frequency': 'r', 'synset': 'spiny_lobster.n.02', 'synonyms': ['crawfish', 'crayfish'], 'id': 998, 'def': 'large edible marine crustacean having a spiny carapace but lacking the large pincers of true lobsters', 'name': 'crawfish'}, {'frequency': 'c', 'synset': 'sponge.n.01', 'synonyms': ['sponge'], 'id': 999, 'def': 'a porous mass usable to absorb water typically used for cleaning', 'name': 'sponge'}, {'frequency': 'f', 'synset': 'spoon.n.01', 'synonyms': ['spoon'], 'id': 1000, 'def': 'a piece of cutlery with a shallow bowl-shaped container and a handle', 'name': 'spoon'}, {'frequency': 'c', 'synset': 'sportswear.n.01', 'synonyms': ['sportswear', 'athletic_wear', 'activewear'], 'id': 1001, 'def': 'attire worn for sport or for casual wear', 'name': 'sportswear'}, {'frequency': 'c', 'synset': 'spotlight.n.02', 'synonyms': ['spotlight'], 'id': 1002, 'def': 'a lamp that produces a strong beam of light to illuminate a restricted area; used to focus attention of a stage performer', 'name': 'spotlight'}, {'frequency': 'r', 'synset': 'squid.n.01', 'synonyms': ['squid_(food)', 'calamari', 'calamary'], 'id': 1003, 'def': '(Italian cuisine) squid prepared as food', 'name': 'squid_(food)'}, {'frequency': 'c', 'synset': 'squirrel.n.01', 'synonyms': ['squirrel'], 'id': 1004, 'def': 'a kind of arboreal rodent having a long bushy tail', 'name': 'squirrel'}, {'frequency': 'r', 'synset': 'stagecoach.n.01', 'synonyms': ['stagecoach'], 'id': 1005, 'def': 'a large coach-and-four formerly used to carry passengers and mail on regular routes between towns', 'name': 'stagecoach'}, {'frequency': 'c', 'synset': 'stapler.n.01', 'synonyms': ['stapler_(stapling_machine)'], 'id': 1006, 'def': 'a machine that inserts staples into sheets of paper in order to fasten them together', 'name': 'stapler_(stapling_machine)'}, {'frequency': 'c', 'synset': 'starfish.n.01', 'synonyms': ['starfish', 'sea_star'], 'id': 1007, 'def': 'echinoderms characterized by five arms extending from a central disk', 'name': 'starfish'}, {'frequency': 'f', 'synset': 'statue.n.01', 'synonyms': ['statue_(sculpture)'], 'id': 1008, 'def': 'a sculpture representing a human or animal', 'name': 'statue_(sculpture)'}, {'frequency': 'c', 'synset': 'steak.n.01', 'synonyms': ['steak_(food)'], 'id': 1009, 'def': 'a slice of meat cut from the fleshy part of an animal or large fish', 'name': 'steak_(food)'}, {'frequency': 'r', 'synset': 'steak_knife.n.01', 'synonyms': ['steak_knife'], 'id': 1010, 'def': 'a sharp table knife used in eating steak', 'name': 'steak_knife'}, {'frequency': 'f', 'synset': 'steering_wheel.n.01', 'synonyms': ['steering_wheel'], 'id': 1011, 'def': 'a handwheel that is used for steering', 'name': 'steering_wheel'}, {'frequency': 'r', 'synset': 'step_ladder.n.01', 'synonyms': ['stepladder'], 'id': 1012, 'def': 'a folding portable ladder hinged at the top', 'name': 'stepladder'}, {'frequency': 'c', 'synset': 'step_stool.n.01', 'synonyms': ['step_stool'], 'id': 1013, 'def': 'a stool that has one or two steps that fold under the seat', 'name': 'step_stool'}, {'frequency': 'c', 'synset': 'stereo.n.01', 'synonyms': ['stereo_(sound_system)'], 'id': 1014, 'def': 'electronic device for playing audio', 'name': 'stereo_(sound_system)'}, {'frequency': 'r', 'synset': 'stew.n.02', 'synonyms': ['stew'], 'id': 1015, 'def': 'food prepared by stewing especially meat or fish with vegetables', 'name': 'stew'}, {'frequency': 'r', 'synset': 'stirrer.n.02', 'synonyms': ['stirrer'], 'id': 1016, 'def': 'an implement used for stirring', 'name': 'stirrer'}, {'frequency': 'f', 'synset': 'stirrup.n.01', 'synonyms': ['stirrup'], 'id': 1017, 'def': "support consisting of metal loops into which rider's feet go", 'name': 'stirrup'}, {'frequency': 'f', 'synset': 'stool.n.01', 'synonyms': ['stool'], 'id': 1018, 'def': 'a simple seat without a back or arms', 'name': 'stool'}, {'frequency': 'f', 'synset': 'stop_sign.n.01', 'synonyms': ['stop_sign'], 'id': 1019, 'def': 'a traffic sign to notify drivers that they must come to a complete stop', 'name': 'stop_sign'}, {'frequency': 'f', 'synset': 'stoplight.n.01', 'synonyms': ['brake_light'], 'id': 1020, 'def': 'a red light on the rear of a motor vehicle that signals when the brakes are applied', 'name': 'brake_light'}, {'frequency': 'f', 'synset': 'stove.n.01', 'synonyms': ['stove', 'kitchen_stove', 'range_(kitchen_appliance)', 'kitchen_range', 'cooking_stove'], 'id': 1021, 'def': 'a kitchen appliance used for cooking food', 'name': 'stove'}, {'frequency': 'c', 'synset': 'strainer.n.01', 'synonyms': ['strainer'], 'id': 1022, 'def': 'a filter to retain larger pieces while smaller pieces and liquids pass through', 'name': 'strainer'}, {'frequency': 'f', 'synset': 'strap.n.01', 'synonyms': ['strap'], 'id': 1023, 'def': 'an elongated strip of material for binding things together or holding', 'name': 'strap'}, {'frequency': 'f', 'synset': 'straw.n.04', 'synonyms': ['straw_(for_drinking)', 'drinking_straw'], 'id': 1024, 'def': 'a thin paper or plastic tube used to suck liquids into the mouth', 'name': 'straw_(for_drinking)'}, {'frequency': 'f', 'synset': 'strawberry.n.01', 'synonyms': ['strawberry'], 'id': 1025, 'def': 'sweet fleshy red fruit', 'name': 'strawberry'}, {'frequency': 'f', 'synset': 'street_sign.n.01', 'synonyms': ['street_sign'], 'id': 1026, 'def': 'a sign visible from the street', 'name': 'street_sign'}, {'frequency': 'f', 'synset': 'streetlight.n.01', 'synonyms': ['streetlight', 'street_lamp'], 'id': 1027, 'def': 'a lamp supported on a lamppost; for illuminating a street', 'name': 'streetlight'}, {'frequency': 'r', 'synset': 'string_cheese.n.01', 'synonyms': ['string_cheese'], 'id': 1028, 'def': 'cheese formed in long strings twisted together', 'name': 'string_cheese'}, {'frequency': 'r', 'synset': 'stylus.n.02', 'synonyms': ['stylus'], 'id': 1029, 'def': 'a pointed tool for writing or drawing or engraving, including pens', 'name': 'stylus'}, {'frequency': 'r', 'synset': 'subwoofer.n.01', 'synonyms': ['subwoofer'], 'id': 1030, 'def': 'a loudspeaker that is designed to reproduce very low bass frequencies', 'name': 'subwoofer'}, {'frequency': 'r', 'synset': 'sugar_bowl.n.01', 'synonyms': ['sugar_bowl'], 'id': 1031, 'def': 'a dish in which sugar is served', 'name': 'sugar_bowl'}, {'frequency': 'r', 'synset': 'sugarcane.n.01', 'synonyms': ['sugarcane_(plant)'], 'id': 1032, 'def': 'juicy canes whose sap is a source of molasses and commercial sugar; fresh canes are sometimes chewed for the juice', 'name': 'sugarcane_(plant)'}, {'frequency': 'f', 'synset': 'suit.n.01', 'synonyms': ['suit_(clothing)'], 'id': 1033, 'def': 'a set of garments (usually including a jacket and trousers or skirt) for outerwear all of the same fabric and color', 'name': 'suit_(clothing)'}, {'frequency': 'c', 'synset': 'sunflower.n.01', 'synonyms': ['sunflower'], 'id': 1034, 'def': 'any plant of the genus Helianthus having large flower heads with dark disk florets and showy yellow rays', 'name': 'sunflower'}, {'frequency': 'f', 'synset': 'sunglasses.n.01', 'synonyms': ['sunglasses'], 'id': 1035, 'def': 'spectacles that are darkened or polarized to protect the eyes from the glare of the sun', 'name': 'sunglasses'}, {'frequency': 'c', 'synset': 'sunhat.n.01', 'synonyms': ['sunhat'], 'id': 1036, 'def': 'a hat with a broad brim that protects the face from direct exposure to the sun', 'name': 'sunhat'}, {'frequency': 'f', 'synset': 'surfboard.n.01', 'synonyms': ['surfboard'], 'id': 1037, 'def': 'a narrow buoyant board for riding surf', 'name': 'surfboard'}, {'frequency': 'c', 'synset': 'sushi.n.01', 'synonyms': ['sushi'], 'id': 1038, 'def': 'rice (with raw fish) wrapped in seaweed', 'name': 'sushi'}, {'frequency': 'c', 'synset': 'swab.n.02', 'synonyms': ['mop'], 'id': 1039, 'def': 'cleaning implement consisting of absorbent material fastened to a handle; for cleaning floors', 'name': 'mop'}, {'frequency': 'c', 'synset': 'sweat_pants.n.01', 'synonyms': ['sweat_pants'], 'id': 1040, 'def': 'loose-fitting trousers with elastic cuffs; worn by athletes', 'name': 'sweat_pants'}, {'frequency': 'c', 'synset': 'sweatband.n.02', 'synonyms': ['sweatband'], 'id': 1041, 'def': 'a band of material tied around the forehead or wrist to absorb sweat', 'name': 'sweatband'}, {'frequency': 'f', 'synset': 'sweater.n.01', 'synonyms': ['sweater'], 'id': 1042, 'def': 'a crocheted or knitted garment covering the upper part of the body', 'name': 'sweater'}, {'frequency': 'f', 'synset': 'sweatshirt.n.01', 'synonyms': ['sweatshirt'], 'id': 1043, 'def': 'cotton knit pullover with long sleeves worn during athletic activity', 'name': 'sweatshirt'}, {'frequency': 'c', 'synset': 'sweet_potato.n.02', 'synonyms': ['sweet_potato'], 'id': 1044, 'def': 'the edible tuberous root of the sweet potato vine', 'name': 'sweet_potato'}, {'frequency': 'f', 'synset': 'swimsuit.n.01', 'synonyms': ['swimsuit', 'swimwear', 'bathing_suit', 'swimming_costume', 'bathing_costume', 'swimming_trunks', 'bathing_trunks'], 'id': 1045, 'def': 'garment worn for swimming', 'name': 'swimsuit'}, {'frequency': 'c', 'synset': 'sword.n.01', 'synonyms': ['sword'], 'id': 1046, 'def': 'a cutting or thrusting weapon that has a long metal blade', 'name': 'sword'}, {'frequency': 'r', 'synset': 'syringe.n.01', 'synonyms': ['syringe'], 'id': 1047, 'def': 'a medical instrument used to inject or withdraw fluids', 'name': 'syringe'}, {'frequency': 'r', 'synset': 'tabasco.n.02', 'synonyms': ['Tabasco_sauce'], 'id': 1048, 'def': 'very spicy sauce (trade name Tabasco) made from fully-aged red peppers', 'name': 'Tabasco_sauce'}, {'frequency': 'r', 'synset': 'table-tennis_table.n.01', 'synonyms': ['table-tennis_table', 'ping-pong_table'], 'id': 1049, 'def': 'a table used for playing table tennis', 'name': 'table-tennis_table'}, {'frequency': 'f', 'synset': 'table.n.02', 'synonyms': ['table'], 'id': 1050, 'def': 'a piece of furniture having a smooth flat top that is usually supported by one or more vertical legs', 'name': 'table'}, {'frequency': 'c', 'synset': 'table_lamp.n.01', 'synonyms': ['table_lamp'], 'id': 1051, 'def': 'a lamp that sits on a table', 'name': 'table_lamp'}, {'frequency': 'f', 'synset': 'tablecloth.n.01', 'synonyms': ['tablecloth'], 'id': 1052, 'def': 'a covering spread over a dining table', 'name': 'tablecloth'}, {'frequency': 'r', 'synset': 'tachometer.n.01', 'synonyms': ['tachometer'], 'id': 1053, 'def': 'measuring instrument for indicating speed of rotation', 'name': 'tachometer'}, {'frequency': 'r', 'synset': 'taco.n.02', 'synonyms': ['taco'], 'id': 1054, 'def': 'a small tortilla cupped around a filling', 'name': 'taco'}, {'frequency': 'f', 'synset': 'tag.n.02', 'synonyms': ['tag'], 'id': 1055, 'def': 'a label associated with something for the purpose of identification or information', 'name': 'tag'}, {'frequency': 'f', 'synset': 'taillight.n.01', 'synonyms': ['taillight', 'rear_light'], 'id': 1056, 'def': 'lamp (usually red) mounted at the rear of a motor vehicle', 'name': 'taillight'}, {'frequency': 'r', 'synset': 'tambourine.n.01', 'synonyms': ['tambourine'], 'id': 1057, 'def': 'a shallow drum with a single drumhead and with metallic disks in the sides', 'name': 'tambourine'}, {'frequency': 'r', 'synset': 'tank.n.01', 'synonyms': ['army_tank', 'armored_combat_vehicle', 'armoured_combat_vehicle'], 'id': 1058, 'def': 'an enclosed armored military vehicle; has a cannon and moves on caterpillar treads', 'name': 'army_tank'}, {'frequency': 'f', 'synset': 'tank.n.02', 'synonyms': ['tank_(storage_vessel)', 'storage_tank'], 'id': 1059, 'def': 'a large (usually metallic) vessel for holding gases or liquids', 'name': 'tank_(storage_vessel)'}, {'frequency': 'f', 'synset': 'tank_top.n.01', 'synonyms': ['tank_top_(clothing)'], 'id': 1060, 'def': 'a tight-fitting sleeveless shirt with wide shoulder straps and low neck and no front opening', 'name': 'tank_top_(clothing)'}, {'frequency': 'f', 'synset': 'tape.n.01', 'synonyms': ['tape_(sticky_cloth_or_paper)'], 'id': 1061, 'def': 'a long thin piece of cloth or paper as used for binding or fastening', 'name': 'tape_(sticky_cloth_or_paper)'}, {'frequency': 'c', 'synset': 'tape.n.04', 'synonyms': ['tape_measure', 'measuring_tape'], 'id': 1062, 'def': 'measuring instrument consisting of a narrow strip (cloth or metal) marked in inches or centimeters and used for measuring lengths', 'name': 'tape_measure'}, {'frequency': 'c', 'synset': 'tapestry.n.02', 'synonyms': ['tapestry'], 'id': 1063, 'def': 'a heavy textile with a woven design; used for curtains and upholstery', 'name': 'tapestry'}, {'frequency': 'f', 'synset': 'tarpaulin.n.01', 'synonyms': ['tarp'], 'id': 1064, 'def': 'waterproofed canvas', 'name': 'tarp'}, {'frequency': 'c', 'synset': 'tartan.n.01', 'synonyms': ['tartan', 'plaid'], 'id': 1065, 'def': 'a cloth having a crisscross design', 'name': 'tartan'}, {'frequency': 'c', 'synset': 'tassel.n.01', 'synonyms': ['tassel'], 'id': 1066, 'def': 'adornment consisting of a bunch of cords fastened at one end', 'name': 'tassel'}, {'frequency': 'c', 'synset': 'tea_bag.n.01', 'synonyms': ['tea_bag'], 'id': 1067, 'def': 'a measured amount of tea in a bag for an individual serving of tea', 'name': 'tea_bag'}, {'frequency': 'c', 'synset': 'teacup.n.02', 'synonyms': ['teacup'], 'id': 1068, 'def': 'a cup from which tea is drunk', 'name': 'teacup'}, {'frequency': 'c', 'synset': 'teakettle.n.01', 'synonyms': ['teakettle'], 'id': 1069, 'def': 'kettle for boiling water to make tea', 'name': 'teakettle'}, {'frequency': 'f', 'synset': 'teapot.n.01', 'synonyms': ['teapot'], 'id': 1070, 'def': 'pot for brewing tea; usually has a spout and handle', 'name': 'teapot'}, {'frequency': 'f', 'synset': 'teddy.n.01', 'synonyms': ['teddy_bear'], 'id': 1071, 'def': "plaything consisting of a child's toy bear (usually plush and stuffed with soft materials)", 'name': 'teddy_bear'}, {'frequency': 'f', 'synset': 'telephone.n.01', 'synonyms': ['telephone', 'phone', 'telephone_set'], 'id': 1072, 'def': 'electronic device for communicating by voice over long distances (includes wired and wireless/cell phones)', 'name': 'telephone'}, {'frequency': 'c', 'synset': 'telephone_booth.n.01', 'synonyms': ['telephone_booth', 'phone_booth', 'call_box', 'telephone_box', 'telephone_kiosk'], 'id': 1073, 'def': 'booth for using a telephone', 'name': 'telephone_booth'}, {'frequency': 'f', 'synset': 'telephone_pole.n.01', 'synonyms': ['telephone_pole', 'telegraph_pole', 'telegraph_post'], 'id': 1074, 'def': 'tall pole supporting telephone wires', 'name': 'telephone_pole'}, {'frequency': 'r', 'synset': 'telephoto_lens.n.01', 'synonyms': ['telephoto_lens', 'zoom_lens'], 'id': 1075, 'def': 'a camera lens that magnifies the image', 'name': 'telephoto_lens'}, {'frequency': 'c', 'synset': 'television_camera.n.01', 'synonyms': ['television_camera', 'tv_camera'], 'id': 1076, 'def': 'television equipment for capturing and recording video', 'name': 'television_camera'}, {'frequency': 'f', 'synset': 'television_receiver.n.01', 'synonyms': ['television_set', 'tv', 'tv_set'], 'id': 1077, 'def': 'an electronic device that receives television signals and displays them on a screen', 'name': 'television_set'}, {'frequency': 'f', 'synset': 'tennis_ball.n.01', 'synonyms': ['tennis_ball'], 'id': 1078, 'def': 'ball about the size of a fist used in playing tennis', 'name': 'tennis_ball'}, {'frequency': 'f', 'synset': 'tennis_racket.n.01', 'synonyms': ['tennis_racket'], 'id': 1079, 'def': 'a racket used to play tennis', 'name': 'tennis_racket'}, {'frequency': 'r', 'synset': 'tequila.n.01', 'synonyms': ['tequila'], 'id': 1080, 'def': 'Mexican liquor made from fermented juices of an agave plant', 'name': 'tequila'}, {'frequency': 'c', 'synset': 'thermometer.n.01', 'synonyms': ['thermometer'], 'id': 1081, 'def': 'measuring instrument for measuring temperature', 'name': 'thermometer'}, {'frequency': 'c', 'synset': 'thermos.n.01', 'synonyms': ['thermos_bottle'], 'id': 1082, 'def': 'vacuum flask that preserves temperature of hot or cold drinks', 'name': 'thermos_bottle'}, {'frequency': 'f', 'synset': 'thermostat.n.01', 'synonyms': ['thermostat'], 'id': 1083, 'def': 'a regulator for automatically regulating temperature by starting or stopping the supply of heat', 'name': 'thermostat'}, {'frequency': 'r', 'synset': 'thimble.n.02', 'synonyms': ['thimble'], 'id': 1084, 'def': 'a small metal cap to protect the finger while sewing; can be used as a small container', 'name': 'thimble'}, {'frequency': 'c', 'synset': 'thread.n.01', 'synonyms': ['thread', 'yarn'], 'id': 1085, 'def': 'a fine cord of twisted fibers (of cotton or silk or wool or nylon etc.) used in sewing and weaving', 'name': 'thread'}, {'frequency': 'c', 'synset': 'thumbtack.n.01', 'synonyms': ['thumbtack', 'drawing_pin', 'pushpin'], 'id': 1086, 'def': 'a tack for attaching papers to a bulletin board or drawing board', 'name': 'thumbtack'}, {'frequency': 'c', 'synset': 'tiara.n.01', 'synonyms': ['tiara'], 'id': 1087, 'def': 'a jeweled headdress worn by women on formal occasions', 'name': 'tiara'}, {'frequency': 'c', 'synset': 'tiger.n.02', 'synonyms': ['tiger'], 'id': 1088, 'def': 'large feline of forests in most of Asia having a tawny coat with black stripes', 'name': 'tiger'}, {'frequency': 'c', 'synset': 'tights.n.01', 'synonyms': ['tights_(clothing)', 'leotards'], 'id': 1089, 'def': 'skintight knit hose covering the body from the waist to the feet worn by acrobats and dancers and as stockings by women and girls', 'name': 'tights_(clothing)'}, {'frequency': 'c', 'synset': 'timer.n.01', 'synonyms': ['timer', 'stopwatch'], 'id': 1090, 'def': 'a timepiece that measures a time interval and signals its end', 'name': 'timer'}, {'frequency': 'f', 'synset': 'tinfoil.n.01', 'synonyms': ['tinfoil'], 'id': 1091, 'def': 'foil made of tin or an alloy of tin and lead', 'name': 'tinfoil'}, {'frequency': 'c', 'synset': 'tinsel.n.01', 'synonyms': ['tinsel'], 'id': 1092, 'def': 'a showy decoration that is basically valueless', 'name': 'tinsel'}, {'frequency': 'f', 'synset': 'tissue.n.02', 'synonyms': ['tissue_paper'], 'id': 1093, 'def': 'a soft thin (usually translucent) paper', 'name': 'tissue_paper'}, {'frequency': 'c', 'synset': 'toast.n.01', 'synonyms': ['toast_(food)'], 'id': 1094, 'def': 'slice of bread that has been toasted', 'name': 'toast_(food)'}, {'frequency': 'f', 'synset': 'toaster.n.02', 'synonyms': ['toaster'], 'id': 1095, 'def': 'a kitchen appliance (usually electric) for toasting bread', 'name': 'toaster'}, {'frequency': 'f', 'synset': 'toaster_oven.n.01', 'synonyms': ['toaster_oven'], 'id': 1096, 'def': 'kitchen appliance consisting of a small electric oven for toasting or warming food', 'name': 'toaster_oven'}, {'frequency': 'f', 'synset': 'toilet.n.02', 'synonyms': ['toilet'], 'id': 1097, 'def': 'a plumbing fixture for defecation and urination', 'name': 'toilet'}, {'frequency': 'f', 'synset': 'toilet_tissue.n.01', 'synonyms': ['toilet_tissue', 'toilet_paper', 'bathroom_tissue'], 'id': 1098, 'def': 'a soft thin absorbent paper for use in toilets', 'name': 'toilet_tissue'}, {'frequency': 'f', 'synset': 'tomato.n.01', 'synonyms': ['tomato'], 'id': 1099, 'def': 'mildly acid red or yellow pulpy fruit eaten as a vegetable', 'name': 'tomato'}, {'frequency': 'f', 'synset': 'tongs.n.01', 'synonyms': ['tongs'], 'id': 1100, 'def': 'any of various devices for taking hold of objects; usually have two hinged legs with handles above and pointed hooks below', 'name': 'tongs'}, {'frequency': 'c', 'synset': 'toolbox.n.01', 'synonyms': ['toolbox'], 'id': 1101, 'def': 'a box or chest or cabinet for holding hand tools', 'name': 'toolbox'}, {'frequency': 'f', 'synset': 'toothbrush.n.01', 'synonyms': ['toothbrush'], 'id': 1102, 'def': 'small brush; has long handle; used to clean teeth', 'name': 'toothbrush'}, {'frequency': 'f', 'synset': 'toothpaste.n.01', 'synonyms': ['toothpaste'], 'id': 1103, 'def': 'a dentifrice in the form of a paste', 'name': 'toothpaste'}, {'frequency': 'f', 'synset': 'toothpick.n.01', 'synonyms': ['toothpick'], 'id': 1104, 'def': 'pick consisting of a small strip of wood or plastic; used to pick food from between the teeth', 'name': 'toothpick'}, {'frequency': 'f', 'synset': 'top.n.09', 'synonyms': ['cover'], 'id': 1105, 'def': 'covering for a hole (especially a hole in the top of a container)', 'name': 'cover'}, {'frequency': 'c', 'synset': 'tortilla.n.01', 'synonyms': ['tortilla'], 'id': 1106, 'def': 'thin unleavened pancake made from cornmeal or wheat flour', 'name': 'tortilla'}, {'frequency': 'c', 'synset': 'tow_truck.n.01', 'synonyms': ['tow_truck'], 'id': 1107, 'def': 'a truck equipped to hoist and pull wrecked cars (or to remove cars from no-parking zones)', 'name': 'tow_truck'}, {'frequency': 'f', 'synset': 'towel.n.01', 'synonyms': ['towel'], 'id': 1108, 'def': 'a rectangular piece of absorbent cloth (or paper) for drying or wiping', 'name': 'towel'}, {'frequency': 'f', 'synset': 'towel_rack.n.01', 'synonyms': ['towel_rack', 'towel_rail', 'towel_bar'], 'id': 1109, 'def': 'a rack consisting of one or more bars on which towels can be hung', 'name': 'towel_rack'}, {'frequency': 'f', 'synset': 'toy.n.03', 'synonyms': ['toy'], 'id': 1110, 'def': 'a device regarded as providing amusement', 'name': 'toy'}, {'frequency': 'c', 'synset': 'tractor.n.01', 'synonyms': ['tractor_(farm_equipment)'], 'id': 1111, 'def': 'a wheeled vehicle with large wheels; used in farming and other applications', 'name': 'tractor_(farm_equipment)'}, {'frequency': 'f', 'synset': 'traffic_light.n.01', 'synonyms': ['traffic_light'], 'id': 1112, 'def': 'a device to control vehicle traffic often consisting of three or more lights', 'name': 'traffic_light'}, {'frequency': 'c', 'synset': 'trail_bike.n.01', 'synonyms': ['dirt_bike'], 'id': 1113, 'def': 'a lightweight motorcycle equipped with rugged tires and suspension for off-road use', 'name': 'dirt_bike'}, {'frequency': 'f', 'synset': 'trailer_truck.n.01', 'synonyms': ['trailer_truck', 'tractor_trailer', 'trucking_rig', 'articulated_lorry', 'semi_truck'], 'id': 1114, 'def': 'a truck consisting of a tractor and trailer together', 'name': 'trailer_truck'}, {'frequency': 'f', 'synset': 'train.n.01', 'synonyms': ['train_(railroad_vehicle)', 'railroad_train'], 'id': 1115, 'def': 'public or private transport provided by a line of railway cars coupled together and drawn by a locomotive', 'name': 'train_(railroad_vehicle)'}, {'frequency': 'r', 'synset': 'trampoline.n.01', 'synonyms': ['trampoline'], 'id': 1116, 'def': 'gymnastic apparatus consisting of a strong canvas sheet attached with springs to a metal frame', 'name': 'trampoline'}, {'frequency': 'f', 'synset': 'tray.n.01', 'synonyms': ['tray'], 'id': 1117, 'def': 'an open receptacle for holding or displaying or serving articles or food', 'name': 'tray'}, {'frequency': 'r', 'synset': 'trench_coat.n.01', 'synonyms': ['trench_coat'], 'id': 1118, 'def': 'a military style raincoat; belted with deep pockets', 'name': 'trench_coat'}, {'frequency': 'r', 'synset': 'triangle.n.05', 'synonyms': ['triangle_(musical_instrument)'], 'id': 1119, 'def': 'a percussion instrument consisting of a metal bar bent in the shape of an open triangle', 'name': 'triangle_(musical_instrument)'}, {'frequency': 'c', 'synset': 'tricycle.n.01', 'synonyms': ['tricycle'], 'id': 1120, 'def': 'a vehicle with three wheels that is moved by foot pedals', 'name': 'tricycle'}, {'frequency': 'f', 'synset': 'tripod.n.01', 'synonyms': ['tripod'], 'id': 1121, 'def': 'a three-legged rack used for support', 'name': 'tripod'}, {'frequency': 'f', 'synset': 'trouser.n.01', 'synonyms': ['trousers', 'pants_(clothing)'], 'id': 1122, 'def': 'a garment extending from the waist to the knee or ankle, covering each leg separately', 'name': 'trousers'}, {'frequency': 'f', 'synset': 'truck.n.01', 'synonyms': ['truck'], 'id': 1123, 'def': 'an automotive vehicle suitable for hauling', 'name': 'truck'}, {'frequency': 'r', 'synset': 'truffle.n.03', 'synonyms': ['truffle_(chocolate)', 'chocolate_truffle'], 'id': 1124, 'def': 'creamy chocolate candy', 'name': 'truffle_(chocolate)'}, {'frequency': 'c', 'synset': 'trunk.n.02', 'synonyms': ['trunk'], 'id': 1125, 'def': 'luggage consisting of a large strong case used when traveling or for storage', 'name': 'trunk'}, {'frequency': 'r', 'synset': 'tub.n.02', 'synonyms': ['vat'], 'id': 1126, 'def': 'a large vessel for holding or storing liquids', 'name': 'vat'}, {'frequency': 'c', 'synset': 'turban.n.01', 'synonyms': ['turban'], 'id': 1127, 'def': 'a traditional headdress consisting of a long scarf wrapped around the head', 'name': 'turban'}, {'frequency': 'c', 'synset': 'turkey.n.04', 'synonyms': ['turkey_(food)'], 'id': 1128, 'def': 'flesh of large domesticated fowl usually roasted', 'name': 'turkey_(food)'}, {'frequency': 'r', 'synset': 'turnip.n.01', 'synonyms': ['turnip'], 'id': 1129, 'def': 'widely cultivated plant having a large fleshy edible white or yellow root', 'name': 'turnip'}, {'frequency': 'c', 'synset': 'turtle.n.02', 'synonyms': ['turtle'], 'id': 1130, 'def': 'any of various aquatic and land reptiles having a bony shell and flipper-like limbs for swimming', 'name': 'turtle'}, {'frequency': 'c', 'synset': 'turtleneck.n.01', 'synonyms': ['turtleneck_(clothing)', 'polo-neck'], 'id': 1131, 'def': 'a sweater or jersey with a high close-fitting collar', 'name': 'turtleneck_(clothing)'}, {'frequency': 'c', 'synset': 'typewriter.n.01', 'synonyms': ['typewriter'], 'id': 1132, 'def': 'hand-operated character printer for printing written messages one character at a time', 'name': 'typewriter'}, {'frequency': 'f', 'synset': 'umbrella.n.01', 'synonyms': ['umbrella'], 'id': 1133, 'def': 'a lightweight handheld collapsible canopy', 'name': 'umbrella'}, {'frequency': 'f', 'synset': 'underwear.n.01', 'synonyms': ['underwear', 'underclothes', 'underclothing', 'underpants'], 'id': 1134, 'def': 'undergarment worn next to the skin and under the outer garments', 'name': 'underwear'}, {'frequency': 'r', 'synset': 'unicycle.n.01', 'synonyms': ['unicycle'], 'id': 1135, 'def': 'a vehicle with a single wheel that is driven by pedals', 'name': 'unicycle'}, {'frequency': 'f', 'synset': 'urinal.n.01', 'synonyms': ['urinal'], 'id': 1136, 'def': 'a plumbing fixture (usually attached to the wall) used by men to urinate', 'name': 'urinal'}, {'frequency': 'c', 'synset': 'urn.n.01', 'synonyms': ['urn'], 'id': 1137, 'def': 'a large vase that usually has a pedestal or feet', 'name': 'urn'}, {'frequency': 'c', 'synset': 'vacuum.n.04', 'synonyms': ['vacuum_cleaner'], 'id': 1138, 'def': 'an electrical home appliance that cleans by suction', 'name': 'vacuum_cleaner'}, {'frequency': 'f', 'synset': 'vase.n.01', 'synonyms': ['vase'], 'id': 1139, 'def': 'an open jar of glass or porcelain used as an ornament or to hold flowers', 'name': 'vase'}, {'frequency': 'c', 'synset': 'vending_machine.n.01', 'synonyms': ['vending_machine'], 'id': 1140, 'def': 'a slot machine for selling goods', 'name': 'vending_machine'}, {'frequency': 'f', 'synset': 'vent.n.01', 'synonyms': ['vent', 'blowhole', 'air_vent'], 'id': 1141, 'def': 'a hole for the escape of gas or air', 'name': 'vent'}, {'frequency': 'f', 'synset': 'vest.n.01', 'synonyms': ['vest', 'waistcoat'], 'id': 1142, 'def': "a man's sleeveless garment worn underneath a coat", 'name': 'vest'}, {'frequency': 'c', 'synset': 'videotape.n.01', 'synonyms': ['videotape'], 'id': 1143, 'def': 'a video recording made on magnetic tape', 'name': 'videotape'}, {'frequency': 'r', 'synset': 'vinegar.n.01', 'synonyms': ['vinegar'], 'id': 1144, 'def': 'sour-tasting liquid produced usually by oxidation of the alcohol in wine or cider and used as a condiment or food preservative', 'name': 'vinegar'}, {'frequency': 'r', 'synset': 'violin.n.01', 'synonyms': ['violin', 'fiddle'], 'id': 1145, 'def': 'bowed stringed instrument that is the highest member of the violin family', 'name': 'violin'}, {'frequency': 'r', 'synset': 'vodka.n.01', 'synonyms': ['vodka'], 'id': 1146, 'def': 'unaged colorless liquor originating in Russia', 'name': 'vodka'}, {'frequency': 'c', 'synset': 'volleyball.n.02', 'synonyms': ['volleyball'], 'id': 1147, 'def': 'an inflated ball used in playing volleyball', 'name': 'volleyball'}, {'frequency': 'r', 'synset': 'vulture.n.01', 'synonyms': ['vulture'], 'id': 1148, 'def': 'any of various large birds of prey having naked heads and weak claws and feeding chiefly on carrion', 'name': 'vulture'}, {'frequency': 'c', 'synset': 'waffle.n.01', 'synonyms': ['waffle'], 'id': 1149, 'def': 'pancake batter baked in a waffle iron', 'name': 'waffle'}, {'frequency': 'r', 'synset': 'waffle_iron.n.01', 'synonyms': ['waffle_iron'], 'id': 1150, 'def': 'a kitchen appliance for baking waffles', 'name': 'waffle_iron'}, {'frequency': 'c', 'synset': 'wagon.n.01', 'synonyms': ['wagon'], 'id': 1151, 'def': 'any of various kinds of wheeled vehicles drawn by an animal or a tractor', 'name': 'wagon'}, {'frequency': 'c', 'synset': 'wagon_wheel.n.01', 'synonyms': ['wagon_wheel'], 'id': 1152, 'def': 'a wheel of a wagon', 'name': 'wagon_wheel'}, {'frequency': 'c', 'synset': 'walking_stick.n.01', 'synonyms': ['walking_stick'], 'id': 1153, 'def': 'a stick carried in the hand for support in walking', 'name': 'walking_stick'}, {'frequency': 'c', 'synset': 'wall_clock.n.01', 'synonyms': ['wall_clock'], 'id': 1154, 'def': 'a clock mounted on a wall', 'name': 'wall_clock'}, {'frequency': 'f', 'synset': 'wall_socket.n.01', 'synonyms': ['wall_socket', 'wall_plug', 'electric_outlet', 'electrical_outlet', 'outlet', 'electric_receptacle'], 'id': 1155, 'def': 'receptacle providing a place in a wiring system where current can be taken to run electrical devices', 'name': 'wall_socket'}, {'frequency': 'f', 'synset': 'wallet.n.01', 'synonyms': ['wallet', 'billfold'], 'id': 1156, 'def': 'a pocket-size case for holding papers and paper money', 'name': 'wallet'}, {'frequency': 'r', 'synset': 'walrus.n.01', 'synonyms': ['walrus'], 'id': 1157, 'def': 'either of two large northern marine mammals having ivory tusks and tough hide over thick blubber', 'name': 'walrus'}, {'frequency': 'r', 'synset': 'wardrobe.n.01', 'synonyms': ['wardrobe'], 'id': 1158, 'def': 'a tall piece of furniture that provides storage space for clothes; has a door and rails or hooks for hanging clothes', 'name': 'wardrobe'}, {'frequency': 'r', 'synset': 'washbasin.n.01', 'synonyms': ['washbasin', 'basin_(for_washing)', 'washbowl', 'washstand', 'handbasin'], 'id': 1159, 'def': 'a bathroom sink that is permanently installed and connected to a water supply and drainpipe; where you can wash your hands and face', 'name': 'washbasin'}, {'frequency': 'c', 'synset': 'washer.n.03', 'synonyms': ['automatic_washer', 'washing_machine'], 'id': 1160, 'def': 'a home appliance for washing clothes and linens automatically', 'name': 'automatic_washer'}, {'frequency': 'f', 'synset': 'watch.n.01', 'synonyms': ['watch', 'wristwatch'], 'id': 1161, 'def': 'a small, portable timepiece', 'name': 'watch'}, {'frequency': 'f', 'synset': 'water_bottle.n.01', 'synonyms': ['water_bottle'], 'id': 1162, 'def': 'a bottle for holding water', 'name': 'water_bottle'}, {'frequency': 'c', 'synset': 'water_cooler.n.01', 'synonyms': ['water_cooler'], 'id': 1163, 'def': 'a device for cooling and dispensing drinking water', 'name': 'water_cooler'}, {'frequency': 'c', 'synset': 'water_faucet.n.01', 'synonyms': ['water_faucet', 'water_tap', 'tap_(water_faucet)'], 'id': 1164, 'def': 'a faucet for drawing water from a pipe or cask', 'name': 'water_faucet'}, {'frequency': 'r', 'synset': 'water_heater.n.01', 'synonyms': ['water_heater', 'hot-water_heater'], 'id': 1165, 'def': 'a heater and storage tank to supply heated water', 'name': 'water_heater'}, {'frequency': 'c', 'synset': 'water_jug.n.01', 'synonyms': ['water_jug'], 'id': 1166, 'def': 'a jug that holds water', 'name': 'water_jug'}, {'frequency': 'r', 'synset': 'water_pistol.n.01', 'synonyms': ['water_gun', 'squirt_gun'], 'id': 1167, 'def': 'plaything consisting of a toy pistol that squirts water', 'name': 'water_gun'}, {'frequency': 'c', 'synset': 'water_scooter.n.01', 'synonyms': ['water_scooter', 'sea_scooter', 'jet_ski'], 'id': 1168, 'def': 'a motorboat resembling a motor scooter (NOT A SURFBOARD OR WATER SKI)', 'name': 'water_scooter'}, {'frequency': 'c', 'synset': 'water_ski.n.01', 'synonyms': ['water_ski'], 'id': 1169, 'def': 'broad ski for skimming over water towed by a speedboat (DO NOT MARK WATER)', 'name': 'water_ski'}, {'frequency': 'c', 'synset': 'water_tower.n.01', 'synonyms': ['water_tower'], 'id': 1170, 'def': 'a large reservoir for water', 'name': 'water_tower'}, {'frequency': 'c', 'synset': 'watering_can.n.01', 'synonyms': ['watering_can'], 'id': 1171, 'def': 'a container with a handle and a spout with a perforated nozzle; used to sprinkle water over plants', 'name': 'watering_can'}, {'frequency': 'f', 'synset': 'watermelon.n.02', 'synonyms': ['watermelon'], 'id': 1172, 'def': 'large oblong or roundish melon with a hard green rind and sweet watery red or occasionally yellowish pulp', 'name': 'watermelon'}, {'frequency': 'f', 'synset': 'weathervane.n.01', 'synonyms': ['weathervane', 'vane_(weathervane)', 'wind_vane'], 'id': 1173, 'def': 'mechanical device attached to an elevated structure; rotates freely to show the direction of the wind', 'name': 'weathervane'}, {'frequency': 'c', 'synset': 'webcam.n.01', 'synonyms': ['webcam'], 'id': 1174, 'def': 'a digital camera designed to take digital photographs and transmit them over the internet', 'name': 'webcam'}, {'frequency': 'c', 'synset': 'wedding_cake.n.01', 'synonyms': ['wedding_cake', 'bridecake'], 'id': 1175, 'def': 'a rich cake with two or more tiers and covered with frosting and decorations; served at a wedding reception', 'name': 'wedding_cake'}, {'frequency': 'c', 'synset': 'wedding_ring.n.01', 'synonyms': ['wedding_ring', 'wedding_band'], 'id': 1176, 'def': 'a ring given to the bride and/or groom at the wedding', 'name': 'wedding_ring'}, {'frequency': 'f', 'synset': 'wet_suit.n.01', 'synonyms': ['wet_suit'], 'id': 1177, 'def': 'a close-fitting garment made of a permeable material; worn in cold water to retain body heat', 'name': 'wet_suit'}, {'frequency': 'f', 'synset': 'wheel.n.01', 'synonyms': ['wheel'], 'id': 1178, 'def': 'a circular frame with spokes (or a solid disc) that can rotate on a shaft or axle', 'name': 'wheel'}, {'frequency': 'c', 'synset': 'wheelchair.n.01', 'synonyms': ['wheelchair'], 'id': 1179, 'def': 'a movable chair mounted on large wheels', 'name': 'wheelchair'}, {'frequency': 'c', 'synset': 'whipped_cream.n.01', 'synonyms': ['whipped_cream'], 'id': 1180, 'def': 'cream that has been beaten until light and fluffy', 'name': 'whipped_cream'}, {'frequency': 'c', 'synset': 'whistle.n.03', 'synonyms': ['whistle'], 'id': 1181, 'def': 'a small wind instrument that produces a whistling sound by blowing into it', 'name': 'whistle'}, {'frequency': 'c', 'synset': 'wig.n.01', 'synonyms': ['wig'], 'id': 1182, 'def': 'hairpiece covering the head and made of real or synthetic hair', 'name': 'wig'}, {'frequency': 'c', 'synset': 'wind_chime.n.01', 'synonyms': ['wind_chime'], 'id': 1183, 'def': 'a decorative arrangement of pieces of metal or glass or pottery that hang together loosely so the wind can cause them to tinkle', 'name': 'wind_chime'}, {'frequency': 'c', 'synset': 'windmill.n.01', 'synonyms': ['windmill'], 'id': 1184, 'def': 'A mill or turbine that is powered by wind', 'name': 'windmill'}, {'frequency': 'c', 'synset': 'window_box.n.01', 'synonyms': ['window_box_(for_plants)'], 'id': 1185, 'def': 'a container for growing plants on a windowsill', 'name': 'window_box_(for_plants)'}, {'frequency': 'f', 'synset': 'windshield_wiper.n.01', 'synonyms': ['windshield_wiper', 'windscreen_wiper', 'wiper_(for_windshield/screen)'], 'id': 1186, 'def': 'a mechanical device that cleans the windshield', 'name': 'windshield_wiper'}, {'frequency': 'c', 'synset': 'windsock.n.01', 'synonyms': ['windsock', 'air_sock', 'air-sleeve', 'wind_sleeve', 'wind_cone'], 'id': 1187, 'def': 'a truncated cloth cone mounted on a mast/pole; shows wind direction', 'name': 'windsock'}, {'frequency': 'f', 'synset': 'wine_bottle.n.01', 'synonyms': ['wine_bottle'], 'id': 1188, 'def': 'a bottle for holding wine', 'name': 'wine_bottle'}, {'frequency': 'c', 'synset': 'wine_bucket.n.01', 'synonyms': ['wine_bucket', 'wine_cooler'], 'id': 1189, 'def': 'a bucket of ice used to chill a bottle of wine', 'name': 'wine_bucket'}, {'frequency': 'f', 'synset': 'wineglass.n.01', 'synonyms': ['wineglass'], 'id': 1190, 'def': 'a glass that has a stem and in which wine is served', 'name': 'wineglass'}, {'frequency': 'f', 'synset': 'winker.n.02', 'synonyms': ['blinder_(for_horses)'], 'id': 1191, 'def': 'blinds that prevent a horse from seeing something on either side', 'name': 'blinder_(for_horses)'}, {'frequency': 'c', 'synset': 'wok.n.01', 'synonyms': ['wok'], 'id': 1192, 'def': 'pan with a convex bottom; used for frying in Chinese cooking', 'name': 'wok'}, {'frequency': 'r', 'synset': 'wolf.n.01', 'synonyms': ['wolf'], 'id': 1193, 'def': 'a wild carnivorous mammal of the dog family, living and hunting in packs', 'name': 'wolf'}, {'frequency': 'c', 'synset': 'wooden_spoon.n.02', 'synonyms': ['wooden_spoon'], 'id': 1194, 'def': 'a spoon made of wood', 'name': 'wooden_spoon'}, {'frequency': 'c', 'synset': 'wreath.n.01', 'synonyms': ['wreath'], 'id': 1195, 'def': 'an arrangement of flowers, leaves, or stems fastened in a ring', 'name': 'wreath'}, {'frequency': 'c', 'synset': 'wrench.n.03', 'synonyms': ['wrench', 'spanner'], 'id': 1196, 'def': 'a hand tool that is used to hold or twist a nut or bolt', 'name': 'wrench'}, {'frequency': 'f', 'synset': 'wristband.n.01', 'synonyms': ['wristband'], 'id': 1197, 'def': 'band consisting of a part of a sleeve that covers the wrist', 'name': 'wristband'}, {'frequency': 'f', 'synset': 'wristlet.n.01', 'synonyms': ['wristlet', 'wrist_band'], 'id': 1198, 'def': 'a band or bracelet worn around the wrist', 'name': 'wristlet'}, {'frequency': 'c', 'synset': 'yacht.n.01', 'synonyms': ['yacht'], 'id': 1199, 'def': 'an expensive vessel propelled by sail or power and used for cruising or racing', 'name': 'yacht'}, {'frequency': 'c', 'synset': 'yogurt.n.01', 'synonyms': ['yogurt', 'yoghurt', 'yoghourt'], 'id': 1200, 'def': 'a custard-like food made from curdled milk', 'name': 'yogurt'}, {'frequency': 'c', 'synset': 'yoke.n.07', 'synonyms': ['yoke_(animal_equipment)'], 'id': 1201, 'def': 'gear joining two animals at the neck; NOT egg yolk', 'name': 'yoke_(animal_equipment)'}, {'frequency': 'f', 'synset': 'zebra.n.01', 'synonyms': ['zebra'], 'id': 1202, 'def': 'any of several fleet black-and-white striped African equines', 'name': 'zebra'}, {'frequency': 'c', 'synset': 'zucchini.n.02', 'synonyms': ['zucchini', 'courgette'], 'id': 1203, 'def': 'small cucumber-shaped vegetable marrow; typically dark green', 'name': 'zucchini'}] # noqa
+# fmt: on
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/lvis_v1_category_image_count.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/lvis_v1_category_image_count.py
new file mode 100644
index 0000000000000000000000000000000000000000..31bf0cfcd5096ab87835db86a28671d474514c40
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/lvis_v1_category_image_count.py
@@ -0,0 +1,20 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# Autogen with
+# with open("lvis_v1_train.json", "r") as f:
+# a = json.load(f)
+# c = a["categories"]
+# for x in c:
+# del x["name"]
+# del x["instance_count"]
+# del x["def"]
+# del x["synonyms"]
+# del x["frequency"]
+# del x["synset"]
+# LVIS_CATEGORY_IMAGE_COUNT = repr(c) + " # noqa"
+# with open("/tmp/lvis_category_image_count.py", "wt") as f:
+# f.write(f"LVIS_CATEGORY_IMAGE_COUNT = {LVIS_CATEGORY_IMAGE_COUNT}")
+# Then paste the contents of that file below
+
+# fmt: off
+LVIS_CATEGORY_IMAGE_COUNT = [{'id': 1, 'image_count': 64}, {'id': 2, 'image_count': 364}, {'id': 3, 'image_count': 1911}, {'id': 4, 'image_count': 149}, {'id': 5, 'image_count': 29}, {'id': 6, 'image_count': 26}, {'id': 7, 'image_count': 59}, {'id': 8, 'image_count': 22}, {'id': 9, 'image_count': 12}, {'id': 10, 'image_count': 28}, {'id': 11, 'image_count': 505}, {'id': 12, 'image_count': 1207}, {'id': 13, 'image_count': 4}, {'id': 14, 'image_count': 10}, {'id': 15, 'image_count': 500}, {'id': 16, 'image_count': 33}, {'id': 17, 'image_count': 3}, {'id': 18, 'image_count': 44}, {'id': 19, 'image_count': 561}, {'id': 20, 'image_count': 8}, {'id': 21, 'image_count': 9}, {'id': 22, 'image_count': 33}, {'id': 23, 'image_count': 1883}, {'id': 24, 'image_count': 98}, {'id': 25, 'image_count': 70}, {'id': 26, 'image_count': 46}, {'id': 27, 'image_count': 117}, {'id': 28, 'image_count': 41}, {'id': 29, 'image_count': 1395}, {'id': 30, 'image_count': 7}, {'id': 31, 'image_count': 1}, {'id': 32, 'image_count': 314}, {'id': 33, 'image_count': 31}, {'id': 34, 'image_count': 1905}, {'id': 35, 'image_count': 1859}, {'id': 36, 'image_count': 1623}, {'id': 37, 'image_count': 47}, {'id': 38, 'image_count': 3}, {'id': 39, 'image_count': 3}, {'id': 40, 'image_count': 1}, {'id': 41, 'image_count': 305}, {'id': 42, 'image_count': 6}, {'id': 43, 'image_count': 210}, {'id': 44, 'image_count': 36}, {'id': 45, 'image_count': 1787}, {'id': 46, 'image_count': 17}, {'id': 47, 'image_count': 51}, {'id': 48, 'image_count': 138}, {'id': 49, 'image_count': 3}, {'id': 50, 'image_count': 1470}, {'id': 51, 'image_count': 3}, {'id': 52, 'image_count': 2}, {'id': 53, 'image_count': 186}, {'id': 54, 'image_count': 76}, {'id': 55, 'image_count': 26}, {'id': 56, 'image_count': 303}, {'id': 57, 'image_count': 738}, {'id': 58, 'image_count': 1799}, {'id': 59, 'image_count': 1934}, {'id': 60, 'image_count': 1609}, {'id': 61, 'image_count': 1622}, {'id': 62, 'image_count': 41}, {'id': 63, 'image_count': 4}, {'id': 64, 'image_count': 11}, {'id': 65, 'image_count': 270}, {'id': 66, 'image_count': 349}, {'id': 67, 'image_count': 42}, {'id': 68, 'image_count': 823}, {'id': 69, 'image_count': 6}, {'id': 70, 'image_count': 48}, {'id': 71, 'image_count': 3}, {'id': 72, 'image_count': 42}, {'id': 73, 'image_count': 24}, {'id': 74, 'image_count': 16}, {'id': 75, 'image_count': 605}, {'id': 76, 'image_count': 646}, {'id': 77, 'image_count': 1765}, {'id': 78, 'image_count': 2}, {'id': 79, 'image_count': 125}, {'id': 80, 'image_count': 1420}, {'id': 81, 'image_count': 140}, {'id': 82, 'image_count': 4}, {'id': 83, 'image_count': 322}, {'id': 84, 'image_count': 60}, {'id': 85, 'image_count': 2}, {'id': 86, 'image_count': 231}, {'id': 87, 'image_count': 333}, {'id': 88, 'image_count': 1941}, {'id': 89, 'image_count': 367}, {'id': 90, 'image_count': 1922}, {'id': 91, 'image_count': 18}, {'id': 92, 'image_count': 81}, {'id': 93, 'image_count': 1}, {'id': 94, 'image_count': 1852}, {'id': 95, 'image_count': 430}, {'id': 96, 'image_count': 247}, {'id': 97, 'image_count': 94}, {'id': 98, 'image_count': 21}, {'id': 99, 'image_count': 1821}, {'id': 100, 'image_count': 16}, {'id': 101, 'image_count': 12}, {'id': 102, 'image_count': 25}, {'id': 103, 'image_count': 41}, {'id': 104, 'image_count': 244}, {'id': 105, 'image_count': 7}, {'id': 106, 'image_count': 1}, {'id': 107, 'image_count': 40}, {'id': 108, 'image_count': 40}, {'id': 109, 'image_count': 104}, {'id': 110, 'image_count': 1671}, {'id': 111, 'image_count': 49}, {'id': 112, 'image_count': 243}, {'id': 113, 'image_count': 2}, {'id': 114, 'image_count': 242}, {'id': 115, 'image_count': 271}, {'id': 116, 'image_count': 104}, {'id': 117, 'image_count': 8}, {'id': 118, 'image_count': 1758}, {'id': 119, 'image_count': 1}, {'id': 120, 'image_count': 48}, {'id': 121, 'image_count': 14}, {'id': 122, 'image_count': 40}, {'id': 123, 'image_count': 1}, {'id': 124, 'image_count': 37}, {'id': 125, 'image_count': 1510}, {'id': 126, 'image_count': 6}, {'id': 127, 'image_count': 1903}, {'id': 128, 'image_count': 70}, {'id': 129, 'image_count': 86}, {'id': 130, 'image_count': 7}, {'id': 131, 'image_count': 5}, {'id': 132, 'image_count': 1406}, {'id': 133, 'image_count': 1901}, {'id': 134, 'image_count': 15}, {'id': 135, 'image_count': 28}, {'id': 136, 'image_count': 6}, {'id': 137, 'image_count': 494}, {'id': 138, 'image_count': 234}, {'id': 139, 'image_count': 1922}, {'id': 140, 'image_count': 1}, {'id': 141, 'image_count': 35}, {'id': 142, 'image_count': 5}, {'id': 143, 'image_count': 1828}, {'id': 144, 'image_count': 8}, {'id': 145, 'image_count': 63}, {'id': 146, 'image_count': 1668}, {'id': 147, 'image_count': 4}, {'id': 148, 'image_count': 95}, {'id': 149, 'image_count': 17}, {'id': 150, 'image_count': 1567}, {'id': 151, 'image_count': 2}, {'id': 152, 'image_count': 103}, {'id': 153, 'image_count': 50}, {'id': 154, 'image_count': 1309}, {'id': 155, 'image_count': 6}, {'id': 156, 'image_count': 92}, {'id': 157, 'image_count': 19}, {'id': 158, 'image_count': 37}, {'id': 159, 'image_count': 4}, {'id': 160, 'image_count': 709}, {'id': 161, 'image_count': 9}, {'id': 162, 'image_count': 82}, {'id': 163, 'image_count': 15}, {'id': 164, 'image_count': 3}, {'id': 165, 'image_count': 61}, {'id': 166, 'image_count': 51}, {'id': 167, 'image_count': 5}, {'id': 168, 'image_count': 13}, {'id': 169, 'image_count': 642}, {'id': 170, 'image_count': 24}, {'id': 171, 'image_count': 255}, {'id': 172, 'image_count': 9}, {'id': 173, 'image_count': 1808}, {'id': 174, 'image_count': 31}, {'id': 175, 'image_count': 158}, {'id': 176, 'image_count': 80}, {'id': 177, 'image_count': 1884}, {'id': 178, 'image_count': 158}, {'id': 179, 'image_count': 2}, {'id': 180, 'image_count': 12}, {'id': 181, 'image_count': 1659}, {'id': 182, 'image_count': 7}, {'id': 183, 'image_count': 834}, {'id': 184, 'image_count': 57}, {'id': 185, 'image_count': 174}, {'id': 186, 'image_count': 95}, {'id': 187, 'image_count': 27}, {'id': 188, 'image_count': 22}, {'id': 189, 'image_count': 1391}, {'id': 190, 'image_count': 90}, {'id': 191, 'image_count': 40}, {'id': 192, 'image_count': 445}, {'id': 193, 'image_count': 21}, {'id': 194, 'image_count': 1132}, {'id': 195, 'image_count': 177}, {'id': 196, 'image_count': 4}, {'id': 197, 'image_count': 17}, {'id': 198, 'image_count': 84}, {'id': 199, 'image_count': 55}, {'id': 200, 'image_count': 30}, {'id': 201, 'image_count': 25}, {'id': 202, 'image_count': 2}, {'id': 203, 'image_count': 125}, {'id': 204, 'image_count': 1135}, {'id': 205, 'image_count': 19}, {'id': 206, 'image_count': 72}, {'id': 207, 'image_count': 1926}, {'id': 208, 'image_count': 159}, {'id': 209, 'image_count': 7}, {'id': 210, 'image_count': 1}, {'id': 211, 'image_count': 13}, {'id': 212, 'image_count': 35}, {'id': 213, 'image_count': 18}, {'id': 214, 'image_count': 8}, {'id': 215, 'image_count': 6}, {'id': 216, 'image_count': 35}, {'id': 217, 'image_count': 1222}, {'id': 218, 'image_count': 103}, {'id': 219, 'image_count': 28}, {'id': 220, 'image_count': 63}, {'id': 221, 'image_count': 28}, {'id': 222, 'image_count': 5}, {'id': 223, 'image_count': 7}, {'id': 224, 'image_count': 14}, {'id': 225, 'image_count': 1918}, {'id': 226, 'image_count': 133}, {'id': 227, 'image_count': 16}, {'id': 228, 'image_count': 27}, {'id': 229, 'image_count': 110}, {'id': 230, 'image_count': 1895}, {'id': 231, 'image_count': 4}, {'id': 232, 'image_count': 1927}, {'id': 233, 'image_count': 8}, {'id': 234, 'image_count': 1}, {'id': 235, 'image_count': 263}, {'id': 236, 'image_count': 10}, {'id': 237, 'image_count': 2}, {'id': 238, 'image_count': 3}, {'id': 239, 'image_count': 87}, {'id': 240, 'image_count': 9}, {'id': 241, 'image_count': 71}, {'id': 242, 'image_count': 13}, {'id': 243, 'image_count': 18}, {'id': 244, 'image_count': 2}, {'id': 245, 'image_count': 5}, {'id': 246, 'image_count': 45}, {'id': 247, 'image_count': 1}, {'id': 248, 'image_count': 23}, {'id': 249, 'image_count': 32}, {'id': 250, 'image_count': 4}, {'id': 251, 'image_count': 1}, {'id': 252, 'image_count': 858}, {'id': 253, 'image_count': 661}, {'id': 254, 'image_count': 168}, {'id': 255, 'image_count': 210}, {'id': 256, 'image_count': 65}, {'id': 257, 'image_count': 4}, {'id': 258, 'image_count': 2}, {'id': 259, 'image_count': 159}, {'id': 260, 'image_count': 31}, {'id': 261, 'image_count': 811}, {'id': 262, 'image_count': 1}, {'id': 263, 'image_count': 42}, {'id': 264, 'image_count': 27}, {'id': 265, 'image_count': 2}, {'id': 266, 'image_count': 5}, {'id': 267, 'image_count': 95}, {'id': 268, 'image_count': 32}, {'id': 269, 'image_count': 1}, {'id': 270, 'image_count': 1}, {'id': 271, 'image_count': 1844}, {'id': 272, 'image_count': 897}, {'id': 273, 'image_count': 31}, {'id': 274, 'image_count': 23}, {'id': 275, 'image_count': 1}, {'id': 276, 'image_count': 202}, {'id': 277, 'image_count': 746}, {'id': 278, 'image_count': 44}, {'id': 279, 'image_count': 14}, {'id': 280, 'image_count': 26}, {'id': 281, 'image_count': 1}, {'id': 282, 'image_count': 2}, {'id': 283, 'image_count': 25}, {'id': 284, 'image_count': 238}, {'id': 285, 'image_count': 592}, {'id': 286, 'image_count': 26}, {'id': 287, 'image_count': 5}, {'id': 288, 'image_count': 42}, {'id': 289, 'image_count': 13}, {'id': 290, 'image_count': 46}, {'id': 291, 'image_count': 1}, {'id': 292, 'image_count': 8}, {'id': 293, 'image_count': 34}, {'id': 294, 'image_count': 5}, {'id': 295, 'image_count': 1}, {'id': 296, 'image_count': 1871}, {'id': 297, 'image_count': 717}, {'id': 298, 'image_count': 1010}, {'id': 299, 'image_count': 679}, {'id': 300, 'image_count': 3}, {'id': 301, 'image_count': 4}, {'id': 302, 'image_count': 1}, {'id': 303, 'image_count': 166}, {'id': 304, 'image_count': 2}, {'id': 305, 'image_count': 266}, {'id': 306, 'image_count': 101}, {'id': 307, 'image_count': 6}, {'id': 308, 'image_count': 14}, {'id': 309, 'image_count': 133}, {'id': 310, 'image_count': 2}, {'id': 311, 'image_count': 38}, {'id': 312, 'image_count': 95}, {'id': 313, 'image_count': 1}, {'id': 314, 'image_count': 12}, {'id': 315, 'image_count': 49}, {'id': 316, 'image_count': 5}, {'id': 317, 'image_count': 5}, {'id': 318, 'image_count': 16}, {'id': 319, 'image_count': 216}, {'id': 320, 'image_count': 12}, {'id': 321, 'image_count': 1}, {'id': 322, 'image_count': 54}, {'id': 323, 'image_count': 5}, {'id': 324, 'image_count': 245}, {'id': 325, 'image_count': 12}, {'id': 326, 'image_count': 7}, {'id': 327, 'image_count': 35}, {'id': 328, 'image_count': 36}, {'id': 329, 'image_count': 32}, {'id': 330, 'image_count': 1027}, {'id': 331, 'image_count': 10}, {'id': 332, 'image_count': 12}, {'id': 333, 'image_count': 1}, {'id': 334, 'image_count': 67}, {'id': 335, 'image_count': 71}, {'id': 336, 'image_count': 30}, {'id': 337, 'image_count': 48}, {'id': 338, 'image_count': 249}, {'id': 339, 'image_count': 13}, {'id': 340, 'image_count': 29}, {'id': 341, 'image_count': 14}, {'id': 342, 'image_count': 236}, {'id': 343, 'image_count': 15}, {'id': 344, 'image_count': 1521}, {'id': 345, 'image_count': 25}, {'id': 346, 'image_count': 249}, {'id': 347, 'image_count': 139}, {'id': 348, 'image_count': 2}, {'id': 349, 'image_count': 2}, {'id': 350, 'image_count': 1890}, {'id': 351, 'image_count': 1240}, {'id': 352, 'image_count': 1}, {'id': 353, 'image_count': 9}, {'id': 354, 'image_count': 1}, {'id': 355, 'image_count': 3}, {'id': 356, 'image_count': 11}, {'id': 357, 'image_count': 4}, {'id': 358, 'image_count': 236}, {'id': 359, 'image_count': 44}, {'id': 360, 'image_count': 19}, {'id': 361, 'image_count': 1100}, {'id': 362, 'image_count': 7}, {'id': 363, 'image_count': 69}, {'id': 364, 'image_count': 2}, {'id': 365, 'image_count': 8}, {'id': 366, 'image_count': 5}, {'id': 367, 'image_count': 227}, {'id': 368, 'image_count': 6}, {'id': 369, 'image_count': 106}, {'id': 370, 'image_count': 81}, {'id': 371, 'image_count': 17}, {'id': 372, 'image_count': 134}, {'id': 373, 'image_count': 312}, {'id': 374, 'image_count': 8}, {'id': 375, 'image_count': 271}, {'id': 376, 'image_count': 2}, {'id': 377, 'image_count': 103}, {'id': 378, 'image_count': 1938}, {'id': 379, 'image_count': 574}, {'id': 380, 'image_count': 120}, {'id': 381, 'image_count': 2}, {'id': 382, 'image_count': 2}, {'id': 383, 'image_count': 13}, {'id': 384, 'image_count': 29}, {'id': 385, 'image_count': 1710}, {'id': 386, 'image_count': 66}, {'id': 387, 'image_count': 1008}, {'id': 388, 'image_count': 1}, {'id': 389, 'image_count': 3}, {'id': 390, 'image_count': 1942}, {'id': 391, 'image_count': 19}, {'id': 392, 'image_count': 1488}, {'id': 393, 'image_count': 46}, {'id': 394, 'image_count': 106}, {'id': 395, 'image_count': 115}, {'id': 396, 'image_count': 19}, {'id': 397, 'image_count': 2}, {'id': 398, 'image_count': 1}, {'id': 399, 'image_count': 28}, {'id': 400, 'image_count': 9}, {'id': 401, 'image_count': 192}, {'id': 402, 'image_count': 12}, {'id': 403, 'image_count': 21}, {'id': 404, 'image_count': 247}, {'id': 405, 'image_count': 6}, {'id': 406, 'image_count': 64}, {'id': 407, 'image_count': 7}, {'id': 408, 'image_count': 40}, {'id': 409, 'image_count': 542}, {'id': 410, 'image_count': 2}, {'id': 411, 'image_count': 1898}, {'id': 412, 'image_count': 36}, {'id': 413, 'image_count': 4}, {'id': 414, 'image_count': 1}, {'id': 415, 'image_count': 191}, {'id': 416, 'image_count': 6}, {'id': 417, 'image_count': 41}, {'id': 418, 'image_count': 39}, {'id': 419, 'image_count': 46}, {'id': 420, 'image_count': 1}, {'id': 421, 'image_count': 1451}, {'id': 422, 'image_count': 1878}, {'id': 423, 'image_count': 11}, {'id': 424, 'image_count': 82}, {'id': 425, 'image_count': 18}, {'id': 426, 'image_count': 1}, {'id': 427, 'image_count': 7}, {'id': 428, 'image_count': 3}, {'id': 429, 'image_count': 575}, {'id': 430, 'image_count': 1907}, {'id': 431, 'image_count': 8}, {'id': 432, 'image_count': 4}, {'id': 433, 'image_count': 32}, {'id': 434, 'image_count': 11}, {'id': 435, 'image_count': 4}, {'id': 436, 'image_count': 54}, {'id': 437, 'image_count': 202}, {'id': 438, 'image_count': 32}, {'id': 439, 'image_count': 3}, {'id': 440, 'image_count': 130}, {'id': 441, 'image_count': 119}, {'id': 442, 'image_count': 141}, {'id': 443, 'image_count': 29}, {'id': 444, 'image_count': 525}, {'id': 445, 'image_count': 1323}, {'id': 446, 'image_count': 2}, {'id': 447, 'image_count': 113}, {'id': 448, 'image_count': 16}, {'id': 449, 'image_count': 7}, {'id': 450, 'image_count': 35}, {'id': 451, 'image_count': 1908}, {'id': 452, 'image_count': 353}, {'id': 453, 'image_count': 18}, {'id': 454, 'image_count': 14}, {'id': 455, 'image_count': 77}, {'id': 456, 'image_count': 8}, {'id': 457, 'image_count': 37}, {'id': 458, 'image_count': 1}, {'id': 459, 'image_count': 346}, {'id': 460, 'image_count': 19}, {'id': 461, 'image_count': 1779}, {'id': 462, 'image_count': 23}, {'id': 463, 'image_count': 25}, {'id': 464, 'image_count': 67}, {'id': 465, 'image_count': 19}, {'id': 466, 'image_count': 28}, {'id': 467, 'image_count': 4}, {'id': 468, 'image_count': 27}, {'id': 469, 'image_count': 1861}, {'id': 470, 'image_count': 11}, {'id': 471, 'image_count': 13}, {'id': 472, 'image_count': 13}, {'id': 473, 'image_count': 32}, {'id': 474, 'image_count': 1767}, {'id': 475, 'image_count': 42}, {'id': 476, 'image_count': 17}, {'id': 477, 'image_count': 128}, {'id': 478, 'image_count': 1}, {'id': 479, 'image_count': 9}, {'id': 480, 'image_count': 10}, {'id': 481, 'image_count': 4}, {'id': 482, 'image_count': 9}, {'id': 483, 'image_count': 18}, {'id': 484, 'image_count': 41}, {'id': 485, 'image_count': 28}, {'id': 486, 'image_count': 3}, {'id': 487, 'image_count': 65}, {'id': 488, 'image_count': 9}, {'id': 489, 'image_count': 23}, {'id': 490, 'image_count': 24}, {'id': 491, 'image_count': 1}, {'id': 492, 'image_count': 2}, {'id': 493, 'image_count': 59}, {'id': 494, 'image_count': 48}, {'id': 495, 'image_count': 17}, {'id': 496, 'image_count': 1877}, {'id': 497, 'image_count': 18}, {'id': 498, 'image_count': 1920}, {'id': 499, 'image_count': 50}, {'id': 500, 'image_count': 1890}, {'id': 501, 'image_count': 99}, {'id': 502, 'image_count': 1530}, {'id': 503, 'image_count': 3}, {'id': 504, 'image_count': 11}, {'id': 505, 'image_count': 19}, {'id': 506, 'image_count': 3}, {'id': 507, 'image_count': 63}, {'id': 508, 'image_count': 5}, {'id': 509, 'image_count': 6}, {'id': 510, 'image_count': 233}, {'id': 511, 'image_count': 54}, {'id': 512, 'image_count': 36}, {'id': 513, 'image_count': 10}, {'id': 514, 'image_count': 124}, {'id': 515, 'image_count': 101}, {'id': 516, 'image_count': 3}, {'id': 517, 'image_count': 363}, {'id': 518, 'image_count': 3}, {'id': 519, 'image_count': 30}, {'id': 520, 'image_count': 18}, {'id': 521, 'image_count': 199}, {'id': 522, 'image_count': 97}, {'id': 523, 'image_count': 32}, {'id': 524, 'image_count': 121}, {'id': 525, 'image_count': 16}, {'id': 526, 'image_count': 12}, {'id': 527, 'image_count': 2}, {'id': 528, 'image_count': 214}, {'id': 529, 'image_count': 48}, {'id': 530, 'image_count': 26}, {'id': 531, 'image_count': 13}, {'id': 532, 'image_count': 4}, {'id': 533, 'image_count': 11}, {'id': 534, 'image_count': 123}, {'id': 535, 'image_count': 7}, {'id': 536, 'image_count': 200}, {'id': 537, 'image_count': 91}, {'id': 538, 'image_count': 9}, {'id': 539, 'image_count': 72}, {'id': 540, 'image_count': 1886}, {'id': 541, 'image_count': 4}, {'id': 542, 'image_count': 1}, {'id': 543, 'image_count': 1}, {'id': 544, 'image_count': 1932}, {'id': 545, 'image_count': 4}, {'id': 546, 'image_count': 56}, {'id': 547, 'image_count': 854}, {'id': 548, 'image_count': 755}, {'id': 549, 'image_count': 1843}, {'id': 550, 'image_count': 96}, {'id': 551, 'image_count': 7}, {'id': 552, 'image_count': 74}, {'id': 553, 'image_count': 66}, {'id': 554, 'image_count': 57}, {'id': 555, 'image_count': 44}, {'id': 556, 'image_count': 1905}, {'id': 557, 'image_count': 4}, {'id': 558, 'image_count': 90}, {'id': 559, 'image_count': 1635}, {'id': 560, 'image_count': 8}, {'id': 561, 'image_count': 5}, {'id': 562, 'image_count': 50}, {'id': 563, 'image_count': 545}, {'id': 564, 'image_count': 20}, {'id': 565, 'image_count': 193}, {'id': 566, 'image_count': 285}, {'id': 567, 'image_count': 3}, {'id': 568, 'image_count': 1}, {'id': 569, 'image_count': 1904}, {'id': 570, 'image_count': 294}, {'id': 571, 'image_count': 3}, {'id': 572, 'image_count': 5}, {'id': 573, 'image_count': 24}, {'id': 574, 'image_count': 2}, {'id': 575, 'image_count': 2}, {'id': 576, 'image_count': 16}, {'id': 577, 'image_count': 8}, {'id': 578, 'image_count': 154}, {'id': 579, 'image_count': 66}, {'id': 580, 'image_count': 1}, {'id': 581, 'image_count': 24}, {'id': 582, 'image_count': 1}, {'id': 583, 'image_count': 4}, {'id': 584, 'image_count': 75}, {'id': 585, 'image_count': 6}, {'id': 586, 'image_count': 126}, {'id': 587, 'image_count': 24}, {'id': 588, 'image_count': 22}, {'id': 589, 'image_count': 1872}, {'id': 590, 'image_count': 16}, {'id': 591, 'image_count': 423}, {'id': 592, 'image_count': 1927}, {'id': 593, 'image_count': 38}, {'id': 594, 'image_count': 3}, {'id': 595, 'image_count': 1945}, {'id': 596, 'image_count': 35}, {'id': 597, 'image_count': 1}, {'id': 598, 'image_count': 13}, {'id': 599, 'image_count': 9}, {'id': 600, 'image_count': 14}, {'id': 601, 'image_count': 37}, {'id': 602, 'image_count': 3}, {'id': 603, 'image_count': 4}, {'id': 604, 'image_count': 100}, {'id': 605, 'image_count': 195}, {'id': 606, 'image_count': 1}, {'id': 607, 'image_count': 12}, {'id': 608, 'image_count': 24}, {'id': 609, 'image_count': 489}, {'id': 610, 'image_count': 10}, {'id': 611, 'image_count': 1689}, {'id': 612, 'image_count': 42}, {'id': 613, 'image_count': 81}, {'id': 614, 'image_count': 894}, {'id': 615, 'image_count': 1868}, {'id': 616, 'image_count': 7}, {'id': 617, 'image_count': 1567}, {'id': 618, 'image_count': 10}, {'id': 619, 'image_count': 8}, {'id': 620, 'image_count': 7}, {'id': 621, 'image_count': 629}, {'id': 622, 'image_count': 89}, {'id': 623, 'image_count': 15}, {'id': 624, 'image_count': 134}, {'id': 625, 'image_count': 4}, {'id': 626, 'image_count': 1802}, {'id': 627, 'image_count': 595}, {'id': 628, 'image_count': 1210}, {'id': 629, 'image_count': 48}, {'id': 630, 'image_count': 418}, {'id': 631, 'image_count': 1846}, {'id': 632, 'image_count': 5}, {'id': 633, 'image_count': 221}, {'id': 634, 'image_count': 10}, {'id': 635, 'image_count': 7}, {'id': 636, 'image_count': 76}, {'id': 637, 'image_count': 22}, {'id': 638, 'image_count': 10}, {'id': 639, 'image_count': 341}, {'id': 640, 'image_count': 1}, {'id': 641, 'image_count': 705}, {'id': 642, 'image_count': 1900}, {'id': 643, 'image_count': 188}, {'id': 644, 'image_count': 227}, {'id': 645, 'image_count': 861}, {'id': 646, 'image_count': 6}, {'id': 647, 'image_count': 115}, {'id': 648, 'image_count': 5}, {'id': 649, 'image_count': 43}, {'id': 650, 'image_count': 14}, {'id': 651, 'image_count': 6}, {'id': 652, 'image_count': 15}, {'id': 653, 'image_count': 1167}, {'id': 654, 'image_count': 15}, {'id': 655, 'image_count': 994}, {'id': 656, 'image_count': 28}, {'id': 657, 'image_count': 2}, {'id': 658, 'image_count': 338}, {'id': 659, 'image_count': 334}, {'id': 660, 'image_count': 15}, {'id': 661, 'image_count': 102}, {'id': 662, 'image_count': 1}, {'id': 663, 'image_count': 8}, {'id': 664, 'image_count': 1}, {'id': 665, 'image_count': 1}, {'id': 666, 'image_count': 28}, {'id': 667, 'image_count': 91}, {'id': 668, 'image_count': 260}, {'id': 669, 'image_count': 131}, {'id': 670, 'image_count': 128}, {'id': 671, 'image_count': 3}, {'id': 672, 'image_count': 10}, {'id': 673, 'image_count': 39}, {'id': 674, 'image_count': 2}, {'id': 675, 'image_count': 925}, {'id': 676, 'image_count': 354}, {'id': 677, 'image_count': 31}, {'id': 678, 'image_count': 10}, {'id': 679, 'image_count': 215}, {'id': 680, 'image_count': 71}, {'id': 681, 'image_count': 43}, {'id': 682, 'image_count': 28}, {'id': 683, 'image_count': 34}, {'id': 684, 'image_count': 16}, {'id': 685, 'image_count': 273}, {'id': 686, 'image_count': 2}, {'id': 687, 'image_count': 999}, {'id': 688, 'image_count': 4}, {'id': 689, 'image_count': 107}, {'id': 690, 'image_count': 2}, {'id': 691, 'image_count': 1}, {'id': 692, 'image_count': 454}, {'id': 693, 'image_count': 9}, {'id': 694, 'image_count': 1901}, {'id': 695, 'image_count': 61}, {'id': 696, 'image_count': 91}, {'id': 697, 'image_count': 46}, {'id': 698, 'image_count': 1402}, {'id': 699, 'image_count': 74}, {'id': 700, 'image_count': 421}, {'id': 701, 'image_count': 226}, {'id': 702, 'image_count': 10}, {'id': 703, 'image_count': 1720}, {'id': 704, 'image_count': 261}, {'id': 705, 'image_count': 1337}, {'id': 706, 'image_count': 293}, {'id': 707, 'image_count': 62}, {'id': 708, 'image_count': 814}, {'id': 709, 'image_count': 407}, {'id': 710, 'image_count': 6}, {'id': 711, 'image_count': 16}, {'id': 712, 'image_count': 7}, {'id': 713, 'image_count': 1791}, {'id': 714, 'image_count': 2}, {'id': 715, 'image_count': 1915}, {'id': 716, 'image_count': 1940}, {'id': 717, 'image_count': 13}, {'id': 718, 'image_count': 16}, {'id': 719, 'image_count': 448}, {'id': 720, 'image_count': 12}, {'id': 721, 'image_count': 18}, {'id': 722, 'image_count': 4}, {'id': 723, 'image_count': 71}, {'id': 724, 'image_count': 189}, {'id': 725, 'image_count': 74}, {'id': 726, 'image_count': 103}, {'id': 727, 'image_count': 3}, {'id': 728, 'image_count': 110}, {'id': 729, 'image_count': 5}, {'id': 730, 'image_count': 9}, {'id': 731, 'image_count': 15}, {'id': 732, 'image_count': 25}, {'id': 733, 'image_count': 7}, {'id': 734, 'image_count': 647}, {'id': 735, 'image_count': 824}, {'id': 736, 'image_count': 100}, {'id': 737, 'image_count': 47}, {'id': 738, 'image_count': 121}, {'id': 739, 'image_count': 731}, {'id': 740, 'image_count': 73}, {'id': 741, 'image_count': 49}, {'id': 742, 'image_count': 23}, {'id': 743, 'image_count': 4}, {'id': 744, 'image_count': 62}, {'id': 745, 'image_count': 118}, {'id': 746, 'image_count': 99}, {'id': 747, 'image_count': 40}, {'id': 748, 'image_count': 1036}, {'id': 749, 'image_count': 105}, {'id': 750, 'image_count': 21}, {'id': 751, 'image_count': 229}, {'id': 752, 'image_count': 7}, {'id': 753, 'image_count': 72}, {'id': 754, 'image_count': 9}, {'id': 755, 'image_count': 10}, {'id': 756, 'image_count': 328}, {'id': 757, 'image_count': 468}, {'id': 758, 'image_count': 1}, {'id': 759, 'image_count': 2}, {'id': 760, 'image_count': 24}, {'id': 761, 'image_count': 11}, {'id': 762, 'image_count': 72}, {'id': 763, 'image_count': 17}, {'id': 764, 'image_count': 10}, {'id': 765, 'image_count': 17}, {'id': 766, 'image_count': 489}, {'id': 767, 'image_count': 47}, {'id': 768, 'image_count': 93}, {'id': 769, 'image_count': 1}, {'id': 770, 'image_count': 12}, {'id': 771, 'image_count': 228}, {'id': 772, 'image_count': 5}, {'id': 773, 'image_count': 76}, {'id': 774, 'image_count': 71}, {'id': 775, 'image_count': 30}, {'id': 776, 'image_count': 109}, {'id': 777, 'image_count': 14}, {'id': 778, 'image_count': 1}, {'id': 779, 'image_count': 8}, {'id': 780, 'image_count': 26}, {'id': 781, 'image_count': 339}, {'id': 782, 'image_count': 153}, {'id': 783, 'image_count': 2}, {'id': 784, 'image_count': 3}, {'id': 785, 'image_count': 8}, {'id': 786, 'image_count': 47}, {'id': 787, 'image_count': 8}, {'id': 788, 'image_count': 6}, {'id': 789, 'image_count': 116}, {'id': 790, 'image_count': 69}, {'id': 791, 'image_count': 13}, {'id': 792, 'image_count': 6}, {'id': 793, 'image_count': 1928}, {'id': 794, 'image_count': 79}, {'id': 795, 'image_count': 14}, {'id': 796, 'image_count': 7}, {'id': 797, 'image_count': 20}, {'id': 798, 'image_count': 114}, {'id': 799, 'image_count': 221}, {'id': 800, 'image_count': 502}, {'id': 801, 'image_count': 62}, {'id': 802, 'image_count': 87}, {'id': 803, 'image_count': 4}, {'id': 804, 'image_count': 1912}, {'id': 805, 'image_count': 7}, {'id': 806, 'image_count': 186}, {'id': 807, 'image_count': 18}, {'id': 808, 'image_count': 4}, {'id': 809, 'image_count': 3}, {'id': 810, 'image_count': 7}, {'id': 811, 'image_count': 1413}, {'id': 812, 'image_count': 7}, {'id': 813, 'image_count': 12}, {'id': 814, 'image_count': 248}, {'id': 815, 'image_count': 4}, {'id': 816, 'image_count': 1881}, {'id': 817, 'image_count': 529}, {'id': 818, 'image_count': 1932}, {'id': 819, 'image_count': 50}, {'id': 820, 'image_count': 3}, {'id': 821, 'image_count': 28}, {'id': 822, 'image_count': 10}, {'id': 823, 'image_count': 5}, {'id': 824, 'image_count': 5}, {'id': 825, 'image_count': 18}, {'id': 826, 'image_count': 14}, {'id': 827, 'image_count': 1890}, {'id': 828, 'image_count': 660}, {'id': 829, 'image_count': 8}, {'id': 830, 'image_count': 25}, {'id': 831, 'image_count': 10}, {'id': 832, 'image_count': 218}, {'id': 833, 'image_count': 36}, {'id': 834, 'image_count': 16}, {'id': 835, 'image_count': 808}, {'id': 836, 'image_count': 479}, {'id': 837, 'image_count': 1404}, {'id': 838, 'image_count': 307}, {'id': 839, 'image_count': 57}, {'id': 840, 'image_count': 28}, {'id': 841, 'image_count': 80}, {'id': 842, 'image_count': 11}, {'id': 843, 'image_count': 92}, {'id': 844, 'image_count': 20}, {'id': 845, 'image_count': 194}, {'id': 846, 'image_count': 23}, {'id': 847, 'image_count': 52}, {'id': 848, 'image_count': 673}, {'id': 849, 'image_count': 2}, {'id': 850, 'image_count': 2}, {'id': 851, 'image_count': 1}, {'id': 852, 'image_count': 2}, {'id': 853, 'image_count': 8}, {'id': 854, 'image_count': 80}, {'id': 855, 'image_count': 3}, {'id': 856, 'image_count': 3}, {'id': 857, 'image_count': 15}, {'id': 858, 'image_count': 2}, {'id': 859, 'image_count': 10}, {'id': 860, 'image_count': 386}, {'id': 861, 'image_count': 65}, {'id': 862, 'image_count': 3}, {'id': 863, 'image_count': 35}, {'id': 864, 'image_count': 5}, {'id': 865, 'image_count': 180}, {'id': 866, 'image_count': 99}, {'id': 867, 'image_count': 49}, {'id': 868, 'image_count': 28}, {'id': 869, 'image_count': 1}, {'id': 870, 'image_count': 52}, {'id': 871, 'image_count': 36}, {'id': 872, 'image_count': 70}, {'id': 873, 'image_count': 6}, {'id': 874, 'image_count': 29}, {'id': 875, 'image_count': 24}, {'id': 876, 'image_count': 1115}, {'id': 877, 'image_count': 61}, {'id': 878, 'image_count': 18}, {'id': 879, 'image_count': 18}, {'id': 880, 'image_count': 665}, {'id': 881, 'image_count': 1096}, {'id': 882, 'image_count': 29}, {'id': 883, 'image_count': 8}, {'id': 884, 'image_count': 14}, {'id': 885, 'image_count': 1622}, {'id': 886, 'image_count': 2}, {'id': 887, 'image_count': 3}, {'id': 888, 'image_count': 32}, {'id': 889, 'image_count': 55}, {'id': 890, 'image_count': 1}, {'id': 891, 'image_count': 10}, {'id': 892, 'image_count': 10}, {'id': 893, 'image_count': 47}, {'id': 894, 'image_count': 3}, {'id': 895, 'image_count': 29}, {'id': 896, 'image_count': 342}, {'id': 897, 'image_count': 25}, {'id': 898, 'image_count': 1469}, {'id': 899, 'image_count': 521}, {'id': 900, 'image_count': 347}, {'id': 901, 'image_count': 35}, {'id': 902, 'image_count': 7}, {'id': 903, 'image_count': 207}, {'id': 904, 'image_count': 108}, {'id': 905, 'image_count': 2}, {'id': 906, 'image_count': 34}, {'id': 907, 'image_count': 12}, {'id': 908, 'image_count': 10}, {'id': 909, 'image_count': 13}, {'id': 910, 'image_count': 361}, {'id': 911, 'image_count': 1023}, {'id': 912, 'image_count': 782}, {'id': 913, 'image_count': 2}, {'id': 914, 'image_count': 5}, {'id': 915, 'image_count': 247}, {'id': 916, 'image_count': 221}, {'id': 917, 'image_count': 4}, {'id': 918, 'image_count': 8}, {'id': 919, 'image_count': 158}, {'id': 920, 'image_count': 3}, {'id': 921, 'image_count': 752}, {'id': 922, 'image_count': 64}, {'id': 923, 'image_count': 707}, {'id': 924, 'image_count': 143}, {'id': 925, 'image_count': 1}, {'id': 926, 'image_count': 49}, {'id': 927, 'image_count': 126}, {'id': 928, 'image_count': 76}, {'id': 929, 'image_count': 11}, {'id': 930, 'image_count': 11}, {'id': 931, 'image_count': 4}, {'id': 932, 'image_count': 39}, {'id': 933, 'image_count': 11}, {'id': 934, 'image_count': 13}, {'id': 935, 'image_count': 91}, {'id': 936, 'image_count': 14}, {'id': 937, 'image_count': 5}, {'id': 938, 'image_count': 3}, {'id': 939, 'image_count': 10}, {'id': 940, 'image_count': 18}, {'id': 941, 'image_count': 9}, {'id': 942, 'image_count': 6}, {'id': 943, 'image_count': 951}, {'id': 944, 'image_count': 2}, {'id': 945, 'image_count': 1}, {'id': 946, 'image_count': 19}, {'id': 947, 'image_count': 1942}, {'id': 948, 'image_count': 1916}, {'id': 949, 'image_count': 139}, {'id': 950, 'image_count': 43}, {'id': 951, 'image_count': 1969}, {'id': 952, 'image_count': 5}, {'id': 953, 'image_count': 134}, {'id': 954, 'image_count': 74}, {'id': 955, 'image_count': 381}, {'id': 956, 'image_count': 1}, {'id': 957, 'image_count': 381}, {'id': 958, 'image_count': 6}, {'id': 959, 'image_count': 1826}, {'id': 960, 'image_count': 28}, {'id': 961, 'image_count': 1635}, {'id': 962, 'image_count': 1967}, {'id': 963, 'image_count': 16}, {'id': 964, 'image_count': 1926}, {'id': 965, 'image_count': 1789}, {'id': 966, 'image_count': 401}, {'id': 967, 'image_count': 1968}, {'id': 968, 'image_count': 1167}, {'id': 969, 'image_count': 1}, {'id': 970, 'image_count': 56}, {'id': 971, 'image_count': 17}, {'id': 972, 'image_count': 1}, {'id': 973, 'image_count': 58}, {'id': 974, 'image_count': 9}, {'id': 975, 'image_count': 8}, {'id': 976, 'image_count': 1124}, {'id': 977, 'image_count': 31}, {'id': 978, 'image_count': 16}, {'id': 979, 'image_count': 491}, {'id': 980, 'image_count': 432}, {'id': 981, 'image_count': 1945}, {'id': 982, 'image_count': 1899}, {'id': 983, 'image_count': 5}, {'id': 984, 'image_count': 28}, {'id': 985, 'image_count': 7}, {'id': 986, 'image_count': 146}, {'id': 987, 'image_count': 1}, {'id': 988, 'image_count': 25}, {'id': 989, 'image_count': 22}, {'id': 990, 'image_count': 1}, {'id': 991, 'image_count': 10}, {'id': 992, 'image_count': 9}, {'id': 993, 'image_count': 308}, {'id': 994, 'image_count': 4}, {'id': 995, 'image_count': 1969}, {'id': 996, 'image_count': 45}, {'id': 997, 'image_count': 12}, {'id': 998, 'image_count': 1}, {'id': 999, 'image_count': 85}, {'id': 1000, 'image_count': 1127}, {'id': 1001, 'image_count': 11}, {'id': 1002, 'image_count': 60}, {'id': 1003, 'image_count': 1}, {'id': 1004, 'image_count': 16}, {'id': 1005, 'image_count': 1}, {'id': 1006, 'image_count': 65}, {'id': 1007, 'image_count': 13}, {'id': 1008, 'image_count': 655}, {'id': 1009, 'image_count': 51}, {'id': 1010, 'image_count': 1}, {'id': 1011, 'image_count': 673}, {'id': 1012, 'image_count': 5}, {'id': 1013, 'image_count': 36}, {'id': 1014, 'image_count': 54}, {'id': 1015, 'image_count': 5}, {'id': 1016, 'image_count': 8}, {'id': 1017, 'image_count': 305}, {'id': 1018, 'image_count': 297}, {'id': 1019, 'image_count': 1053}, {'id': 1020, 'image_count': 223}, {'id': 1021, 'image_count': 1037}, {'id': 1022, 'image_count': 63}, {'id': 1023, 'image_count': 1881}, {'id': 1024, 'image_count': 507}, {'id': 1025, 'image_count': 333}, {'id': 1026, 'image_count': 1911}, {'id': 1027, 'image_count': 1765}, {'id': 1028, 'image_count': 1}, {'id': 1029, 'image_count': 5}, {'id': 1030, 'image_count': 1}, {'id': 1031, 'image_count': 9}, {'id': 1032, 'image_count': 2}, {'id': 1033, 'image_count': 151}, {'id': 1034, 'image_count': 82}, {'id': 1035, 'image_count': 1931}, {'id': 1036, 'image_count': 41}, {'id': 1037, 'image_count': 1895}, {'id': 1038, 'image_count': 24}, {'id': 1039, 'image_count': 22}, {'id': 1040, 'image_count': 35}, {'id': 1041, 'image_count': 69}, {'id': 1042, 'image_count': 962}, {'id': 1043, 'image_count': 588}, {'id': 1044, 'image_count': 21}, {'id': 1045, 'image_count': 825}, {'id': 1046, 'image_count': 52}, {'id': 1047, 'image_count': 5}, {'id': 1048, 'image_count': 5}, {'id': 1049, 'image_count': 5}, {'id': 1050, 'image_count': 1860}, {'id': 1051, 'image_count': 56}, {'id': 1052, 'image_count': 1582}, {'id': 1053, 'image_count': 7}, {'id': 1054, 'image_count': 2}, {'id': 1055, 'image_count': 1562}, {'id': 1056, 'image_count': 1885}, {'id': 1057, 'image_count': 1}, {'id': 1058, 'image_count': 5}, {'id': 1059, 'image_count': 137}, {'id': 1060, 'image_count': 1094}, {'id': 1061, 'image_count': 134}, {'id': 1062, 'image_count': 29}, {'id': 1063, 'image_count': 22}, {'id': 1064, 'image_count': 522}, {'id': 1065, 'image_count': 50}, {'id': 1066, 'image_count': 68}, {'id': 1067, 'image_count': 16}, {'id': 1068, 'image_count': 40}, {'id': 1069, 'image_count': 35}, {'id': 1070, 'image_count': 135}, {'id': 1071, 'image_count': 1413}, {'id': 1072, 'image_count': 772}, {'id': 1073, 'image_count': 50}, {'id': 1074, 'image_count': 1015}, {'id': 1075, 'image_count': 1}, {'id': 1076, 'image_count': 65}, {'id': 1077, 'image_count': 1900}, {'id': 1078, 'image_count': 1302}, {'id': 1079, 'image_count': 1977}, {'id': 1080, 'image_count': 2}, {'id': 1081, 'image_count': 29}, {'id': 1082, 'image_count': 36}, {'id': 1083, 'image_count': 138}, {'id': 1084, 'image_count': 4}, {'id': 1085, 'image_count': 67}, {'id': 1086, 'image_count': 26}, {'id': 1087, 'image_count': 25}, {'id': 1088, 'image_count': 33}, {'id': 1089, 'image_count': 37}, {'id': 1090, 'image_count': 50}, {'id': 1091, 'image_count': 270}, {'id': 1092, 'image_count': 12}, {'id': 1093, 'image_count': 316}, {'id': 1094, 'image_count': 41}, {'id': 1095, 'image_count': 224}, {'id': 1096, 'image_count': 105}, {'id': 1097, 'image_count': 1925}, {'id': 1098, 'image_count': 1021}, {'id': 1099, 'image_count': 1213}, {'id': 1100, 'image_count': 172}, {'id': 1101, 'image_count': 28}, {'id': 1102, 'image_count': 745}, {'id': 1103, 'image_count': 187}, {'id': 1104, 'image_count': 147}, {'id': 1105, 'image_count': 136}, {'id': 1106, 'image_count': 34}, {'id': 1107, 'image_count': 41}, {'id': 1108, 'image_count': 636}, {'id': 1109, 'image_count': 570}, {'id': 1110, 'image_count': 1149}, {'id': 1111, 'image_count': 61}, {'id': 1112, 'image_count': 1890}, {'id': 1113, 'image_count': 18}, {'id': 1114, 'image_count': 143}, {'id': 1115, 'image_count': 1517}, {'id': 1116, 'image_count': 7}, {'id': 1117, 'image_count': 943}, {'id': 1118, 'image_count': 6}, {'id': 1119, 'image_count': 1}, {'id': 1120, 'image_count': 11}, {'id': 1121, 'image_count': 101}, {'id': 1122, 'image_count': 1909}, {'id': 1123, 'image_count': 800}, {'id': 1124, 'image_count': 1}, {'id': 1125, 'image_count': 44}, {'id': 1126, 'image_count': 3}, {'id': 1127, 'image_count': 44}, {'id': 1128, 'image_count': 31}, {'id': 1129, 'image_count': 7}, {'id': 1130, 'image_count': 20}, {'id': 1131, 'image_count': 11}, {'id': 1132, 'image_count': 13}, {'id': 1133, 'image_count': 1924}, {'id': 1134, 'image_count': 113}, {'id': 1135, 'image_count': 2}, {'id': 1136, 'image_count': 139}, {'id': 1137, 'image_count': 12}, {'id': 1138, 'image_count': 37}, {'id': 1139, 'image_count': 1866}, {'id': 1140, 'image_count': 47}, {'id': 1141, 'image_count': 1468}, {'id': 1142, 'image_count': 729}, {'id': 1143, 'image_count': 24}, {'id': 1144, 'image_count': 1}, {'id': 1145, 'image_count': 10}, {'id': 1146, 'image_count': 3}, {'id': 1147, 'image_count': 14}, {'id': 1148, 'image_count': 4}, {'id': 1149, 'image_count': 29}, {'id': 1150, 'image_count': 4}, {'id': 1151, 'image_count': 70}, {'id': 1152, 'image_count': 46}, {'id': 1153, 'image_count': 14}, {'id': 1154, 'image_count': 48}, {'id': 1155, 'image_count': 1855}, {'id': 1156, 'image_count': 113}, {'id': 1157, 'image_count': 1}, {'id': 1158, 'image_count': 1}, {'id': 1159, 'image_count': 10}, {'id': 1160, 'image_count': 54}, {'id': 1161, 'image_count': 1923}, {'id': 1162, 'image_count': 630}, {'id': 1163, 'image_count': 31}, {'id': 1164, 'image_count': 69}, {'id': 1165, 'image_count': 7}, {'id': 1166, 'image_count': 11}, {'id': 1167, 'image_count': 1}, {'id': 1168, 'image_count': 30}, {'id': 1169, 'image_count': 50}, {'id': 1170, 'image_count': 45}, {'id': 1171, 'image_count': 28}, {'id': 1172, 'image_count': 114}, {'id': 1173, 'image_count': 193}, {'id': 1174, 'image_count': 21}, {'id': 1175, 'image_count': 91}, {'id': 1176, 'image_count': 31}, {'id': 1177, 'image_count': 1469}, {'id': 1178, 'image_count': 1924}, {'id': 1179, 'image_count': 87}, {'id': 1180, 'image_count': 77}, {'id': 1181, 'image_count': 11}, {'id': 1182, 'image_count': 47}, {'id': 1183, 'image_count': 21}, {'id': 1184, 'image_count': 47}, {'id': 1185, 'image_count': 70}, {'id': 1186, 'image_count': 1838}, {'id': 1187, 'image_count': 19}, {'id': 1188, 'image_count': 531}, {'id': 1189, 'image_count': 11}, {'id': 1190, 'image_count': 941}, {'id': 1191, 'image_count': 113}, {'id': 1192, 'image_count': 26}, {'id': 1193, 'image_count': 5}, {'id': 1194, 'image_count': 56}, {'id': 1195, 'image_count': 73}, {'id': 1196, 'image_count': 32}, {'id': 1197, 'image_count': 128}, {'id': 1198, 'image_count': 623}, {'id': 1199, 'image_count': 12}, {'id': 1200, 'image_count': 52}, {'id': 1201, 'image_count': 11}, {'id': 1202, 'image_count': 1674}, {'id': 1203, 'image_count': 81}] # noqa
+# fmt: on
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/pascal_voc.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/pascal_voc.py
new file mode 100644
index 0000000000000000000000000000000000000000..d67ce3ee5a6e323bd0859f8d6ce5c84a6c3cc21b
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/pascal_voc.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import numpy as np
+import os
+import xml.etree.ElementTree as ET
+from typing import List, Tuple, Union
+
+from custom_detectron2.data import DatasetCatalog, MetadataCatalog
+from custom_detectron2.structures import BoxMode
+from custom_detectron2.utils.file_io import PathManager
+
+__all__ = ["load_voc_instances", "register_pascal_voc"]
+
+
+# fmt: off
+CLASS_NAMES = (
+ "aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat",
+ "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person",
+ "pottedplant", "sheep", "sofa", "train", "tvmonitor"
+)
+# fmt: on
+
+
+def load_voc_instances(dirname: str, split: str, class_names: Union[List[str], Tuple[str, ...]]):
+ """
+ Load Pascal VOC detection annotations to Detectron2 format.
+
+ Args:
+ dirname: Contain "Annotations", "ImageSets", "JPEGImages"
+ split (str): one of "train", "test", "val", "trainval"
+ class_names: list or tuple of class names
+ """
+ with PathManager.open(os.path.join(dirname, "ImageSets", "Main", split + ".txt")) as f:
+ fileids = np.loadtxt(f, dtype=np.str)
+
+ # Needs to read many small annotation files. Makes sense at local
+ annotation_dirname = PathManager.get_local_path(os.path.join(dirname, "Annotations/"))
+ dicts = []
+ for fileid in fileids:
+ anno_file = os.path.join(annotation_dirname, fileid + ".xml")
+ jpeg_file = os.path.join(dirname, "JPEGImages", fileid + ".jpg")
+
+ with PathManager.open(anno_file) as f:
+ tree = ET.parse(f)
+
+ r = {
+ "file_name": jpeg_file,
+ "image_id": fileid,
+ "height": int(tree.findall("./size/height")[0].text),
+ "width": int(tree.findall("./size/width")[0].text),
+ }
+ instances = []
+
+ for obj in tree.findall("object"):
+ cls = obj.find("name").text
+ # We include "difficult" samples in training.
+ # Based on limited experiments, they don't hurt accuracy.
+ # difficult = int(obj.find("difficult").text)
+ # if difficult == 1:
+ # continue
+ bbox = obj.find("bndbox")
+ bbox = [float(bbox.find(x).text) for x in ["xmin", "ymin", "xmax", "ymax"]]
+ # Original annotations are integers in the range [1, W or H]
+ # Assuming they mean 1-based pixel indices (inclusive),
+ # a box with annotation (xmin=1, xmax=W) covers the whole image.
+ # In coordinate space this is represented by (xmin=0, xmax=W)
+ bbox[0] -= 1.0
+ bbox[1] -= 1.0
+ instances.append(
+ {"category_id": class_names.index(cls), "bbox": bbox, "bbox_mode": BoxMode.XYXY_ABS}
+ )
+ r["annotations"] = instances
+ dicts.append(r)
+ return dicts
+
+
+def register_pascal_voc(name, dirname, split, year, class_names=CLASS_NAMES):
+ DatasetCatalog.register(name, lambda: load_voc_instances(dirname, split, class_names))
+ MetadataCatalog.get(name).set(
+ thing_classes=list(class_names), dirname=dirname, year=year, split=split
+ )
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/register_coco.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/register_coco.py
new file mode 100644
index 0000000000000000000000000000000000000000..e564438d5bf016bcdbb65b4bbdc215d79f579f8a
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/datasets/register_coco.py
@@ -0,0 +1,3 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+from .coco import register_coco_instances # noqa
+from .coco_panoptic import register_coco_panoptic_separated # noqa
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/detection_utils.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/detection_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..425f3e2d501b1f68bec23f8d662a6aff83ddcebc
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/detection_utils.py
@@ -0,0 +1,659 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+"""
+Common data processing utilities that are used in a
+typical object detection data pipeline.
+"""
+import logging
+import numpy as np
+from typing import List, Union
+import custom_pycocotools.mask as mask_util
+import torch
+from PIL import Image
+
+from custom_detectron2.structures import (
+ BitMasks,
+ Boxes,
+ BoxMode,
+ Instances,
+ Keypoints,
+ PolygonMasks,
+ RotatedBoxes,
+ polygons_to_bitmask,
+)
+from custom_detectron2.utils.file_io import PathManager
+
+from . import transforms as T
+from .catalog import MetadataCatalog
+
+__all__ = [
+ "SizeMismatchError",
+ "convert_image_to_rgb",
+ "check_image_size",
+ "transform_proposals",
+ "transform_instance_annotations",
+ "annotations_to_instances",
+ "annotations_to_instances_rotated",
+ "build_augmentation",
+ "build_transform_gen",
+ "create_keypoint_hflip_indices",
+ "filter_empty_instances",
+ "read_image",
+]
+
+
+class SizeMismatchError(ValueError):
+ """
+ When loaded image has difference width/height compared with annotation.
+ """
+
+
+# https://en.wikipedia.org/wiki/YUV#SDTV_with_BT.601
+_M_RGB2YUV = [[0.299, 0.587, 0.114], [-0.14713, -0.28886, 0.436], [0.615, -0.51499, -0.10001]]
+_M_YUV2RGB = [[1.0, 0.0, 1.13983], [1.0, -0.39465, -0.58060], [1.0, 2.03211, 0.0]]
+
+# https://www.exiv2.org/tags.html
+_EXIF_ORIENT = 274 # exif 'Orientation' tag
+
+
+def convert_PIL_to_numpy(image, format):
+ """
+ Convert PIL image to numpy array of target format.
+
+ Args:
+ image (PIL.Image): a PIL image
+ format (str): the format of output image
+
+ Returns:
+ (np.ndarray): also see `read_image`
+ """
+ if format is not None:
+ # PIL only supports RGB, so convert to RGB and flip channels over below
+ conversion_format = format
+ if format in ["BGR", "YUV-BT.601"]:
+ conversion_format = "RGB"
+ image = image.convert(conversion_format)
+ image = np.asarray(image)
+ # PIL squeezes out the channel dimension for "L", so make it HWC
+ if format == "L":
+ image = np.expand_dims(image, -1)
+
+ # handle formats not supported by PIL
+ elif format == "BGR":
+ # flip channels if needed
+ image = image[:, :, ::-1]
+ elif format == "YUV-BT.601":
+ image = image / 255.0
+ image = np.dot(image, np.array(_M_RGB2YUV).T)
+
+ return image
+
+
+def convert_image_to_rgb(image, format):
+ """
+ Convert an image from given format to RGB.
+
+ Args:
+ image (np.ndarray or Tensor): an HWC image
+ format (str): the format of input image, also see `read_image`
+
+ Returns:
+ (np.ndarray): (H,W,3) RGB image in 0-255 range, can be either float or uint8
+ """
+ if isinstance(image, torch.Tensor):
+ image = image.cpu().numpy()
+ if format == "BGR":
+ image = image[:, :, [2, 1, 0]]
+ elif format == "YUV-BT.601":
+ image = np.dot(image, np.array(_M_YUV2RGB).T)
+ image = image * 255.0
+ else:
+ if format == "L":
+ image = image[:, :, 0]
+ image = image.astype(np.uint8)
+ image = np.asarray(Image.fromarray(image, mode=format).convert("RGB"))
+ return image
+
+
+def _apply_exif_orientation(image):
+ """
+ Applies the exif orientation correctly.
+
+ This code exists per the bug:
+ https://github.com/python-pillow/Pillow/issues/3973
+ with the function `ImageOps.exif_transpose`. The Pillow source raises errors with
+ various methods, especially `tobytes`
+
+ Function based on:
+ https://github.com/wkentaro/labelme/blob/v4.5.4/labelme/utils/image.py#L59
+ https://github.com/python-pillow/Pillow/blob/7.1.2/src/PIL/ImageOps.py#L527
+
+ Args:
+ image (PIL.Image): a PIL image
+
+ Returns:
+ (PIL.Image): the PIL image with exif orientation applied, if applicable
+ """
+ if not hasattr(image, "getexif"):
+ return image
+
+ try:
+ exif = image.getexif()
+ except Exception: # https://github.com/facebookresearch/detectron2/issues/1885
+ exif = None
+
+ if exif is None:
+ return image
+
+ orientation = exif.get(_EXIF_ORIENT)
+
+ method = {
+ 2: Image.FLIP_LEFT_RIGHT,
+ 3: Image.ROTATE_180,
+ 4: Image.FLIP_TOP_BOTTOM,
+ 5: Image.TRANSPOSE,
+ 6: Image.ROTATE_270,
+ 7: Image.TRANSVERSE,
+ 8: Image.ROTATE_90,
+ }.get(orientation)
+
+ if method is not None:
+ return image.transpose(method)
+ return image
+
+
+def read_image(file_name, format=None):
+ """
+ Read an image into the given format.
+ Will apply rotation and flipping if the image has such exif information.
+
+ Args:
+ file_name (str): image file path
+ format (str): one of the supported image modes in PIL, or "BGR" or "YUV-BT.601".
+
+ Returns:
+ image (np.ndarray):
+ an HWC image in the given format, which is 0-255, uint8 for
+ supported image modes in PIL or "BGR"; float (0-1 for Y) for YUV-BT.601.
+ """
+ with PathManager.open(file_name, "rb") as f:
+ image = Image.open(f)
+
+ # work around this bug: https://github.com/python-pillow/Pillow/issues/3973
+ image = _apply_exif_orientation(image)
+ return convert_PIL_to_numpy(image, format)
+
+
+def check_image_size(dataset_dict, image):
+ """
+ Raise an error if the image does not match the size specified in the dict.
+ """
+ if "width" in dataset_dict or "height" in dataset_dict:
+ image_wh = (image.shape[1], image.shape[0])
+ expected_wh = (dataset_dict["width"], dataset_dict["height"])
+ if not image_wh == expected_wh:
+ raise SizeMismatchError(
+ "Mismatched image shape{}, got {}, expect {}.".format(
+ " for image " + dataset_dict["file_name"]
+ if "file_name" in dataset_dict
+ else "",
+ image_wh,
+ expected_wh,
+ )
+ + " Please check the width/height in your annotation."
+ )
+
+ # To ensure bbox always remap to original image size
+ if "width" not in dataset_dict:
+ dataset_dict["width"] = image.shape[1]
+ if "height" not in dataset_dict:
+ dataset_dict["height"] = image.shape[0]
+
+
+def transform_proposals(dataset_dict, image_shape, transforms, *, proposal_topk, min_box_size=0):
+ """
+ Apply transformations to the proposals in dataset_dict, if any.
+
+ Args:
+ dataset_dict (dict): a dict read from the dataset, possibly
+ contains fields "proposal_boxes", "proposal_objectness_logits", "proposal_bbox_mode"
+ image_shape (tuple): height, width
+ transforms (TransformList):
+ proposal_topk (int): only keep top-K scoring proposals
+ min_box_size (int): proposals with either side smaller than this
+ threshold are removed
+
+ The input dict is modified in-place, with abovementioned keys removed. A new
+ key "proposals" will be added. Its value is an `Instances`
+ object which contains the transformed proposals in its field
+ "proposal_boxes" and "objectness_logits".
+ """
+ if "proposal_boxes" in dataset_dict:
+ # Transform proposal boxes
+ boxes = transforms.apply_box(
+ BoxMode.convert(
+ dataset_dict.pop("proposal_boxes"),
+ dataset_dict.pop("proposal_bbox_mode"),
+ BoxMode.XYXY_ABS,
+ )
+ )
+ boxes = Boxes(boxes)
+ objectness_logits = torch.as_tensor(
+ dataset_dict.pop("proposal_objectness_logits").astype("float32")
+ )
+
+ boxes.clip(image_shape)
+ keep = boxes.nonempty(threshold=min_box_size)
+ boxes = boxes[keep]
+ objectness_logits = objectness_logits[keep]
+
+ proposals = Instances(image_shape)
+ proposals.proposal_boxes = boxes[:proposal_topk]
+ proposals.objectness_logits = objectness_logits[:proposal_topk]
+ dataset_dict["proposals"] = proposals
+
+
+def get_bbox(annotation):
+ """
+ Get bbox from data
+ Args:
+ annotation (dict): dict of instance annotations for a single instance.
+ Returns:
+ bbox (ndarray): x1, y1, x2, y2 coordinates
+ """
+ # bbox is 1d (per-instance bounding box)
+ bbox = BoxMode.convert(annotation["bbox"], annotation["bbox_mode"], BoxMode.XYXY_ABS)
+ return bbox
+
+
+def transform_instance_annotations(
+ annotation, transforms, image_size, *, keypoint_hflip_indices=None
+):
+ """
+ Apply transforms to box, segmentation and keypoints annotations of a single instance.
+
+ It will use `transforms.apply_box` for the box, and
+ `transforms.apply_coords` for segmentation polygons & keypoints.
+ If you need anything more specially designed for each data structure,
+ you'll need to implement your own version of this function or the transforms.
+
+ Args:
+ annotation (dict): dict of instance annotations for a single instance.
+ It will be modified in-place.
+ transforms (TransformList or list[Transform]):
+ image_size (tuple): the height, width of the transformed image
+ keypoint_hflip_indices (ndarray[int]): see `create_keypoint_hflip_indices`.
+
+ Returns:
+ dict:
+ the same input dict with fields "bbox", "segmentation", "keypoints"
+ transformed according to `transforms`.
+ The "bbox_mode" field will be set to XYXY_ABS.
+ """
+ if isinstance(transforms, (tuple, list)):
+ transforms = T.TransformList(transforms)
+ # bbox is 1d (per-instance bounding box)
+ bbox = BoxMode.convert(annotation["bbox"], annotation["bbox_mode"], BoxMode.XYXY_ABS)
+ # clip transformed bbox to image size
+ bbox = transforms.apply_box(np.array([bbox]))[0].clip(min=0)
+ annotation["bbox"] = np.minimum(bbox, list(image_size + image_size)[::-1])
+ annotation["bbox_mode"] = BoxMode.XYXY_ABS
+
+ if "segmentation" in annotation:
+ # each instance contains 1 or more polygons
+ segm = annotation["segmentation"]
+ if isinstance(segm, list):
+ # polygons
+ polygons = [np.asarray(p).reshape(-1, 2) for p in segm]
+ annotation["segmentation"] = [
+ p.reshape(-1) for p in transforms.apply_polygons(polygons)
+ ]
+ elif isinstance(segm, dict):
+ # RLE
+ mask = mask_util.decode(segm)
+ mask = transforms.apply_segmentation(mask)
+ assert tuple(mask.shape[:2]) == image_size
+ annotation["segmentation"] = mask
+ else:
+ raise ValueError(
+ "Cannot transform segmentation of type '{}'!"
+ "Supported types are: polygons as list[list[float] or ndarray],"
+ " COCO-style RLE as a dict.".format(type(segm))
+ )
+
+ if "keypoints" in annotation:
+ keypoints = transform_keypoint_annotations(
+ annotation["keypoints"], transforms, image_size, keypoint_hflip_indices
+ )
+ annotation["keypoints"] = keypoints
+
+ return annotation
+
+
+def transform_keypoint_annotations(keypoints, transforms, image_size, keypoint_hflip_indices=None):
+ """
+ Transform keypoint annotations of an image.
+ If a keypoint is transformed out of image boundary, it will be marked "unlabeled" (visibility=0)
+
+ Args:
+ keypoints (list[float]): Nx3 float in Detectron2's Dataset format.
+ Each point is represented by (x, y, visibility).
+ transforms (TransformList):
+ image_size (tuple): the height, width of the transformed image
+ keypoint_hflip_indices (ndarray[int]): see `create_keypoint_hflip_indices`.
+ When `transforms` includes horizontal flip, will use the index
+ mapping to flip keypoints.
+ """
+ # (N*3,) -> (N, 3)
+ keypoints = np.asarray(keypoints, dtype="float64").reshape(-1, 3)
+ keypoints_xy = transforms.apply_coords(keypoints[:, :2])
+
+ # Set all out-of-boundary points to "unlabeled"
+ inside = (keypoints_xy >= np.array([0, 0])) & (keypoints_xy <= np.array(image_size[::-1]))
+ inside = inside.all(axis=1)
+ keypoints[:, :2] = keypoints_xy
+ keypoints[:, 2][~inside] = 0
+
+ # This assumes that HorizFlipTransform is the only one that does flip
+ do_hflip = sum(isinstance(t, T.HFlipTransform) for t in transforms.transforms) % 2 == 1
+
+ # Alternative way: check if probe points was horizontally flipped.
+ # probe = np.asarray([[0.0, 0.0], [image_width, 0.0]])
+ # probe_aug = transforms.apply_coords(probe.copy())
+ # do_hflip = np.sign(probe[1][0] - probe[0][0]) != np.sign(probe_aug[1][0] - probe_aug[0][0]) # noqa
+
+ # If flipped, swap each keypoint with its opposite-handed equivalent
+ if do_hflip:
+ if keypoint_hflip_indices is None:
+ raise ValueError("Cannot flip keypoints without providing flip indices!")
+ if len(keypoints) != len(keypoint_hflip_indices):
+ raise ValueError(
+ "Keypoint data has {} points, but metadata "
+ "contains {} points!".format(len(keypoints), len(keypoint_hflip_indices))
+ )
+ keypoints = keypoints[np.asarray(keypoint_hflip_indices, dtype=np.int32), :]
+
+ # Maintain COCO convention that if visibility == 0 (unlabeled), then x, y = 0
+ keypoints[keypoints[:, 2] == 0] = 0
+ return keypoints
+
+
+def annotations_to_instances(annos, image_size, mask_format="polygon"):
+ """
+ Create an :class:`Instances` object used by the models,
+ from instance annotations in the dataset dict.
+
+ Args:
+ annos (list[dict]): a list of instance annotations in one image, each
+ element for one instance.
+ image_size (tuple): height, width
+
+ Returns:
+ Instances:
+ It will contain fields "gt_boxes", "gt_classes",
+ "gt_masks", "gt_keypoints", if they can be obtained from `annos`.
+ This is the format that builtin models expect.
+ """
+ boxes = (
+ np.stack(
+ [BoxMode.convert(obj["bbox"], obj["bbox_mode"], BoxMode.XYXY_ABS) for obj in annos]
+ )
+ if len(annos)
+ else np.zeros((0, 4))
+ )
+ target = Instances(image_size)
+ target.gt_boxes = Boxes(boxes)
+
+ classes = [int(obj["category_id"]) for obj in annos]
+ classes = torch.tensor(classes, dtype=torch.int64)
+ target.gt_classes = classes
+
+ if len(annos) and "segmentation" in annos[0]:
+ segms = [obj["segmentation"] for obj in annos]
+ if mask_format == "polygon":
+ try:
+ masks = PolygonMasks(segms)
+ except ValueError as e:
+ raise ValueError(
+ "Failed to use mask_format=='polygon' from the given annotations!"
+ ) from e
+ else:
+ assert mask_format == "bitmask", mask_format
+ masks = []
+ for segm in segms:
+ if isinstance(segm, list):
+ # polygon
+ masks.append(polygons_to_bitmask(segm, *image_size))
+ elif isinstance(segm, dict):
+ # COCO RLE
+ masks.append(mask_util.decode(segm))
+ elif isinstance(segm, np.ndarray):
+ assert segm.ndim == 2, "Expect segmentation of 2 dimensions, got {}.".format(
+ segm.ndim
+ )
+ # mask array
+ masks.append(segm)
+ else:
+ raise ValueError(
+ "Cannot convert segmentation of type '{}' to BitMasks!"
+ "Supported types are: polygons as list[list[float] or ndarray],"
+ " COCO-style RLE as a dict, or a binary segmentation mask "
+ " in a 2D numpy array of shape HxW.".format(type(segm))
+ )
+ # torch.from_numpy does not support array with negative stride.
+ masks = BitMasks(
+ torch.stack([torch.from_numpy(np.ascontiguousarray(x)) for x in masks])
+ )
+ target.gt_masks = masks
+
+ if len(annos) and "keypoints" in annos[0]:
+ kpts = [obj.get("keypoints", []) for obj in annos]
+ target.gt_keypoints = Keypoints(kpts)
+
+ return target
+
+
+def annotations_to_instances_rotated(annos, image_size):
+ """
+ Create an :class:`Instances` object used by the models,
+ from instance annotations in the dataset dict.
+ Compared to `annotations_to_instances`, this function is for rotated boxes only
+
+ Args:
+ annos (list[dict]): a list of instance annotations in one image, each
+ element for one instance.
+ image_size (tuple): height, width
+
+ Returns:
+ Instances:
+ Containing fields "gt_boxes", "gt_classes",
+ if they can be obtained from `annos`.
+ This is the format that builtin models expect.
+ """
+ boxes = [obj["bbox"] for obj in annos]
+ target = Instances(image_size)
+ boxes = target.gt_boxes = RotatedBoxes(boxes)
+ boxes.clip(image_size)
+
+ classes = [obj["category_id"] for obj in annos]
+ classes = torch.tensor(classes, dtype=torch.int64)
+ target.gt_classes = classes
+
+ return target
+
+
+def filter_empty_instances(
+ instances, by_box=True, by_mask=True, box_threshold=1e-5, return_mask=False
+):
+ """
+ Filter out empty instances in an `Instances` object.
+
+ Args:
+ instances (Instances):
+ by_box (bool): whether to filter out instances with empty boxes
+ by_mask (bool): whether to filter out instances with empty masks
+ box_threshold (float): minimum width and height to be considered non-empty
+ return_mask (bool): whether to return boolean mask of filtered instances
+
+ Returns:
+ Instances: the filtered instances.
+ tensor[bool], optional: boolean mask of filtered instances
+ """
+ assert by_box or by_mask
+ r = []
+ if by_box:
+ r.append(instances.gt_boxes.nonempty(threshold=box_threshold))
+ if instances.has("gt_masks") and by_mask:
+ r.append(instances.gt_masks.nonempty())
+
+ # TODO: can also filter visible keypoints
+
+ if not r:
+ return instances
+ m = r[0]
+ for x in r[1:]:
+ m = m & x
+ if return_mask:
+ return instances[m], m
+ return instances[m]
+
+
+def create_keypoint_hflip_indices(dataset_names: Union[str, List[str]]) -> List[int]:
+ """
+ Args:
+ dataset_names: list of dataset names
+
+ Returns:
+ list[int]: a list of size=#keypoints, storing the
+ horizontally-flipped keypoint indices.
+ """
+ if isinstance(dataset_names, str):
+ dataset_names = [dataset_names]
+
+ check_metadata_consistency("keypoint_names", dataset_names)
+ check_metadata_consistency("keypoint_flip_map", dataset_names)
+
+ meta = MetadataCatalog.get(dataset_names[0])
+ names = meta.keypoint_names
+ # TODO flip -> hflip
+ flip_map = dict(meta.keypoint_flip_map)
+ flip_map.update({v: k for k, v in flip_map.items()})
+ flipped_names = [i if i not in flip_map else flip_map[i] for i in names]
+ flip_indices = [names.index(i) for i in flipped_names]
+ return flip_indices
+
+
+def get_fed_loss_cls_weights(dataset_names: Union[str, List[str]], freq_weight_power=1.0):
+ """
+ Get frequency weight for each class sorted by class id.
+ We now calcualte freqency weight using image_count to the power freq_weight_power.
+
+ Args:
+ dataset_names: list of dataset names
+ freq_weight_power: power value
+ """
+ if isinstance(dataset_names, str):
+ dataset_names = [dataset_names]
+
+ check_metadata_consistency("class_image_count", dataset_names)
+
+ meta = MetadataCatalog.get(dataset_names[0])
+ class_freq_meta = meta.class_image_count
+ class_freq = torch.tensor(
+ [c["image_count"] for c in sorted(class_freq_meta, key=lambda x: x["id"])]
+ )
+ class_freq_weight = class_freq.float() ** freq_weight_power
+ return class_freq_weight
+
+
+def gen_crop_transform_with_instance(crop_size, image_size, instance):
+ """
+ Generate a CropTransform so that the cropping region contains
+ the center of the given instance.
+
+ Args:
+ crop_size (tuple): h, w in pixels
+ image_size (tuple): h, w
+ instance (dict): an annotation dict of one instance, in Detectron2's
+ dataset format.
+ """
+ crop_size = np.asarray(crop_size, dtype=np.int32)
+ bbox = BoxMode.convert(instance["bbox"], instance["bbox_mode"], BoxMode.XYXY_ABS)
+ center_yx = (bbox[1] + bbox[3]) * 0.5, (bbox[0] + bbox[2]) * 0.5
+ assert (
+ image_size[0] >= center_yx[0] and image_size[1] >= center_yx[1]
+ ), "The annotation bounding box is outside of the image!"
+ assert (
+ image_size[0] >= crop_size[0] and image_size[1] >= crop_size[1]
+ ), "Crop size is larger than image size!"
+
+ min_yx = np.maximum(np.floor(center_yx).astype(np.int32) - crop_size, 0)
+ max_yx = np.maximum(np.asarray(image_size, dtype=np.int32) - crop_size, 0)
+ max_yx = np.minimum(max_yx, np.ceil(center_yx).astype(np.int32))
+
+ y0 = np.random.randint(min_yx[0], max_yx[0] + 1)
+ x0 = np.random.randint(min_yx[1], max_yx[1] + 1)
+ return T.CropTransform(x0, y0, crop_size[1], crop_size[0])
+
+
+def check_metadata_consistency(key, dataset_names):
+ """
+ Check that the datasets have consistent metadata.
+
+ Args:
+ key (str): a metadata key
+ dataset_names (list[str]): a list of dataset names
+
+ Raises:
+ AttributeError: if the key does not exist in the metadata
+ ValueError: if the given datasets do not have the same metadata values defined by key
+ """
+ if len(dataset_names) == 0:
+ return
+ logger = logging.getLogger(__name__)
+ entries_per_dataset = [getattr(MetadataCatalog.get(d), key) for d in dataset_names]
+ for idx, entry in enumerate(entries_per_dataset):
+ if entry != entries_per_dataset[0]:
+ logger.error(
+ "Metadata '{}' for dataset '{}' is '{}'".format(key, dataset_names[idx], str(entry))
+ )
+ logger.error(
+ "Metadata '{}' for dataset '{}' is '{}'".format(
+ key, dataset_names[0], str(entries_per_dataset[0])
+ )
+ )
+ raise ValueError("Datasets have different metadata '{}'!".format(key))
+
+
+def build_augmentation(cfg, is_train):
+ """
+ Create a list of default :class:`Augmentation` from config.
+ Now it includes resizing and flipping.
+
+ Returns:
+ list[Augmentation]
+ """
+ if is_train:
+ min_size = cfg.INPUT.MIN_SIZE_TRAIN
+ max_size = cfg.INPUT.MAX_SIZE_TRAIN
+ sample_style = cfg.INPUT.MIN_SIZE_TRAIN_SAMPLING
+ else:
+ min_size = cfg.INPUT.MIN_SIZE_TEST
+ max_size = cfg.INPUT.MAX_SIZE_TEST
+ sample_style = "choice"
+ augmentation = [T.ResizeShortestEdge(min_size, max_size, sample_style)]
+ if is_train and cfg.INPUT.RANDOM_FLIP != "none":
+ augmentation.append(
+ T.RandomFlip(
+ horizontal=cfg.INPUT.RANDOM_FLIP == "horizontal",
+ vertical=cfg.INPUT.RANDOM_FLIP == "vertical",
+ )
+ )
+ return augmentation
+
+
+build_transform_gen = build_augmentation
+"""
+Alias for backward-compatibility.
+"""
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/samplers/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/samplers/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..85c9f1a9df8a4038fbd4246239b699402e382309
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/samplers/__init__.py
@@ -0,0 +1,17 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+from .distributed_sampler import (
+ InferenceSampler,
+ RandomSubsetTrainingSampler,
+ RepeatFactorTrainingSampler,
+ TrainingSampler,
+)
+
+from .grouped_batch_sampler import GroupedBatchSampler
+
+__all__ = [
+ "GroupedBatchSampler",
+ "TrainingSampler",
+ "RandomSubsetTrainingSampler",
+ "InferenceSampler",
+ "RepeatFactorTrainingSampler",
+]
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/samplers/distributed_sampler.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/samplers/distributed_sampler.py
new file mode 100644
index 0000000000000000000000000000000000000000..eedc4c3ba88886c614a26dbc38fa1860bb503505
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/samplers/distributed_sampler.py
@@ -0,0 +1,278 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import itertools
+import logging
+import math
+from collections import defaultdict
+from typing import Optional
+import torch
+from torch.utils.data.sampler import Sampler
+
+from custom_detectron2.utils import comm
+
+logger = logging.getLogger(__name__)
+
+
+class TrainingSampler(Sampler):
+ """
+ In training, we only care about the "infinite stream" of training data.
+ So this sampler produces an infinite stream of indices and
+ all workers cooperate to correctly shuffle the indices and sample different indices.
+
+ The samplers in each worker effectively produces `indices[worker_id::num_workers]`
+ where `indices` is an infinite stream of indices consisting of
+ `shuffle(range(size)) + shuffle(range(size)) + ...` (if shuffle is True)
+ or `range(size) + range(size) + ...` (if shuffle is False)
+
+ Note that this sampler does not shard based on pytorch DataLoader worker id.
+ A sampler passed to pytorch DataLoader is used only with map-style dataset
+ and will not be executed inside workers.
+ But if this sampler is used in a way that it gets execute inside a dataloader
+ worker, then extra work needs to be done to shard its outputs based on worker id.
+ This is required so that workers don't produce identical data.
+ :class:`ToIterableDataset` implements this logic.
+ This note is true for all samplers in detectron2.
+ """
+
+ def __init__(self, size: int, shuffle: bool = True, seed: Optional[int] = None):
+ """
+ Args:
+ size (int): the total number of data of the underlying dataset to sample from
+ shuffle (bool): whether to shuffle the indices or not
+ seed (int): the initial seed of the shuffle. Must be the same
+ across all workers. If None, will use a random seed shared
+ among workers (require synchronization among all workers).
+ """
+ if not isinstance(size, int):
+ raise TypeError(f"TrainingSampler(size=) expects an int. Got type {type(size)}.")
+ if size <= 0:
+ raise ValueError(f"TrainingSampler(size=) expects a positive int. Got {size}.")
+ self._size = size
+ self._shuffle = shuffle
+ if seed is None:
+ seed = comm.shared_random_seed()
+ self._seed = int(seed)
+
+ self._rank = comm.get_rank()
+ self._world_size = comm.get_world_size()
+
+ def __iter__(self):
+ start = self._rank
+ yield from itertools.islice(self._infinite_indices(), start, None, self._world_size)
+
+ def _infinite_indices(self):
+ g = torch.Generator()
+ g.manual_seed(self._seed)
+ while True:
+ if self._shuffle:
+ yield from torch.randperm(self._size, generator=g).tolist()
+ else:
+ yield from torch.arange(self._size).tolist()
+
+
+class RandomSubsetTrainingSampler(TrainingSampler):
+ """
+ Similar to TrainingSampler, but only sample a random subset of indices.
+ This is useful when you want to estimate the accuracy vs data-number curves by
+ training the model with different subset_ratio.
+ """
+
+ def __init__(
+ self,
+ size: int,
+ subset_ratio: float,
+ shuffle: bool = True,
+ seed_shuffle: Optional[int] = None,
+ seed_subset: Optional[int] = None,
+ ):
+ """
+ Args:
+ size (int): the total number of data of the underlying dataset to sample from
+ subset_ratio (float): the ratio of subset data to sample from the underlying dataset
+ shuffle (bool): whether to shuffle the indices or not
+ seed_shuffle (int): the initial seed of the shuffle. Must be the same
+ across all workers. If None, will use a random seed shared
+ among workers (require synchronization among all workers).
+ seed_subset (int): the seed to randomize the subset to be sampled.
+ Must be the same across all workers. If None, will use a random seed shared
+ among workers (require synchronization among all workers).
+ """
+ super().__init__(size=size, shuffle=shuffle, seed=seed_shuffle)
+
+ assert 0.0 < subset_ratio <= 1.0
+ self._size_subset = int(size * subset_ratio)
+ assert self._size_subset > 0
+ if seed_subset is None:
+ seed_subset = comm.shared_random_seed()
+ self._seed_subset = int(seed_subset)
+
+ # randomly generate the subset indexes to be sampled from
+ g = torch.Generator()
+ g.manual_seed(self._seed_subset)
+ indexes_randperm = torch.randperm(self._size, generator=g)
+ self._indexes_subset = indexes_randperm[: self._size_subset]
+
+ logger.info("Using RandomSubsetTrainingSampler......")
+ logger.info(f"Randomly sample {self._size_subset} data from the original {self._size} data")
+
+ def _infinite_indices(self):
+ g = torch.Generator()
+ g.manual_seed(self._seed) # self._seed equals seed_shuffle from __init__()
+ while True:
+ if self._shuffle:
+ # generate a random permutation to shuffle self._indexes_subset
+ randperm = torch.randperm(self._size_subset, generator=g)
+ yield from self._indexes_subset[randperm].tolist()
+ else:
+ yield from self._indexes_subset.tolist()
+
+
+class RepeatFactorTrainingSampler(Sampler):
+ """
+ Similar to TrainingSampler, but a sample may appear more times than others based
+ on its "repeat factor". This is suitable for training on class imbalanced datasets like LVIS.
+ """
+
+ def __init__(self, repeat_factors, *, shuffle=True, seed=None):
+ """
+ Args:
+ repeat_factors (Tensor): a float vector, the repeat factor for each indice. When it's
+ full of ones, it is equivalent to ``TrainingSampler(len(repeat_factors), ...)``.
+ shuffle (bool): whether to shuffle the indices or not
+ seed (int): the initial seed of the shuffle. Must be the same
+ across all workers. If None, will use a random seed shared
+ among workers (require synchronization among all workers).
+ """
+ self._shuffle = shuffle
+ if seed is None:
+ seed = comm.shared_random_seed()
+ self._seed = int(seed)
+
+ self._rank = comm.get_rank()
+ self._world_size = comm.get_world_size()
+
+ # Split into whole number (_int_part) and fractional (_frac_part) parts.
+ self._int_part = torch.trunc(repeat_factors)
+ self._frac_part = repeat_factors - self._int_part
+
+ @staticmethod
+ def repeat_factors_from_category_frequency(dataset_dicts, repeat_thresh):
+ """
+ Compute (fractional) per-image repeat factors based on category frequency.
+ The repeat factor for an image is a function of the frequency of the rarest
+ category labeled in that image. The "frequency of category c" in [0, 1] is defined
+ as the fraction of images in the training set (without repeats) in which category c
+ appears.
+ See :paper:`lvis` (>= v2) Appendix B.2.
+
+ Args:
+ dataset_dicts (list[dict]): annotations in Detectron2 dataset format.
+ repeat_thresh (float): frequency threshold below which data is repeated.
+ If the frequency is half of `repeat_thresh`, the image will be
+ repeated twice.
+
+ Returns:
+ torch.Tensor:
+ the i-th element is the repeat factor for the dataset image at index i.
+ """
+ # 1. For each category c, compute the fraction of images that contain it: f(c)
+ category_freq = defaultdict(int)
+ for dataset_dict in dataset_dicts: # For each image (without repeats)
+ cat_ids = {ann["category_id"] for ann in dataset_dict["annotations"]}
+ for cat_id in cat_ids:
+ category_freq[cat_id] += 1
+ num_images = len(dataset_dicts)
+ for k, v in category_freq.items():
+ category_freq[k] = v / num_images
+
+ # 2. For each category c, compute the category-level repeat factor:
+ # r(c) = max(1, sqrt(t / f(c)))
+ category_rep = {
+ cat_id: max(1.0, math.sqrt(repeat_thresh / cat_freq))
+ for cat_id, cat_freq in category_freq.items()
+ }
+
+ # 3. For each image I, compute the image-level repeat factor:
+ # r(I) = max_{c in I} r(c)
+ rep_factors = []
+ for dataset_dict in dataset_dicts:
+ cat_ids = {ann["category_id"] for ann in dataset_dict["annotations"]}
+ rep_factor = max({category_rep[cat_id] for cat_id in cat_ids}, default=1.0)
+ rep_factors.append(rep_factor)
+
+ return torch.tensor(rep_factors, dtype=torch.float32)
+
+ def _get_epoch_indices(self, generator):
+ """
+ Create a list of dataset indices (with repeats) to use for one epoch.
+
+ Args:
+ generator (torch.Generator): pseudo random number generator used for
+ stochastic rounding.
+
+ Returns:
+ torch.Tensor: list of dataset indices to use in one epoch. Each index
+ is repeated based on its calculated repeat factor.
+ """
+ # Since repeat factors are fractional, we use stochastic rounding so
+ # that the target repeat factor is achieved in expectation over the
+ # course of training
+ rands = torch.rand(len(self._frac_part), generator=generator)
+ rep_factors = self._int_part + (rands < self._frac_part).float()
+ # Construct a list of indices in which we repeat images as specified
+ indices = []
+ for dataset_index, rep_factor in enumerate(rep_factors):
+ indices.extend([dataset_index] * int(rep_factor.item()))
+ return torch.tensor(indices, dtype=torch.int64)
+
+ def __iter__(self):
+ start = self._rank
+ yield from itertools.islice(self._infinite_indices(), start, None, self._world_size)
+
+ def _infinite_indices(self):
+ g = torch.Generator()
+ g.manual_seed(self._seed)
+ while True:
+ # Sample indices with repeats determined by stochastic rounding; each
+ # "epoch" may have a slightly different size due to the rounding.
+ indices = self._get_epoch_indices(g)
+ if self._shuffle:
+ randperm = torch.randperm(len(indices), generator=g)
+ yield from indices[randperm].tolist()
+ else:
+ yield from indices.tolist()
+
+
+class InferenceSampler(Sampler):
+ """
+ Produce indices for inference across all workers.
+ Inference needs to run on the __exact__ set of samples,
+ therefore when the total number of samples is not divisible by the number of workers,
+ this sampler produces different number of samples on different workers.
+ """
+
+ def __init__(self, size: int):
+ """
+ Args:
+ size (int): the total number of data of the underlying dataset to sample from
+ """
+ self._size = size
+ assert size > 0
+ self._rank = comm.get_rank()
+ self._world_size = comm.get_world_size()
+ self._local_indices = self._get_local_indices(size, self._world_size, self._rank)
+
+ @staticmethod
+ def _get_local_indices(total_size, world_size, rank):
+ shard_size = total_size // world_size
+ left = total_size % world_size
+ shard_sizes = [shard_size + int(r < left) for r in range(world_size)]
+
+ begin = sum(shard_sizes[:rank])
+ end = min(sum(shard_sizes[: rank + 1]), total_size)
+ return range(begin, end)
+
+ def __iter__(self):
+ yield from self._local_indices
+
+ def __len__(self):
+ return len(self._local_indices)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/samplers/grouped_batch_sampler.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/samplers/grouped_batch_sampler.py
new file mode 100644
index 0000000000000000000000000000000000000000..5b247730aacd04dd0c752664acde3257c4eddd71
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/samplers/grouped_batch_sampler.py
@@ -0,0 +1,47 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import numpy as np
+from torch.utils.data.sampler import BatchSampler, Sampler
+
+
+class GroupedBatchSampler(BatchSampler):
+ """
+ Wraps another sampler to yield a mini-batch of indices.
+ It enforces that the batch only contain elements from the same group.
+ It also tries to provide mini-batches which follows an ordering which is
+ as close as possible to the ordering from the original sampler.
+ """
+
+ def __init__(self, sampler, group_ids, batch_size):
+ """
+ Args:
+ sampler (Sampler): Base sampler.
+ group_ids (list[int]): If the sampler produces indices in range [0, N),
+ `group_ids` must be a list of `N` ints which contains the group id of each sample.
+ The group ids must be a set of integers in the range [0, num_groups).
+ batch_size (int): Size of mini-batch.
+ """
+ if not isinstance(sampler, Sampler):
+ raise ValueError(
+ "sampler should be an instance of "
+ "torch.utils.data.Sampler, but got sampler={}".format(sampler)
+ )
+ self.sampler = sampler
+ self.group_ids = np.asarray(group_ids)
+ assert self.group_ids.ndim == 1
+ self.batch_size = batch_size
+ groups = np.unique(self.group_ids).tolist()
+
+ # buffer the indices of each group until batch size is reached
+ self.buffer_per_group = {k: [] for k in groups}
+
+ def __iter__(self):
+ for idx in self.sampler:
+ group_id = self.group_ids[idx]
+ group_buffer = self.buffer_per_group[group_id]
+ group_buffer.append(idx)
+ if len(group_buffer) == self.batch_size:
+ yield group_buffer[:] # yield a copy of the list
+ del group_buffer[:]
+
+ def __len__(self):
+ raise NotImplementedError("len() of GroupedBatchSampler is not well-defined.")
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/transforms/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/transforms/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..b1d3721fc22e5aa3b4100458235f99c45498646d
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/transforms/__init__.py
@@ -0,0 +1,14 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+from fvcore.transforms.transform import Transform, TransformList # order them first
+from fvcore.transforms.transform import *
+from .transform import *
+from .augmentation import *
+from .augmentation_impl import *
+
+__all__ = [k for k in globals().keys() if not k.startswith("_")]
+
+
+from custom_detectron2.utils.env import fixup_module_metadata
+
+fixup_module_metadata(__name__, globals(), __all__)
+del fixup_module_metadata
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/transforms/augmentation.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/transforms/augmentation.py
new file mode 100644
index 0000000000000000000000000000000000000000..63dd41aef658c9b51c7246880399405a029c5580
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/transforms/augmentation.py
@@ -0,0 +1,380 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import inspect
+import numpy as np
+import pprint
+from typing import Any, List, Optional, Tuple, Union
+from fvcore.transforms.transform import Transform, TransformList
+
+"""
+See "Data Augmentation" tutorial for an overview of the system:
+https://detectron2.readthedocs.io/tutorials/augmentation.html
+"""
+
+
+__all__ = [
+ "Augmentation",
+ "AugmentationList",
+ "AugInput",
+ "TransformGen",
+ "apply_transform_gens",
+ "StandardAugInput",
+ "apply_augmentations",
+]
+
+
+def _check_img_dtype(img):
+ assert isinstance(img, np.ndarray), "[Augmentation] Needs an numpy array, but got a {}!".format(
+ type(img)
+ )
+ assert not isinstance(img.dtype, np.integer) or (
+ img.dtype == np.uint8
+ ), "[Augmentation] Got image of type {}, use uint8 or floating points instead!".format(
+ img.dtype
+ )
+ assert img.ndim in [2, 3], img.ndim
+
+
+def _get_aug_input_args(aug, aug_input) -> List[Any]:
+ """
+ Get the arguments to be passed to ``aug.get_transform`` from the input ``aug_input``.
+ """
+ if aug.input_args is None:
+ # Decide what attributes are needed automatically
+ prms = list(inspect.signature(aug.get_transform).parameters.items())
+ # The default behavior is: if there is one parameter, then its "image"
+ # (work automatically for majority of use cases, and also avoid BC breaking),
+ # Otherwise, use the argument names.
+ if len(prms) == 1:
+ names = ("image",)
+ else:
+ names = []
+ for name, prm in prms:
+ if prm.kind in (
+ inspect.Parameter.VAR_POSITIONAL,
+ inspect.Parameter.VAR_KEYWORD,
+ ):
+ raise TypeError(
+ f""" \
+The default implementation of `{type(aug)}.__call__` does not allow \
+`{type(aug)}.get_transform` to use variable-length arguments (*args, **kwargs)! \
+If arguments are unknown, reimplement `__call__` instead. \
+"""
+ )
+ names.append(name)
+ aug.input_args = tuple(names)
+
+ args = []
+ for f in aug.input_args:
+ try:
+ args.append(getattr(aug_input, f))
+ except AttributeError as e:
+ raise AttributeError(
+ f"{type(aug)}.get_transform needs input attribute '{f}', "
+ f"but it is not an attribute of {type(aug_input)}!"
+ ) from e
+ return args
+
+
+class Augmentation:
+ """
+ Augmentation defines (often random) policies/strategies to generate :class:`Transform`
+ from data. It is often used for pre-processing of input data.
+
+ A "policy" that generates a :class:`Transform` may, in the most general case,
+ need arbitrary information from input data in order to determine what transforms
+ to apply. Therefore, each :class:`Augmentation` instance defines the arguments
+ needed by its :meth:`get_transform` method. When called with the positional arguments,
+ the :meth:`get_transform` method executes the policy.
+
+ Note that :class:`Augmentation` defines the policies to create a :class:`Transform`,
+ but not how to execute the actual transform operations to those data.
+ Its :meth:`__call__` method will use :meth:`AugInput.transform` to execute the transform.
+
+ The returned `Transform` object is meant to describe deterministic transformation, which means
+ it can be re-applied on associated data, e.g. the geometry of an image and its segmentation
+ masks need to be transformed together.
+ (If such re-application is not needed, then determinism is not a crucial requirement.)
+ """
+
+ input_args: Optional[Tuple[str]] = None
+ """
+ Stores the attribute names needed by :meth:`get_transform`, e.g. ``("image", "sem_seg")``.
+ By default, it is just a tuple of argument names in :meth:`self.get_transform`, which often only
+ contain "image". As long as the argument name convention is followed, there is no need for
+ users to touch this attribute.
+ """
+
+ def _init(self, params=None):
+ if params:
+ for k, v in params.items():
+ if k != "self" and not k.startswith("_"):
+ setattr(self, k, v)
+
+ def get_transform(self, *args) -> Transform:
+ """
+ Execute the policy based on input data, and decide what transform to apply to inputs.
+
+ Args:
+ args: Any fixed-length positional arguments. By default, the name of the arguments
+ should exist in the :class:`AugInput` to be used.
+
+ Returns:
+ Transform: Returns the deterministic transform to apply to the input.
+
+ Examples:
+ ::
+ class MyAug:
+ # if a policy needs to know both image and semantic segmentation
+ def get_transform(image, sem_seg) -> T.Transform:
+ pass
+ tfm: Transform = MyAug().get_transform(image, sem_seg)
+ new_image = tfm.apply_image(image)
+
+ Notes:
+ Users can freely use arbitrary new argument names in custom
+ :meth:`get_transform` method, as long as they are available in the
+ input data. In detectron2 we use the following convention:
+
+ * image: (H,W) or (H,W,C) ndarray of type uint8 in range [0, 255], or
+ floating point in range [0, 1] or [0, 255].
+ * boxes: (N,4) ndarray of float32. It represents the instance bounding boxes
+ of N instances. Each is in XYXY format in unit of absolute coordinates.
+ * sem_seg: (H,W) ndarray of type uint8. Each element is an integer label of pixel.
+
+ We do not specify convention for other types and do not include builtin
+ :class:`Augmentation` that uses other types in detectron2.
+ """
+ raise NotImplementedError
+
+ def __call__(self, aug_input) -> Transform:
+ """
+ Augment the given `aug_input` **in-place**, and return the transform that's used.
+
+ This method will be called to apply the augmentation. In most augmentation, it
+ is enough to use the default implementation, which calls :meth:`get_transform`
+ using the inputs. But a subclass can overwrite it to have more complicated logic.
+
+ Args:
+ aug_input (AugInput): an object that has attributes needed by this augmentation
+ (defined by ``self.get_transform``). Its ``transform`` method will be called
+ to in-place transform it.
+
+ Returns:
+ Transform: the transform that is applied on the input.
+ """
+ args = _get_aug_input_args(self, aug_input)
+ tfm = self.get_transform(*args)
+ assert isinstance(tfm, (Transform, TransformList)), (
+ f"{type(self)}.get_transform must return an instance of Transform! "
+ f"Got {type(tfm)} instead."
+ )
+ aug_input.transform(tfm)
+ return tfm
+
+ def _rand_range(self, low=1.0, high=None, size=None):
+ """
+ Uniform float random number between low and high.
+ """
+ if high is None:
+ low, high = 0, low
+ if size is None:
+ size = []
+ return np.random.uniform(low, high, size)
+
+ def __repr__(self):
+ """
+ Produce something like:
+ "MyAugmentation(field1={self.field1}, field2={self.field2})"
+ """
+ try:
+ sig = inspect.signature(self.__init__)
+ classname = type(self).__name__
+ argstr = []
+ for name, param in sig.parameters.items():
+ assert (
+ param.kind != param.VAR_POSITIONAL and param.kind != param.VAR_KEYWORD
+ ), "The default __repr__ doesn't support *args or **kwargs"
+ assert hasattr(self, name), (
+ "Attribute {} not found! "
+ "Default __repr__ only works if attributes match the constructor.".format(name)
+ )
+ attr = getattr(self, name)
+ default = param.default
+ if default is attr:
+ continue
+ attr_str = pprint.pformat(attr)
+ if "\n" in attr_str:
+ # don't show it if pformat decides to use >1 lines
+ attr_str = "..."
+ argstr.append("{}={}".format(name, attr_str))
+ return "{}({})".format(classname, ", ".join(argstr))
+ except AssertionError:
+ return super().__repr__()
+
+ __str__ = __repr__
+
+
+class _TransformToAug(Augmentation):
+ def __init__(self, tfm: Transform):
+ self.tfm = tfm
+
+ def get_transform(self, *args):
+ return self.tfm
+
+ def __repr__(self):
+ return repr(self.tfm)
+
+ __str__ = __repr__
+
+
+def _transform_to_aug(tfm_or_aug):
+ """
+ Wrap Transform into Augmentation.
+ Private, used internally to implement augmentations.
+ """
+ assert isinstance(tfm_or_aug, (Transform, Augmentation)), tfm_or_aug
+ if isinstance(tfm_or_aug, Augmentation):
+ return tfm_or_aug
+ else:
+ return _TransformToAug(tfm_or_aug)
+
+
+class AugmentationList(Augmentation):
+ """
+ Apply a sequence of augmentations.
+
+ It has ``__call__`` method to apply the augmentations.
+
+ Note that :meth:`get_transform` method is impossible (will throw error if called)
+ for :class:`AugmentationList`, because in order to apply a sequence of augmentations,
+ the kth augmentation must be applied first, to provide inputs needed by the (k+1)th
+ augmentation.
+ """
+
+ def __init__(self, augs):
+ """
+ Args:
+ augs (list[Augmentation or Transform]):
+ """
+ super().__init__()
+ self.augs = [_transform_to_aug(x) for x in augs]
+
+ def __call__(self, aug_input) -> TransformList:
+ tfms = []
+ for x in self.augs:
+ tfm = x(aug_input)
+ tfms.append(tfm)
+ return TransformList(tfms)
+
+ def __repr__(self):
+ msgs = [str(x) for x in self.augs]
+ return "AugmentationList[{}]".format(", ".join(msgs))
+
+ __str__ = __repr__
+
+
+class AugInput:
+ """
+ Input that can be used with :meth:`Augmentation.__call__`.
+ This is a standard implementation for the majority of use cases.
+ This class provides the standard attributes **"image", "boxes", "sem_seg"**
+ defined in :meth:`__init__` and they may be needed by different augmentations.
+ Most augmentation policies do not need attributes beyond these three.
+
+ After applying augmentations to these attributes (using :meth:`AugInput.transform`),
+ the returned transforms can then be used to transform other data structures that users have.
+
+ Examples:
+ ::
+ input = AugInput(image, boxes=boxes)
+ tfms = augmentation(input)
+ transformed_image = input.image
+ transformed_boxes = input.boxes
+ transformed_other_data = tfms.apply_other(other_data)
+
+ An extended project that works with new data types may implement augmentation policies
+ that need other inputs. An algorithm may need to transform inputs in a way different
+ from the standard approach defined in this class. In those rare situations, users can
+ implement a class similar to this class, that satify the following condition:
+
+ * The input must provide access to these data in the form of attribute access
+ (``getattr``). For example, if an :class:`Augmentation` to be applied needs "image"
+ and "sem_seg" arguments, its input must have the attribute "image" and "sem_seg".
+ * The input must have a ``transform(tfm: Transform) -> None`` method which
+ in-place transforms all its attributes.
+ """
+
+ # TODO maybe should support more builtin data types here
+ def __init__(
+ self,
+ image: np.ndarray,
+ *,
+ boxes: Optional[np.ndarray] = None,
+ sem_seg: Optional[np.ndarray] = None,
+ ):
+ """
+ Args:
+ image (ndarray): (H,W) or (H,W,C) ndarray of type uint8 in range [0, 255], or
+ floating point in range [0, 1] or [0, 255]. The meaning of C is up
+ to users.
+ boxes (ndarray or None): Nx4 float32 boxes in XYXY_ABS mode
+ sem_seg (ndarray or None): HxW uint8 semantic segmentation mask. Each element
+ is an integer label of pixel.
+ """
+ _check_img_dtype(image)
+ self.image = image
+ self.boxes = boxes
+ self.sem_seg = sem_seg
+
+ def transform(self, tfm: Transform) -> None:
+ """
+ In-place transform all attributes of this class.
+
+ By "in-place", it means after calling this method, accessing an attribute such
+ as ``self.image`` will return transformed data.
+ """
+ self.image = tfm.apply_image(self.image)
+ if self.boxes is not None:
+ self.boxes = tfm.apply_box(self.boxes)
+ if self.sem_seg is not None:
+ self.sem_seg = tfm.apply_segmentation(self.sem_seg)
+
+ def apply_augmentations(
+ self, augmentations: List[Union[Augmentation, Transform]]
+ ) -> TransformList:
+ """
+ Equivalent of ``AugmentationList(augmentations)(self)``
+ """
+ return AugmentationList(augmentations)(self)
+
+
+def apply_augmentations(augmentations: List[Union[Transform, Augmentation]], inputs):
+ """
+ Use ``T.AugmentationList(augmentations)(inputs)`` instead.
+ """
+ if isinstance(inputs, np.ndarray):
+ # handle the common case of image-only Augmentation, also for backward compatibility
+ image_only = True
+ inputs = AugInput(inputs)
+ else:
+ image_only = False
+ tfms = inputs.apply_augmentations(augmentations)
+ return inputs.image if image_only else inputs, tfms
+
+
+apply_transform_gens = apply_augmentations
+"""
+Alias for backward-compatibility.
+"""
+
+TransformGen = Augmentation
+"""
+Alias for Augmentation, since it is something that generates :class:`Transform`s
+"""
+
+StandardAugInput = AugInput
+"""
+Alias for compatibility. It's not worth the complexity to have two classes.
+"""
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/transforms/augmentation_impl.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/transforms/augmentation_impl.py
new file mode 100644
index 0000000000000000000000000000000000000000..228b16318e8d4aff3c759ddd7367a1e92e6cbc07
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/transforms/augmentation_impl.py
@@ -0,0 +1,736 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+"""
+Implement many useful :class:`Augmentation`.
+"""
+import numpy as np
+import sys
+from numpy import random
+from typing import Tuple
+import torch
+from fvcore.transforms.transform import (
+ BlendTransform,
+ CropTransform,
+ HFlipTransform,
+ NoOpTransform,
+ PadTransform,
+ Transform,
+ TransformList,
+ VFlipTransform,
+)
+from PIL import Image
+
+from custom_detectron2.structures import Boxes, pairwise_iou
+
+from .augmentation import Augmentation, _transform_to_aug
+from .transform import ExtentTransform, ResizeTransform, RotationTransform
+
+__all__ = [
+ "FixedSizeCrop",
+ "RandomApply",
+ "RandomBrightness",
+ "RandomContrast",
+ "RandomCrop",
+ "RandomExtent",
+ "RandomFlip",
+ "RandomSaturation",
+ "RandomLighting",
+ "RandomRotation",
+ "Resize",
+ "ResizeScale",
+ "ResizeShortestEdge",
+ "RandomCrop_CategoryAreaConstraint",
+ "RandomResize",
+ "MinIoURandomCrop",
+]
+
+
+class RandomApply(Augmentation):
+ """
+ Randomly apply an augmentation with a given probability.
+ """
+
+ def __init__(self, tfm_or_aug, prob=0.5):
+ """
+ Args:
+ tfm_or_aug (Transform, Augmentation): the transform or augmentation
+ to be applied. It can either be a `Transform` or `Augmentation`
+ instance.
+ prob (float): probability between 0.0 and 1.0 that
+ the wrapper transformation is applied
+ """
+ super().__init__()
+ self.aug = _transform_to_aug(tfm_or_aug)
+ assert 0.0 <= prob <= 1.0, f"Probablity must be between 0.0 and 1.0 (given: {prob})"
+ self.prob = prob
+
+ def get_transform(self, *args):
+ do = self._rand_range() < self.prob
+ if do:
+ return self.aug.get_transform(*args)
+ else:
+ return NoOpTransform()
+
+ def __call__(self, aug_input):
+ do = self._rand_range() < self.prob
+ if do:
+ return self.aug(aug_input)
+ else:
+ return NoOpTransform()
+
+
+class RandomFlip(Augmentation):
+ """
+ Flip the image horizontally or vertically with the given probability.
+ """
+
+ def __init__(self, prob=0.5, *, horizontal=True, vertical=False):
+ """
+ Args:
+ prob (float): probability of flip.
+ horizontal (boolean): whether to apply horizontal flipping
+ vertical (boolean): whether to apply vertical flipping
+ """
+ super().__init__()
+
+ if horizontal and vertical:
+ raise ValueError("Cannot do both horiz and vert. Please use two Flip instead.")
+ if not horizontal and not vertical:
+ raise ValueError("At least one of horiz or vert has to be True!")
+ self._init(locals())
+
+ def get_transform(self, image):
+ h, w = image.shape[:2]
+ do = self._rand_range() < self.prob
+ if do:
+ if self.horizontal:
+ return HFlipTransform(w)
+ elif self.vertical:
+ return VFlipTransform(h)
+ else:
+ return NoOpTransform()
+
+
+class Resize(Augmentation):
+ """Resize image to a fixed target size"""
+
+ def __init__(self, shape, interp=Image.BILINEAR):
+ """
+ Args:
+ shape: (h, w) tuple or a int
+ interp: PIL interpolation method
+ """
+ if isinstance(shape, int):
+ shape = (shape, shape)
+ shape = tuple(shape)
+ self._init(locals())
+
+ def get_transform(self, image):
+ return ResizeTransform(
+ image.shape[0], image.shape[1], self.shape[0], self.shape[1], self.interp
+ )
+
+
+class ResizeShortestEdge(Augmentation):
+ """
+ Resize the image while keeping the aspect ratio unchanged.
+ It attempts to scale the shorter edge to the given `short_edge_length`,
+ as long as the longer edge does not exceed `max_size`.
+ If `max_size` is reached, then downscale so that the longer edge does not exceed max_size.
+ """
+
+ @torch.jit.unused
+ def __init__(
+ self, short_edge_length, max_size=sys.maxsize, sample_style="range", interp=Image.BILINEAR
+ ):
+ """
+ Args:
+ short_edge_length (list[int]): If ``sample_style=="range"``,
+ a [min, max] interval from which to sample the shortest edge length.
+ If ``sample_style=="choice"``, a list of shortest edge lengths to sample from.
+ max_size (int): maximum allowed longest edge length.
+ sample_style (str): either "range" or "choice".
+ """
+ super().__init__()
+ assert sample_style in ["range", "choice"], sample_style
+
+ self.is_range = sample_style == "range"
+ if isinstance(short_edge_length, int):
+ short_edge_length = (short_edge_length, short_edge_length)
+ if self.is_range:
+ assert len(short_edge_length) == 2, (
+ "short_edge_length must be two values using 'range' sample style."
+ f" Got {short_edge_length}!"
+ )
+ self._init(locals())
+
+ @torch.jit.unused
+ def get_transform(self, image):
+ h, w = image.shape[:2]
+ if self.is_range:
+ size = np.random.randint(self.short_edge_length[0], self.short_edge_length[1] + 1)
+ else:
+ size = np.random.choice(self.short_edge_length)
+ if size == 0:
+ return NoOpTransform()
+
+ newh, neww = ResizeShortestEdge.get_output_shape(h, w, size, self.max_size)
+ return ResizeTransform(h, w, newh, neww, self.interp)
+
+ @staticmethod
+ def get_output_shape(
+ oldh: int, oldw: int, short_edge_length: int, max_size: int
+ ) -> Tuple[int, int]:
+ """
+ Compute the output size given input size and target short edge length.
+ """
+ h, w = oldh, oldw
+ size = short_edge_length * 1.0
+ scale = size / min(h, w)
+ if h < w:
+ newh, neww = size, scale * w
+ else:
+ newh, neww = scale * h, size
+ if max(newh, neww) > max_size:
+ scale = max_size * 1.0 / max(newh, neww)
+ newh = newh * scale
+ neww = neww * scale
+ neww = int(neww + 0.5)
+ newh = int(newh + 0.5)
+ return (newh, neww)
+
+
+class ResizeScale(Augmentation):
+ """
+ Takes target size as input and randomly scales the given target size between `min_scale`
+ and `max_scale`. It then scales the input image such that it fits inside the scaled target
+ box, keeping the aspect ratio constant.
+ This implements the resize part of the Google's 'resize_and_crop' data augmentation:
+ https://github.com/tensorflow/tpu/blob/master/models/official/detection/utils/input_utils.py#L127
+ """
+
+ def __init__(
+ self,
+ min_scale: float,
+ max_scale: float,
+ target_height: int,
+ target_width: int,
+ interp: int = Image.BILINEAR,
+ ):
+ """
+ Args:
+ min_scale: minimum image scale range.
+ max_scale: maximum image scale range.
+ target_height: target image height.
+ target_width: target image width.
+ interp: image interpolation method.
+ """
+ super().__init__()
+ self._init(locals())
+
+ def _get_resize(self, image: np.ndarray, scale: float) -> Transform:
+ input_size = image.shape[:2]
+
+ # Compute new target size given a scale.
+ target_size = (self.target_height, self.target_width)
+ target_scale_size = np.multiply(target_size, scale)
+
+ # Compute actual rescaling applied to input image and output size.
+ output_scale = np.minimum(
+ target_scale_size[0] / input_size[0], target_scale_size[1] / input_size[1]
+ )
+ output_size = np.round(np.multiply(input_size, output_scale)).astype(int)
+
+ return ResizeTransform(
+ input_size[0], input_size[1], output_size[0], output_size[1], self.interp
+ )
+
+ def get_transform(self, image: np.ndarray) -> Transform:
+ random_scale = np.random.uniform(self.min_scale, self.max_scale)
+ return self._get_resize(image, random_scale)
+
+
+class RandomRotation(Augmentation):
+ """
+ This method returns a copy of this image, rotated the given
+ number of degrees counter clockwise around the given center.
+ """
+
+ def __init__(self, angle, expand=True, center=None, sample_style="range", interp=None):
+ """
+ Args:
+ angle (list[float]): If ``sample_style=="range"``,
+ a [min, max] interval from which to sample the angle (in degrees).
+ If ``sample_style=="choice"``, a list of angles to sample from
+ expand (bool): choose if the image should be resized to fit the whole
+ rotated image (default), or simply cropped
+ center (list[[float, float]]): If ``sample_style=="range"``,
+ a [[minx, miny], [maxx, maxy]] relative interval from which to sample the center,
+ [0, 0] being the top left of the image and [1, 1] the bottom right.
+ If ``sample_style=="choice"``, a list of centers to sample from
+ Default: None, which means that the center of rotation is the center of the image
+ center has no effect if expand=True because it only affects shifting
+ """
+ super().__init__()
+ assert sample_style in ["range", "choice"], sample_style
+ self.is_range = sample_style == "range"
+ if isinstance(angle, (float, int)):
+ angle = (angle, angle)
+ if center is not None and isinstance(center[0], (float, int)):
+ center = (center, center)
+ self._init(locals())
+
+ def get_transform(self, image):
+ h, w = image.shape[:2]
+ center = None
+ if self.is_range:
+ angle = np.random.uniform(self.angle[0], self.angle[1])
+ if self.center is not None:
+ center = (
+ np.random.uniform(self.center[0][0], self.center[1][0]),
+ np.random.uniform(self.center[0][1], self.center[1][1]),
+ )
+ else:
+ angle = np.random.choice(self.angle)
+ if self.center is not None:
+ center = np.random.choice(self.center)
+
+ if center is not None:
+ center = (w * center[0], h * center[1]) # Convert to absolute coordinates
+
+ if angle % 360 == 0:
+ return NoOpTransform()
+
+ return RotationTransform(h, w, angle, expand=self.expand, center=center, interp=self.interp)
+
+
+class FixedSizeCrop(Augmentation):
+ """
+ If `crop_size` is smaller than the input image size, then it uses a random crop of
+ the crop size. If `crop_size` is larger than the input image size, then it pads
+ the right and the bottom of the image to the crop size if `pad` is True, otherwise
+ it returns the smaller image.
+ """
+
+ def __init__(
+ self,
+ crop_size: Tuple[int],
+ pad: bool = True,
+ pad_value: float = 128.0,
+ seg_pad_value: int = 255,
+ ):
+ """
+ Args:
+ crop_size: target image (height, width).
+ pad: if True, will pad images smaller than `crop_size` up to `crop_size`
+ pad_value: the padding value to the image.
+ seg_pad_value: the padding value to the segmentation mask.
+ """
+ super().__init__()
+ self._init(locals())
+
+ def _get_crop(self, image: np.ndarray) -> Transform:
+ # Compute the image scale and scaled size.
+ input_size = image.shape[:2]
+ output_size = self.crop_size
+
+ # Add random crop if the image is scaled up.
+ max_offset = np.subtract(input_size, output_size)
+ max_offset = np.maximum(max_offset, 0)
+ offset = np.multiply(max_offset, np.random.uniform(0.0, 1.0))
+ offset = np.round(offset).astype(int)
+ return CropTransform(
+ offset[1], offset[0], output_size[1], output_size[0], input_size[1], input_size[0]
+ )
+
+ def _get_pad(self, image: np.ndarray) -> Transform:
+ # Compute the image scale and scaled size.
+ input_size = image.shape[:2]
+ output_size = self.crop_size
+
+ # Add padding if the image is scaled down.
+ pad_size = np.subtract(output_size, input_size)
+ pad_size = np.maximum(pad_size, 0)
+ original_size = np.minimum(input_size, output_size)
+ return PadTransform(
+ 0,
+ 0,
+ pad_size[1],
+ pad_size[0],
+ original_size[1],
+ original_size[0],
+ self.pad_value,
+ self.seg_pad_value,
+ )
+
+ def get_transform(self, image: np.ndarray) -> TransformList:
+ transforms = [self._get_crop(image)]
+ if self.pad:
+ transforms.append(self._get_pad(image))
+ return TransformList(transforms)
+
+
+class RandomCrop(Augmentation):
+ """
+ Randomly crop a rectangle region out of an image.
+ """
+
+ def __init__(self, crop_type: str, crop_size):
+ """
+ Args:
+ crop_type (str): one of "relative_range", "relative", "absolute", "absolute_range".
+ crop_size (tuple[float, float]): two floats, explained below.
+
+ - "relative": crop a (H * crop_size[0], W * crop_size[1]) region from an input image of
+ size (H, W). crop size should be in (0, 1]
+ - "relative_range": uniformly sample two values from [crop_size[0], 1]
+ and [crop_size[1]], 1], and use them as in "relative" crop type.
+ - "absolute" crop a (crop_size[0], crop_size[1]) region from input image.
+ crop_size must be smaller than the input image size.
+ - "absolute_range", for an input of size (H, W), uniformly sample H_crop in
+ [crop_size[0], min(H, crop_size[1])] and W_crop in [crop_size[0], min(W, crop_size[1])].
+ Then crop a region (H_crop, W_crop).
+ """
+ # TODO style of relative_range and absolute_range are not consistent:
+ # one takes (h, w) but another takes (min, max)
+ super().__init__()
+ assert crop_type in ["relative_range", "relative", "absolute", "absolute_range"]
+ self._init(locals())
+
+ def get_transform(self, image):
+ h, w = image.shape[:2]
+ croph, cropw = self.get_crop_size((h, w))
+ assert h >= croph and w >= cropw, "Shape computation in {} has bugs.".format(self)
+ h0 = np.random.randint(h - croph + 1)
+ w0 = np.random.randint(w - cropw + 1)
+ return CropTransform(w0, h0, cropw, croph)
+
+ def get_crop_size(self, image_size):
+ """
+ Args:
+ image_size (tuple): height, width
+
+ Returns:
+ crop_size (tuple): height, width in absolute pixels
+ """
+ h, w = image_size
+ if self.crop_type == "relative":
+ ch, cw = self.crop_size
+ return int(h * ch + 0.5), int(w * cw + 0.5)
+ elif self.crop_type == "relative_range":
+ crop_size = np.asarray(self.crop_size, dtype=np.float32)
+ ch, cw = crop_size + np.random.rand(2) * (1 - crop_size)
+ return int(h * ch + 0.5), int(w * cw + 0.5)
+ elif self.crop_type == "absolute":
+ return (min(self.crop_size[0], h), min(self.crop_size[1], w))
+ elif self.crop_type == "absolute_range":
+ assert self.crop_size[0] <= self.crop_size[1]
+ ch = np.random.randint(min(h, self.crop_size[0]), min(h, self.crop_size[1]) + 1)
+ cw = np.random.randint(min(w, self.crop_size[0]), min(w, self.crop_size[1]) + 1)
+ return ch, cw
+ else:
+ raise NotImplementedError("Unknown crop type {}".format(self.crop_type))
+
+
+class RandomCrop_CategoryAreaConstraint(Augmentation):
+ """
+ Similar to :class:`RandomCrop`, but find a cropping window such that no single category
+ occupies a ratio of more than `single_category_max_area` in semantic segmentation ground
+ truth, which can cause unstability in training. The function attempts to find such a valid
+ cropping window for at most 10 times.
+ """
+
+ def __init__(
+ self,
+ crop_type: str,
+ crop_size,
+ single_category_max_area: float = 1.0,
+ ignored_category: int = None,
+ ):
+ """
+ Args:
+ crop_type, crop_size: same as in :class:`RandomCrop`
+ single_category_max_area: the maximum allowed area ratio of a
+ category. Set to 1.0 to disable
+ ignored_category: allow this category in the semantic segmentation
+ ground truth to exceed the area ratio. Usually set to the category
+ that's ignored in training.
+ """
+ self.crop_aug = RandomCrop(crop_type, crop_size)
+ self._init(locals())
+
+ def get_transform(self, image, sem_seg):
+ if self.single_category_max_area >= 1.0:
+ return self.crop_aug.get_transform(image)
+ else:
+ h, w = sem_seg.shape
+ for _ in range(10):
+ crop_size = self.crop_aug.get_crop_size((h, w))
+ y0 = np.random.randint(h - crop_size[0] + 1)
+ x0 = np.random.randint(w - crop_size[1] + 1)
+ sem_seg_temp = sem_seg[y0 : y0 + crop_size[0], x0 : x0 + crop_size[1]]
+ labels, cnt = np.unique(sem_seg_temp, return_counts=True)
+ if self.ignored_category is not None:
+ cnt = cnt[labels != self.ignored_category]
+ if len(cnt) > 1 and np.max(cnt) < np.sum(cnt) * self.single_category_max_area:
+ break
+ crop_tfm = CropTransform(x0, y0, crop_size[1], crop_size[0])
+ return crop_tfm
+
+
+class RandomExtent(Augmentation):
+ """
+ Outputs an image by cropping a random "subrect" of the source image.
+
+ The subrect can be parameterized to include pixels outside the source image,
+ in which case they will be set to zeros (i.e. black). The size of the output
+ image will vary with the size of the random subrect.
+ """
+
+ def __init__(self, scale_range, shift_range):
+ """
+ Args:
+ output_size (h, w): Dimensions of output image
+ scale_range (l, h): Range of input-to-output size scaling factor
+ shift_range (x, y): Range of shifts of the cropped subrect. The rect
+ is shifted by [w / 2 * Uniform(-x, x), h / 2 * Uniform(-y, y)],
+ where (w, h) is the (width, height) of the input image. Set each
+ component to zero to crop at the image's center.
+ """
+ super().__init__()
+ self._init(locals())
+
+ def get_transform(self, image):
+ img_h, img_w = image.shape[:2]
+
+ # Initialize src_rect to fit the input image.
+ src_rect = np.array([-0.5 * img_w, -0.5 * img_h, 0.5 * img_w, 0.5 * img_h])
+
+ # Apply a random scaling to the src_rect.
+ src_rect *= np.random.uniform(self.scale_range[0], self.scale_range[1])
+
+ # Apply a random shift to the coordinates origin.
+ src_rect[0::2] += self.shift_range[0] * img_w * (np.random.rand() - 0.5)
+ src_rect[1::2] += self.shift_range[1] * img_h * (np.random.rand() - 0.5)
+
+ # Map src_rect coordinates into image coordinates (center at corner).
+ src_rect[0::2] += 0.5 * img_w
+ src_rect[1::2] += 0.5 * img_h
+
+ return ExtentTransform(
+ src_rect=(src_rect[0], src_rect[1], src_rect[2], src_rect[3]),
+ output_size=(int(src_rect[3] - src_rect[1]), int(src_rect[2] - src_rect[0])),
+ )
+
+
+class RandomContrast(Augmentation):
+ """
+ Randomly transforms image contrast.
+
+ Contrast intensity is uniformly sampled in (intensity_min, intensity_max).
+ - intensity < 1 will reduce contrast
+ - intensity = 1 will preserve the input image
+ - intensity > 1 will increase contrast
+
+ See: https://pillow.readthedocs.io/en/3.0.x/reference/ImageEnhance.html
+ """
+
+ def __init__(self, intensity_min, intensity_max):
+ """
+ Args:
+ intensity_min (float): Minimum augmentation
+ intensity_max (float): Maximum augmentation
+ """
+ super().__init__()
+ self._init(locals())
+
+ def get_transform(self, image):
+ w = np.random.uniform(self.intensity_min, self.intensity_max)
+ return BlendTransform(src_image=image.mean(), src_weight=1 - w, dst_weight=w)
+
+
+class RandomBrightness(Augmentation):
+ """
+ Randomly transforms image brightness.
+
+ Brightness intensity is uniformly sampled in (intensity_min, intensity_max).
+ - intensity < 1 will reduce brightness
+ - intensity = 1 will preserve the input image
+ - intensity > 1 will increase brightness
+
+ See: https://pillow.readthedocs.io/en/3.0.x/reference/ImageEnhance.html
+ """
+
+ def __init__(self, intensity_min, intensity_max):
+ """
+ Args:
+ intensity_min (float): Minimum augmentation
+ intensity_max (float): Maximum augmentation
+ """
+ super().__init__()
+ self._init(locals())
+
+ def get_transform(self, image):
+ w = np.random.uniform(self.intensity_min, self.intensity_max)
+ return BlendTransform(src_image=0, src_weight=1 - w, dst_weight=w)
+
+
+class RandomSaturation(Augmentation):
+ """
+ Randomly transforms saturation of an RGB image.
+ Input images are assumed to have 'RGB' channel order.
+
+ Saturation intensity is uniformly sampled in (intensity_min, intensity_max).
+ - intensity < 1 will reduce saturation (make the image more grayscale)
+ - intensity = 1 will preserve the input image
+ - intensity > 1 will increase saturation
+
+ See: https://pillow.readthedocs.io/en/3.0.x/reference/ImageEnhance.html
+ """
+
+ def __init__(self, intensity_min, intensity_max):
+ """
+ Args:
+ intensity_min (float): Minimum augmentation (1 preserves input).
+ intensity_max (float): Maximum augmentation (1 preserves input).
+ """
+ super().__init__()
+ self._init(locals())
+
+ def get_transform(self, image):
+ assert image.shape[-1] == 3, "RandomSaturation only works on RGB images"
+ w = np.random.uniform(self.intensity_min, self.intensity_max)
+ grayscale = image.dot([0.299, 0.587, 0.114])[:, :, np.newaxis]
+ return BlendTransform(src_image=grayscale, src_weight=1 - w, dst_weight=w)
+
+
+class RandomLighting(Augmentation):
+ """
+ The "lighting" augmentation described in AlexNet, using fixed PCA over ImageNet.
+ Input images are assumed to have 'RGB' channel order.
+
+ The degree of color jittering is randomly sampled via a normal distribution,
+ with standard deviation given by the scale parameter.
+ """
+
+ def __init__(self, scale):
+ """
+ Args:
+ scale (float): Standard deviation of principal component weighting.
+ """
+ super().__init__()
+ self._init(locals())
+ self.eigen_vecs = np.array(
+ [[-0.5675, 0.7192, 0.4009], [-0.5808, -0.0045, -0.8140], [-0.5836, -0.6948, 0.4203]]
+ )
+ self.eigen_vals = np.array([0.2175, 0.0188, 0.0045])
+
+ def get_transform(self, image):
+ assert image.shape[-1] == 3, "RandomLighting only works on RGB images"
+ weights = np.random.normal(scale=self.scale, size=3)
+ return BlendTransform(
+ src_image=self.eigen_vecs.dot(weights * self.eigen_vals), src_weight=1.0, dst_weight=1.0
+ )
+
+
+class RandomResize(Augmentation):
+ """Randomly resize image to a target size in shape_list"""
+
+ def __init__(self, shape_list, interp=Image.BILINEAR):
+ """
+ Args:
+ shape_list: a list of shapes in (h, w)
+ interp: PIL interpolation method
+ """
+ self.shape_list = shape_list
+ self._init(locals())
+
+ def get_transform(self, image):
+ shape_idx = np.random.randint(low=0, high=len(self.shape_list))
+ h, w = self.shape_list[shape_idx]
+ return ResizeTransform(image.shape[0], image.shape[1], h, w, self.interp)
+
+
+class MinIoURandomCrop(Augmentation):
+ """Random crop the image & bboxes, the cropped patches have minimum IoU
+ requirement with original image & bboxes, the IoU threshold is randomly
+ selected from min_ious.
+
+ Args:
+ min_ious (tuple): minimum IoU threshold for all intersections with
+ bounding boxes
+ min_crop_size (float): minimum crop's size (i.e. h,w := a*h, a*w,
+ where a >= min_crop_size)
+ mode_trials: number of trials for sampling min_ious threshold
+ crop_trials: number of trials for sampling crop_size after cropping
+ """
+
+ def __init__(
+ self,
+ min_ious=(0.1, 0.3, 0.5, 0.7, 0.9),
+ min_crop_size=0.3,
+ mode_trials=1000,
+ crop_trials=50,
+ ):
+ self.min_ious = min_ious
+ self.sample_mode = (1, *min_ious, 0)
+ self.min_crop_size = min_crop_size
+ self.mode_trials = mode_trials
+ self.crop_trials = crop_trials
+
+ def get_transform(self, image, boxes):
+ """Call function to crop images and bounding boxes with minimum IoU
+ constraint.
+
+ Args:
+ boxes: ground truth boxes in (x1, y1, x2, y2) format
+ """
+ if boxes is None:
+ return NoOpTransform()
+ h, w, c = image.shape
+ for _ in range(self.mode_trials):
+ mode = random.choice(self.sample_mode)
+ self.mode = mode
+ if mode == 1:
+ return NoOpTransform()
+
+ min_iou = mode
+ for _ in range(self.crop_trials):
+ new_w = random.uniform(self.min_crop_size * w, w)
+ new_h = random.uniform(self.min_crop_size * h, h)
+
+ # h / w in [0.5, 2]
+ if new_h / new_w < 0.5 or new_h / new_w > 2:
+ continue
+
+ left = random.uniform(w - new_w)
+ top = random.uniform(h - new_h)
+
+ patch = np.array((int(left), int(top), int(left + new_w), int(top + new_h)))
+ # Line or point crop is not allowed
+ if patch[2] == patch[0] or patch[3] == patch[1]:
+ continue
+ overlaps = pairwise_iou(
+ Boxes(patch.reshape(-1, 4)), Boxes(boxes.reshape(-1, 4))
+ ).reshape(-1)
+ if len(overlaps) > 0 and overlaps.min() < min_iou:
+ continue
+
+ # center of boxes should inside the crop img
+ # only adjust boxes and instance masks when the gt is not empty
+ if len(overlaps) > 0:
+ # adjust boxes
+ def is_center_of_bboxes_in_patch(boxes, patch):
+ center = (boxes[:, :2] + boxes[:, 2:]) / 2
+ mask = (
+ (center[:, 0] > patch[0])
+ * (center[:, 1] > patch[1])
+ * (center[:, 0] < patch[2])
+ * (center[:, 1] < patch[3])
+ )
+ return mask
+
+ mask = is_center_of_bboxes_in_patch(boxes, patch)
+ if not mask.any():
+ continue
+ return CropTransform(int(left), int(top), int(new_w), int(new_h))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/transforms/transform.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/transforms/transform.py
new file mode 100644
index 0000000000000000000000000000000000000000..46769a2569ffc6223a95990f8db5973757e7d23f
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/data/transforms/transform.py
@@ -0,0 +1,351 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+"""
+See "Data Augmentation" tutorial for an overview of the system:
+https://detectron2.readthedocs.io/tutorials/augmentation.html
+"""
+
+import numpy as np
+import torch
+import torch.nn.functional as F
+from fvcore.transforms.transform import (
+ CropTransform,
+ HFlipTransform,
+ NoOpTransform,
+ Transform,
+ TransformList,
+)
+from PIL import Image
+
+try:
+ import cv2 # noqa
+except ImportError:
+ # OpenCV is an optional dependency at the moment
+ pass
+
+__all__ = [
+ "ExtentTransform",
+ "ResizeTransform",
+ "RotationTransform",
+ "ColorTransform",
+ "PILColorTransform",
+]
+
+
+class ExtentTransform(Transform):
+ """
+ Extracts a subregion from the source image and scales it to the output size.
+
+ The fill color is used to map pixels from the source rect that fall outside
+ the source image.
+
+ See: https://pillow.readthedocs.io/en/latest/PIL.html#PIL.ImageTransform.ExtentTransform
+ """
+
+ def __init__(self, src_rect, output_size, interp=Image.BILINEAR, fill=0):
+ """
+ Args:
+ src_rect (x0, y0, x1, y1): src coordinates
+ output_size (h, w): dst image size
+ interp: PIL interpolation methods
+ fill: Fill color used when src_rect extends outside image
+ """
+ super().__init__()
+ self._set_attributes(locals())
+
+ def apply_image(self, img, interp=None):
+ h, w = self.output_size
+ if len(img.shape) > 2 and img.shape[2] == 1:
+ pil_image = Image.fromarray(img[:, :, 0], mode="L")
+ else:
+ pil_image = Image.fromarray(img)
+ pil_image = pil_image.transform(
+ size=(w, h),
+ method=Image.EXTENT,
+ data=self.src_rect,
+ resample=interp if interp else self.interp,
+ fill=self.fill,
+ )
+ ret = np.asarray(pil_image)
+ if len(img.shape) > 2 and img.shape[2] == 1:
+ ret = np.expand_dims(ret, -1)
+ return ret
+
+ def apply_coords(self, coords):
+ # Transform image center from source coordinates into output coordinates
+ # and then map the new origin to the corner of the output image.
+ h, w = self.output_size
+ x0, y0, x1, y1 = self.src_rect
+ new_coords = coords.astype(np.float32)
+ new_coords[:, 0] -= 0.5 * (x0 + x1)
+ new_coords[:, 1] -= 0.5 * (y0 + y1)
+ new_coords[:, 0] *= w / (x1 - x0)
+ new_coords[:, 1] *= h / (y1 - y0)
+ new_coords[:, 0] += 0.5 * w
+ new_coords[:, 1] += 0.5 * h
+ return new_coords
+
+ def apply_segmentation(self, segmentation):
+ segmentation = self.apply_image(segmentation, interp=Image.NEAREST)
+ return segmentation
+
+
+class ResizeTransform(Transform):
+ """
+ Resize the image to a target size.
+ """
+
+ def __init__(self, h, w, new_h, new_w, interp=None):
+ """
+ Args:
+ h, w (int): original image size
+ new_h, new_w (int): new image size
+ interp: PIL interpolation methods, defaults to bilinear.
+ """
+ # TODO decide on PIL vs opencv
+ super().__init__()
+ if interp is None:
+ interp = Image.BILINEAR
+ self._set_attributes(locals())
+
+ def apply_image(self, img, interp=None):
+ assert img.shape[:2] == (self.h, self.w)
+ assert len(img.shape) <= 4
+ interp_method = interp if interp is not None else self.interp
+
+ if img.dtype == np.uint8:
+ if len(img.shape) > 2 and img.shape[2] == 1:
+ pil_image = Image.fromarray(img[:, :, 0], mode="L")
+ else:
+ pil_image = Image.fromarray(img)
+ pil_image = pil_image.resize((self.new_w, self.new_h), interp_method)
+ ret = np.asarray(pil_image)
+ if len(img.shape) > 2 and img.shape[2] == 1:
+ ret = np.expand_dims(ret, -1)
+ else:
+ # PIL only supports uint8
+ if any(x < 0 for x in img.strides):
+ img = np.ascontiguousarray(img)
+ img = torch.from_numpy(img)
+ shape = list(img.shape)
+ shape_4d = shape[:2] + [1] * (4 - len(shape)) + shape[2:]
+ img = img.view(shape_4d).permute(2, 3, 0, 1) # hw(c) -> nchw
+ _PIL_RESIZE_TO_INTERPOLATE_MODE = {
+ Image.NEAREST: "nearest",
+ Image.BILINEAR: "bilinear",
+ Image.BICUBIC: "bicubic",
+ }
+ mode = _PIL_RESIZE_TO_INTERPOLATE_MODE[interp_method]
+ align_corners = None if mode == "nearest" else False
+ img = F.interpolate(
+ img, (self.new_h, self.new_w), mode=mode, align_corners=align_corners
+ )
+ shape[:2] = (self.new_h, self.new_w)
+ ret = img.permute(2, 3, 0, 1).view(shape).numpy() # nchw -> hw(c)
+
+ return ret
+
+ def apply_coords(self, coords):
+ coords[:, 0] = coords[:, 0] * (self.new_w * 1.0 / self.w)
+ coords[:, 1] = coords[:, 1] * (self.new_h * 1.0 / self.h)
+ return coords
+
+ def apply_segmentation(self, segmentation):
+ segmentation = self.apply_image(segmentation, interp=Image.NEAREST)
+ return segmentation
+
+ def inverse(self):
+ return ResizeTransform(self.new_h, self.new_w, self.h, self.w, self.interp)
+
+
+class RotationTransform(Transform):
+ """
+ This method returns a copy of this image, rotated the given
+ number of degrees counter clockwise around its center.
+ """
+
+ def __init__(self, h, w, angle, expand=True, center=None, interp=None):
+ """
+ Args:
+ h, w (int): original image size
+ angle (float): degrees for rotation
+ expand (bool): choose if the image should be resized to fit the whole
+ rotated image (default), or simply cropped
+ center (tuple (width, height)): coordinates of the rotation center
+ if left to None, the center will be fit to the center of each image
+ center has no effect if expand=True because it only affects shifting
+ interp: cv2 interpolation method, default cv2.INTER_LINEAR
+ """
+ super().__init__()
+ image_center = np.array((w / 2, h / 2))
+ if center is None:
+ center = image_center
+ if interp is None:
+ interp = cv2.INTER_LINEAR
+ abs_cos, abs_sin = (abs(np.cos(np.deg2rad(angle))), abs(np.sin(np.deg2rad(angle))))
+ if expand:
+ # find the new width and height bounds
+ bound_w, bound_h = np.rint(
+ [h * abs_sin + w * abs_cos, h * abs_cos + w * abs_sin]
+ ).astype(int)
+ else:
+ bound_w, bound_h = w, h
+
+ self._set_attributes(locals())
+ self.rm_coords = self.create_rotation_matrix()
+ # Needed because of this problem https://github.com/opencv/opencv/issues/11784
+ self.rm_image = self.create_rotation_matrix(offset=-0.5)
+
+ def apply_image(self, img, interp=None):
+ """
+ img should be a numpy array, formatted as Height * Width * Nchannels
+ """
+ if len(img) == 0 or self.angle % 360 == 0:
+ return img
+ assert img.shape[:2] == (self.h, self.w)
+ interp = interp if interp is not None else self.interp
+ return cv2.warpAffine(img, self.rm_image, (self.bound_w, self.bound_h), flags=interp)
+
+ def apply_coords(self, coords):
+ """
+ coords should be a N * 2 array-like, containing N couples of (x, y) points
+ """
+ coords = np.asarray(coords, dtype=float)
+ if len(coords) == 0 or self.angle % 360 == 0:
+ return coords
+ return cv2.transform(coords[:, np.newaxis, :], self.rm_coords)[:, 0, :]
+
+ def apply_segmentation(self, segmentation):
+ segmentation = self.apply_image(segmentation, interp=cv2.INTER_NEAREST)
+ return segmentation
+
+ def create_rotation_matrix(self, offset=0):
+ center = (self.center[0] + offset, self.center[1] + offset)
+ rm = cv2.getRotationMatrix2D(tuple(center), self.angle, 1)
+ if self.expand:
+ # Find the coordinates of the center of rotation in the new image
+ # The only point for which we know the future coordinates is the center of the image
+ rot_im_center = cv2.transform(self.image_center[None, None, :] + offset, rm)[0, 0, :]
+ new_center = np.array([self.bound_w / 2, self.bound_h / 2]) + offset - rot_im_center
+ # shift the rotation center to the new coordinates
+ rm[:, 2] += new_center
+ return rm
+
+ def inverse(self):
+ """
+ The inverse is to rotate it back with expand, and crop to get the original shape.
+ """
+ if not self.expand: # Not possible to inverse if a part of the image is lost
+ raise NotImplementedError()
+ rotation = RotationTransform(
+ self.bound_h, self.bound_w, -self.angle, True, None, self.interp
+ )
+ crop = CropTransform(
+ (rotation.bound_w - self.w) // 2, (rotation.bound_h - self.h) // 2, self.w, self.h
+ )
+ return TransformList([rotation, crop])
+
+
+class ColorTransform(Transform):
+ """
+ Generic wrapper for any photometric transforms.
+ These transformations should only affect the color space and
+ not the coordinate space of the image (e.g. annotation
+ coordinates such as bounding boxes should not be changed)
+ """
+
+ def __init__(self, op):
+ """
+ Args:
+ op (Callable): operation to be applied to the image,
+ which takes in an ndarray and returns an ndarray.
+ """
+ if not callable(op):
+ raise ValueError("op parameter should be callable")
+ super().__init__()
+ self._set_attributes(locals())
+
+ def apply_image(self, img):
+ return self.op(img)
+
+ def apply_coords(self, coords):
+ return coords
+
+ def inverse(self):
+ return NoOpTransform()
+
+ def apply_segmentation(self, segmentation):
+ return segmentation
+
+
+class PILColorTransform(ColorTransform):
+ """
+ Generic wrapper for PIL Photometric image transforms,
+ which affect the color space and not the coordinate
+ space of the image
+ """
+
+ def __init__(self, op):
+ """
+ Args:
+ op (Callable): operation to be applied to the image,
+ which takes in a PIL Image and returns a transformed
+ PIL Image.
+ For reference on possible operations see:
+ - https://pillow.readthedocs.io/en/stable/
+ """
+ if not callable(op):
+ raise ValueError("op parameter should be callable")
+ super().__init__(op)
+
+ def apply_image(self, img):
+ img = Image.fromarray(img)
+ return np.asarray(super().apply_image(img))
+
+
+def HFlip_rotated_box(transform, rotated_boxes):
+ """
+ Apply the horizontal flip transform on rotated boxes.
+
+ Args:
+ rotated_boxes (ndarray): Nx5 floating point array of
+ (x_center, y_center, width, height, angle_degrees) format
+ in absolute coordinates.
+ """
+ # Transform x_center
+ rotated_boxes[:, 0] = transform.width - rotated_boxes[:, 0]
+ # Transform angle
+ rotated_boxes[:, 4] = -rotated_boxes[:, 4]
+ return rotated_boxes
+
+
+def Resize_rotated_box(transform, rotated_boxes):
+ """
+ Apply the resizing transform on rotated boxes. For details of how these (approximation)
+ formulas are derived, please refer to :meth:`RotatedBoxes.scale`.
+
+ Args:
+ rotated_boxes (ndarray): Nx5 floating point array of
+ (x_center, y_center, width, height, angle_degrees) format
+ in absolute coordinates.
+ """
+ scale_factor_x = transform.new_w * 1.0 / transform.w
+ scale_factor_y = transform.new_h * 1.0 / transform.h
+ rotated_boxes[:, 0] *= scale_factor_x
+ rotated_boxes[:, 1] *= scale_factor_y
+ theta = rotated_boxes[:, 4] * np.pi / 180.0
+ c = np.cos(theta)
+ s = np.sin(theta)
+ rotated_boxes[:, 2] *= np.sqrt(np.square(scale_factor_x * c) + np.square(scale_factor_y * s))
+ rotated_boxes[:, 3] *= np.sqrt(np.square(scale_factor_x * s) + np.square(scale_factor_y * c))
+ rotated_boxes[:, 4] = np.arctan2(scale_factor_x * s, scale_factor_y * c) * 180 / np.pi
+
+ return rotated_boxes
+
+
+HFlipTransform.register_type("rotated_box", HFlip_rotated_box)
+ResizeTransform.register_type("rotated_box", Resize_rotated_box)
+
+# not necessary any more with latest fvcore
+NoOpTransform.register_type("rotated_box", lambda t, x: x)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/engine/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/engine/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..08a61572b4c7d09c8d400e903a96cbf5b2cc4763
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/engine/__init__.py
@@ -0,0 +1,12 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+from .launch import *
+from .train_loop import *
+
+__all__ = [k for k in globals().keys() if not k.startswith("_")]
+
+
+# prefer to let hooks and defaults live in separate namespaces (therefore not in __all__)
+# but still make them available here
+from .hooks import *
+from .defaults import *
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/engine/defaults.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/engine/defaults.py
new file mode 100644
index 0000000000000000000000000000000000000000..574f87cd7a11d11dfb91b9db62c795ba6403aaa9
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/engine/defaults.py
@@ -0,0 +1,715 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+"""
+This file contains components with some default boilerplate logic user may need
+in training / testing. They will not work for everyone, but many users may find them useful.
+
+The behavior of functions/classes in this file is subject to change,
+since they are meant to represent the "common default behavior" people need in their projects.
+"""
+
+import argparse
+import logging
+import os
+import sys
+import weakref
+from collections import OrderedDict
+from typing import Optional
+import torch
+from fvcore.nn.precise_bn import get_bn_modules
+from omegaconf import OmegaConf
+from torch.nn.parallel import DistributedDataParallel
+
+import custom_detectron2.data.transforms as T
+from custom_detectron2.checkpoint import DetectionCheckpointer
+from custom_detectron2.config import CfgNode, LazyConfig
+from custom_detectron2.data import (
+ MetadataCatalog,
+ build_detection_test_loader,
+ build_detection_train_loader,
+)
+from custom_detectron2.evaluation import (
+ DatasetEvaluator,
+ inference_on_dataset,
+ print_csv_format,
+ verify_results,
+)
+from custom_detectron2.modeling import build_model
+from custom_detectron2.solver import build_lr_scheduler, build_optimizer
+from custom_detectron2.utils import comm
+from custom_detectron2.utils.collect_env import collect_env_info
+from custom_detectron2.utils.env import seed_all_rng
+from custom_detectron2.utils.events import CommonMetricPrinter, JSONWriter, TensorboardXWriter
+from custom_detectron2.utils.file_io import PathManager
+from custom_detectron2.utils.logger import setup_logger
+
+from . import hooks
+from .train_loop import AMPTrainer, SimpleTrainer, TrainerBase
+
+__all__ = [
+ "create_ddp_model",
+ "default_argument_parser",
+ "default_setup",
+ "default_writers",
+ "DefaultPredictor",
+ "DefaultTrainer",
+]
+
+
+def create_ddp_model(model, *, fp16_compression=False, **kwargs):
+ """
+ Create a DistributedDataParallel model if there are >1 processes.
+
+ Args:
+ model: a torch.nn.Module
+ fp16_compression: add fp16 compression hooks to the ddp object.
+ See more at https://pytorch.org/docs/stable/ddp_comm_hooks.html#torch.distributed.algorithms.ddp_comm_hooks.default_hooks.fp16_compress_hook
+ kwargs: other arguments of :module:`torch.nn.parallel.DistributedDataParallel`.
+ """ # noqa
+ if comm.get_world_size() == 1:
+ return model
+ if "device_ids" not in kwargs:
+ kwargs["device_ids"] = [comm.get_local_rank()]
+ ddp = DistributedDataParallel(model, **kwargs)
+ if fp16_compression:
+ from torch.distributed.algorithms.ddp_comm_hooks import default as comm_hooks
+
+ ddp.register_comm_hook(state=None, hook=comm_hooks.fp16_compress_hook)
+ return ddp
+
+
+def default_argument_parser(epilog=None):
+ """
+ Create a parser with some common arguments used by detectron2 users.
+
+ Args:
+ epilog (str): epilog passed to ArgumentParser describing the usage.
+
+ Returns:
+ argparse.ArgumentParser:
+ """
+ parser = argparse.ArgumentParser(
+ epilog=epilog
+ or f"""
+Examples:
+
+Run on single machine:
+ $ {sys.argv[0]} --num-gpus 8 --config-file cfg.yaml
+
+Change some config options:
+ $ {sys.argv[0]} --config-file cfg.yaml MODEL.WEIGHTS /path/to/weight.pth SOLVER.BASE_LR 0.001
+
+Run on multiple machines:
+ (machine0)$ {sys.argv[0]} --machine-rank 0 --num-machines 2 --dist-url [--other-flags]
+ (machine1)$ {sys.argv[0]} --machine-rank 1 --num-machines 2 --dist-url [--other-flags]
+""",
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ )
+ parser.add_argument("--config-file", default="", metavar="FILE", help="path to config file")
+ parser.add_argument(
+ "--resume",
+ action="store_true",
+ help="Whether to attempt to resume from the checkpoint directory. "
+ "See documentation of `DefaultTrainer.resume_or_load()` for what it means.",
+ )
+ parser.add_argument("--eval-only", action="store_true", help="perform evaluation only")
+ parser.add_argument("--num-gpus", type=int, default=1, help="number of gpus *per machine*")
+ parser.add_argument("--num-machines", type=int, default=1, help="total number of machines")
+ parser.add_argument(
+ "--machine-rank", type=int, default=0, help="the rank of this machine (unique per machine)"
+ )
+
+ # PyTorch still may leave orphan processes in multi-gpu training.
+ # Therefore we use a deterministic way to obtain port,
+ # so that users are aware of orphan processes by seeing the port occupied.
+ port = 2**15 + 2**14 + hash(os.getuid() if sys.platform != "win32" else 1) % 2**14
+ parser.add_argument(
+ "--dist-url",
+ default="tcp://127.0.0.1:{}".format(port),
+ help="initialization URL for pytorch distributed backend. See "
+ "https://pytorch.org/docs/stable/distributed.html for details.",
+ )
+ parser.add_argument(
+ "opts",
+ help="""
+Modify config options at the end of the command. For Yacs configs, use
+space-separated "PATH.KEY VALUE" pairs.
+For python-based LazyConfig, use "path.key=value".
+ """.strip(),
+ default=None,
+ nargs=argparse.REMAINDER,
+ )
+ return parser
+
+
+def _try_get_key(cfg, *keys, default=None):
+ """
+ Try select keys from cfg until the first key that exists. Otherwise return default.
+ """
+ if isinstance(cfg, CfgNode):
+ cfg = OmegaConf.create(cfg.dump())
+ for k in keys:
+ none = object()
+ p = OmegaConf.select(cfg, k, default=none)
+ if p is not none:
+ return p
+ return default
+
+
+def _highlight(code, filename):
+ try:
+ import pygments
+ except ImportError:
+ return code
+
+ from pygments.lexers import Python3Lexer, YamlLexer
+ from pygments.formatters import Terminal256Formatter
+
+ lexer = Python3Lexer() if filename.endswith(".py") else YamlLexer()
+ code = pygments.highlight(code, lexer, Terminal256Formatter(style="monokai"))
+ return code
+
+
+def default_setup(cfg, args):
+ """
+ Perform some basic common setups at the beginning of a job, including:
+
+ 1. Set up the detectron2 logger
+ 2. Log basic information about environment, cmdline arguments, and config
+ 3. Backup the config to the output directory
+
+ Args:
+ cfg (CfgNode or omegaconf.DictConfig): the full config to be used
+ args (argparse.NameSpace): the command line arguments to be logged
+ """
+ output_dir = _try_get_key(cfg, "OUTPUT_DIR", "output_dir", "train.output_dir")
+ if comm.is_main_process() and output_dir:
+ PathManager.mkdirs(output_dir)
+
+ rank = comm.get_rank()
+ setup_logger(output_dir, distributed_rank=rank, name="fvcore")
+ logger = setup_logger(output_dir, distributed_rank=rank)
+
+ logger.info("Rank of current process: {}. World size: {}".format(rank, comm.get_world_size()))
+ logger.info("Environment info:\n" + collect_env_info())
+
+ logger.info("Command line arguments: " + str(args))
+ if hasattr(args, "config_file") and args.config_file != "":
+ logger.info(
+ "Contents of args.config_file={}:\n{}".format(
+ args.config_file,
+ _highlight(PathManager.open(args.config_file, "r").read(), args.config_file),
+ )
+ )
+
+ if comm.is_main_process() and output_dir:
+ # Note: some of our scripts may expect the existence of
+ # config.yaml in output directory
+ path = os.path.join(output_dir, "config.yaml")
+ if isinstance(cfg, CfgNode):
+ logger.info("Running with full config:\n{}".format(_highlight(cfg.dump(), ".yaml")))
+ with PathManager.open(path, "w") as f:
+ f.write(cfg.dump())
+ else:
+ LazyConfig.save(cfg, path)
+ logger.info("Full config saved to {}".format(path))
+
+ # make sure each worker has a different, yet deterministic seed if specified
+ seed = _try_get_key(cfg, "SEED", "train.seed", default=-1)
+ seed_all_rng(None if seed < 0 else seed + rank)
+
+ # cudnn benchmark has large overhead. It shouldn't be used considering the small size of
+ # typical validation set.
+ if not (hasattr(args, "eval_only") and args.eval_only):
+ torch.backends.cudnn.benchmark = _try_get_key(
+ cfg, "CUDNN_BENCHMARK", "train.cudnn_benchmark", default=False
+ )
+
+
+def default_writers(output_dir: str, max_iter: Optional[int] = None):
+ """
+ Build a list of :class:`EventWriter` to be used.
+ It now consists of a :class:`CommonMetricPrinter`,
+ :class:`TensorboardXWriter` and :class:`JSONWriter`.
+
+ Args:
+ output_dir: directory to store JSON metrics and tensorboard events
+ max_iter: the total number of iterations
+
+ Returns:
+ list[EventWriter]: a list of :class:`EventWriter` objects.
+ """
+ PathManager.mkdirs(output_dir)
+ return [
+ # It may not always print what you want to see, since it prints "common" metrics only.
+ CommonMetricPrinter(max_iter),
+ JSONWriter(os.path.join(output_dir, "metrics.json")),
+ TensorboardXWriter(output_dir),
+ ]
+
+
+class DefaultPredictor:
+ """
+ Create a simple end-to-end predictor with the given config that runs on
+ single device for a single input image.
+
+ Compared to using the model directly, this class does the following additions:
+
+ 1. Load checkpoint from `cfg.MODEL.WEIGHTS`.
+ 2. Always take BGR image as the input and apply conversion defined by `cfg.INPUT.FORMAT`.
+ 3. Apply resizing defined by `cfg.INPUT.{MIN,MAX}_SIZE_TEST`.
+ 4. Take one input image and produce a single output, instead of a batch.
+
+ This is meant for simple demo purposes, so it does the above steps automatically.
+ This is not meant for benchmarks or running complicated inference logic.
+ If you'd like to do anything more complicated, please refer to its source code as
+ examples to build and use the model manually.
+
+ Attributes:
+ metadata (Metadata): the metadata of the underlying dataset, obtained from
+ cfg.DATASETS.TEST.
+
+ Examples:
+ ::
+ pred = DefaultPredictor(cfg)
+ inputs = cv2.imread("input.jpg")
+ outputs = pred(inputs)
+ """
+
+ def __init__(self, cfg):
+ self.cfg = cfg.clone() # cfg can be modified by model
+ self.model = build_model(self.cfg)
+ self.model.eval()
+ if len(cfg.DATASETS.TEST):
+ self.metadata = MetadataCatalog.get(cfg.DATASETS.TEST[0])
+
+ checkpointer = DetectionCheckpointer(self.model)
+ checkpointer.load(cfg.MODEL.WEIGHTS)
+
+ self.aug = T.ResizeShortestEdge(
+ [cfg.INPUT.MIN_SIZE_TEST, cfg.INPUT.MIN_SIZE_TEST], cfg.INPUT.MAX_SIZE_TEST
+ )
+
+ self.input_format = cfg.INPUT.FORMAT
+ assert self.input_format in ["RGB", "BGR"], self.input_format
+
+ def __call__(self, original_image):
+ """
+ Args:
+ original_image (np.ndarray): an image of shape (H, W, C) (in BGR order).
+
+ Returns:
+ predictions (dict):
+ the output of the model for one image only.
+ See :doc:`/tutorials/models` for details about the format.
+ """
+ with torch.no_grad(): # https://github.com/sphinx-doc/sphinx/issues/4258
+ # Apply pre-processing to image.
+ if self.input_format == "RGB":
+ # whether the model expects BGR inputs or RGB
+ original_image = original_image[:, :, ::-1]
+ height, width = original_image.shape[:2]
+ image = self.aug.get_transform(original_image).apply_image(original_image)
+ image = torch.as_tensor(image.astype("float32").transpose(2, 0, 1))
+
+ inputs = {"image": image, "height": height, "width": width}
+ predictions = self.model([inputs])[0]
+ return predictions
+
+
+class DefaultTrainer(TrainerBase):
+ """
+ A trainer with default training logic. It does the following:
+
+ 1. Create a :class:`SimpleTrainer` using model, optimizer, dataloader
+ defined by the given config. Create a LR scheduler defined by the config.
+ 2. Load the last checkpoint or `cfg.MODEL.WEIGHTS`, if exists, when
+ `resume_or_load` is called.
+ 3. Register a few common hooks defined by the config.
+
+ It is created to simplify the **standard model training workflow** and reduce code boilerplate
+ for users who only need the standard training workflow, with standard features.
+ It means this class makes *many assumptions* about your training logic that
+ may easily become invalid in a new research. In fact, any assumptions beyond those made in the
+ :class:`SimpleTrainer` are too much for research.
+
+ The code of this class has been annotated about restrictive assumptions it makes.
+ When they do not work for you, you're encouraged to:
+
+ 1. Overwrite methods of this class, OR:
+ 2. Use :class:`SimpleTrainer`, which only does minimal SGD training and
+ nothing else. You can then add your own hooks if needed. OR:
+ 3. Write your own training loop similar to `tools/plain_train_net.py`.
+
+ See the :doc:`/tutorials/training` tutorials for more details.
+
+ Note that the behavior of this class, like other functions/classes in
+ this file, is not stable, since it is meant to represent the "common default behavior".
+ It is only guaranteed to work well with the standard models and training workflow in detectron2.
+ To obtain more stable behavior, write your own training logic with other public APIs.
+
+ Examples:
+ ::
+ trainer = DefaultTrainer(cfg)
+ trainer.resume_or_load() # load last checkpoint or MODEL.WEIGHTS
+ trainer.train()
+
+ Attributes:
+ scheduler:
+ checkpointer (DetectionCheckpointer):
+ cfg (CfgNode):
+ """
+
+ def __init__(self, cfg):
+ """
+ Args:
+ cfg (CfgNode):
+ """
+ super().__init__()
+ logger = logging.getLogger("detectron2")
+ if not logger.isEnabledFor(logging.INFO): # setup_logger is not called for d2
+ setup_logger()
+ cfg = DefaultTrainer.auto_scale_workers(cfg, comm.get_world_size())
+
+ # Assume these objects must be constructed in this order.
+ model = self.build_model(cfg)
+ optimizer = self.build_optimizer(cfg, model)
+ data_loader = self.build_train_loader(cfg)
+
+ model = create_ddp_model(model, broadcast_buffers=False)
+ self._trainer = (AMPTrainer if cfg.SOLVER.AMP.ENABLED else SimpleTrainer)(
+ model, data_loader, optimizer
+ )
+
+ self.scheduler = self.build_lr_scheduler(cfg, optimizer)
+ self.checkpointer = DetectionCheckpointer(
+ # Assume you want to save checkpoints together with logs/statistics
+ model,
+ cfg.OUTPUT_DIR,
+ trainer=weakref.proxy(self),
+ )
+ self.start_iter = 0
+ self.max_iter = cfg.SOLVER.MAX_ITER
+ self.cfg = cfg
+
+ self.register_hooks(self.build_hooks())
+
+ def resume_or_load(self, resume=True):
+ """
+ If `resume==True` and `cfg.OUTPUT_DIR` contains the last checkpoint (defined by
+ a `last_checkpoint` file), resume from the file. Resuming means loading all
+ available states (eg. optimizer and scheduler) and update iteration counter
+ from the checkpoint. ``cfg.MODEL.WEIGHTS`` will not be used.
+
+ Otherwise, this is considered as an independent training. The method will load model
+ weights from the file `cfg.MODEL.WEIGHTS` (but will not load other states) and start
+ from iteration 0.
+
+ Args:
+ resume (bool): whether to do resume or not
+ """
+ self.checkpointer.resume_or_load(self.cfg.MODEL.WEIGHTS, resume=resume)
+ if resume and self.checkpointer.has_checkpoint():
+ # The checkpoint stores the training iteration that just finished, thus we start
+ # at the next iteration
+ self.start_iter = self.iter + 1
+
+ def build_hooks(self):
+ """
+ Build a list of default hooks, including timing, evaluation,
+ checkpointing, lr scheduling, precise BN, writing events.
+
+ Returns:
+ list[HookBase]:
+ """
+ cfg = self.cfg.clone()
+ cfg.defrost()
+ cfg.DATALOADER.NUM_WORKERS = 0 # save some memory and time for PreciseBN
+
+ ret = [
+ hooks.IterationTimer(),
+ hooks.LRScheduler(),
+ hooks.PreciseBN(
+ # Run at the same freq as (but before) evaluation.
+ cfg.TEST.EVAL_PERIOD,
+ self.model,
+ # Build a new data loader to not affect training
+ self.build_train_loader(cfg),
+ cfg.TEST.PRECISE_BN.NUM_ITER,
+ )
+ if cfg.TEST.PRECISE_BN.ENABLED and get_bn_modules(self.model)
+ else None,
+ ]
+
+ # Do PreciseBN before checkpointer, because it updates the model and need to
+ # be saved by checkpointer.
+ # This is not always the best: if checkpointing has a different frequency,
+ # some checkpoints may have more precise statistics than others.
+ if comm.is_main_process():
+ ret.append(hooks.PeriodicCheckpointer(self.checkpointer, cfg.SOLVER.CHECKPOINT_PERIOD))
+
+ def test_and_save_results():
+ self._last_eval_results = self.test(self.cfg, self.model)
+ return self._last_eval_results
+
+ # Do evaluation after checkpointer, because then if it fails,
+ # we can use the saved checkpoint to debug.
+ ret.append(hooks.EvalHook(cfg.TEST.EVAL_PERIOD, test_and_save_results))
+
+ if comm.is_main_process():
+ # Here the default print/log frequency of each writer is used.
+ # run writers in the end, so that evaluation metrics are written
+ ret.append(hooks.PeriodicWriter(self.build_writers(), period=20))
+ return ret
+
+ def build_writers(self):
+ """
+ Build a list of writers to be used using :func:`default_writers()`.
+ If you'd like a different list of writers, you can overwrite it in
+ your trainer.
+
+ Returns:
+ list[EventWriter]: a list of :class:`EventWriter` objects.
+ """
+ return default_writers(self.cfg.OUTPUT_DIR, self.max_iter)
+
+ def train(self):
+ """
+ Run training.
+
+ Returns:
+ OrderedDict of results, if evaluation is enabled. Otherwise None.
+ """
+ super().train(self.start_iter, self.max_iter)
+ if len(self.cfg.TEST.EXPECTED_RESULTS) and comm.is_main_process():
+ assert hasattr(
+ self, "_last_eval_results"
+ ), "No evaluation results obtained during training!"
+ verify_results(self.cfg, self._last_eval_results)
+ return self._last_eval_results
+
+ def run_step(self):
+ self._trainer.iter = self.iter
+ self._trainer.run_step()
+
+ def state_dict(self):
+ ret = super().state_dict()
+ ret["_trainer"] = self._trainer.state_dict()
+ return ret
+
+ def load_state_dict(self, state_dict):
+ super().load_state_dict(state_dict)
+ self._trainer.load_state_dict(state_dict["_trainer"])
+
+ @classmethod
+ def build_model(cls, cfg):
+ """
+ Returns:
+ torch.nn.Module:
+
+ It now calls :func:`detectron2.modeling.build_model`.
+ Overwrite it if you'd like a different model.
+ """
+ model = build_model(cfg)
+ logger = logging.getLogger(__name__)
+ logger.info("Model:\n{}".format(model))
+ return model
+
+ @classmethod
+ def build_optimizer(cls, cfg, model):
+ """
+ Returns:
+ torch.optim.Optimizer:
+
+ It now calls :func:`detectron2.solver.build_optimizer`.
+ Overwrite it if you'd like a different optimizer.
+ """
+ return build_optimizer(cfg, model)
+
+ @classmethod
+ def build_lr_scheduler(cls, cfg, optimizer):
+ """
+ It now calls :func:`detectron2.solver.build_lr_scheduler`.
+ Overwrite it if you'd like a different scheduler.
+ """
+ return build_lr_scheduler(cfg, optimizer)
+
+ @classmethod
+ def build_train_loader(cls, cfg):
+ """
+ Returns:
+ iterable
+
+ It now calls :func:`detectron2.data.build_detection_train_loader`.
+ Overwrite it if you'd like a different data loader.
+ """
+ return build_detection_train_loader(cfg)
+
+ @classmethod
+ def build_test_loader(cls, cfg, dataset_name):
+ """
+ Returns:
+ iterable
+
+ It now calls :func:`detectron2.data.build_detection_test_loader`.
+ Overwrite it if you'd like a different data loader.
+ """
+ return build_detection_test_loader(cfg, dataset_name)
+
+ @classmethod
+ def build_evaluator(cls, cfg, dataset_name):
+ """
+ Returns:
+ DatasetEvaluator or None
+
+ It is not implemented by default.
+ """
+ raise NotImplementedError(
+ """
+If you want DefaultTrainer to automatically run evaluation,
+please implement `build_evaluator()` in subclasses (see train_net.py for example).
+Alternatively, you can call evaluation functions yourself (see Colab balloon tutorial for example).
+"""
+ )
+
+ @classmethod
+ def test(cls, cfg, model, evaluators=None):
+ """
+ Evaluate the given model. The given model is expected to already contain
+ weights to evaluate.
+
+ Args:
+ cfg (CfgNode):
+ model (nn.Module):
+ evaluators (list[DatasetEvaluator] or None): if None, will call
+ :meth:`build_evaluator`. Otherwise, must have the same length as
+ ``cfg.DATASETS.TEST``.
+
+ Returns:
+ dict: a dict of result metrics
+ """
+ logger = logging.getLogger(__name__)
+ if isinstance(evaluators, DatasetEvaluator):
+ evaluators = [evaluators]
+ if evaluators is not None:
+ assert len(cfg.DATASETS.TEST) == len(evaluators), "{} != {}".format(
+ len(cfg.DATASETS.TEST), len(evaluators)
+ )
+
+ results = OrderedDict()
+ for idx, dataset_name in enumerate(cfg.DATASETS.TEST):
+ data_loader = cls.build_test_loader(cfg, dataset_name)
+ # When evaluators are passed in as arguments,
+ # implicitly assume that evaluators can be created before data_loader.
+ if evaluators is not None:
+ evaluator = evaluators[idx]
+ else:
+ try:
+ evaluator = cls.build_evaluator(cfg, dataset_name)
+ except NotImplementedError:
+ logger.warn(
+ "No evaluator found. Use `DefaultTrainer.test(evaluators=)`, "
+ "or implement its `build_evaluator` method."
+ )
+ results[dataset_name] = {}
+ continue
+ results_i = inference_on_dataset(model, data_loader, evaluator)
+ results[dataset_name] = results_i
+ if comm.is_main_process():
+ assert isinstance(
+ results_i, dict
+ ), "Evaluator must return a dict on the main process. Got {} instead.".format(
+ results_i
+ )
+ logger.info("Evaluation results for {} in csv format:".format(dataset_name))
+ print_csv_format(results_i)
+
+ if len(results) == 1:
+ results = list(results.values())[0]
+ return results
+
+ @staticmethod
+ def auto_scale_workers(cfg, num_workers: int):
+ """
+ When the config is defined for certain number of workers (according to
+ ``cfg.SOLVER.REFERENCE_WORLD_SIZE``) that's different from the number of
+ workers currently in use, returns a new cfg where the total batch size
+ is scaled so that the per-GPU batch size stays the same as the
+ original ``IMS_PER_BATCH // REFERENCE_WORLD_SIZE``.
+
+ Other config options are also scaled accordingly:
+ * training steps and warmup steps are scaled inverse proportionally.
+ * learning rate are scaled proportionally, following :paper:`ImageNet in 1h`.
+
+ For example, with the original config like the following:
+
+ .. code-block:: yaml
+
+ IMS_PER_BATCH: 16
+ BASE_LR: 0.1
+ REFERENCE_WORLD_SIZE: 8
+ MAX_ITER: 5000
+ STEPS: (4000,)
+ CHECKPOINT_PERIOD: 1000
+
+ When this config is used on 16 GPUs instead of the reference number 8,
+ calling this method will return a new config with:
+
+ .. code-block:: yaml
+
+ IMS_PER_BATCH: 32
+ BASE_LR: 0.2
+ REFERENCE_WORLD_SIZE: 16
+ MAX_ITER: 2500
+ STEPS: (2000,)
+ CHECKPOINT_PERIOD: 500
+
+ Note that both the original config and this new config can be trained on 16 GPUs.
+ It's up to user whether to enable this feature (by setting ``REFERENCE_WORLD_SIZE``).
+
+ Returns:
+ CfgNode: a new config. Same as original if ``cfg.SOLVER.REFERENCE_WORLD_SIZE==0``.
+ """
+ old_world_size = cfg.SOLVER.REFERENCE_WORLD_SIZE
+ if old_world_size == 0 or old_world_size == num_workers:
+ return cfg
+ cfg = cfg.clone()
+ frozen = cfg.is_frozen()
+ cfg.defrost()
+
+ assert (
+ cfg.SOLVER.IMS_PER_BATCH % old_world_size == 0
+ ), "Invalid REFERENCE_WORLD_SIZE in config!"
+ scale = num_workers / old_world_size
+ bs = cfg.SOLVER.IMS_PER_BATCH = int(round(cfg.SOLVER.IMS_PER_BATCH * scale))
+ lr = cfg.SOLVER.BASE_LR = cfg.SOLVER.BASE_LR * scale
+ max_iter = cfg.SOLVER.MAX_ITER = int(round(cfg.SOLVER.MAX_ITER / scale))
+ warmup_iter = cfg.SOLVER.WARMUP_ITERS = int(round(cfg.SOLVER.WARMUP_ITERS / scale))
+ cfg.SOLVER.STEPS = tuple(int(round(s / scale)) for s in cfg.SOLVER.STEPS)
+ cfg.TEST.EVAL_PERIOD = int(round(cfg.TEST.EVAL_PERIOD / scale))
+ cfg.SOLVER.CHECKPOINT_PERIOD = int(round(cfg.SOLVER.CHECKPOINT_PERIOD / scale))
+ cfg.SOLVER.REFERENCE_WORLD_SIZE = num_workers # maintain invariant
+ logger = logging.getLogger(__name__)
+ logger.info(
+ f"Auto-scaling the config to batch_size={bs}, learning_rate={lr}, "
+ f"max_iter={max_iter}, warmup={warmup_iter}."
+ )
+
+ if frozen:
+ cfg.freeze()
+ return cfg
+
+
+# Access basic attributes from the underlying trainer
+for _attr in ["model", "data_loader", "optimizer"]:
+ setattr(
+ DefaultTrainer,
+ _attr,
+ property(
+ # getter
+ lambda self, x=_attr: getattr(self._trainer, x),
+ # setter
+ lambda self, value, x=_attr: setattr(self._trainer, x, value),
+ ),
+ )
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/engine/hooks.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/engine/hooks.py
new file mode 100644
index 0000000000000000000000000000000000000000..a8773d612a05bb028e6db98b60030fcd4fe04981
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/engine/hooks.py
@@ -0,0 +1,690 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import datetime
+import itertools
+import logging
+import math
+import operator
+import os
+import tempfile
+import time
+import warnings
+from collections import Counter
+import torch
+from fvcore.common.checkpoint import Checkpointer
+from fvcore.common.checkpoint import PeriodicCheckpointer as _PeriodicCheckpointer
+from fvcore.common.param_scheduler import ParamScheduler
+from fvcore.common.timer import Timer
+from fvcore.nn.precise_bn import get_bn_modules, update_bn_stats
+
+import custom_detectron2.utils.comm as comm
+from custom_detectron2.evaluation.testing import flatten_results_dict
+from custom_detectron2.solver import LRMultiplier
+from custom_detectron2.solver import LRScheduler as _LRScheduler
+from custom_detectron2.utils.events import EventStorage, EventWriter
+from custom_detectron2.utils.file_io import PathManager
+
+from .train_loop import HookBase
+
+__all__ = [
+ "CallbackHook",
+ "IterationTimer",
+ "PeriodicWriter",
+ "PeriodicCheckpointer",
+ "BestCheckpointer",
+ "LRScheduler",
+ "AutogradProfiler",
+ "EvalHook",
+ "PreciseBN",
+ "TorchProfiler",
+ "TorchMemoryStats",
+]
+
+
+"""
+Implement some common hooks.
+"""
+
+
+class CallbackHook(HookBase):
+ """
+ Create a hook using callback functions provided by the user.
+ """
+
+ def __init__(self, *, before_train=None, after_train=None, before_step=None, after_step=None):
+ """
+ Each argument is a function that takes one argument: the trainer.
+ """
+ self._before_train = before_train
+ self._before_step = before_step
+ self._after_step = after_step
+ self._after_train = after_train
+
+ def before_train(self):
+ if self._before_train:
+ self._before_train(self.trainer)
+
+ def after_train(self):
+ if self._after_train:
+ self._after_train(self.trainer)
+ # The functions may be closures that hold reference to the trainer
+ # Therefore, delete them to avoid circular reference.
+ del self._before_train, self._after_train
+ del self._before_step, self._after_step
+
+ def before_step(self):
+ if self._before_step:
+ self._before_step(self.trainer)
+
+ def after_step(self):
+ if self._after_step:
+ self._after_step(self.trainer)
+
+
+class IterationTimer(HookBase):
+ """
+ Track the time spent for each iteration (each run_step call in the trainer).
+ Print a summary in the end of training.
+
+ This hook uses the time between the call to its :meth:`before_step`
+ and :meth:`after_step` methods.
+ Under the convention that :meth:`before_step` of all hooks should only
+ take negligible amount of time, the :class:`IterationTimer` hook should be
+ placed at the beginning of the list of hooks to obtain accurate timing.
+ """
+
+ def __init__(self, warmup_iter=3):
+ """
+ Args:
+ warmup_iter (int): the number of iterations at the beginning to exclude
+ from timing.
+ """
+ self._warmup_iter = warmup_iter
+ self._step_timer = Timer()
+ self._start_time = time.perf_counter()
+ self._total_timer = Timer()
+
+ def before_train(self):
+ self._start_time = time.perf_counter()
+ self._total_timer.reset()
+ self._total_timer.pause()
+
+ def after_train(self):
+ logger = logging.getLogger(__name__)
+ total_time = time.perf_counter() - self._start_time
+ total_time_minus_hooks = self._total_timer.seconds()
+ hook_time = total_time - total_time_minus_hooks
+
+ num_iter = self.trainer.storage.iter + 1 - self.trainer.start_iter - self._warmup_iter
+
+ if num_iter > 0 and total_time_minus_hooks > 0:
+ # Speed is meaningful only after warmup
+ # NOTE this format is parsed by grep in some scripts
+ logger.info(
+ "Overall training speed: {} iterations in {} ({:.4f} s / it)".format(
+ num_iter,
+ str(datetime.timedelta(seconds=int(total_time_minus_hooks))),
+ total_time_minus_hooks / num_iter,
+ )
+ )
+
+ logger.info(
+ "Total training time: {} ({} on hooks)".format(
+ str(datetime.timedelta(seconds=int(total_time))),
+ str(datetime.timedelta(seconds=int(hook_time))),
+ )
+ )
+
+ def before_step(self):
+ self._step_timer.reset()
+ self._total_timer.resume()
+
+ def after_step(self):
+ # +1 because we're in after_step, the current step is done
+ # but not yet counted
+ iter_done = self.trainer.storage.iter - self.trainer.start_iter + 1
+ if iter_done >= self._warmup_iter:
+ sec = self._step_timer.seconds()
+ self.trainer.storage.put_scalars(time=sec)
+ else:
+ self._start_time = time.perf_counter()
+ self._total_timer.reset()
+
+ self._total_timer.pause()
+
+
+class PeriodicWriter(HookBase):
+ """
+ Write events to EventStorage (by calling ``writer.write()``) periodically.
+
+ It is executed every ``period`` iterations and after the last iteration.
+ Note that ``period`` does not affect how data is smoothed by each writer.
+ """
+
+ def __init__(self, writers, period=20):
+ """
+ Args:
+ writers (list[EventWriter]): a list of EventWriter objects
+ period (int):
+ """
+ self._writers = writers
+ for w in writers:
+ assert isinstance(w, EventWriter), w
+ self._period = period
+
+ def after_step(self):
+ if (self.trainer.iter + 1) % self._period == 0 or (
+ self.trainer.iter == self.trainer.max_iter - 1
+ ):
+ for writer in self._writers:
+ writer.write()
+
+ def after_train(self):
+ for writer in self._writers:
+ # If any new data is found (e.g. produced by other after_train),
+ # write them before closing
+ writer.write()
+ writer.close()
+
+
+class PeriodicCheckpointer(_PeriodicCheckpointer, HookBase):
+ """
+ Same as :class:`detectron2.checkpoint.PeriodicCheckpointer`, but as a hook.
+
+ Note that when used as a hook,
+ it is unable to save additional data other than what's defined
+ by the given `checkpointer`.
+
+ It is executed every ``period`` iterations and after the last iteration.
+ """
+
+ def before_train(self):
+ self.max_iter = self.trainer.max_iter
+
+ def after_step(self):
+ # No way to use **kwargs
+ self.step(self.trainer.iter)
+
+
+class BestCheckpointer(HookBase):
+ """
+ Checkpoints best weights based off given metric.
+
+ This hook should be used in conjunction to and executed after the hook
+ that produces the metric, e.g. `EvalHook`.
+ """
+
+ def __init__(
+ self,
+ eval_period: int,
+ checkpointer: Checkpointer,
+ val_metric: str,
+ mode: str = "max",
+ file_prefix: str = "model_best",
+ ) -> None:
+ """
+ Args:
+ eval_period (int): the period `EvalHook` is set to run.
+ checkpointer: the checkpointer object used to save checkpoints.
+ val_metric (str): validation metric to track for best checkpoint, e.g. "bbox/AP50"
+ mode (str): one of {'max', 'min'}. controls whether the chosen val metric should be
+ maximized or minimized, e.g. for "bbox/AP50" it should be "max"
+ file_prefix (str): the prefix of checkpoint's filename, defaults to "model_best"
+ """
+ self._logger = logging.getLogger(__name__)
+ self._period = eval_period
+ self._val_metric = val_metric
+ assert mode in [
+ "max",
+ "min",
+ ], f'Mode "{mode}" to `BestCheckpointer` is unknown. It should be one of {"max", "min"}.'
+ if mode == "max":
+ self._compare = operator.gt
+ else:
+ self._compare = operator.lt
+ self._checkpointer = checkpointer
+ self._file_prefix = file_prefix
+ self.best_metric = None
+ self.best_iter = None
+
+ def _update_best(self, val, iteration):
+ if math.isnan(val) or math.isinf(val):
+ return False
+ self.best_metric = val
+ self.best_iter = iteration
+ return True
+
+ def _best_checking(self):
+ metric_tuple = self.trainer.storage.latest().get(self._val_metric)
+ if metric_tuple is None:
+ self._logger.warning(
+ f"Given val metric {self._val_metric} does not seem to be computed/stored."
+ "Will not be checkpointing based on it."
+ )
+ return
+ else:
+ latest_metric, metric_iter = metric_tuple
+
+ if self.best_metric is None:
+ if self._update_best(latest_metric, metric_iter):
+ additional_state = {"iteration": metric_iter}
+ self._checkpointer.save(f"{self._file_prefix}", **additional_state)
+ self._logger.info(
+ f"Saved first model at {self.best_metric:0.5f} @ {self.best_iter} steps"
+ )
+ elif self._compare(latest_metric, self.best_metric):
+ additional_state = {"iteration": metric_iter}
+ self._checkpointer.save(f"{self._file_prefix}", **additional_state)
+ self._logger.info(
+ f"Saved best model as latest eval score for {self._val_metric} is "
+ f"{latest_metric:0.5f}, better than last best score "
+ f"{self.best_metric:0.5f} @ iteration {self.best_iter}."
+ )
+ self._update_best(latest_metric, metric_iter)
+ else:
+ self._logger.info(
+ f"Not saving as latest eval score for {self._val_metric} is {latest_metric:0.5f}, "
+ f"not better than best score {self.best_metric:0.5f} @ iteration {self.best_iter}."
+ )
+
+ def after_step(self):
+ # same conditions as `EvalHook`
+ next_iter = self.trainer.iter + 1
+ if (
+ self._period > 0
+ and next_iter % self._period == 0
+ and next_iter != self.trainer.max_iter
+ ):
+ self._best_checking()
+
+ def after_train(self):
+ # same conditions as `EvalHook`
+ if self.trainer.iter + 1 >= self.trainer.max_iter:
+ self._best_checking()
+
+
+class LRScheduler(HookBase):
+ """
+ A hook which executes a torch builtin LR scheduler and summarizes the LR.
+ It is executed after every iteration.
+ """
+
+ def __init__(self, optimizer=None, scheduler=None):
+ """
+ Args:
+ optimizer (torch.optim.Optimizer):
+ scheduler (torch.optim.LRScheduler or fvcore.common.param_scheduler.ParamScheduler):
+ if a :class:`ParamScheduler` object, it defines the multiplier over the base LR
+ in the optimizer.
+
+ If any argument is not given, will try to obtain it from the trainer.
+ """
+ self._optimizer = optimizer
+ self._scheduler = scheduler
+
+ def before_train(self):
+ self._optimizer = self._optimizer or self.trainer.optimizer
+ if isinstance(self.scheduler, ParamScheduler):
+ self._scheduler = LRMultiplier(
+ self._optimizer,
+ self.scheduler,
+ self.trainer.max_iter,
+ last_iter=self.trainer.iter - 1,
+ )
+ self._best_param_group_id = LRScheduler.get_best_param_group_id(self._optimizer)
+
+ @staticmethod
+ def get_best_param_group_id(optimizer):
+ # NOTE: some heuristics on what LR to summarize
+ # summarize the param group with most parameters
+ largest_group = max(len(g["params"]) for g in optimizer.param_groups)
+
+ if largest_group == 1:
+ # If all groups have one parameter,
+ # then find the most common initial LR, and use it for summary
+ lr_count = Counter([g["lr"] for g in optimizer.param_groups])
+ lr = lr_count.most_common()[0][0]
+ for i, g in enumerate(optimizer.param_groups):
+ if g["lr"] == lr:
+ return i
+ else:
+ for i, g in enumerate(optimizer.param_groups):
+ if len(g["params"]) == largest_group:
+ return i
+
+ def after_step(self):
+ lr = self._optimizer.param_groups[self._best_param_group_id]["lr"]
+ self.trainer.storage.put_scalar("lr", lr, smoothing_hint=False)
+ self.scheduler.step()
+
+ @property
+ def scheduler(self):
+ return self._scheduler or self.trainer.scheduler
+
+ def state_dict(self):
+ if isinstance(self.scheduler, _LRScheduler):
+ return self.scheduler.state_dict()
+ return {}
+
+ def load_state_dict(self, state_dict):
+ if isinstance(self.scheduler, _LRScheduler):
+ logger = logging.getLogger(__name__)
+ logger.info("Loading scheduler from state_dict ...")
+ self.scheduler.load_state_dict(state_dict)
+
+
+class TorchProfiler(HookBase):
+ """
+ A hook which runs `torch.profiler.profile`.
+
+ Examples:
+ ::
+ hooks.TorchProfiler(
+ lambda trainer: 10 < trainer.iter < 20, self.cfg.OUTPUT_DIR
+ )
+
+ The above example will run the profiler for iteration 10~20 and dump
+ results to ``OUTPUT_DIR``. We did not profile the first few iterations
+ because they are typically slower than the rest.
+ The result files can be loaded in the ``chrome://tracing`` page in chrome browser,
+ and the tensorboard visualizations can be visualized using
+ ``tensorboard --logdir OUTPUT_DIR/log``
+ """
+
+ def __init__(self, enable_predicate, output_dir, *, activities=None, save_tensorboard=True):
+ """
+ Args:
+ enable_predicate (callable[trainer -> bool]): a function which takes a trainer,
+ and returns whether to enable the profiler.
+ It will be called once every step, and can be used to select which steps to profile.
+ output_dir (str): the output directory to dump tracing files.
+ activities (iterable): same as in `torch.profiler.profile`.
+ save_tensorboard (bool): whether to save tensorboard visualizations at (output_dir)/log/
+ """
+ self._enable_predicate = enable_predicate
+ self._activities = activities
+ self._output_dir = output_dir
+ self._save_tensorboard = save_tensorboard
+
+ def before_step(self):
+ if self._enable_predicate(self.trainer):
+ if self._save_tensorboard:
+ on_trace_ready = torch.profiler.tensorboard_trace_handler(
+ os.path.join(
+ self._output_dir,
+ "log",
+ "profiler-tensorboard-iter{}".format(self.trainer.iter),
+ ),
+ f"worker{comm.get_rank()}",
+ )
+ else:
+ on_trace_ready = None
+ self._profiler = torch.profiler.profile(
+ activities=self._activities,
+ on_trace_ready=on_trace_ready,
+ record_shapes=True,
+ profile_memory=True,
+ with_stack=True,
+ with_flops=True,
+ )
+ self._profiler.__enter__()
+ else:
+ self._profiler = None
+
+ def after_step(self):
+ if self._profiler is None:
+ return
+ self._profiler.__exit__(None, None, None)
+ if not self._save_tensorboard:
+ PathManager.mkdirs(self._output_dir)
+ out_file = os.path.join(
+ self._output_dir, "profiler-trace-iter{}.json".format(self.trainer.iter)
+ )
+ if "://" not in out_file:
+ self._profiler.export_chrome_trace(out_file)
+ else:
+ # Support non-posix filesystems
+ with tempfile.TemporaryDirectory(prefix="detectron2_profiler") as d:
+ tmp_file = os.path.join(d, "tmp.json")
+ self._profiler.export_chrome_trace(tmp_file)
+ with open(tmp_file) as f:
+ content = f.read()
+ with PathManager.open(out_file, "w") as f:
+ f.write(content)
+
+
+class AutogradProfiler(TorchProfiler):
+ """
+ A hook which runs `torch.autograd.profiler.profile`.
+
+ Examples:
+ ::
+ hooks.AutogradProfiler(
+ lambda trainer: 10 < trainer.iter < 20, self.cfg.OUTPUT_DIR
+ )
+
+ The above example will run the profiler for iteration 10~20 and dump
+ results to ``OUTPUT_DIR``. We did not profile the first few iterations
+ because they are typically slower than the rest.
+ The result files can be loaded in the ``chrome://tracing`` page in chrome browser.
+
+ Note:
+ When used together with NCCL on older version of GPUs,
+ autograd profiler may cause deadlock because it unnecessarily allocates
+ memory on every device it sees. The memory management calls, if
+ interleaved with NCCL calls, lead to deadlock on GPUs that do not
+ support ``cudaLaunchCooperativeKernelMultiDevice``.
+ """
+
+ def __init__(self, enable_predicate, output_dir, *, use_cuda=True):
+ """
+ Args:
+ enable_predicate (callable[trainer -> bool]): a function which takes a trainer,
+ and returns whether to enable the profiler.
+ It will be called once every step, and can be used to select which steps to profile.
+ output_dir (str): the output directory to dump tracing files.
+ use_cuda (bool): same as in `torch.autograd.profiler.profile`.
+ """
+ warnings.warn("AutogradProfiler has been deprecated in favor of TorchProfiler.")
+ self._enable_predicate = enable_predicate
+ self._use_cuda = use_cuda
+ self._output_dir = output_dir
+
+ def before_step(self):
+ if self._enable_predicate(self.trainer):
+ self._profiler = torch.autograd.profiler.profile(use_cuda=self._use_cuda)
+ self._profiler.__enter__()
+ else:
+ self._profiler = None
+
+
+class EvalHook(HookBase):
+ """
+ Run an evaluation function periodically, and at the end of training.
+
+ It is executed every ``eval_period`` iterations and after the last iteration.
+ """
+
+ def __init__(self, eval_period, eval_function, eval_after_train=True):
+ """
+ Args:
+ eval_period (int): the period to run `eval_function`. Set to 0 to
+ not evaluate periodically (but still evaluate after the last iteration
+ if `eval_after_train` is True).
+ eval_function (callable): a function which takes no arguments, and
+ returns a nested dict of evaluation metrics.
+ eval_after_train (bool): whether to evaluate after the last iteration
+
+ Note:
+ This hook must be enabled in all or none workers.
+ If you would like only certain workers to perform evaluation,
+ give other workers a no-op function (`eval_function=lambda: None`).
+ """
+ self._period = eval_period
+ self._func = eval_function
+ self._eval_after_train = eval_after_train
+
+ def _do_eval(self):
+ results = self._func()
+
+ if results:
+ assert isinstance(
+ results, dict
+ ), "Eval function must return a dict. Got {} instead.".format(results)
+
+ flattened_results = flatten_results_dict(results)
+ for k, v in flattened_results.items():
+ try:
+ v = float(v)
+ except Exception as e:
+ raise ValueError(
+ "[EvalHook] eval_function should return a nested dict of float. "
+ "Got '{}: {}' instead.".format(k, v)
+ ) from e
+ self.trainer.storage.put_scalars(**flattened_results, smoothing_hint=False)
+
+ # Evaluation may take different time among workers.
+ # A barrier make them start the next iteration together.
+ comm.synchronize()
+
+ def after_step(self):
+ next_iter = self.trainer.iter + 1
+ if self._period > 0 and next_iter % self._period == 0:
+ # do the last eval in after_train
+ if next_iter != self.trainer.max_iter:
+ self._do_eval()
+
+ def after_train(self):
+ # This condition is to prevent the eval from running after a failed training
+ if self._eval_after_train and self.trainer.iter + 1 >= self.trainer.max_iter:
+ self._do_eval()
+ # func is likely a closure that holds reference to the trainer
+ # therefore we clean it to avoid circular reference in the end
+ del self._func
+
+
+class PreciseBN(HookBase):
+ """
+ The standard implementation of BatchNorm uses EMA in inference, which is
+ sometimes suboptimal.
+ This class computes the true average of statistics rather than the moving average,
+ and put true averages to every BN layer in the given model.
+
+ It is executed every ``period`` iterations and after the last iteration.
+ """
+
+ def __init__(self, period, model, data_loader, num_iter):
+ """
+ Args:
+ period (int): the period this hook is run, or 0 to not run during training.
+ The hook will always run in the end of training.
+ model (nn.Module): a module whose all BN layers in training mode will be
+ updated by precise BN.
+ Note that user is responsible for ensuring the BN layers to be
+ updated are in training mode when this hook is triggered.
+ data_loader (iterable): it will produce data to be run by `model(data)`.
+ num_iter (int): number of iterations used to compute the precise
+ statistics.
+ """
+ self._logger = logging.getLogger(__name__)
+ if len(get_bn_modules(model)) == 0:
+ self._logger.info(
+ "PreciseBN is disabled because model does not contain BN layers in training mode."
+ )
+ self._disabled = True
+ return
+
+ self._model = model
+ self._data_loader = data_loader
+ self._num_iter = num_iter
+ self._period = period
+ self._disabled = False
+
+ self._data_iter = None
+
+ def after_step(self):
+ next_iter = self.trainer.iter + 1
+ is_final = next_iter == self.trainer.max_iter
+ if is_final or (self._period > 0 and next_iter % self._period == 0):
+ self.update_stats()
+
+ def update_stats(self):
+ """
+ Update the model with precise statistics. Users can manually call this method.
+ """
+ if self._disabled:
+ return
+
+ if self._data_iter is None:
+ self._data_iter = iter(self._data_loader)
+
+ def data_loader():
+ for num_iter in itertools.count(1):
+ if num_iter % 100 == 0:
+ self._logger.info(
+ "Running precise-BN ... {}/{} iterations.".format(num_iter, self._num_iter)
+ )
+ # This way we can reuse the same iterator
+ yield next(self._data_iter)
+
+ with EventStorage(): # capture events in a new storage to discard them
+ self._logger.info(
+ "Running precise-BN for {} iterations... ".format(self._num_iter)
+ + "Note that this could produce different statistics every time."
+ )
+ update_bn_stats(self._model, data_loader(), self._num_iter)
+
+
+class TorchMemoryStats(HookBase):
+ """
+ Writes pytorch's cuda memory statistics periodically.
+ """
+
+ def __init__(self, period=20, max_runs=10):
+ """
+ Args:
+ period (int): Output stats each 'period' iterations
+ max_runs (int): Stop the logging after 'max_runs'
+ """
+
+ self._logger = logging.getLogger(__name__)
+ self._period = period
+ self._max_runs = max_runs
+ self._runs = 0
+
+ def after_step(self):
+ if self._runs > self._max_runs:
+ return
+
+ if (self.trainer.iter + 1) % self._period == 0 or (
+ self.trainer.iter == self.trainer.max_iter - 1
+ ):
+ if torch.cuda.is_available():
+ max_reserved_mb = torch.cuda.max_memory_reserved() / 1024.0 / 1024.0
+ reserved_mb = torch.cuda.memory_reserved() / 1024.0 / 1024.0
+ max_allocated_mb = torch.cuda.max_memory_allocated() / 1024.0 / 1024.0
+ allocated_mb = torch.cuda.memory_allocated() / 1024.0 / 1024.0
+
+ self._logger.info(
+ (
+ " iter: {} "
+ " max_reserved_mem: {:.0f}MB "
+ " reserved_mem: {:.0f}MB "
+ " max_allocated_mem: {:.0f}MB "
+ " allocated_mem: {:.0f}MB "
+ ).format(
+ self.trainer.iter,
+ max_reserved_mb,
+ reserved_mb,
+ max_allocated_mb,
+ allocated_mb,
+ )
+ )
+
+ self._runs += 1
+ if self._runs == self._max_runs:
+ mem_summary = torch.cuda.memory_summary()
+ self._logger.info("\n" + mem_summary)
+
+ torch.cuda.reset_peak_memory_stats()
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/engine/launch.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/engine/launch.py
new file mode 100644
index 0000000000000000000000000000000000000000..91ce1305187778143b0f2f6e487bfadd2700fffd
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/engine/launch.py
@@ -0,0 +1,123 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import logging
+from datetime import timedelta
+import torch
+import torch.distributed as dist
+import torch.multiprocessing as mp
+
+from custom_detectron2.utils import comm
+
+__all__ = ["DEFAULT_TIMEOUT", "launch"]
+
+DEFAULT_TIMEOUT = timedelta(minutes=30)
+
+
+def _find_free_port():
+ import socket
+
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ # Binding to port 0 will cause the OS to find an available port for us
+ sock.bind(("", 0))
+ port = sock.getsockname()[1]
+ sock.close()
+ # NOTE: there is still a chance the port could be taken by other processes.
+ return port
+
+
+def launch(
+ main_func,
+ # Should be num_processes_per_machine, but kept for compatibility.
+ num_gpus_per_machine,
+ num_machines=1,
+ machine_rank=0,
+ dist_url=None,
+ args=(),
+ timeout=DEFAULT_TIMEOUT,
+):
+ """
+ Launch multi-process or distributed training.
+ This function must be called on all machines involved in the training.
+ It will spawn child processes (defined by ``num_gpus_per_machine``) on each machine.
+
+ Args:
+ main_func: a function that will be called by `main_func(*args)`
+ num_gpus_per_machine (int): number of processes per machine. When
+ using GPUs, this should be the number of GPUs.
+ num_machines (int): the total number of machines
+ machine_rank (int): the rank of this machine
+ dist_url (str): url to connect to for distributed jobs, including protocol
+ e.g. "tcp://127.0.0.1:8686".
+ Can be set to "auto" to automatically select a free port on localhost
+ timeout (timedelta): timeout of the distributed workers
+ args (tuple): arguments passed to main_func
+ """
+ world_size = num_machines * num_gpus_per_machine
+ if world_size > 1:
+ # https://github.com/pytorch/pytorch/pull/14391
+ # TODO prctl in spawned processes
+
+ if dist_url == "auto":
+ assert num_machines == 1, "dist_url=auto not supported in multi-machine jobs."
+ port = _find_free_port()
+ dist_url = f"tcp://127.0.0.1:{port}"
+ if num_machines > 1 and dist_url.startswith("file://"):
+ logger = logging.getLogger(__name__)
+ logger.warning(
+ "file:// is not a reliable init_method in multi-machine jobs. Prefer tcp://"
+ )
+
+ mp.start_processes(
+ _distributed_worker,
+ nprocs=num_gpus_per_machine,
+ args=(
+ main_func,
+ world_size,
+ num_gpus_per_machine,
+ machine_rank,
+ dist_url,
+ args,
+ timeout,
+ ),
+ daemon=False,
+ )
+ else:
+ main_func(*args)
+
+
+def _distributed_worker(
+ local_rank,
+ main_func,
+ world_size,
+ num_gpus_per_machine,
+ machine_rank,
+ dist_url,
+ args,
+ timeout=DEFAULT_TIMEOUT,
+):
+ has_gpu = torch.cuda.is_available()
+ if has_gpu:
+ assert num_gpus_per_machine <= torch.cuda.device_count()
+ global_rank = machine_rank * num_gpus_per_machine + local_rank
+ try:
+ dist.init_process_group(
+ backend="NCCL" if has_gpu else "GLOO",
+ init_method=dist_url,
+ world_size=world_size,
+ rank=global_rank,
+ timeout=timeout,
+ )
+ except Exception as e:
+ logger = logging.getLogger(__name__)
+ logger.error("Process group URL: {}".format(dist_url))
+ raise e
+
+ # Setup the local process group.
+ comm.create_local_process_group(num_gpus_per_machine)
+ if has_gpu:
+ torch.cuda.set_device(local_rank)
+
+ # synchronize is needed here to prevent a possible timeout after calling init_process_group
+ # See: https://github.com/facebookresearch/maskrcnn-benchmark/issues/172
+ comm.synchronize()
+
+ main_func(*args)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/engine/train_loop.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/engine/train_loop.py
new file mode 100644
index 0000000000000000000000000000000000000000..066055a99be04d87bde48efdcdbef162bfb27792
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/engine/train_loop.py
@@ -0,0 +1,469 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import logging
+import numpy as np
+import time
+import weakref
+from typing import List, Mapping, Optional
+import torch
+from torch.nn.parallel import DataParallel, DistributedDataParallel
+
+import custom_detectron2.utils.comm as comm
+from custom_detectron2.utils.events import EventStorage, get_event_storage
+from custom_detectron2.utils.logger import _log_api_usage
+
+__all__ = ["HookBase", "TrainerBase", "SimpleTrainer", "AMPTrainer"]
+
+
+class HookBase:
+ """
+ Base class for hooks that can be registered with :class:`TrainerBase`.
+
+ Each hook can implement 4 methods. The way they are called is demonstrated
+ in the following snippet:
+ ::
+ hook.before_train()
+ for iter in range(start_iter, max_iter):
+ hook.before_step()
+ trainer.run_step()
+ hook.after_step()
+ iter += 1
+ hook.after_train()
+
+ Notes:
+ 1. In the hook method, users can access ``self.trainer`` to access more
+ properties about the context (e.g., model, current iteration, or config
+ if using :class:`DefaultTrainer`).
+
+ 2. A hook that does something in :meth:`before_step` can often be
+ implemented equivalently in :meth:`after_step`.
+ If the hook takes non-trivial time, it is strongly recommended to
+ implement the hook in :meth:`after_step` instead of :meth:`before_step`.
+ The convention is that :meth:`before_step` should only take negligible time.
+
+ Following this convention will allow hooks that do care about the difference
+ between :meth:`before_step` and :meth:`after_step` (e.g., timer) to
+ function properly.
+
+ """
+
+ trainer: "TrainerBase" = None
+ """
+ A weak reference to the trainer object. Set by the trainer when the hook is registered.
+ """
+
+ def before_train(self):
+ """
+ Called before the first iteration.
+ """
+ pass
+
+ def after_train(self):
+ """
+ Called after the last iteration.
+ """
+ pass
+
+ def before_step(self):
+ """
+ Called before each iteration.
+ """
+ pass
+
+ def after_backward(self):
+ """
+ Called after the backward pass of each iteration.
+ """
+ pass
+
+ def after_step(self):
+ """
+ Called after each iteration.
+ """
+ pass
+
+ def state_dict(self):
+ """
+ Hooks are stateless by default, but can be made checkpointable by
+ implementing `state_dict` and `load_state_dict`.
+ """
+ return {}
+
+
+class TrainerBase:
+ """
+ Base class for iterative trainer with hooks.
+
+ The only assumption we made here is: the training runs in a loop.
+ A subclass can implement what the loop is.
+ We made no assumptions about the existence of dataloader, optimizer, model, etc.
+
+ Attributes:
+ iter(int): the current iteration.
+
+ start_iter(int): The iteration to start with.
+ By convention the minimum possible value is 0.
+
+ max_iter(int): The iteration to end training.
+
+ storage(EventStorage): An EventStorage that's opened during the course of training.
+ """
+
+ def __init__(self) -> None:
+ self._hooks: List[HookBase] = []
+ self.iter: int = 0
+ self.start_iter: int = 0
+ self.max_iter: int
+ self.storage: EventStorage
+ _log_api_usage("trainer." + self.__class__.__name__)
+
+ def register_hooks(self, hooks: List[Optional[HookBase]]) -> None:
+ """
+ Register hooks to the trainer. The hooks are executed in the order
+ they are registered.
+
+ Args:
+ hooks (list[Optional[HookBase]]): list of hooks
+ """
+ hooks = [h for h in hooks if h is not None]
+ for h in hooks:
+ assert isinstance(h, HookBase)
+ # To avoid circular reference, hooks and trainer cannot own each other.
+ # This normally does not matter, but will cause memory leak if the
+ # involved objects contain __del__:
+ # See http://engineering.hearsaysocial.com/2013/06/16/circular-references-in-python/
+ h.trainer = weakref.proxy(self)
+ self._hooks.extend(hooks)
+
+ def train(self, start_iter: int, max_iter: int):
+ """
+ Args:
+ start_iter, max_iter (int): See docs above
+ """
+ logger = logging.getLogger(__name__)
+ logger.info("Starting training from iteration {}".format(start_iter))
+
+ self.iter = self.start_iter = start_iter
+ self.max_iter = max_iter
+
+ with EventStorage(start_iter) as self.storage:
+ try:
+ self.before_train()
+ for self.iter in range(start_iter, max_iter):
+ self.before_step()
+ self.run_step()
+ self.after_step()
+ # self.iter == max_iter can be used by `after_train` to
+ # tell whether the training successfully finished or failed
+ # due to exceptions.
+ self.iter += 1
+ except Exception:
+ logger.exception("Exception during training:")
+ raise
+ finally:
+ self.after_train()
+
+ def before_train(self):
+ for h in self._hooks:
+ h.before_train()
+
+ def after_train(self):
+ self.storage.iter = self.iter
+ for h in self._hooks:
+ h.after_train()
+
+ def before_step(self):
+ # Maintain the invariant that storage.iter == trainer.iter
+ # for the entire execution of each step
+ self.storage.iter = self.iter
+
+ for h in self._hooks:
+ h.before_step()
+
+ def after_backward(self):
+ for h in self._hooks:
+ h.after_backward()
+
+ def after_step(self):
+ for h in self._hooks:
+ h.after_step()
+
+ def run_step(self):
+ raise NotImplementedError
+
+ def state_dict(self):
+ ret = {"iteration": self.iter}
+ hooks_state = {}
+ for h in self._hooks:
+ sd = h.state_dict()
+ if sd:
+ name = type(h).__qualname__
+ if name in hooks_state:
+ # TODO handle repetitive stateful hooks
+ continue
+ hooks_state[name] = sd
+ if hooks_state:
+ ret["hooks"] = hooks_state
+ return ret
+
+ def load_state_dict(self, state_dict):
+ logger = logging.getLogger(__name__)
+ self.iter = state_dict["iteration"]
+ for key, value in state_dict.get("hooks", {}).items():
+ for h in self._hooks:
+ try:
+ name = type(h).__qualname__
+ except AttributeError:
+ continue
+ if name == key:
+ h.load_state_dict(value)
+ break
+ else:
+ logger.warning(f"Cannot find the hook '{key}', its state_dict is ignored.")
+
+
+class SimpleTrainer(TrainerBase):
+ """
+ A simple trainer for the most common type of task:
+ single-cost single-optimizer single-data-source iterative optimization,
+ optionally using data-parallelism.
+ It assumes that every step, you:
+
+ 1. Compute the loss with a data from the data_loader.
+ 2. Compute the gradients with the above loss.
+ 3. Update the model with the optimizer.
+
+ All other tasks during training (checkpointing, logging, evaluation, LR schedule)
+ are maintained by hooks, which can be registered by :meth:`TrainerBase.register_hooks`.
+
+ If you want to do anything fancier than this,
+ either subclass TrainerBase and implement your own `run_step`,
+ or write your own training loop.
+ """
+
+ def __init__(self, model, data_loader, optimizer, gather_metric_period=1):
+ """
+ Args:
+ model: a torch Module. Takes a data from data_loader and returns a
+ dict of losses.
+ data_loader: an iterable. Contains data to be used to call model.
+ optimizer: a torch optimizer.
+ gather_metric_period: an int. Every gather_metric_period iterations
+ the metrics are gathered from all the ranks to rank 0 and logged.
+ """
+ super().__init__()
+
+ """
+ We set the model to training mode in the trainer.
+ However it's valid to train a model that's in eval mode.
+ If you want your model (or a submodule of it) to behave
+ like evaluation during training, you can overwrite its train() method.
+ """
+ model.train()
+
+ self.model = model
+ self.data_loader = data_loader
+ # to access the data loader iterator, call `self._data_loader_iter`
+ self._data_loader_iter_obj = None
+ self.optimizer = optimizer
+ self.gather_metric_period = gather_metric_period
+
+ def run_step(self):
+ """
+ Implement the standard training logic described above.
+ """
+ assert self.model.training, "[SimpleTrainer] model was changed to eval mode!"
+ start = time.perf_counter()
+ """
+ If you want to do something with the data, you can wrap the dataloader.
+ """
+ data = next(self._data_loader_iter)
+ data_time = time.perf_counter() - start
+
+ """
+ If you want to do something with the losses, you can wrap the model.
+ """
+ loss_dict = self.model(data)
+ if isinstance(loss_dict, torch.Tensor):
+ losses = loss_dict
+ loss_dict = {"total_loss": loss_dict}
+ else:
+ losses = sum(loss_dict.values())
+
+ """
+ If you need to accumulate gradients or do something similar, you can
+ wrap the optimizer with your custom `zero_grad()` method.
+ """
+ self.optimizer.zero_grad()
+ losses.backward()
+
+ self.after_backward()
+
+ self._write_metrics(loss_dict, data_time)
+
+ """
+ If you need gradient clipping/scaling or other processing, you can
+ wrap the optimizer with your custom `step()` method. But it is
+ suboptimal as explained in https://arxiv.org/abs/2006.15704 Sec 3.2.4
+ """
+ self.optimizer.step()
+
+ @property
+ def _data_loader_iter(self):
+ # only create the data loader iterator when it is used
+ if self._data_loader_iter_obj is None:
+ self._data_loader_iter_obj = iter(self.data_loader)
+ return self._data_loader_iter_obj
+
+ def reset_data_loader(self, data_loader_builder):
+ """
+ Delete and replace the current data loader with a new one, which will be created
+ by calling `data_loader_builder` (without argument).
+ """
+ del self.data_loader
+ data_loader = data_loader_builder()
+ self.data_loader = data_loader
+ self._data_loader_iter_obj = None
+
+ def _write_metrics(
+ self,
+ loss_dict: Mapping[str, torch.Tensor],
+ data_time: float,
+ prefix: str = "",
+ ) -> None:
+ if (self.iter + 1) % self.gather_metric_period == 0:
+ SimpleTrainer.write_metrics(loss_dict, data_time, prefix)
+
+ @staticmethod
+ def write_metrics(
+ loss_dict: Mapping[str, torch.Tensor],
+ data_time: float,
+ prefix: str = "",
+ ) -> None:
+ """
+ Args:
+ loss_dict (dict): dict of scalar losses
+ data_time (float): time taken by the dataloader iteration
+ prefix (str): prefix for logging keys
+ """
+ metrics_dict = {k: v.detach().cpu().item() for k, v in loss_dict.items()}
+ metrics_dict["data_time"] = data_time
+
+ # Gather metrics among all workers for logging
+ # This assumes we do DDP-style training, which is currently the only
+ # supported method in detectron2.
+ all_metrics_dict = comm.gather(metrics_dict)
+
+ if comm.is_main_process():
+ storage = get_event_storage()
+
+ # data_time among workers can have high variance. The actual latency
+ # caused by data_time is the maximum among workers.
+ data_time = np.max([x.pop("data_time") for x in all_metrics_dict])
+ storage.put_scalar("data_time", data_time)
+
+ # average the rest metrics
+ metrics_dict = {
+ k: np.mean([x[k] for x in all_metrics_dict]) for k in all_metrics_dict[0].keys()
+ }
+ total_losses_reduced = sum(metrics_dict.values())
+ if not np.isfinite(total_losses_reduced):
+ raise FloatingPointError(
+ f"Loss became infinite or NaN at iteration={storage.iter}!\n"
+ f"loss_dict = {metrics_dict}"
+ )
+
+ storage.put_scalar("{}total_loss".format(prefix), total_losses_reduced)
+ if len(metrics_dict) > 1:
+ storage.put_scalars(**metrics_dict)
+
+ def state_dict(self):
+ ret = super().state_dict()
+ ret["optimizer"] = self.optimizer.state_dict()
+ return ret
+
+ def load_state_dict(self, state_dict):
+ super().load_state_dict(state_dict)
+ self.optimizer.load_state_dict(state_dict["optimizer"])
+
+
+class AMPTrainer(SimpleTrainer):
+ """
+ Like :class:`SimpleTrainer`, but uses PyTorch's native automatic mixed precision
+ in the training loop.
+ """
+
+ def __init__(
+ self,
+ model,
+ data_loader,
+ optimizer,
+ gather_metric_period=1,
+ grad_scaler=None,
+ precision: torch.dtype = torch.float16,
+ log_grad_scaler: bool = False,
+ ):
+ """
+ Args:
+ model, data_loader, optimizer, gather_metric_period: same as in :class:`SimpleTrainer`.
+ grad_scaler: torch GradScaler to automatically scale gradients.
+ precision: torch.dtype as the target precision to cast to in computations
+ """
+ unsupported = "AMPTrainer does not support single-process multi-device training!"
+ if isinstance(model, DistributedDataParallel):
+ assert not (model.device_ids and len(model.device_ids) > 1), unsupported
+ assert not isinstance(model, DataParallel), unsupported
+
+ super().__init__(model, data_loader, optimizer, gather_metric_period)
+
+ if grad_scaler is None:
+ from torch.cuda.amp import GradScaler
+
+ grad_scaler = GradScaler()
+ self.grad_scaler = grad_scaler
+ self.precision = precision
+ self.log_grad_scaler = log_grad_scaler
+
+ def run_step(self):
+ """
+ Implement the AMP training logic.
+ """
+ assert self.model.training, "[AMPTrainer] model was changed to eval mode!"
+ assert torch.cuda.is_available(), "[AMPTrainer] CUDA is required for AMP training!"
+ from torch.cuda.amp import autocast
+
+ start = time.perf_counter()
+ data = next(self._data_loader_iter)
+ data_time = time.perf_counter() - start
+
+ with autocast(dtype=self.precision):
+ loss_dict = self.model(data)
+ if isinstance(loss_dict, torch.Tensor):
+ losses = loss_dict
+ loss_dict = {"total_loss": loss_dict}
+ else:
+ losses = sum(loss_dict.values())
+
+ self.optimizer.zero_grad()
+ self.grad_scaler.scale(losses).backward()
+
+ if self.log_grad_scaler:
+ storage = get_event_storage()
+ storage.put_scalar("[metric]grad_scaler", self.grad_scaler.get_scale())
+
+ self.after_backward()
+
+ self._write_metrics(loss_dict, data_time)
+
+ self.grad_scaler.step(self.optimizer)
+ self.grad_scaler.update()
+
+ def state_dict(self):
+ ret = super().state_dict()
+ ret["grad_scaler"] = self.grad_scaler.state_dict()
+ return ret
+
+ def load_state_dict(self, state_dict):
+ super().load_state_dict(state_dict)
+ self.grad_scaler.load_state_dict(state_dict["grad_scaler"])
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d96609e8f2261a6800fe85fcf3e1eaeaa44455c6
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/__init__.py
@@ -0,0 +1,12 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+from .cityscapes_evaluation import CityscapesInstanceEvaluator, CityscapesSemSegEvaluator
+from .coco_evaluation import COCOEvaluator
+from .rotated_coco_evaluation import RotatedCOCOEvaluator
+from .evaluator import DatasetEvaluator, DatasetEvaluators, inference_context, inference_on_dataset
+from .lvis_evaluation import LVISEvaluator
+from .panoptic_evaluation import COCOPanopticEvaluator
+from .pascal_voc_evaluation import PascalVOCDetectionEvaluator
+from .sem_seg_evaluation import SemSegEvaluator
+from .testing import print_csv_format, verify_results
+
+__all__ = [k for k in globals().keys() if not k.startswith("_")]
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/cityscapes_evaluation.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/cityscapes_evaluation.py
new file mode 100644
index 0000000000000000000000000000000000000000..881aed078e2e1322dbd48e2006785888652441e4
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/cityscapes_evaluation.py
@@ -0,0 +1,197 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import glob
+import logging
+import numpy as np
+import os
+import tempfile
+from collections import OrderedDict
+import torch
+from PIL import Image
+
+from custom_detectron2.data import MetadataCatalog
+from custom_detectron2.utils import comm
+from custom_detectron2.utils.file_io import PathManager
+
+from .evaluator import DatasetEvaluator
+
+
+class CityscapesEvaluator(DatasetEvaluator):
+ """
+ Base class for evaluation using cityscapes API.
+ """
+
+ def __init__(self, dataset_name):
+ """
+ Args:
+ dataset_name (str): the name of the dataset.
+ It must have the following metadata associated with it:
+ "thing_classes", "gt_dir".
+ """
+ self._metadata = MetadataCatalog.get(dataset_name)
+ self._cpu_device = torch.device("cpu")
+ self._logger = logging.getLogger(__name__)
+
+ def reset(self):
+ self._working_dir = tempfile.TemporaryDirectory(prefix="cityscapes_eval_")
+ self._temp_dir = self._working_dir.name
+ # All workers will write to the same results directory
+ # TODO this does not work in distributed training
+ assert (
+ comm.get_local_size() == comm.get_world_size()
+ ), "CityscapesEvaluator currently do not work with multiple machines."
+ self._temp_dir = comm.all_gather(self._temp_dir)[0]
+ if self._temp_dir != self._working_dir.name:
+ self._working_dir.cleanup()
+ self._logger.info(
+ "Writing cityscapes results to temporary directory {} ...".format(self._temp_dir)
+ )
+
+
+class CityscapesInstanceEvaluator(CityscapesEvaluator):
+ """
+ Evaluate instance segmentation results on cityscapes dataset using cityscapes API.
+
+ Note:
+ * It does not work in multi-machine distributed training.
+ * It contains a synchronization, therefore has to be used on all ranks.
+ * Only the main process runs evaluation.
+ """
+
+ def process(self, inputs, outputs):
+ from cityscapesscripts.helpers.labels import name2label
+
+ for input, output in zip(inputs, outputs):
+ file_name = input["file_name"]
+ basename = os.path.splitext(os.path.basename(file_name))[0]
+ pred_txt = os.path.join(self._temp_dir, basename + "_pred.txt")
+
+ if "instances" in output:
+ output = output["instances"].to(self._cpu_device)
+ num_instances = len(output)
+ with open(pred_txt, "w") as fout:
+ for i in range(num_instances):
+ pred_class = output.pred_classes[i]
+ classes = self._metadata.thing_classes[pred_class]
+ class_id = name2label[classes].id
+ score = output.scores[i]
+ mask = output.pred_masks[i].numpy().astype("uint8")
+ png_filename = os.path.join(
+ self._temp_dir, basename + "_{}_{}.png".format(i, classes)
+ )
+
+ Image.fromarray(mask * 255).save(png_filename)
+ fout.write(
+ "{} {} {}\n".format(os.path.basename(png_filename), class_id, score)
+ )
+ else:
+ # Cityscapes requires a prediction file for every ground truth image.
+ with open(pred_txt, "w") as fout:
+ pass
+
+ def evaluate(self):
+ """
+ Returns:
+ dict: has a key "segm", whose value is a dict of "AP" and "AP50".
+ """
+ comm.synchronize()
+ if comm.get_rank() > 0:
+ return
+ import cityscapesscripts.evaluation.evalInstanceLevelSemanticLabeling as cityscapes_eval
+
+ self._logger.info("Evaluating results under {} ...".format(self._temp_dir))
+
+ # set some global states in cityscapes evaluation API, before evaluating
+ cityscapes_eval.args.predictionPath = os.path.abspath(self._temp_dir)
+ cityscapes_eval.args.predictionWalk = None
+ cityscapes_eval.args.JSONOutput = False
+ cityscapes_eval.args.colorized = False
+ cityscapes_eval.args.gtInstancesFile = os.path.join(self._temp_dir, "gtInstances.json")
+
+ # These lines are adopted from
+ # https://github.com/mcordts/cityscapesScripts/blob/master/cityscapesscripts/evaluation/evalInstanceLevelSemanticLabeling.py # noqa
+ gt_dir = PathManager.get_local_path(self._metadata.gt_dir)
+ groundTruthImgList = glob.glob(os.path.join(gt_dir, "*", "*_gtFine_instanceIds.png"))
+ assert len(
+ groundTruthImgList
+ ), "Cannot find any ground truth images to use for evaluation. Searched for: {}".format(
+ cityscapes_eval.args.groundTruthSearch
+ )
+ predictionImgList = []
+ for gt in groundTruthImgList:
+ predictionImgList.append(cityscapes_eval.getPrediction(gt, cityscapes_eval.args))
+ results = cityscapes_eval.evaluateImgLists(
+ predictionImgList, groundTruthImgList, cityscapes_eval.args
+ )["averages"]
+
+ ret = OrderedDict()
+ ret["segm"] = {"AP": results["allAp"] * 100, "AP50": results["allAp50%"] * 100}
+ self._working_dir.cleanup()
+ return ret
+
+
+class CityscapesSemSegEvaluator(CityscapesEvaluator):
+ """
+ Evaluate semantic segmentation results on cityscapes dataset using cityscapes API.
+
+ Note:
+ * It does not work in multi-machine distributed training.
+ * It contains a synchronization, therefore has to be used on all ranks.
+ * Only the main process runs evaluation.
+ """
+
+ def process(self, inputs, outputs):
+ from cityscapesscripts.helpers.labels import trainId2label
+
+ for input, output in zip(inputs, outputs):
+ file_name = input["file_name"]
+ basename = os.path.splitext(os.path.basename(file_name))[0]
+ pred_filename = os.path.join(self._temp_dir, basename + "_pred.png")
+
+ output = output["sem_seg"].argmax(dim=0).to(self._cpu_device).numpy()
+ pred = 255 * np.ones(output.shape, dtype=np.uint8)
+ for train_id, label in trainId2label.items():
+ if label.ignoreInEval:
+ continue
+ pred[output == train_id] = label.id
+ Image.fromarray(pred).save(pred_filename)
+
+ def evaluate(self):
+ comm.synchronize()
+ if comm.get_rank() > 0:
+ return
+ # Load the Cityscapes eval script *after* setting the required env var,
+ # since the script reads CITYSCAPES_DATASET into global variables at load time.
+ import cityscapesscripts.evaluation.evalPixelLevelSemanticLabeling as cityscapes_eval
+
+ self._logger.info("Evaluating results under {} ...".format(self._temp_dir))
+
+ # set some global states in cityscapes evaluation API, before evaluating
+ cityscapes_eval.args.predictionPath = os.path.abspath(self._temp_dir)
+ cityscapes_eval.args.predictionWalk = None
+ cityscapes_eval.args.JSONOutput = False
+ cityscapes_eval.args.colorized = False
+
+ # These lines are adopted from
+ # https://github.com/mcordts/cityscapesScripts/blob/master/cityscapesscripts/evaluation/evalPixelLevelSemanticLabeling.py # noqa
+ gt_dir = PathManager.get_local_path(self._metadata.gt_dir)
+ groundTruthImgList = glob.glob(os.path.join(gt_dir, "*", "*_gtFine_labelIds.png"))
+ assert len(
+ groundTruthImgList
+ ), "Cannot find any ground truth images to use for evaluation. Searched for: {}".format(
+ cityscapes_eval.args.groundTruthSearch
+ )
+ predictionImgList = []
+ for gt in groundTruthImgList:
+ predictionImgList.append(cityscapes_eval.getPrediction(cityscapes_eval.args, gt))
+ results = cityscapes_eval.evaluateImgLists(
+ predictionImgList, groundTruthImgList, cityscapes_eval.args
+ )
+ ret = OrderedDict()
+ ret["sem_seg"] = {
+ "IoU": 100.0 * results["averageScoreClasses"],
+ "iIoU": 100.0 * results["averageScoreInstClasses"],
+ "IoU_sup": 100.0 * results["averageScoreCategories"],
+ "iIoU_sup": 100.0 * results["averageScoreInstCategories"],
+ }
+ self._working_dir.cleanup()
+ return ret
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/coco_evaluation.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/coco_evaluation.py
new file mode 100644
index 0000000000000000000000000000000000000000..9d651a9fe2eb90d0c6a682ab1a832debedacaf12
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/coco_evaluation.py
@@ -0,0 +1,722 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import contextlib
+import copy
+import io
+import itertools
+import json
+import logging
+import numpy as np
+import os
+import pickle
+from collections import OrderedDict
+import custom_pycocotools.mask as mask_util
+import torch
+from custom_pycocotools.coco import COCO
+from custom_pycocotools.cocoeval import COCOeval
+from tabulate import tabulate
+
+import custom_detectron2.utils.comm as comm
+from custom_detectron2.config import CfgNode
+from custom_detectron2.data import MetadataCatalog
+from custom_detectron2.data.datasets.coco import convert_to_coco_json
+from custom_detectron2.structures import Boxes, BoxMode, pairwise_iou
+from custom_detectron2.utils.file_io import PathManager
+from custom_detectron2.utils.logger import create_small_table
+
+from .evaluator import DatasetEvaluator
+
+try:
+ from custom_detectron2.evaluation.fast_eval_api import COCOeval_opt
+except ImportError:
+ COCOeval_opt = COCOeval
+
+
+class COCOEvaluator(DatasetEvaluator):
+ """
+ Evaluate AR for object proposals, AP for instance detection/segmentation, AP
+ for keypoint detection outputs using COCO's metrics.
+ See http://cocodataset.org/#detection-eval and
+ http://cocodataset.org/#keypoints-eval to understand its metrics.
+ The metrics range from 0 to 100 (instead of 0 to 1), where a -1 or NaN means
+ the metric cannot be computed (e.g. due to no predictions made).
+
+ In addition to COCO, this evaluator is able to support any bounding box detection,
+ instance segmentation, or keypoint detection dataset.
+ """
+
+ def __init__(
+ self,
+ dataset_name,
+ tasks=None,
+ distributed=True,
+ output_dir=None,
+ *,
+ max_dets_per_image=None,
+ use_fast_impl=True,
+ kpt_oks_sigmas=(),
+ allow_cached_coco=True,
+ ):
+ """
+ Args:
+ dataset_name (str): name of the dataset to be evaluated.
+ It must have either the following corresponding metadata:
+
+ "json_file": the path to the COCO format annotation
+
+ Or it must be in detectron2's standard dataset format
+ so it can be converted to COCO format automatically.
+ tasks (tuple[str]): tasks that can be evaluated under the given
+ configuration. A task is one of "bbox", "segm", "keypoints".
+ By default, will infer this automatically from predictions.
+ distributed (True): if True, will collect results from all ranks and run evaluation
+ in the main process.
+ Otherwise, will only evaluate the results in the current process.
+ output_dir (str): optional, an output directory to dump all
+ results predicted on the dataset. The dump contains two files:
+
+ 1. "instances_predictions.pth" a file that can be loaded with `torch.load` and
+ contains all the results in the format they are produced by the model.
+ 2. "coco_instances_results.json" a json file in COCO's result format.
+ max_dets_per_image (int): limit on the maximum number of detections per image.
+ By default in COCO, this limit is to 100, but this can be customized
+ to be greater, as is needed in evaluation metrics AP fixed and AP pool
+ (see https://arxiv.org/pdf/2102.01066.pdf)
+ This doesn't affect keypoint evaluation.
+ use_fast_impl (bool): use a fast but **unofficial** implementation to compute AP.
+ Although the results should be very close to the official implementation in COCO
+ API, it is still recommended to compute results with the official API for use in
+ papers. The faster implementation also uses more RAM.
+ kpt_oks_sigmas (list[float]): The sigmas used to calculate keypoint OKS.
+ See http://cocodataset.org/#keypoints-eval
+ When empty, it will use the defaults in COCO.
+ Otherwise it should be the same length as ROI_KEYPOINT_HEAD.NUM_KEYPOINTS.
+ allow_cached_coco (bool): Whether to use cached coco json from previous validation
+ runs. You should set this to False if you need to use different validation data.
+ Defaults to True.
+ """
+ self._logger = logging.getLogger(__name__)
+ self._distributed = distributed
+ self._output_dir = output_dir
+
+ if use_fast_impl and (COCOeval_opt is COCOeval):
+ self._logger.info("Fast COCO eval is not built. Falling back to official COCO eval.")
+ use_fast_impl = False
+ self._use_fast_impl = use_fast_impl
+
+ # COCOeval requires the limit on the number of detections per image (maxDets) to be a list
+ # with at least 3 elements. The default maxDets in COCOeval is [1, 10, 100], in which the
+ # 3rd element (100) is used as the limit on the number of detections per image when
+ # evaluating AP. COCOEvaluator expects an integer for max_dets_per_image, so for COCOeval,
+ # we reformat max_dets_per_image into [1, 10, max_dets_per_image], based on the defaults.
+ if max_dets_per_image is None:
+ max_dets_per_image = [1, 10, 100]
+ else:
+ max_dets_per_image = [1, 10, max_dets_per_image]
+ self._max_dets_per_image = max_dets_per_image
+
+ if tasks is not None and isinstance(tasks, CfgNode):
+ kpt_oks_sigmas = (
+ tasks.TEST.KEYPOINT_OKS_SIGMAS if not kpt_oks_sigmas else kpt_oks_sigmas
+ )
+ self._logger.warn(
+ "COCO Evaluator instantiated using config, this is deprecated behavior."
+ " Please pass in explicit arguments instead."
+ )
+ self._tasks = None # Infering it from predictions should be better
+ else:
+ self._tasks = tasks
+
+ self._cpu_device = torch.device("cpu")
+
+ self._metadata = MetadataCatalog.get(dataset_name)
+ if not hasattr(self._metadata, "json_file"):
+ if output_dir is None:
+ raise ValueError(
+ "output_dir must be provided to COCOEvaluator "
+ "for datasets not in COCO format."
+ )
+ self._logger.info(f"Trying to convert '{dataset_name}' to COCO format ...")
+
+ cache_path = os.path.join(output_dir, f"{dataset_name}_coco_format.json")
+ self._metadata.json_file = cache_path
+ convert_to_coco_json(dataset_name, cache_path, allow_cached=allow_cached_coco)
+
+ json_file = PathManager.get_local_path(self._metadata.json_file)
+ with contextlib.redirect_stdout(io.StringIO()):
+ self._coco_api = COCO(json_file)
+
+ # Test set json files do not contain annotations (evaluation must be
+ # performed using the COCO evaluation server).
+ self._do_evaluation = "annotations" in self._coco_api.dataset
+ if self._do_evaluation:
+ self._kpt_oks_sigmas = kpt_oks_sigmas
+
+ def reset(self):
+ self._predictions = []
+
+ def process(self, inputs, outputs):
+ """
+ Args:
+ inputs: the inputs to a COCO model (e.g., GeneralizedRCNN).
+ It is a list of dict. Each dict corresponds to an image and
+ contains keys like "height", "width", "file_name", "image_id".
+ outputs: the outputs of a COCO model. It is a list of dicts with key
+ "instances" that contains :class:`Instances`.
+ """
+ for input, output in zip(inputs, outputs):
+ prediction = {"image_id": input["image_id"]}
+
+ if "instances" in output:
+ instances = output["instances"].to(self._cpu_device)
+ prediction["instances"] = instances_to_coco_json(instances, input["image_id"])
+ if "proposals" in output:
+ prediction["proposals"] = output["proposals"].to(self._cpu_device)
+ if len(prediction) > 1:
+ self._predictions.append(prediction)
+
+ def evaluate(self, img_ids=None):
+ """
+ Args:
+ img_ids: a list of image IDs to evaluate on. Default to None for the whole dataset
+ """
+ if self._distributed:
+ comm.synchronize()
+ predictions = comm.gather(self._predictions, dst=0)
+ predictions = list(itertools.chain(*predictions))
+
+ if not comm.is_main_process():
+ return {}
+ else:
+ predictions = self._predictions
+
+ if len(predictions) == 0:
+ self._logger.warning("[COCOEvaluator] Did not receive valid predictions.")
+ return {}
+
+ if self._output_dir:
+ PathManager.mkdirs(self._output_dir)
+ file_path = os.path.join(self._output_dir, "instances_predictions.pth")
+ with PathManager.open(file_path, "wb") as f:
+ torch.save(predictions, f)
+
+ self._results = OrderedDict()
+ if "proposals" in predictions[0]:
+ self._eval_box_proposals(predictions)
+ if "instances" in predictions[0]:
+ self._eval_predictions(predictions, img_ids=img_ids)
+ # Copy so the caller can do whatever with results
+ return copy.deepcopy(self._results)
+
+ def _tasks_from_predictions(self, predictions):
+ """
+ Get COCO API "tasks" (i.e. iou_type) from COCO-format predictions.
+ """
+ tasks = {"bbox"}
+ for pred in predictions:
+ if "segmentation" in pred:
+ tasks.add("segm")
+ if "keypoints" in pred:
+ tasks.add("keypoints")
+ return sorted(tasks)
+
+ def _eval_predictions(self, predictions, img_ids=None):
+ """
+ Evaluate predictions. Fill self._results with the metrics of the tasks.
+ """
+ self._logger.info("Preparing results for COCO format ...")
+ coco_results = list(itertools.chain(*[x["instances"] for x in predictions]))
+ tasks = self._tasks or self._tasks_from_predictions(coco_results)
+
+ # unmap the category ids for COCO
+ if hasattr(self._metadata, "thing_dataset_id_to_contiguous_id"):
+ dataset_id_to_contiguous_id = self._metadata.thing_dataset_id_to_contiguous_id
+ all_contiguous_ids = list(dataset_id_to_contiguous_id.values())
+ num_classes = len(all_contiguous_ids)
+ assert min(all_contiguous_ids) == 0 and max(all_contiguous_ids) == num_classes - 1
+
+ reverse_id_mapping = {v: k for k, v in dataset_id_to_contiguous_id.items()}
+ for result in coco_results:
+ category_id = result["category_id"]
+ assert category_id < num_classes, (
+ f"A prediction has class={category_id}, "
+ f"but the dataset only has {num_classes} classes and "
+ f"predicted class id should be in [0, {num_classes - 1}]."
+ )
+ result["category_id"] = reverse_id_mapping[category_id]
+
+ if self._output_dir:
+ file_path = os.path.join(self._output_dir, "coco_instances_results.json")
+ self._logger.info("Saving results to {}".format(file_path))
+ with PathManager.open(file_path, "w") as f:
+ f.write(json.dumps(coco_results))
+ f.flush()
+
+ if not self._do_evaluation:
+ self._logger.info("Annotations are not available for evaluation.")
+ return
+
+ self._logger.info(
+ "Evaluating predictions with {} COCO API...".format(
+ "unofficial" if self._use_fast_impl else "official"
+ )
+ )
+ for task in sorted(tasks):
+ assert task in {"bbox", "segm", "keypoints"}, f"Got unknown task: {task}!"
+ coco_eval = (
+ _evaluate_predictions_on_coco(
+ self._coco_api,
+ coco_results,
+ task,
+ kpt_oks_sigmas=self._kpt_oks_sigmas,
+ cocoeval_fn=COCOeval_opt if self._use_fast_impl else COCOeval,
+ img_ids=img_ids,
+ max_dets_per_image=self._max_dets_per_image,
+ )
+ if len(coco_results) > 0
+ else None # cocoapi does not handle empty results very well
+ )
+
+ res = self._derive_coco_results(
+ coco_eval, task, class_names=self._metadata.get("thing_classes")
+ )
+ self._results[task] = res
+
+ def _eval_box_proposals(self, predictions):
+ """
+ Evaluate the box proposals in predictions.
+ Fill self._results with the metrics for "box_proposals" task.
+ """
+ if self._output_dir:
+ # Saving generated box proposals to file.
+ # Predicted box_proposals are in XYXY_ABS mode.
+ bbox_mode = BoxMode.XYXY_ABS.value
+ ids, boxes, objectness_logits = [], [], []
+ for prediction in predictions:
+ ids.append(prediction["image_id"])
+ boxes.append(prediction["proposals"].proposal_boxes.tensor.numpy())
+ objectness_logits.append(prediction["proposals"].objectness_logits.numpy())
+
+ proposal_data = {
+ "boxes": boxes,
+ "objectness_logits": objectness_logits,
+ "ids": ids,
+ "bbox_mode": bbox_mode,
+ }
+ with PathManager.open(os.path.join(self._output_dir, "box_proposals.pkl"), "wb") as f:
+ pickle.dump(proposal_data, f)
+
+ if not self._do_evaluation:
+ self._logger.info("Annotations are not available for evaluation.")
+ return
+
+ self._logger.info("Evaluating bbox proposals ...")
+ res = {}
+ areas = {"all": "", "small": "s", "medium": "m", "large": "l"}
+ for limit in [100, 1000]:
+ for area, suffix in areas.items():
+ stats = _evaluate_box_proposals(predictions, self._coco_api, area=area, limit=limit)
+ key = "AR{}@{:d}".format(suffix, limit)
+ res[key] = float(stats["ar"].item() * 100)
+ self._logger.info("Proposal metrics: \n" + create_small_table(res))
+ self._results["box_proposals"] = res
+
+ def _derive_coco_results(self, coco_eval, iou_type, class_names=None):
+ """
+ Derive the desired score numbers from summarized COCOeval.
+
+ Args:
+ coco_eval (None or COCOEval): None represents no predictions from model.
+ iou_type (str):
+ class_names (None or list[str]): if provided, will use it to predict
+ per-category AP.
+
+ Returns:
+ a dict of {metric name: score}
+ """
+
+ metrics = {
+ "bbox": ["AP", "AP50", "AP75", "APs", "APm", "APl"],
+ "segm": ["AP", "AP50", "AP75", "APs", "APm", "APl"],
+ "keypoints": ["AP", "AP50", "AP75", "APm", "APl"],
+ }[iou_type]
+
+ if coco_eval is None:
+ self._logger.warn("No predictions from the model!")
+ return {metric: float("nan") for metric in metrics}
+
+ # the standard metrics
+ results = {
+ metric: float(coco_eval.stats[idx] * 100 if coco_eval.stats[idx] >= 0 else "nan")
+ for idx, metric in enumerate(metrics)
+ }
+ self._logger.info(
+ "Evaluation results for {}: \n".format(iou_type) + create_small_table(results)
+ )
+ if not np.isfinite(sum(results.values())):
+ self._logger.info("Some metrics cannot be computed and is shown as NaN.")
+
+ if class_names is None or len(class_names) <= 1:
+ return results
+ # Compute per-category AP
+ # from https://github.com/facebookresearch/Detectron/blob/a6a835f5b8208c45d0dce217ce9bbda915f44df7/detectron/datasets/json_dataset_evaluator.py#L222-L252 # noqa
+ precisions = coco_eval.eval["precision"]
+ # precision has dims (iou, recall, cls, area range, max dets)
+ assert len(class_names) == precisions.shape[2]
+
+ results_per_category = []
+ for idx, name in enumerate(class_names):
+ # area range index 0: all area ranges
+ # max dets index -1: typically 100 per image
+ precision = precisions[:, :, idx, 0, -1]
+ precision = precision[precision > -1]
+ ap = np.mean(precision) if precision.size else float("nan")
+ results_per_category.append(("{}".format(name), float(ap * 100)))
+
+ # tabulate it
+ N_COLS = min(6, len(results_per_category) * 2)
+ results_flatten = list(itertools.chain(*results_per_category))
+ results_2d = itertools.zip_longest(*[results_flatten[i::N_COLS] for i in range(N_COLS)])
+ table = tabulate(
+ results_2d,
+ tablefmt="pipe",
+ floatfmt=".3f",
+ headers=["category", "AP"] * (N_COLS // 2),
+ numalign="left",
+ )
+ self._logger.info("Per-category {} AP: \n".format(iou_type) + table)
+
+ results.update({"AP-" + name: ap for name, ap in results_per_category})
+ return results
+
+
+def instances_to_coco_json(instances, img_id):
+ """
+ Dump an "Instances" object to a COCO-format json that's used for evaluation.
+
+ Args:
+ instances (Instances):
+ img_id (int): the image id
+
+ Returns:
+ list[dict]: list of json annotations in COCO format.
+ """
+ num_instance = len(instances)
+ if num_instance == 0:
+ return []
+
+ boxes = instances.pred_boxes.tensor.numpy()
+ boxes = BoxMode.convert(boxes, BoxMode.XYXY_ABS, BoxMode.XYWH_ABS)
+ boxes = boxes.tolist()
+ scores = instances.scores.tolist()
+ classes = instances.pred_classes.tolist()
+
+ has_mask = instances.has("pred_masks")
+ if has_mask:
+ # use RLE to encode the masks, because they are too large and takes memory
+ # since this evaluator stores outputs of the entire dataset
+ rles = [
+ mask_util.encode(np.array(mask[:, :, None], order="F", dtype="uint8"))[0]
+ for mask in instances.pred_masks
+ ]
+ for rle in rles:
+ # "counts" is an array encoded by mask_util as a byte-stream. Python3's
+ # json writer which always produces strings cannot serialize a bytestream
+ # unless you decode it. Thankfully, utf-8 works out (which is also what
+ # the custom_pycocotools/_mask.pyx does).
+ rle["counts"] = rle["counts"].decode("utf-8")
+
+ has_keypoints = instances.has("pred_keypoints")
+ if has_keypoints:
+ keypoints = instances.pred_keypoints
+
+ results = []
+ for k in range(num_instance):
+ result = {
+ "image_id": img_id,
+ "category_id": classes[k],
+ "bbox": boxes[k],
+ "score": scores[k],
+ }
+ if has_mask:
+ result["segmentation"] = rles[k]
+ if has_keypoints:
+ # In COCO annotations,
+ # keypoints coordinates are pixel indices.
+ # However our predictions are floating point coordinates.
+ # Therefore we subtract 0.5 to be consistent with the annotation format.
+ # This is the inverse of data loading logic in `datasets/coco.py`.
+ keypoints[k][:, :2] -= 0.5
+ result["keypoints"] = keypoints[k].flatten().tolist()
+ results.append(result)
+ return results
+
+
+# inspired from Detectron:
+# https://github.com/facebookresearch/Detectron/blob/a6a835f5b8208c45d0dce217ce9bbda915f44df7/detectron/datasets/json_dataset_evaluator.py#L255 # noqa
+def _evaluate_box_proposals(dataset_predictions, coco_api, thresholds=None, area="all", limit=None):
+ """
+ Evaluate detection proposal recall metrics. This function is a much
+ faster alternative to the official COCO API recall evaluation code. However,
+ it produces slightly different results.
+ """
+ # Record max overlap value for each gt box
+ # Return vector of overlap values
+ areas = {
+ "all": 0,
+ "small": 1,
+ "medium": 2,
+ "large": 3,
+ "96-128": 4,
+ "128-256": 5,
+ "256-512": 6,
+ "512-inf": 7,
+ }
+ area_ranges = [
+ [0**2, 1e5**2], # all
+ [0**2, 32**2], # small
+ [32**2, 96**2], # medium
+ [96**2, 1e5**2], # large
+ [96**2, 128**2], # 96-128
+ [128**2, 256**2], # 128-256
+ [256**2, 512**2], # 256-512
+ [512**2, 1e5**2],
+ ] # 512-inf
+ assert area in areas, "Unknown area range: {}".format(area)
+ area_range = area_ranges[areas[area]]
+ gt_overlaps = []
+ num_pos = 0
+
+ for prediction_dict in dataset_predictions:
+ predictions = prediction_dict["proposals"]
+
+ # sort predictions in descending order
+ # TODO maybe remove this and make it explicit in the documentation
+ inds = predictions.objectness_logits.sort(descending=True)[1]
+ predictions = predictions[inds]
+
+ ann_ids = coco_api.getAnnIds(imgIds=prediction_dict["image_id"])
+ anno = coco_api.loadAnns(ann_ids)
+ gt_boxes = [
+ BoxMode.convert(obj["bbox"], BoxMode.XYWH_ABS, BoxMode.XYXY_ABS)
+ for obj in anno
+ if obj["iscrowd"] == 0
+ ]
+ gt_boxes = torch.as_tensor(gt_boxes).reshape(-1, 4) # guard against no boxes
+ gt_boxes = Boxes(gt_boxes)
+ gt_areas = torch.as_tensor([obj["area"] for obj in anno if obj["iscrowd"] == 0])
+
+ if len(gt_boxes) == 0 or len(predictions) == 0:
+ continue
+
+ valid_gt_inds = (gt_areas >= area_range[0]) & (gt_areas <= area_range[1])
+ gt_boxes = gt_boxes[valid_gt_inds]
+
+ num_pos += len(gt_boxes)
+
+ if len(gt_boxes) == 0:
+ continue
+
+ if limit is not None and len(predictions) > limit:
+ predictions = predictions[:limit]
+
+ overlaps = pairwise_iou(predictions.proposal_boxes, gt_boxes)
+
+ _gt_overlaps = torch.zeros(len(gt_boxes))
+ for j in range(min(len(predictions), len(gt_boxes))):
+ # find which proposal box maximally covers each gt box
+ # and get the iou amount of coverage for each gt box
+ max_overlaps, argmax_overlaps = overlaps.max(dim=0)
+
+ # find which gt box is 'best' covered (i.e. 'best' = most iou)
+ gt_ovr, gt_ind = max_overlaps.max(dim=0)
+ assert gt_ovr >= 0
+ # find the proposal box that covers the best covered gt box
+ box_ind = argmax_overlaps[gt_ind]
+ # record the iou coverage of this gt box
+ _gt_overlaps[j] = overlaps[box_ind, gt_ind]
+ assert _gt_overlaps[j] == gt_ovr
+ # mark the proposal box and the gt box as used
+ overlaps[box_ind, :] = -1
+ overlaps[:, gt_ind] = -1
+
+ # append recorded iou coverage level
+ gt_overlaps.append(_gt_overlaps)
+ gt_overlaps = (
+ torch.cat(gt_overlaps, dim=0) if len(gt_overlaps) else torch.zeros(0, dtype=torch.float32)
+ )
+ gt_overlaps, _ = torch.sort(gt_overlaps)
+
+ if thresholds is None:
+ step = 0.05
+ thresholds = torch.arange(0.5, 0.95 + 1e-5, step, dtype=torch.float32)
+ recalls = torch.zeros_like(thresholds)
+ # compute recall for each iou threshold
+ for i, t in enumerate(thresholds):
+ recalls[i] = (gt_overlaps >= t).float().sum() / float(num_pos)
+ # ar = 2 * np.trapz(recalls, thresholds)
+ ar = recalls.mean()
+ return {
+ "ar": ar,
+ "recalls": recalls,
+ "thresholds": thresholds,
+ "gt_overlaps": gt_overlaps,
+ "num_pos": num_pos,
+ }
+
+
+def _evaluate_predictions_on_coco(
+ coco_gt,
+ coco_results,
+ iou_type,
+ kpt_oks_sigmas=None,
+ cocoeval_fn=COCOeval_opt,
+ img_ids=None,
+ max_dets_per_image=None,
+):
+ """
+ Evaluate the coco results using COCOEval API.
+ """
+ assert len(coco_results) > 0
+
+ if iou_type == "segm":
+ coco_results = copy.deepcopy(coco_results)
+ # When evaluating mask AP, if the results contain bbox, cocoapi will
+ # use the box area as the area of the instance, instead of the mask area.
+ # This leads to a different definition of small/medium/large.
+ # We remove the bbox field to let mask AP use mask area.
+ for c in coco_results:
+ c.pop("bbox", None)
+
+ coco_dt = coco_gt.loadRes(coco_results)
+ coco_eval = cocoeval_fn(coco_gt, coco_dt, iou_type)
+ # For COCO, the default max_dets_per_image is [1, 10, 100].
+ if max_dets_per_image is None:
+ max_dets_per_image = [1, 10, 100] # Default from COCOEval
+ else:
+ assert (
+ len(max_dets_per_image) >= 3
+ ), "COCOeval requires maxDets (and max_dets_per_image) to have length at least 3"
+ # In the case that user supplies a custom input for max_dets_per_image,
+ # apply COCOevalMaxDets to evaluate AP with the custom input.
+ if max_dets_per_image[2] != 100:
+ coco_eval = COCOevalMaxDets(coco_gt, coco_dt, iou_type)
+ if iou_type != "keypoints":
+ coco_eval.params.maxDets = max_dets_per_image
+
+ if img_ids is not None:
+ coco_eval.params.imgIds = img_ids
+
+ if iou_type == "keypoints":
+ # Use the COCO default keypoint OKS sigmas unless overrides are specified
+ if kpt_oks_sigmas:
+ assert hasattr(coco_eval.params, "kpt_oks_sigmas"), "custom_pycocotools is too old!"
+ coco_eval.params.kpt_oks_sigmas = np.array(kpt_oks_sigmas)
+ # COCOAPI requires every detection and every gt to have keypoints, so
+ # we just take the first entry from both
+ num_keypoints_dt = len(coco_results[0]["keypoints"]) // 3
+ num_keypoints_gt = len(next(iter(coco_gt.anns.values()))["keypoints"]) // 3
+ num_keypoints_oks = len(coco_eval.params.kpt_oks_sigmas)
+ assert num_keypoints_oks == num_keypoints_dt == num_keypoints_gt, (
+ f"[COCOEvaluator] Prediction contain {num_keypoints_dt} keypoints. "
+ f"Ground truth contains {num_keypoints_gt} keypoints. "
+ f"The length of cfg.TEST.KEYPOINT_OKS_SIGMAS is {num_keypoints_oks}. "
+ "They have to agree with each other. For meaning of OKS, please refer to "
+ "http://cocodataset.org/#keypoints-eval."
+ )
+
+ coco_eval.evaluate()
+ coco_eval.accumulate()
+ coco_eval.summarize()
+
+ return coco_eval
+
+
+class COCOevalMaxDets(COCOeval):
+ """
+ Modified version of COCOeval for evaluating AP with a custom
+ maxDets (by default for COCO, maxDets is 100)
+ """
+
+ def summarize(self):
+ """
+ Compute and display summary metrics for evaluation results given
+ a custom value for max_dets_per_image
+ """
+
+ def _summarize(ap=1, iouThr=None, areaRng="all", maxDets=100):
+ p = self.params
+ iStr = " {:<18} {} @[ IoU={:<9} | area={:>6s} | maxDets={:>3d} ] = {:0.3f}"
+ titleStr = "Average Precision" if ap == 1 else "Average Recall"
+ typeStr = "(AP)" if ap == 1 else "(AR)"
+ iouStr = (
+ "{:0.2f}:{:0.2f}".format(p.iouThrs[0], p.iouThrs[-1])
+ if iouThr is None
+ else "{:0.2f}".format(iouThr)
+ )
+
+ aind = [i for i, aRng in enumerate(p.areaRngLbl) if aRng == areaRng]
+ mind = [i for i, mDet in enumerate(p.maxDets) if mDet == maxDets]
+ if ap == 1:
+ # dimension of precision: [TxRxKxAxM]
+ s = self.eval["precision"]
+ # IoU
+ if iouThr is not None:
+ t = np.where(iouThr == p.iouThrs)[0]
+ s = s[t]
+ s = s[:, :, :, aind, mind]
+ else:
+ # dimension of recall: [TxKxAxM]
+ s = self.eval["recall"]
+ if iouThr is not None:
+ t = np.where(iouThr == p.iouThrs)[0]
+ s = s[t]
+ s = s[:, :, aind, mind]
+ if len(s[s > -1]) == 0:
+ mean_s = -1
+ else:
+ mean_s = np.mean(s[s > -1])
+ print(iStr.format(titleStr, typeStr, iouStr, areaRng, maxDets, mean_s))
+ return mean_s
+
+ def _summarizeDets():
+ stats = np.zeros((12,))
+ # Evaluate AP using the custom limit on maximum detections per image
+ stats[0] = _summarize(1, maxDets=self.params.maxDets[2])
+ stats[1] = _summarize(1, iouThr=0.5, maxDets=self.params.maxDets[2])
+ stats[2] = _summarize(1, iouThr=0.75, maxDets=self.params.maxDets[2])
+ stats[3] = _summarize(1, areaRng="small", maxDets=self.params.maxDets[2])
+ stats[4] = _summarize(1, areaRng="medium", maxDets=self.params.maxDets[2])
+ stats[5] = _summarize(1, areaRng="large", maxDets=self.params.maxDets[2])
+ stats[6] = _summarize(0, maxDets=self.params.maxDets[0])
+ stats[7] = _summarize(0, maxDets=self.params.maxDets[1])
+ stats[8] = _summarize(0, maxDets=self.params.maxDets[2])
+ stats[9] = _summarize(0, areaRng="small", maxDets=self.params.maxDets[2])
+ stats[10] = _summarize(0, areaRng="medium", maxDets=self.params.maxDets[2])
+ stats[11] = _summarize(0, areaRng="large", maxDets=self.params.maxDets[2])
+ return stats
+
+ def _summarizeKps():
+ stats = np.zeros((10,))
+ stats[0] = _summarize(1, maxDets=20)
+ stats[1] = _summarize(1, maxDets=20, iouThr=0.5)
+ stats[2] = _summarize(1, maxDets=20, iouThr=0.75)
+ stats[3] = _summarize(1, maxDets=20, areaRng="medium")
+ stats[4] = _summarize(1, maxDets=20, areaRng="large")
+ stats[5] = _summarize(0, maxDets=20)
+ stats[6] = _summarize(0, maxDets=20, iouThr=0.5)
+ stats[7] = _summarize(0, maxDets=20, iouThr=0.75)
+ stats[8] = _summarize(0, maxDets=20, areaRng="medium")
+ stats[9] = _summarize(0, maxDets=20, areaRng="large")
+ return stats
+
+ if not self.eval:
+ raise Exception("Please run accumulate() first")
+ iouType = self.params.iouType
+ if iouType == "segm" or iouType == "bbox":
+ summarize = _summarizeDets
+ elif iouType == "keypoints":
+ summarize = _summarizeKps
+ self.stats = summarize()
+
+ def __str__(self):
+ self.summarize()
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/evaluator.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/evaluator.py
new file mode 100644
index 0000000000000000000000000000000000000000..6465b00de7c0c3e6e5ca1de05e7284e7c85bcd80
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/evaluator.py
@@ -0,0 +1,224 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import datetime
+import logging
+import time
+from collections import OrderedDict, abc
+from contextlib import ExitStack, contextmanager
+from typing import List, Union
+import torch
+from torch import nn
+
+from custom_detectron2.utils.comm import get_world_size, is_main_process
+from custom_detectron2.utils.logger import log_every_n_seconds
+
+
+class DatasetEvaluator:
+ """
+ Base class for a dataset evaluator.
+
+ The function :func:`inference_on_dataset` runs the model over
+ all samples in the dataset, and have a DatasetEvaluator to process the inputs/outputs.
+
+ This class will accumulate information of the inputs/outputs (by :meth:`process`),
+ and produce evaluation results in the end (by :meth:`evaluate`).
+ """
+
+ def reset(self):
+ """
+ Preparation for a new round of evaluation.
+ Should be called before starting a round of evaluation.
+ """
+ pass
+
+ def process(self, inputs, outputs):
+ """
+ Process the pair of inputs and outputs.
+ If they contain batches, the pairs can be consumed one-by-one using `zip`:
+
+ .. code-block:: python
+
+ for input_, output in zip(inputs, outputs):
+ # do evaluation on single input/output pair
+ ...
+
+ Args:
+ inputs (list): the inputs that's used to call the model.
+ outputs (list): the return value of `model(inputs)`
+ """
+ pass
+
+ def evaluate(self):
+ """
+ Evaluate/summarize the performance, after processing all input/output pairs.
+
+ Returns:
+ dict:
+ A new evaluator class can return a dict of arbitrary format
+ as long as the user can process the results.
+ In our train_net.py, we expect the following format:
+
+ * key: the name of the task (e.g., bbox)
+ * value: a dict of {metric name: score}, e.g.: {"AP50": 80}
+ """
+ pass
+
+
+class DatasetEvaluators(DatasetEvaluator):
+ """
+ Wrapper class to combine multiple :class:`DatasetEvaluator` instances.
+
+ This class dispatches every evaluation call to
+ all of its :class:`DatasetEvaluator`.
+ """
+
+ def __init__(self, evaluators):
+ """
+ Args:
+ evaluators (list): the evaluators to combine.
+ """
+ super().__init__()
+ self._evaluators = evaluators
+
+ def reset(self):
+ for evaluator in self._evaluators:
+ evaluator.reset()
+
+ def process(self, inputs, outputs):
+ for evaluator in self._evaluators:
+ evaluator.process(inputs, outputs)
+
+ def evaluate(self):
+ results = OrderedDict()
+ for evaluator in self._evaluators:
+ result = evaluator.evaluate()
+ if is_main_process() and result is not None:
+ for k, v in result.items():
+ assert (
+ k not in results
+ ), "Different evaluators produce results with the same key {}".format(k)
+ results[k] = v
+ return results
+
+
+def inference_on_dataset(
+ model, data_loader, evaluator: Union[DatasetEvaluator, List[DatasetEvaluator], None]
+):
+ """
+ Run model on the data_loader and evaluate the metrics with evaluator.
+ Also benchmark the inference speed of `model.__call__` accurately.
+ The model will be used in eval mode.
+
+ Args:
+ model (callable): a callable which takes an object from
+ `data_loader` and returns some outputs.
+
+ If it's an nn.Module, it will be temporarily set to `eval` mode.
+ If you wish to evaluate a model in `training` mode instead, you can
+ wrap the given model and override its behavior of `.eval()` and `.train()`.
+ data_loader: an iterable object with a length.
+ The elements it generates will be the inputs to the model.
+ evaluator: the evaluator(s) to run. Use `None` if you only want to benchmark,
+ but don't want to do any evaluation.
+
+ Returns:
+ The return value of `evaluator.evaluate()`
+ """
+ num_devices = get_world_size()
+ logger = logging.getLogger(__name__)
+ logger.info("Start inference on {} batches".format(len(data_loader)))
+
+ total = len(data_loader) # inference data loader must have a fixed length
+ if evaluator is None:
+ # create a no-op evaluator
+ evaluator = DatasetEvaluators([])
+ if isinstance(evaluator, abc.MutableSequence):
+ evaluator = DatasetEvaluators(evaluator)
+ evaluator.reset()
+
+ num_warmup = min(5, total - 1)
+ start_time = time.perf_counter()
+ total_data_time = 0
+ total_compute_time = 0
+ total_eval_time = 0
+ with ExitStack() as stack:
+ if isinstance(model, nn.Module):
+ stack.enter_context(inference_context(model))
+ stack.enter_context(torch.no_grad())
+
+ start_data_time = time.perf_counter()
+ for idx, inputs in enumerate(data_loader):
+ total_data_time += time.perf_counter() - start_data_time
+ if idx == num_warmup:
+ start_time = time.perf_counter()
+ total_data_time = 0
+ total_compute_time = 0
+ total_eval_time = 0
+
+ start_compute_time = time.perf_counter()
+ outputs = model(inputs)
+ if torch.cuda.is_available():
+ torch.cuda.synchronize()
+ total_compute_time += time.perf_counter() - start_compute_time
+
+ start_eval_time = time.perf_counter()
+ evaluator.process(inputs, outputs)
+ total_eval_time += time.perf_counter() - start_eval_time
+
+ iters_after_start = idx + 1 - num_warmup * int(idx >= num_warmup)
+ data_seconds_per_iter = total_data_time / iters_after_start
+ compute_seconds_per_iter = total_compute_time / iters_after_start
+ eval_seconds_per_iter = total_eval_time / iters_after_start
+ total_seconds_per_iter = (time.perf_counter() - start_time) / iters_after_start
+ if idx >= num_warmup * 2 or compute_seconds_per_iter > 5:
+ eta = datetime.timedelta(seconds=int(total_seconds_per_iter * (total - idx - 1)))
+ log_every_n_seconds(
+ logging.INFO,
+ (
+ f"Inference done {idx + 1}/{total}. "
+ f"Dataloading: {data_seconds_per_iter:.4f} s/iter. "
+ f"Inference: {compute_seconds_per_iter:.4f} s/iter. "
+ f"Eval: {eval_seconds_per_iter:.4f} s/iter. "
+ f"Total: {total_seconds_per_iter:.4f} s/iter. "
+ f"ETA={eta}"
+ ),
+ n=5,
+ )
+ start_data_time = time.perf_counter()
+
+ # Measure the time only for this worker (before the synchronization barrier)
+ total_time = time.perf_counter() - start_time
+ total_time_str = str(datetime.timedelta(seconds=total_time))
+ # NOTE this format is parsed by grep
+ logger.info(
+ "Total inference time: {} ({:.6f} s / iter per device, on {} devices)".format(
+ total_time_str, total_time / (total - num_warmup), num_devices
+ )
+ )
+ total_compute_time_str = str(datetime.timedelta(seconds=int(total_compute_time)))
+ logger.info(
+ "Total inference pure compute time: {} ({:.6f} s / iter per device, on {} devices)".format(
+ total_compute_time_str, total_compute_time / (total - num_warmup), num_devices
+ )
+ )
+
+ results = evaluator.evaluate()
+ # An evaluator may return None when not in main process.
+ # Replace it by an empty dict instead to make it easier for downstream code to handle
+ if results is None:
+ results = {}
+ return results
+
+
+@contextmanager
+def inference_context(model):
+ """
+ A context where the model is temporarily changed to eval mode,
+ and restored to previous mode afterwards.
+
+ Args:
+ model: a torch Module
+ """
+ training_mode = model.training
+ model.eval()
+ yield
+ model.train(training_mode)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/fast_eval_api.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/fast_eval_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..659aabb71b057e7db3b11110e21819516dd39ee1
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/fast_eval_api.py
@@ -0,0 +1,121 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import copy
+import logging
+import numpy as np
+import time
+from custom_pycocotools.cocoeval import COCOeval
+
+from custom_detectron2 import _C
+
+logger = logging.getLogger(__name__)
+
+
+class COCOeval_opt(COCOeval):
+ """
+ This is a slightly modified version of the original COCO API, where the functions evaluateImg()
+ and accumulate() are implemented in C++ to speedup evaluation
+ """
+
+ def evaluate(self):
+ """
+ Run per image evaluation on given images and store results in self.evalImgs_cpp, a
+ datastructure that isn't readable from Python but is used by a c++ implementation of
+ accumulate(). Unlike the original COCO PythonAPI, we don't populate the datastructure
+ self.evalImgs because this datastructure is a computational bottleneck.
+ :return: None
+ """
+ tic = time.time()
+
+ p = self.params
+ # add backward compatibility if useSegm is specified in params
+ if p.useSegm is not None:
+ p.iouType = "segm" if p.useSegm == 1 else "bbox"
+ logger.info("Evaluate annotation type *{}*".format(p.iouType))
+ p.imgIds = list(np.unique(p.imgIds))
+ if p.useCats:
+ p.catIds = list(np.unique(p.catIds))
+ p.maxDets = sorted(p.maxDets)
+ self.params = p
+
+ self._prepare() # bottleneck
+
+ # loop through images, area range, max detection number
+ catIds = p.catIds if p.useCats else [-1]
+
+ if p.iouType == "segm" or p.iouType == "bbox":
+ computeIoU = self.computeIoU
+ elif p.iouType == "keypoints":
+ computeIoU = self.computeOks
+ self.ious = {
+ (imgId, catId): computeIoU(imgId, catId) for imgId in p.imgIds for catId in catIds
+ } # bottleneck
+
+ maxDet = p.maxDets[-1]
+
+ # <<<< Beginning of code differences with original COCO API
+ def convert_instances_to_cpp(instances, is_det=False):
+ # Convert annotations for a list of instances in an image to a format that's fast
+ # to access in C++
+ instances_cpp = []
+ for instance in instances:
+ instance_cpp = _C.InstanceAnnotation(
+ int(instance["id"]),
+ instance["score"] if is_det else instance.get("score", 0.0),
+ instance["area"],
+ bool(instance.get("iscrowd", 0)),
+ bool(instance.get("ignore", 0)),
+ )
+ instances_cpp.append(instance_cpp)
+ return instances_cpp
+
+ # Convert GT annotations, detections, and IOUs to a format that's fast to access in C++
+ ground_truth_instances = [
+ [convert_instances_to_cpp(self._gts[imgId, catId]) for catId in p.catIds]
+ for imgId in p.imgIds
+ ]
+ detected_instances = [
+ [convert_instances_to_cpp(self._dts[imgId, catId], is_det=True) for catId in p.catIds]
+ for imgId in p.imgIds
+ ]
+ ious = [[self.ious[imgId, catId] for catId in catIds] for imgId in p.imgIds]
+
+ if not p.useCats:
+ # For each image, flatten per-category lists into a single list
+ ground_truth_instances = [[[o for c in i for o in c]] for i in ground_truth_instances]
+ detected_instances = [[[o for c in i for o in c]] for i in detected_instances]
+
+ # Call C++ implementation of self.evaluateImgs()
+ self._evalImgs_cpp = _C.COCOevalEvaluateImages(
+ p.areaRng, maxDet, p.iouThrs, ious, ground_truth_instances, detected_instances
+ )
+ self._evalImgs = None
+
+ self._paramsEval = copy.deepcopy(self.params)
+ toc = time.time()
+ logger.info("COCOeval_opt.evaluate() finished in {:0.2f} seconds.".format(toc - tic))
+ # >>>> End of code differences with original COCO API
+
+ def accumulate(self):
+ """
+ Accumulate per image evaluation results and store the result in self.eval. Does not
+ support changing parameter settings from those used by self.evaluate()
+ """
+ logger.info("Accumulating evaluation results...")
+ tic = time.time()
+ assert hasattr(
+ self, "_evalImgs_cpp"
+ ), "evaluate() must be called before accmulate() is called."
+
+ self.eval = _C.COCOevalAccumulate(self._paramsEval, self._evalImgs_cpp)
+
+ # recall is num_iou_thresholds X num_categories X num_area_ranges X num_max_detections
+ self.eval["recall"] = np.array(self.eval["recall"]).reshape(
+ self.eval["counts"][:1] + self.eval["counts"][2:]
+ )
+
+ # precision and scores are num_iou_thresholds X num_recall_thresholds X num_categories X
+ # num_area_ranges X num_max_detections
+ self.eval["precision"] = np.array(self.eval["precision"]).reshape(self.eval["counts"])
+ self.eval["scores"] = np.array(self.eval["scores"]).reshape(self.eval["counts"])
+ toc = time.time()
+ logger.info("COCOeval_opt.accumulate() finished in {:0.2f} seconds.".format(toc - tic))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/lvis_evaluation.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/lvis_evaluation.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f7769b48c3feb96cbcb7ee56794c1a3a35c3540
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/lvis_evaluation.py
@@ -0,0 +1,380 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import copy
+import itertools
+import json
+import logging
+import os
+import pickle
+from collections import OrderedDict
+import torch
+
+import custom_detectron2.utils.comm as comm
+from custom_detectron2.config import CfgNode
+from custom_detectron2.data import MetadataCatalog
+from custom_detectron2.structures import Boxes, BoxMode, pairwise_iou
+from custom_detectron2.utils.file_io import PathManager
+from custom_detectron2.utils.logger import create_small_table
+
+from .coco_evaluation import instances_to_coco_json
+from .evaluator import DatasetEvaluator
+
+
+class LVISEvaluator(DatasetEvaluator):
+ """
+ Evaluate object proposal and instance detection/segmentation outputs using
+ LVIS's metrics and evaluation API.
+ """
+
+ def __init__(
+ self,
+ dataset_name,
+ tasks=None,
+ distributed=True,
+ output_dir=None,
+ *,
+ max_dets_per_image=None,
+ ):
+ """
+ Args:
+ dataset_name (str): name of the dataset to be evaluated.
+ It must have the following corresponding metadata:
+ "json_file": the path to the LVIS format annotation
+ tasks (tuple[str]): tasks that can be evaluated under the given
+ configuration. A task is one of "bbox", "segm".
+ By default, will infer this automatically from predictions.
+ distributed (True): if True, will collect results from all ranks for evaluation.
+ Otherwise, will evaluate the results in the current process.
+ output_dir (str): optional, an output directory to dump results.
+ max_dets_per_image (None or int): limit on maximum detections per image in evaluating AP
+ This limit, by default of the LVIS dataset, is 300.
+ """
+ from lvis import LVIS
+
+ self._logger = logging.getLogger(__name__)
+
+ if tasks is not None and isinstance(tasks, CfgNode):
+ self._logger.warn(
+ "COCO Evaluator instantiated using config, this is deprecated behavior."
+ " Please pass in explicit arguments instead."
+ )
+ self._tasks = None # Infering it from predictions should be better
+ else:
+ self._tasks = tasks
+
+ self._distributed = distributed
+ self._output_dir = output_dir
+ self._max_dets_per_image = max_dets_per_image
+
+ self._cpu_device = torch.device("cpu")
+
+ self._metadata = MetadataCatalog.get(dataset_name)
+ json_file = PathManager.get_local_path(self._metadata.json_file)
+ self._lvis_api = LVIS(json_file)
+ # Test set json files do not contain annotations (evaluation must be
+ # performed using the LVIS evaluation server).
+ self._do_evaluation = len(self._lvis_api.get_ann_ids()) > 0
+
+ def reset(self):
+ self._predictions = []
+
+ def process(self, inputs, outputs):
+ """
+ Args:
+ inputs: the inputs to a LVIS model (e.g., GeneralizedRCNN).
+ It is a list of dict. Each dict corresponds to an image and
+ contains keys like "height", "width", "file_name", "image_id".
+ outputs: the outputs of a LVIS model. It is a list of dicts with key
+ "instances" that contains :class:`Instances`.
+ """
+ for input, output in zip(inputs, outputs):
+ prediction = {"image_id": input["image_id"]}
+
+ if "instances" in output:
+ instances = output["instances"].to(self._cpu_device)
+ prediction["instances"] = instances_to_coco_json(instances, input["image_id"])
+ if "proposals" in output:
+ prediction["proposals"] = output["proposals"].to(self._cpu_device)
+ self._predictions.append(prediction)
+
+ def evaluate(self):
+ if self._distributed:
+ comm.synchronize()
+ predictions = comm.gather(self._predictions, dst=0)
+ predictions = list(itertools.chain(*predictions))
+
+ if not comm.is_main_process():
+ return
+ else:
+ predictions = self._predictions
+
+ if len(predictions) == 0:
+ self._logger.warning("[LVISEvaluator] Did not receive valid predictions.")
+ return {}
+
+ if self._output_dir:
+ PathManager.mkdirs(self._output_dir)
+ file_path = os.path.join(self._output_dir, "instances_predictions.pth")
+ with PathManager.open(file_path, "wb") as f:
+ torch.save(predictions, f)
+
+ self._results = OrderedDict()
+ if "proposals" in predictions[0]:
+ self._eval_box_proposals(predictions)
+ if "instances" in predictions[0]:
+ self._eval_predictions(predictions)
+ # Copy so the caller can do whatever with results
+ return copy.deepcopy(self._results)
+
+ def _tasks_from_predictions(self, predictions):
+ for pred in predictions:
+ if "segmentation" in pred:
+ return ("bbox", "segm")
+ return ("bbox",)
+
+ def _eval_predictions(self, predictions):
+ """
+ Evaluate predictions. Fill self._results with the metrics of the tasks.
+
+ Args:
+ predictions (list[dict]): list of outputs from the model
+ """
+ self._logger.info("Preparing results in the LVIS format ...")
+ lvis_results = list(itertools.chain(*[x["instances"] for x in predictions]))
+ tasks = self._tasks or self._tasks_from_predictions(lvis_results)
+
+ # LVIS evaluator can be used to evaluate results for COCO dataset categories.
+ # In this case `_metadata` variable will have a field with COCO-specific category mapping.
+ if hasattr(self._metadata, "thing_dataset_id_to_contiguous_id"):
+ reverse_id_mapping = {
+ v: k for k, v in self._metadata.thing_dataset_id_to_contiguous_id.items()
+ }
+ for result in lvis_results:
+ result["category_id"] = reverse_id_mapping[result["category_id"]]
+ else:
+ # unmap the category ids for LVIS (from 0-indexed to 1-indexed)
+ for result in lvis_results:
+ result["category_id"] += 1
+
+ if self._output_dir:
+ file_path = os.path.join(self._output_dir, "lvis_instances_results.json")
+ self._logger.info("Saving results to {}".format(file_path))
+ with PathManager.open(file_path, "w") as f:
+ f.write(json.dumps(lvis_results))
+ f.flush()
+
+ if not self._do_evaluation:
+ self._logger.info("Annotations are not available for evaluation.")
+ return
+
+ self._logger.info("Evaluating predictions ...")
+ for task in sorted(tasks):
+ res = _evaluate_predictions_on_lvis(
+ self._lvis_api,
+ lvis_results,
+ task,
+ max_dets_per_image=self._max_dets_per_image,
+ class_names=self._metadata.get("thing_classes"),
+ )
+ self._results[task] = res
+
+ def _eval_box_proposals(self, predictions):
+ """
+ Evaluate the box proposals in predictions.
+ Fill self._results with the metrics for "box_proposals" task.
+ """
+ if self._output_dir:
+ # Saving generated box proposals to file.
+ # Predicted box_proposals are in XYXY_ABS mode.
+ bbox_mode = BoxMode.XYXY_ABS.value
+ ids, boxes, objectness_logits = [], [], []
+ for prediction in predictions:
+ ids.append(prediction["image_id"])
+ boxes.append(prediction["proposals"].proposal_boxes.tensor.numpy())
+ objectness_logits.append(prediction["proposals"].objectness_logits.numpy())
+
+ proposal_data = {
+ "boxes": boxes,
+ "objectness_logits": objectness_logits,
+ "ids": ids,
+ "bbox_mode": bbox_mode,
+ }
+ with PathManager.open(os.path.join(self._output_dir, "box_proposals.pkl"), "wb") as f:
+ pickle.dump(proposal_data, f)
+
+ if not self._do_evaluation:
+ self._logger.info("Annotations are not available for evaluation.")
+ return
+
+ self._logger.info("Evaluating bbox proposals ...")
+ res = {}
+ areas = {"all": "", "small": "s", "medium": "m", "large": "l"}
+ for limit in [100, 1000]:
+ for area, suffix in areas.items():
+ stats = _evaluate_box_proposals(predictions, self._lvis_api, area=area, limit=limit)
+ key = "AR{}@{:d}".format(suffix, limit)
+ res[key] = float(stats["ar"].item() * 100)
+ self._logger.info("Proposal metrics: \n" + create_small_table(res))
+ self._results["box_proposals"] = res
+
+
+# inspired from Detectron:
+# https://github.com/facebookresearch/Detectron/blob/a6a835f5b8208c45d0dce217ce9bbda915f44df7/detectron/datasets/json_dataset_evaluator.py#L255 # noqa
+def _evaluate_box_proposals(dataset_predictions, lvis_api, thresholds=None, area="all", limit=None):
+ """
+ Evaluate detection proposal recall metrics. This function is a much
+ faster alternative to the official LVIS API recall evaluation code. However,
+ it produces slightly different results.
+ """
+ # Record max overlap value for each gt box
+ # Return vector of overlap values
+ areas = {
+ "all": 0,
+ "small": 1,
+ "medium": 2,
+ "large": 3,
+ "96-128": 4,
+ "128-256": 5,
+ "256-512": 6,
+ "512-inf": 7,
+ }
+ area_ranges = [
+ [0**2, 1e5**2], # all
+ [0**2, 32**2], # small
+ [32**2, 96**2], # medium
+ [96**2, 1e5**2], # large
+ [96**2, 128**2], # 96-128
+ [128**2, 256**2], # 128-256
+ [256**2, 512**2], # 256-512
+ [512**2, 1e5**2],
+ ] # 512-inf
+ assert area in areas, "Unknown area range: {}".format(area)
+ area_range = area_ranges[areas[area]]
+ gt_overlaps = []
+ num_pos = 0
+
+ for prediction_dict in dataset_predictions:
+ predictions = prediction_dict["proposals"]
+
+ # sort predictions in descending order
+ # TODO maybe remove this and make it explicit in the documentation
+ inds = predictions.objectness_logits.sort(descending=True)[1]
+ predictions = predictions[inds]
+
+ ann_ids = lvis_api.get_ann_ids(img_ids=[prediction_dict["image_id"]])
+ anno = lvis_api.load_anns(ann_ids)
+ gt_boxes = [
+ BoxMode.convert(obj["bbox"], BoxMode.XYWH_ABS, BoxMode.XYXY_ABS) for obj in anno
+ ]
+ gt_boxes = torch.as_tensor(gt_boxes).reshape(-1, 4) # guard against no boxes
+ gt_boxes = Boxes(gt_boxes)
+ gt_areas = torch.as_tensor([obj["area"] for obj in anno])
+
+ if len(gt_boxes) == 0 or len(predictions) == 0:
+ continue
+
+ valid_gt_inds = (gt_areas >= area_range[0]) & (gt_areas <= area_range[1])
+ gt_boxes = gt_boxes[valid_gt_inds]
+
+ num_pos += len(gt_boxes)
+
+ if len(gt_boxes) == 0:
+ continue
+
+ if limit is not None and len(predictions) > limit:
+ predictions = predictions[:limit]
+
+ overlaps = pairwise_iou(predictions.proposal_boxes, gt_boxes)
+
+ _gt_overlaps = torch.zeros(len(gt_boxes))
+ for j in range(min(len(predictions), len(gt_boxes))):
+ # find which proposal box maximally covers each gt box
+ # and get the iou amount of coverage for each gt box
+ max_overlaps, argmax_overlaps = overlaps.max(dim=0)
+
+ # find which gt box is 'best' covered (i.e. 'best' = most iou)
+ gt_ovr, gt_ind = max_overlaps.max(dim=0)
+ assert gt_ovr >= 0
+ # find the proposal box that covers the best covered gt box
+ box_ind = argmax_overlaps[gt_ind]
+ # record the iou coverage of this gt box
+ _gt_overlaps[j] = overlaps[box_ind, gt_ind]
+ assert _gt_overlaps[j] == gt_ovr
+ # mark the proposal box and the gt box as used
+ overlaps[box_ind, :] = -1
+ overlaps[:, gt_ind] = -1
+
+ # append recorded iou coverage level
+ gt_overlaps.append(_gt_overlaps)
+ gt_overlaps = (
+ torch.cat(gt_overlaps, dim=0) if len(gt_overlaps) else torch.zeros(0, dtype=torch.float32)
+ )
+ gt_overlaps, _ = torch.sort(gt_overlaps)
+
+ if thresholds is None:
+ step = 0.05
+ thresholds = torch.arange(0.5, 0.95 + 1e-5, step, dtype=torch.float32)
+ recalls = torch.zeros_like(thresholds)
+ # compute recall for each iou threshold
+ for i, t in enumerate(thresholds):
+ recalls[i] = (gt_overlaps >= t).float().sum() / float(num_pos)
+ # ar = 2 * np.trapz(recalls, thresholds)
+ ar = recalls.mean()
+ return {
+ "ar": ar,
+ "recalls": recalls,
+ "thresholds": thresholds,
+ "gt_overlaps": gt_overlaps,
+ "num_pos": num_pos,
+ }
+
+
+def _evaluate_predictions_on_lvis(
+ lvis_gt, lvis_results, iou_type, max_dets_per_image=None, class_names=None
+):
+ """
+ Args:
+ iou_type (str):
+ max_dets_per_image (None or int): limit on maximum detections per image in evaluating AP
+ This limit, by default of the LVIS dataset, is 300.
+ class_names (None or list[str]): if provided, will use it to predict
+ per-category AP.
+
+ Returns:
+ a dict of {metric name: score}
+ """
+ metrics = {
+ "bbox": ["AP", "AP50", "AP75", "APs", "APm", "APl", "APr", "APc", "APf"],
+ "segm": ["AP", "AP50", "AP75", "APs", "APm", "APl", "APr", "APc", "APf"],
+ }[iou_type]
+
+ logger = logging.getLogger(__name__)
+
+ if len(lvis_results) == 0: # TODO: check if needed
+ logger.warn("No predictions from the model!")
+ return {metric: float("nan") for metric in metrics}
+
+ if iou_type == "segm":
+ lvis_results = copy.deepcopy(lvis_results)
+ # When evaluating mask AP, if the results contain bbox, LVIS API will
+ # use the box area as the area of the instance, instead of the mask area.
+ # This leads to a different definition of small/medium/large.
+ # We remove the bbox field to let mask AP use mask area.
+ for c in lvis_results:
+ c.pop("bbox", None)
+
+ if max_dets_per_image is None:
+ max_dets_per_image = 300 # Default for LVIS dataset
+
+ from lvis import LVISEval, LVISResults
+
+ logger.info(f"Evaluating with max detections per image = {max_dets_per_image}")
+ lvis_results = LVISResults(lvis_gt, lvis_results, max_dets=max_dets_per_image)
+ lvis_eval = LVISEval(lvis_gt, lvis_results, iou_type)
+ lvis_eval.run()
+ lvis_eval.print_results()
+
+ # Pull the standard metrics from the LVIS results
+ results = lvis_eval.get_results()
+ results = {metric: float(results[metric] * 100) for metric in metrics}
+ logger.info("Evaluation results for {}: \n".format(iou_type) + create_small_table(results))
+ return results
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/panoptic_evaluation.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/panoptic_evaluation.py
new file mode 100644
index 0000000000000000000000000000000000000000..833f3bd2f483360eb5cb7f6a5b6b02a2db0d01d6
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/panoptic_evaluation.py
@@ -0,0 +1,199 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import contextlib
+import io
+import itertools
+import json
+import logging
+import numpy as np
+import os
+import tempfile
+from collections import OrderedDict
+from typing import Optional
+from PIL import Image
+from tabulate import tabulate
+
+from custom_detectron2.data import MetadataCatalog
+from custom_detectron2.utils import comm
+from custom_detectron2.utils.file_io import PathManager
+
+from .evaluator import DatasetEvaluator
+
+logger = logging.getLogger(__name__)
+
+
+class COCOPanopticEvaluator(DatasetEvaluator):
+ """
+ Evaluate Panoptic Quality metrics on COCO using PanopticAPI.
+ It saves panoptic segmentation prediction in `output_dir`
+
+ It contains a synchronize call and has to be called from all workers.
+ """
+
+ def __init__(self, dataset_name: str, output_dir: Optional[str] = None):
+ """
+ Args:
+ dataset_name: name of the dataset
+ output_dir: output directory to save results for evaluation.
+ """
+ self._metadata = MetadataCatalog.get(dataset_name)
+ self._thing_contiguous_id_to_dataset_id = {
+ v: k for k, v in self._metadata.thing_dataset_id_to_contiguous_id.items()
+ }
+ self._stuff_contiguous_id_to_dataset_id = {
+ v: k for k, v in self._metadata.stuff_dataset_id_to_contiguous_id.items()
+ }
+
+ self._output_dir = output_dir
+ if self._output_dir is not None:
+ PathManager.mkdirs(self._output_dir)
+
+ def reset(self):
+ self._predictions = []
+
+ def _convert_category_id(self, segment_info):
+ isthing = segment_info.pop("isthing", None)
+ if isthing is None:
+ # the model produces panoptic category id directly. No more conversion needed
+ return segment_info
+ if isthing is True:
+ segment_info["category_id"] = self._thing_contiguous_id_to_dataset_id[
+ segment_info["category_id"]
+ ]
+ else:
+ segment_info["category_id"] = self._stuff_contiguous_id_to_dataset_id[
+ segment_info["category_id"]
+ ]
+ return segment_info
+
+ def process(self, inputs, outputs):
+ from panopticapi.utils import id2rgb
+
+ for input, output in zip(inputs, outputs):
+ panoptic_img, segments_info = output["panoptic_seg"]
+ panoptic_img = panoptic_img.cpu().numpy()
+ if segments_info is None:
+ # If "segments_info" is None, we assume "panoptic_img" is a
+ # H*W int32 image storing the panoptic_id in the format of
+ # category_id * label_divisor + instance_id. We reserve -1 for
+ # VOID label, and add 1 to panoptic_img since the official
+ # evaluation script uses 0 for VOID label.
+ label_divisor = self._metadata.label_divisor
+ segments_info = []
+ for panoptic_label in np.unique(panoptic_img):
+ if panoptic_label == -1:
+ # VOID region.
+ continue
+ pred_class = panoptic_label // label_divisor
+ isthing = (
+ pred_class in self._metadata.thing_dataset_id_to_contiguous_id.values()
+ )
+ segments_info.append(
+ {
+ "id": int(panoptic_label) + 1,
+ "category_id": int(pred_class),
+ "isthing": bool(isthing),
+ }
+ )
+ # Official evaluation script uses 0 for VOID label.
+ panoptic_img += 1
+
+ file_name = os.path.basename(input["file_name"])
+ file_name_png = os.path.splitext(file_name)[0] + ".png"
+ with io.BytesIO() as out:
+ Image.fromarray(id2rgb(panoptic_img)).save(out, format="PNG")
+ segments_info = [self._convert_category_id(x) for x in segments_info]
+ self._predictions.append(
+ {
+ "image_id": input["image_id"],
+ "file_name": file_name_png,
+ "png_string": out.getvalue(),
+ "segments_info": segments_info,
+ }
+ )
+
+ def evaluate(self):
+ comm.synchronize()
+
+ self._predictions = comm.gather(self._predictions)
+ self._predictions = list(itertools.chain(*self._predictions))
+ if not comm.is_main_process():
+ return
+
+ # PanopticApi requires local files
+ gt_json = PathManager.get_local_path(self._metadata.panoptic_json)
+ gt_folder = PathManager.get_local_path(self._metadata.panoptic_root)
+
+ with tempfile.TemporaryDirectory(prefix="panoptic_eval") as pred_dir:
+ logger.info("Writing all panoptic predictions to {} ...".format(pred_dir))
+ for p in self._predictions:
+ with open(os.path.join(pred_dir, p["file_name"]), "wb") as f:
+ f.write(p.pop("png_string"))
+
+ with open(gt_json, "r") as f:
+ json_data = json.load(f)
+ json_data["annotations"] = self._predictions
+
+ output_dir = self._output_dir or pred_dir
+ predictions_json = os.path.join(output_dir, "predictions.json")
+ with PathManager.open(predictions_json, "w") as f:
+ f.write(json.dumps(json_data))
+
+ from panopticapi.evaluation import pq_compute
+
+ with contextlib.redirect_stdout(io.StringIO()):
+ pq_res = pq_compute(
+ gt_json,
+ PathManager.get_local_path(predictions_json),
+ gt_folder=gt_folder,
+ pred_folder=pred_dir,
+ )
+
+ res = {}
+ res["PQ"] = 100 * pq_res["All"]["pq"]
+ res["SQ"] = 100 * pq_res["All"]["sq"]
+ res["RQ"] = 100 * pq_res["All"]["rq"]
+ res["PQ_th"] = 100 * pq_res["Things"]["pq"]
+ res["SQ_th"] = 100 * pq_res["Things"]["sq"]
+ res["RQ_th"] = 100 * pq_res["Things"]["rq"]
+ res["PQ_st"] = 100 * pq_res["Stuff"]["pq"]
+ res["SQ_st"] = 100 * pq_res["Stuff"]["sq"]
+ res["RQ_st"] = 100 * pq_res["Stuff"]["rq"]
+
+ results = OrderedDict({"panoptic_seg": res})
+ _print_panoptic_results(pq_res)
+
+ return results
+
+
+def _print_panoptic_results(pq_res):
+ headers = ["", "PQ", "SQ", "RQ", "#categories"]
+ data = []
+ for name in ["All", "Things", "Stuff"]:
+ row = [name] + [pq_res[name][k] * 100 for k in ["pq", "sq", "rq"]] + [pq_res[name]["n"]]
+ data.append(row)
+ table = tabulate(
+ data, headers=headers, tablefmt="pipe", floatfmt=".3f", stralign="center", numalign="center"
+ )
+ logger.info("Panoptic Evaluation Results:\n" + table)
+
+
+if __name__ == "__main__":
+ from custom_detectron2.utils.logger import setup_logger
+
+ logger = setup_logger()
+ import argparse
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--gt-json")
+ parser.add_argument("--gt-dir")
+ parser.add_argument("--pred-json")
+ parser.add_argument("--pred-dir")
+ args = parser.parse_args()
+
+ from panopticapi.evaluation import pq_compute
+
+ with contextlib.redirect_stdout(io.StringIO()):
+ pq_res = pq_compute(
+ args.gt_json, args.pred_json, gt_folder=args.gt_dir, pred_folder=args.pred_dir
+ )
+ _print_panoptic_results(pq_res)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/pascal_voc_evaluation.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/pascal_voc_evaluation.py
new file mode 100644
index 0000000000000000000000000000000000000000..7de637cf20a7389ebc60b16c491fbcbd3ab87305
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/pascal_voc_evaluation.py
@@ -0,0 +1,300 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import logging
+import numpy as np
+import os
+import tempfile
+import xml.etree.ElementTree as ET
+from collections import OrderedDict, defaultdict
+from functools import lru_cache
+import torch
+
+from custom_detectron2.data import MetadataCatalog
+from custom_detectron2.utils import comm
+from custom_detectron2.utils.file_io import PathManager
+
+from .evaluator import DatasetEvaluator
+
+
+class PascalVOCDetectionEvaluator(DatasetEvaluator):
+ """
+ Evaluate Pascal VOC style AP for Pascal VOC dataset.
+ It contains a synchronization, therefore has to be called from all ranks.
+
+ Note that the concept of AP can be implemented in different ways and may not
+ produce identical results. This class mimics the implementation of the official
+ Pascal VOC Matlab API, and should produce similar but not identical results to the
+ official API.
+ """
+
+ def __init__(self, dataset_name):
+ """
+ Args:
+ dataset_name (str): name of the dataset, e.g., "voc_2007_test"
+ """
+ self._dataset_name = dataset_name
+ meta = MetadataCatalog.get(dataset_name)
+
+ # Too many tiny files, download all to local for speed.
+ annotation_dir_local = PathManager.get_local_path(
+ os.path.join(meta.dirname, "Annotations/")
+ )
+ self._anno_file_template = os.path.join(annotation_dir_local, "{}.xml")
+ self._image_set_path = os.path.join(meta.dirname, "ImageSets", "Main", meta.split + ".txt")
+ self._class_names = meta.thing_classes
+ assert meta.year in [2007, 2012], meta.year
+ self._is_2007 = meta.year == 2007
+ self._cpu_device = torch.device("cpu")
+ self._logger = logging.getLogger(__name__)
+
+ def reset(self):
+ self._predictions = defaultdict(list) # class name -> list of prediction strings
+
+ def process(self, inputs, outputs):
+ for input, output in zip(inputs, outputs):
+ image_id = input["image_id"]
+ instances = output["instances"].to(self._cpu_device)
+ boxes = instances.pred_boxes.tensor.numpy()
+ scores = instances.scores.tolist()
+ classes = instances.pred_classes.tolist()
+ for box, score, cls in zip(boxes, scores, classes):
+ xmin, ymin, xmax, ymax = box
+ # The inverse of data loading logic in `datasets/pascal_voc.py`
+ xmin += 1
+ ymin += 1
+ self._predictions[cls].append(
+ f"{image_id} {score:.3f} {xmin:.1f} {ymin:.1f} {xmax:.1f} {ymax:.1f}"
+ )
+
+ def evaluate(self):
+ """
+ Returns:
+ dict: has a key "segm", whose value is a dict of "AP", "AP50", and "AP75".
+ """
+ all_predictions = comm.gather(self._predictions, dst=0)
+ if not comm.is_main_process():
+ return
+ predictions = defaultdict(list)
+ for predictions_per_rank in all_predictions:
+ for clsid, lines in predictions_per_rank.items():
+ predictions[clsid].extend(lines)
+ del all_predictions
+
+ self._logger.info(
+ "Evaluating {} using {} metric. "
+ "Note that results do not use the official Matlab API.".format(
+ self._dataset_name, 2007 if self._is_2007 else 2012
+ )
+ )
+
+ with tempfile.TemporaryDirectory(prefix="pascal_voc_eval_") as dirname:
+ res_file_template = os.path.join(dirname, "{}.txt")
+
+ aps = defaultdict(list) # iou -> ap per class
+ for cls_id, cls_name in enumerate(self._class_names):
+ lines = predictions.get(cls_id, [""])
+
+ with open(res_file_template.format(cls_name), "w") as f:
+ f.write("\n".join(lines))
+
+ for thresh in range(50, 100, 5):
+ rec, prec, ap = voc_eval(
+ res_file_template,
+ self._anno_file_template,
+ self._image_set_path,
+ cls_name,
+ ovthresh=thresh / 100.0,
+ use_07_metric=self._is_2007,
+ )
+ aps[thresh].append(ap * 100)
+
+ ret = OrderedDict()
+ mAP = {iou: np.mean(x) for iou, x in aps.items()}
+ ret["bbox"] = {"AP": np.mean(list(mAP.values())), "AP50": mAP[50], "AP75": mAP[75]}
+ return ret
+
+
+##############################################################################
+#
+# Below code is modified from
+# https://github.com/rbgirshick/py-faster-rcnn/blob/master/lib/datasets/voc_eval.py
+# --------------------------------------------------------
+# Fast/er R-CNN
+# Licensed under The MIT License [see LICENSE for details]
+# Written by Bharath Hariharan
+# --------------------------------------------------------
+
+"""Python implementation of the PASCAL VOC devkit's AP evaluation code."""
+
+
+@lru_cache(maxsize=None)
+def parse_rec(filename):
+ """Parse a PASCAL VOC xml file."""
+ with PathManager.open(filename) as f:
+ tree = ET.parse(f)
+ objects = []
+ for obj in tree.findall("object"):
+ obj_struct = {}
+ obj_struct["name"] = obj.find("name").text
+ obj_struct["pose"] = obj.find("pose").text
+ obj_struct["truncated"] = int(obj.find("truncated").text)
+ obj_struct["difficult"] = int(obj.find("difficult").text)
+ bbox = obj.find("bndbox")
+ obj_struct["bbox"] = [
+ int(bbox.find("xmin").text),
+ int(bbox.find("ymin").text),
+ int(bbox.find("xmax").text),
+ int(bbox.find("ymax").text),
+ ]
+ objects.append(obj_struct)
+
+ return objects
+
+
+def voc_ap(rec, prec, use_07_metric=False):
+ """Compute VOC AP given precision and recall. If use_07_metric is true, uses
+ the VOC 07 11-point method (default:False).
+ """
+ if use_07_metric:
+ # 11 point metric
+ ap = 0.0
+ for t in np.arange(0.0, 1.1, 0.1):
+ if np.sum(rec >= t) == 0:
+ p = 0
+ else:
+ p = np.max(prec[rec >= t])
+ ap = ap + p / 11.0
+ else:
+ # correct AP calculation
+ # first append sentinel values at the end
+ mrec = np.concatenate(([0.0], rec, [1.0]))
+ mpre = np.concatenate(([0.0], prec, [0.0]))
+
+ # compute the precision envelope
+ for i in range(mpre.size - 1, 0, -1):
+ mpre[i - 1] = np.maximum(mpre[i - 1], mpre[i])
+
+ # to calculate area under PR curve, look for points
+ # where X axis (recall) changes value
+ i = np.where(mrec[1:] != mrec[:-1])[0]
+
+ # and sum (\Delta recall) * prec
+ ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1])
+ return ap
+
+
+def voc_eval(detpath, annopath, imagesetfile, classname, ovthresh=0.5, use_07_metric=False):
+ """rec, prec, ap = voc_eval(detpath,
+ annopath,
+ imagesetfile,
+ classname,
+ [ovthresh],
+ [use_07_metric])
+
+ Top level function that does the PASCAL VOC evaluation.
+
+ detpath: Path to detections
+ detpath.format(classname) should produce the detection results file.
+ annopath: Path to annotations
+ annopath.format(imagename) should be the xml annotations file.
+ imagesetfile: Text file containing the list of images, one image per line.
+ classname: Category name (duh)
+ [ovthresh]: Overlap threshold (default = 0.5)
+ [use_07_metric]: Whether to use VOC07's 11 point AP computation
+ (default False)
+ """
+ # assumes detections are in detpath.format(classname)
+ # assumes annotations are in annopath.format(imagename)
+ # assumes imagesetfile is a text file with each line an image name
+
+ # first load gt
+ # read list of images
+ with PathManager.open(imagesetfile, "r") as f:
+ lines = f.readlines()
+ imagenames = [x.strip() for x in lines]
+
+ # load annots
+ recs = {}
+ for imagename in imagenames:
+ recs[imagename] = parse_rec(annopath.format(imagename))
+
+ # extract gt objects for this class
+ class_recs = {}
+ npos = 0
+ for imagename in imagenames:
+ R = [obj for obj in recs[imagename] if obj["name"] == classname]
+ bbox = np.array([x["bbox"] for x in R])
+ difficult = np.array([x["difficult"] for x in R]).astype(bool)
+ # difficult = np.array([False for x in R]).astype(bool) # treat all "difficult" as GT
+ det = [False] * len(R)
+ npos = npos + sum(~difficult)
+ class_recs[imagename] = {"bbox": bbox, "difficult": difficult, "det": det}
+
+ # read dets
+ detfile = detpath.format(classname)
+ with open(detfile, "r") as f:
+ lines = f.readlines()
+
+ splitlines = [x.strip().split(" ") for x in lines]
+ image_ids = [x[0] for x in splitlines]
+ confidence = np.array([float(x[1]) for x in splitlines])
+ BB = np.array([[float(z) for z in x[2:]] for x in splitlines]).reshape(-1, 4)
+
+ # sort by confidence
+ sorted_ind = np.argsort(-confidence)
+ BB = BB[sorted_ind, :]
+ image_ids = [image_ids[x] for x in sorted_ind]
+
+ # go down dets and mark TPs and FPs
+ nd = len(image_ids)
+ tp = np.zeros(nd)
+ fp = np.zeros(nd)
+ for d in range(nd):
+ R = class_recs[image_ids[d]]
+ bb = BB[d, :].astype(float)
+ ovmax = -np.inf
+ BBGT = R["bbox"].astype(float)
+
+ if BBGT.size > 0:
+ # compute overlaps
+ # intersection
+ ixmin = np.maximum(BBGT[:, 0], bb[0])
+ iymin = np.maximum(BBGT[:, 1], bb[1])
+ ixmax = np.minimum(BBGT[:, 2], bb[2])
+ iymax = np.minimum(BBGT[:, 3], bb[3])
+ iw = np.maximum(ixmax - ixmin + 1.0, 0.0)
+ ih = np.maximum(iymax - iymin + 1.0, 0.0)
+ inters = iw * ih
+
+ # union
+ uni = (
+ (bb[2] - bb[0] + 1.0) * (bb[3] - bb[1] + 1.0)
+ + (BBGT[:, 2] - BBGT[:, 0] + 1.0) * (BBGT[:, 3] - BBGT[:, 1] + 1.0)
+ - inters
+ )
+
+ overlaps = inters / uni
+ ovmax = np.max(overlaps)
+ jmax = np.argmax(overlaps)
+
+ if ovmax > ovthresh:
+ if not R["difficult"][jmax]:
+ if not R["det"][jmax]:
+ tp[d] = 1.0
+ R["det"][jmax] = 1
+ else:
+ fp[d] = 1.0
+ else:
+ fp[d] = 1.0
+
+ # compute precision recall
+ fp = np.cumsum(fp)
+ tp = np.cumsum(tp)
+ rec = tp / float(npos)
+ # avoid divide by zero in case the first detection matches a difficult
+ # ground truth
+ prec = tp / np.maximum(tp + fp, np.finfo(np.float64).eps)
+ ap = voc_ap(rec, prec, use_07_metric)
+
+ return rec, prec, ap
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/rotated_coco_evaluation.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/rotated_coco_evaluation.py
new file mode 100644
index 0000000000000000000000000000000000000000..d6d8b1a1cbb8243331f29f6a877f07373a7a6d97
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/rotated_coco_evaluation.py
@@ -0,0 +1,207 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import itertools
+import json
+import numpy as np
+import os
+import torch
+from custom_pycocotools.cocoeval import COCOeval, maskUtils
+
+from custom_detectron2.structures import BoxMode, RotatedBoxes, pairwise_iou_rotated
+from custom_detectron2.utils.file_io import PathManager
+
+from .coco_evaluation import COCOEvaluator
+
+
+class RotatedCOCOeval(COCOeval):
+ @staticmethod
+ def is_rotated(box_list):
+ if type(box_list) == np.ndarray:
+ return box_list.shape[1] == 5
+ elif type(box_list) == list:
+ if box_list == []: # cannot decide the box_dim
+ return False
+ return np.all(
+ np.array(
+ [
+ (len(obj) == 5) and ((type(obj) == list) or (type(obj) == np.ndarray))
+ for obj in box_list
+ ]
+ )
+ )
+ return False
+
+ @staticmethod
+ def boxlist_to_tensor(boxlist, output_box_dim):
+ if type(boxlist) == np.ndarray:
+ box_tensor = torch.from_numpy(boxlist)
+ elif type(boxlist) == list:
+ if boxlist == []:
+ return torch.zeros((0, output_box_dim), dtype=torch.float32)
+ else:
+ box_tensor = torch.FloatTensor(boxlist)
+ else:
+ raise Exception("Unrecognized boxlist type")
+
+ input_box_dim = box_tensor.shape[1]
+ if input_box_dim != output_box_dim:
+ if input_box_dim == 4 and output_box_dim == 5:
+ box_tensor = BoxMode.convert(box_tensor, BoxMode.XYWH_ABS, BoxMode.XYWHA_ABS)
+ else:
+ raise Exception(
+ "Unable to convert from {}-dim box to {}-dim box".format(
+ input_box_dim, output_box_dim
+ )
+ )
+ return box_tensor
+
+ def compute_iou_dt_gt(self, dt, gt, is_crowd):
+ if self.is_rotated(dt) or self.is_rotated(gt):
+ # TODO: take is_crowd into consideration
+ assert all(c == 0 for c in is_crowd)
+ dt = RotatedBoxes(self.boxlist_to_tensor(dt, output_box_dim=5))
+ gt = RotatedBoxes(self.boxlist_to_tensor(gt, output_box_dim=5))
+ return pairwise_iou_rotated(dt, gt)
+ else:
+ # This is the same as the classical COCO evaluation
+ return maskUtils.iou(dt, gt, is_crowd)
+
+ def computeIoU(self, imgId, catId):
+ p = self.params
+ if p.useCats:
+ gt = self._gts[imgId, catId]
+ dt = self._dts[imgId, catId]
+ else:
+ gt = [_ for cId in p.catIds for _ in self._gts[imgId, cId]]
+ dt = [_ for cId in p.catIds for _ in self._dts[imgId, cId]]
+ if len(gt) == 0 and len(dt) == 0:
+ return []
+ inds = np.argsort([-d["score"] for d in dt], kind="mergesort")
+ dt = [dt[i] for i in inds]
+ if len(dt) > p.maxDets[-1]:
+ dt = dt[0 : p.maxDets[-1]]
+
+ assert p.iouType == "bbox", "unsupported iouType for iou computation"
+
+ g = [g["bbox"] for g in gt]
+ d = [d["bbox"] for d in dt]
+
+ # compute iou between each dt and gt region
+ iscrowd = [int(o["iscrowd"]) for o in gt]
+
+ # Note: this function is copied from cocoeval.py in cocoapi
+ # and the major difference is here.
+ ious = self.compute_iou_dt_gt(d, g, iscrowd)
+ return ious
+
+
+class RotatedCOCOEvaluator(COCOEvaluator):
+ """
+ Evaluate object proposal/instance detection outputs using COCO-like metrics and APIs,
+ with rotated boxes support.
+ Note: this uses IOU only and does not consider angle differences.
+ """
+
+ def process(self, inputs, outputs):
+ """
+ Args:
+ inputs: the inputs to a COCO model (e.g., GeneralizedRCNN).
+ It is a list of dict. Each dict corresponds to an image and
+ contains keys like "height", "width", "file_name", "image_id".
+ outputs: the outputs of a COCO model. It is a list of dicts with key
+ "instances" that contains :class:`Instances`.
+ """
+ for input, output in zip(inputs, outputs):
+ prediction = {"image_id": input["image_id"]}
+
+ if "instances" in output:
+ instances = output["instances"].to(self._cpu_device)
+
+ prediction["instances"] = self.instances_to_json(instances, input["image_id"])
+ if "proposals" in output:
+ prediction["proposals"] = output["proposals"].to(self._cpu_device)
+ self._predictions.append(prediction)
+
+ def instances_to_json(self, instances, img_id):
+ num_instance = len(instances)
+ if num_instance == 0:
+ return []
+
+ boxes = instances.pred_boxes.tensor.numpy()
+ if boxes.shape[1] == 4:
+ boxes = BoxMode.convert(boxes, BoxMode.XYXY_ABS, BoxMode.XYWH_ABS)
+ boxes = boxes.tolist()
+ scores = instances.scores.tolist()
+ classes = instances.pred_classes.tolist()
+
+ results = []
+ for k in range(num_instance):
+ result = {
+ "image_id": img_id,
+ "category_id": classes[k],
+ "bbox": boxes[k],
+ "score": scores[k],
+ }
+
+ results.append(result)
+ return results
+
+ def _eval_predictions(self, predictions, img_ids=None): # img_ids: unused
+ """
+ Evaluate predictions on the given tasks.
+ Fill self._results with the metrics of the tasks.
+ """
+ self._logger.info("Preparing results for COCO format ...")
+ coco_results = list(itertools.chain(*[x["instances"] for x in predictions]))
+
+ # unmap the category ids for COCO
+ if hasattr(self._metadata, "thing_dataset_id_to_contiguous_id"):
+ reverse_id_mapping = {
+ v: k for k, v in self._metadata.thing_dataset_id_to_contiguous_id.items()
+ }
+ for result in coco_results:
+ result["category_id"] = reverse_id_mapping[result["category_id"]]
+
+ if self._output_dir:
+ file_path = os.path.join(self._output_dir, "coco_instances_results.json")
+ self._logger.info("Saving results to {}".format(file_path))
+ with PathManager.open(file_path, "w") as f:
+ f.write(json.dumps(coco_results))
+ f.flush()
+
+ if not self._do_evaluation:
+ self._logger.info("Annotations are not available for evaluation.")
+ return
+
+ self._logger.info("Evaluating predictions ...")
+
+ assert self._tasks is None or set(self._tasks) == {
+ "bbox"
+ }, "[RotatedCOCOEvaluator] Only bbox evaluation is supported"
+ coco_eval = (
+ self._evaluate_predictions_on_coco(self._coco_api, coco_results)
+ if len(coco_results) > 0
+ else None # cocoapi does not handle empty results very well
+ )
+
+ task = "bbox"
+ res = self._derive_coco_results(
+ coco_eval, task, class_names=self._metadata.get("thing_classes")
+ )
+ self._results[task] = res
+
+ def _evaluate_predictions_on_coco(self, coco_gt, coco_results):
+ """
+ Evaluate the coco results using COCOEval API.
+ """
+ assert len(coco_results) > 0
+
+ coco_dt = coco_gt.loadRes(coco_results)
+
+ # Only bbox is supported for now
+ coco_eval = RotatedCOCOeval(coco_gt, coco_dt, iouType="bbox")
+
+ coco_eval.evaluate()
+ coco_eval.accumulate()
+ coco_eval.summarize()
+
+ return coco_eval
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/sem_seg_evaluation.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/sem_seg_evaluation.py
new file mode 100644
index 0000000000000000000000000000000000000000..b82c23ddee9a916d0ba42b1536465e8a359c7932
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/sem_seg_evaluation.py
@@ -0,0 +1,265 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import itertools
+import json
+import logging
+import numpy as np
+import os
+from collections import OrderedDict
+from typing import Optional, Union
+import custom_pycocotools.mask as mask_util
+import torch
+from PIL import Image
+
+from custom_detectron2.data import DatasetCatalog, MetadataCatalog
+from custom_detectron2.utils.comm import all_gather, is_main_process, synchronize
+from custom_detectron2.utils.file_io import PathManager
+
+from .evaluator import DatasetEvaluator
+
+_CV2_IMPORTED = True
+try:
+ import cv2 # noqa
+except ImportError:
+ # OpenCV is an optional dependency at the moment
+ _CV2_IMPORTED = False
+
+
+def load_image_into_numpy_array(
+ filename: str,
+ copy: bool = False,
+ dtype: Optional[Union[np.dtype, str]] = None,
+) -> np.ndarray:
+ with PathManager.open(filename, "rb") as f:
+ array = np.array(Image.open(f), copy=copy, dtype=dtype)
+ return array
+
+
+class SemSegEvaluator(DatasetEvaluator):
+ """
+ Evaluate semantic segmentation metrics.
+ """
+
+ def __init__(
+ self,
+ dataset_name,
+ distributed=True,
+ output_dir=None,
+ *,
+ sem_seg_loading_fn=load_image_into_numpy_array,
+ num_classes=None,
+ ignore_label=None,
+ ):
+ """
+ Args:
+ dataset_name (str): name of the dataset to be evaluated.
+ distributed (bool): if True, will collect results from all ranks for evaluation.
+ Otherwise, will evaluate the results in the current process.
+ output_dir (str): an output directory to dump results.
+ sem_seg_loading_fn: function to read sem seg file and load into numpy array.
+ Default provided, but projects can customize.
+ num_classes, ignore_label: deprecated argument
+ """
+ self._logger = logging.getLogger(__name__)
+ if num_classes is not None:
+ self._logger.warn(
+ "SemSegEvaluator(num_classes) is deprecated! It should be obtained from metadata."
+ )
+ if ignore_label is not None:
+ self._logger.warn(
+ "SemSegEvaluator(ignore_label) is deprecated! It should be obtained from metadata."
+ )
+ self._dataset_name = dataset_name
+ self._distributed = distributed
+ self._output_dir = output_dir
+
+ self._cpu_device = torch.device("cpu")
+
+ self.input_file_to_gt_file = {
+ dataset_record["file_name"]: dataset_record["sem_seg_file_name"]
+ for dataset_record in DatasetCatalog.get(dataset_name)
+ }
+
+ meta = MetadataCatalog.get(dataset_name)
+ # Dict that maps contiguous training ids to COCO category ids
+ try:
+ c2d = meta.stuff_dataset_id_to_contiguous_id
+ self._contiguous_id_to_dataset_id = {v: k for k, v in c2d.items()}
+ except AttributeError:
+ self._contiguous_id_to_dataset_id = None
+ self._class_names = meta.stuff_classes
+ self.sem_seg_loading_fn = sem_seg_loading_fn
+ self._num_classes = len(meta.stuff_classes)
+ if num_classes is not None:
+ assert self._num_classes == num_classes, f"{self._num_classes} != {num_classes}"
+ self._ignore_label = ignore_label if ignore_label is not None else meta.ignore_label
+
+ # This is because cv2.erode did not work for int datatype. Only works for uint8.
+ self._compute_boundary_iou = True
+ if not _CV2_IMPORTED:
+ self._compute_boundary_iou = False
+ self._logger.warn(
+ """Boundary IoU calculation requires OpenCV. B-IoU metrics are
+ not going to be computed because OpenCV is not available to import."""
+ )
+ if self._num_classes >= np.iinfo(np.uint8).max:
+ self._compute_boundary_iou = False
+ self._logger.warn(
+ f"""SemSegEvaluator(num_classes) is more than supported value for Boundary IoU calculation!
+ B-IoU metrics are not going to be computed. Max allowed value (exclusive)
+ for num_classes for calculating Boundary IoU is {np.iinfo(np.uint8).max}.
+ The number of classes of dataset {self._dataset_name} is {self._num_classes}"""
+ )
+
+ def reset(self):
+ self._conf_matrix = np.zeros((self._num_classes + 1, self._num_classes + 1), dtype=np.int64)
+ self._b_conf_matrix = np.zeros(
+ (self._num_classes + 1, self._num_classes + 1), dtype=np.int64
+ )
+ self._predictions = []
+
+ def process(self, inputs, outputs):
+ """
+ Args:
+ inputs: the inputs to a model.
+ It is a list of dicts. Each dict corresponds to an image and
+ contains keys like "height", "width", "file_name".
+ outputs: the outputs of a model. It is either list of semantic segmentation predictions
+ (Tensor [H, W]) or list of dicts with key "sem_seg" that contains semantic
+ segmentation prediction in the same format.
+ """
+ for input, output in zip(inputs, outputs):
+ output = output["sem_seg"].argmax(dim=0).to(self._cpu_device)
+ pred = np.array(output, dtype=np.int)
+ gt_filename = self.input_file_to_gt_file[input["file_name"]]
+ gt = self.sem_seg_loading_fn(gt_filename, dtype=np.int)
+
+ gt[gt == self._ignore_label] = self._num_classes
+
+ self._conf_matrix += np.bincount(
+ (self._num_classes + 1) * pred.reshape(-1) + gt.reshape(-1),
+ minlength=self._conf_matrix.size,
+ ).reshape(self._conf_matrix.shape)
+
+ if self._compute_boundary_iou:
+ b_gt = self._mask_to_boundary(gt.astype(np.uint8))
+ b_pred = self._mask_to_boundary(pred.astype(np.uint8))
+
+ self._b_conf_matrix += np.bincount(
+ (self._num_classes + 1) * b_pred.reshape(-1) + b_gt.reshape(-1),
+ minlength=self._conf_matrix.size,
+ ).reshape(self._conf_matrix.shape)
+
+ self._predictions.extend(self.encode_json_sem_seg(pred, input["file_name"]))
+
+ def evaluate(self):
+ """
+ Evaluates standard semantic segmentation metrics (http://cocodataset.org/#stuff-eval):
+
+ * Mean intersection-over-union averaged across classes (mIoU)
+ * Frequency Weighted IoU (fwIoU)
+ * Mean pixel accuracy averaged across classes (mACC)
+ * Pixel Accuracy (pACC)
+ """
+ if self._distributed:
+ synchronize()
+ conf_matrix_list = all_gather(self._conf_matrix)
+ b_conf_matrix_list = all_gather(self._b_conf_matrix)
+ self._predictions = all_gather(self._predictions)
+ self._predictions = list(itertools.chain(*self._predictions))
+ if not is_main_process():
+ return
+
+ self._conf_matrix = np.zeros_like(self._conf_matrix)
+ for conf_matrix in conf_matrix_list:
+ self._conf_matrix += conf_matrix
+
+ self._b_conf_matrix = np.zeros_like(self._b_conf_matrix)
+ for b_conf_matrix in b_conf_matrix_list:
+ self._b_conf_matrix += b_conf_matrix
+
+ if self._output_dir:
+ PathManager.mkdirs(self._output_dir)
+ file_path = os.path.join(self._output_dir, "sem_seg_predictions.json")
+ with PathManager.open(file_path, "w") as f:
+ f.write(json.dumps(self._predictions))
+
+ acc = np.full(self._num_classes, np.nan, dtype=np.float)
+ iou = np.full(self._num_classes, np.nan, dtype=np.float)
+ tp = self._conf_matrix.diagonal()[:-1].astype(np.float)
+ pos_gt = np.sum(self._conf_matrix[:-1, :-1], axis=0).astype(np.float)
+ class_weights = pos_gt / np.sum(pos_gt)
+ pos_pred = np.sum(self._conf_matrix[:-1, :-1], axis=1).astype(np.float)
+ acc_valid = pos_gt > 0
+ acc[acc_valid] = tp[acc_valid] / pos_gt[acc_valid]
+ union = pos_gt + pos_pred - tp
+ iou_valid = np.logical_and(acc_valid, union > 0)
+ iou[iou_valid] = tp[iou_valid] / union[iou_valid]
+ macc = np.sum(acc[acc_valid]) / np.sum(acc_valid)
+ miou = np.sum(iou[iou_valid]) / np.sum(iou_valid)
+ fiou = np.sum(iou[iou_valid] * class_weights[iou_valid])
+ pacc = np.sum(tp) / np.sum(pos_gt)
+
+ if self._compute_boundary_iou:
+ b_iou = np.full(self._num_classes, np.nan, dtype=np.float)
+ b_tp = self._b_conf_matrix.diagonal()[:-1].astype(np.float)
+ b_pos_gt = np.sum(self._b_conf_matrix[:-1, :-1], axis=0).astype(np.float)
+ b_pos_pred = np.sum(self._b_conf_matrix[:-1, :-1], axis=1).astype(np.float)
+ b_union = b_pos_gt + b_pos_pred - b_tp
+ b_iou_valid = b_union > 0
+ b_iou[b_iou_valid] = b_tp[b_iou_valid] / b_union[b_iou_valid]
+
+ res = {}
+ res["mIoU"] = 100 * miou
+ res["fwIoU"] = 100 * fiou
+ for i, name in enumerate(self._class_names):
+ res[f"IoU-{name}"] = 100 * iou[i]
+ if self._compute_boundary_iou:
+ res[f"BoundaryIoU-{name}"] = 100 * b_iou[i]
+ res[f"min(IoU, B-Iou)-{name}"] = 100 * min(iou[i], b_iou[i])
+ res["mACC"] = 100 * macc
+ res["pACC"] = 100 * pacc
+ for i, name in enumerate(self._class_names):
+ res[f"ACC-{name}"] = 100 * acc[i]
+
+ if self._output_dir:
+ file_path = os.path.join(self._output_dir, "sem_seg_evaluation.pth")
+ with PathManager.open(file_path, "wb") as f:
+ torch.save(res, f)
+ results = OrderedDict({"sem_seg": res})
+ self._logger.info(results)
+ return results
+
+ def encode_json_sem_seg(self, sem_seg, input_file_name):
+ """
+ Convert semantic segmentation to COCO stuff format with segments encoded as RLEs.
+ See http://cocodataset.org/#format-results
+ """
+ json_list = []
+ for label in np.unique(sem_seg):
+ if self._contiguous_id_to_dataset_id is not None:
+ assert (
+ label in self._contiguous_id_to_dataset_id
+ ), "Label {} is not in the metadata info for {}".format(label, self._dataset_name)
+ dataset_id = self._contiguous_id_to_dataset_id[label]
+ else:
+ dataset_id = int(label)
+ mask = (sem_seg == label).astype(np.uint8)
+ mask_rle = mask_util.encode(np.array(mask[:, :, None], order="F"))[0]
+ mask_rle["counts"] = mask_rle["counts"].decode("utf-8")
+ json_list.append(
+ {"file_name": input_file_name, "category_id": dataset_id, "segmentation": mask_rle}
+ )
+ return json_list
+
+ def _mask_to_boundary(self, mask: np.ndarray, dilation_ratio=0.02):
+ assert mask.ndim == 2, "mask_to_boundary expects a 2-dimensional image"
+ h, w = mask.shape
+ diag_len = np.sqrt(h**2 + w**2)
+ dilation = max(1, int(round(dilation_ratio * diag_len)))
+ kernel = np.ones((3, 3), dtype=np.uint8)
+
+ padded_mask = cv2.copyMakeBorder(mask, 1, 1, 1, 1, cv2.BORDER_CONSTANT, value=0)
+ eroded_mask_with_padding = cv2.erode(padded_mask, kernel, iterations=dilation)
+ eroded_mask = eroded_mask_with_padding[1:-1, 1:-1]
+ boundary = mask - eroded_mask
+ return boundary
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/testing.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/testing.py
new file mode 100644
index 0000000000000000000000000000000000000000..9e5ae625bb0593fc20739dd3ea549157e4df4f3d
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/evaluation/testing.py
@@ -0,0 +1,85 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import logging
+import numpy as np
+import pprint
+import sys
+from collections.abc import Mapping
+
+
+def print_csv_format(results):
+ """
+ Print main metrics in a format similar to Detectron,
+ so that they are easy to copypaste into a spreadsheet.
+
+ Args:
+ results (OrderedDict[dict]): task_name -> {metric -> score}
+ unordered dict can also be printed, but in arbitrary order
+ """
+ assert isinstance(results, Mapping) or not len(results), results
+ logger = logging.getLogger(__name__)
+ for task, res in results.items():
+ if isinstance(res, Mapping):
+ # Don't print "AP-category" metrics since they are usually not tracked.
+ important_res = [(k, v) for k, v in res.items() if "-" not in k]
+ logger.info("copypaste: Task: {}".format(task))
+ logger.info("copypaste: " + ",".join([k[0] for k in important_res]))
+ logger.info("copypaste: " + ",".join(["{0:.4f}".format(k[1]) for k in important_res]))
+ else:
+ logger.info(f"copypaste: {task}={res}")
+
+
+def verify_results(cfg, results):
+ """
+ Args:
+ results (OrderedDict[dict]): task_name -> {metric -> score}
+
+ Returns:
+ bool: whether the verification succeeds or not
+ """
+ expected_results = cfg.TEST.EXPECTED_RESULTS
+ if not len(expected_results):
+ return True
+
+ ok = True
+ for task, metric, expected, tolerance in expected_results:
+ actual = results[task].get(metric, None)
+ if actual is None:
+ ok = False
+ continue
+ if not np.isfinite(actual):
+ ok = False
+ continue
+ diff = abs(actual - expected)
+ if diff > tolerance:
+ ok = False
+
+ logger = logging.getLogger(__name__)
+ if not ok:
+ logger.error("Result verification failed!")
+ logger.error("Expected Results: " + str(expected_results))
+ logger.error("Actual Results: " + pprint.pformat(results))
+
+ sys.exit(1)
+ else:
+ logger.info("Results verification passed.")
+ return ok
+
+
+def flatten_results_dict(results):
+ """
+ Expand a hierarchical dict of scalars into a flat dict of scalars.
+ If results[k1][k2][k3] = v, the returned dict will have the entry
+ {"k1/k2/k3": v}.
+
+ Args:
+ results (dict):
+ """
+ r = {}
+ for k, v in results.items():
+ if isinstance(v, Mapping):
+ v = flatten_results_dict(v)
+ for kk, vv in v.items():
+ r[k + "/" + kk] = vv
+ else:
+ r[k] = v
+ return r
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/README.md b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..c86ff62516f4e8e4b1a6c1f33f11192933cf3861
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/README.md
@@ -0,0 +1,15 @@
+
+This directory contains code to prepare a detectron2 model for deployment.
+Currently it supports exporting a detectron2 model to TorchScript, ONNX, or (deprecated) Caffe2 format.
+
+Please see [documentation](https://detectron2.readthedocs.io/tutorials/deployment.html) for its usage.
+
+
+### Acknowledgements
+
+Thanks to Mobile Vision team at Facebook for developing the Caffe2 conversion tools.
+
+Thanks to Computing Platform Department - PAI team at Alibaba Group (@bddpqq, @chenbohua3) who
+help export Detectron2 models to TorchScript.
+
+Thanks to ONNX Converter team at Microsoft who help export Detectron2 models to ONNX.
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..5a58758f64aae6071fa688be4400622ce6036efa
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/__init__.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+
+import warnings
+
+from .flatten import TracingAdapter
+from .torchscript import dump_torchscript_IR, scripting_with_instances
+
+try:
+ from caffe2.proto import caffe2_pb2 as _tmp
+ from caffe2.python import core
+
+ # caffe2 is optional
+except ImportError:
+ pass
+else:
+ from .api import *
+
+
+# TODO: Update ONNX Opset version and run tests when a newer PyTorch is supported
+STABLE_ONNX_OPSET_VERSION = 11
+
+
+def add_export_config(cfg):
+ warnings.warn(
+ "add_export_config has been deprecated and behaves as no-op function.", DeprecationWarning
+ )
+ return cfg
+
+
+__all__ = [k for k in globals().keys() if not k.startswith("_")]
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/api.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/api.py
new file mode 100644
index 0000000000000000000000000000000000000000..4f9d7dac1d086902f082910059d539083bd85c0d
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/api.py
@@ -0,0 +1,230 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import copy
+import logging
+import os
+import torch
+from caffe2.proto import caffe2_pb2
+from torch import nn
+
+from custom_detectron2.config import CfgNode
+from custom_detectron2.utils.file_io import PathManager
+
+from .caffe2_inference import ProtobufDetectionModel
+from .caffe2_modeling import META_ARCH_CAFFE2_EXPORT_TYPE_MAP, convert_batched_inputs_to_c2_format
+from .shared import get_pb_arg_vali, get_pb_arg_vals, save_graph
+
+__all__ = [
+ "Caffe2Model",
+ "Caffe2Tracer",
+]
+
+
+class Caffe2Tracer:
+ """
+ Make a detectron2 model traceable with Caffe2 operators.
+ This class creates a traceable version of a detectron2 model which:
+
+ 1. Rewrite parts of the model using ops in Caffe2. Note that some ops do
+ not have GPU implementation in Caffe2.
+ 2. Remove post-processing and only produce raw layer outputs
+
+ After making a traceable model, the class provide methods to export such a
+ model to different deployment formats.
+ Exported graph produced by this class take two input tensors:
+
+ 1. (1, C, H, W) float "data" which is an image (usually in [0, 255]).
+ (H, W) often has to be padded to multiple of 32 (depend on the model
+ architecture).
+ 2. 1x3 float "im_info", each row of which is (height, width, 1.0).
+ Height and width are true image shapes before padding.
+
+ The class currently only supports models using builtin meta architectures.
+ Batch inference is not supported, and contributions are welcome.
+ """
+
+ def __init__(self, cfg: CfgNode, model: nn.Module, inputs):
+ """
+ Args:
+ cfg (CfgNode): a detectron2 config used to construct caffe2-compatible model.
+ model (nn.Module): An original pytorch model. Must be among a few official models
+ in detectron2 that can be converted to become caffe2-compatible automatically.
+ Weights have to be already loaded to this model.
+ inputs: sample inputs that the given model takes for inference.
+ Will be used to trace the model. For most models, random inputs with
+ no detected objects will not work as they lead to wrong traces.
+ """
+ assert isinstance(cfg, CfgNode), cfg
+ assert isinstance(model, torch.nn.Module), type(model)
+
+ # TODO make it support custom models, by passing in c2 model directly
+ C2MetaArch = META_ARCH_CAFFE2_EXPORT_TYPE_MAP[cfg.MODEL.META_ARCHITECTURE]
+ self.traceable_model = C2MetaArch(cfg, copy.deepcopy(model))
+ self.inputs = inputs
+ self.traceable_inputs = self.traceable_model.get_caffe2_inputs(inputs)
+
+ def export_caffe2(self):
+ """
+ Export the model to Caffe2's protobuf format.
+ The returned object can be saved with its :meth:`.save_protobuf()` method.
+ The result can be loaded and executed using Caffe2 runtime.
+
+ Returns:
+ :class:`Caffe2Model`
+ """
+ from .caffe2_export import export_caffe2_detection_model
+
+ predict_net, init_net = export_caffe2_detection_model(
+ self.traceable_model, self.traceable_inputs
+ )
+ return Caffe2Model(predict_net, init_net)
+
+ def export_onnx(self):
+ """
+ Export the model to ONNX format.
+ Note that the exported model contains custom ops only available in caffe2, therefore it
+ cannot be directly executed by other runtime (such as onnxruntime or TensorRT).
+ Post-processing or transformation passes may be applied on the model to accommodate
+ different runtimes, but we currently do not provide support for them.
+
+ Returns:
+ onnx.ModelProto: an onnx model.
+ """
+ from .caffe2_export import export_onnx_model as export_onnx_model_impl
+
+ return export_onnx_model_impl(self.traceable_model, (self.traceable_inputs,))
+
+ def export_torchscript(self):
+ """
+ Export the model to a ``torch.jit.TracedModule`` by tracing.
+ The returned object can be saved to a file by ``.save()``.
+
+ Returns:
+ torch.jit.TracedModule: a torch TracedModule
+ """
+ logger = logging.getLogger(__name__)
+ logger.info("Tracing the model with torch.jit.trace ...")
+ with torch.no_grad():
+ return torch.jit.trace(self.traceable_model, (self.traceable_inputs,))
+
+
+class Caffe2Model(nn.Module):
+ """
+ A wrapper around the traced model in Caffe2's protobuf format.
+ The exported graph has different inputs/outputs from the original Pytorch
+ model, as explained in :class:`Caffe2Tracer`. This class wraps around the
+ exported graph to simulate the same interface as the original Pytorch model.
+ It also provides functions to save/load models in Caffe2's format.'
+
+ Examples:
+ ::
+ c2_model = Caffe2Tracer(cfg, torch_model, inputs).export_caffe2()
+ inputs = [{"image": img_tensor_CHW}]
+ outputs = c2_model(inputs)
+ orig_outputs = torch_model(inputs)
+ """
+
+ def __init__(self, predict_net, init_net):
+ super().__init__()
+ self.eval() # always in eval mode
+ self._predict_net = predict_net
+ self._init_net = init_net
+ self._predictor = None
+
+ __init__.__HIDE_SPHINX_DOC__ = True
+
+ @property
+ def predict_net(self):
+ """
+ caffe2.core.Net: the underlying caffe2 predict net
+ """
+ return self._predict_net
+
+ @property
+ def init_net(self):
+ """
+ caffe2.core.Net: the underlying caffe2 init net
+ """
+ return self._init_net
+
+ def save_protobuf(self, output_dir):
+ """
+ Save the model as caffe2's protobuf format.
+ It saves the following files:
+
+ * "model.pb": definition of the graph. Can be visualized with
+ tools like `netron `_.
+ * "model_init.pb": model parameters
+ * "model.pbtxt": human-readable definition of the graph. Not
+ needed for deployment.
+
+ Args:
+ output_dir (str): the output directory to save protobuf files.
+ """
+ logger = logging.getLogger(__name__)
+ logger.info("Saving model to {} ...".format(output_dir))
+ if not PathManager.exists(output_dir):
+ PathManager.mkdirs(output_dir)
+
+ with PathManager.open(os.path.join(output_dir, "model.pb"), "wb") as f:
+ f.write(self._predict_net.SerializeToString())
+ with PathManager.open(os.path.join(output_dir, "model.pbtxt"), "w") as f:
+ f.write(str(self._predict_net))
+ with PathManager.open(os.path.join(output_dir, "model_init.pb"), "wb") as f:
+ f.write(self._init_net.SerializeToString())
+
+ def save_graph(self, output_file, inputs=None):
+ """
+ Save the graph as SVG format.
+
+ Args:
+ output_file (str): a SVG file
+ inputs: optional inputs given to the model.
+ If given, the inputs will be used to run the graph to record
+ shape of every tensor. The shape information will be
+ saved together with the graph.
+ """
+ from .caffe2_export import run_and_save_graph
+
+ if inputs is None:
+ save_graph(self._predict_net, output_file, op_only=False)
+ else:
+ size_divisibility = get_pb_arg_vali(self._predict_net, "size_divisibility", 0)
+ device = get_pb_arg_vals(self._predict_net, "device", b"cpu").decode("ascii")
+ inputs = convert_batched_inputs_to_c2_format(inputs, size_divisibility, device)
+ inputs = [x.cpu().numpy() for x in inputs]
+ run_and_save_graph(self._predict_net, self._init_net, inputs, output_file)
+
+ @staticmethod
+ def load_protobuf(dir):
+ """
+ Args:
+ dir (str): a directory used to save Caffe2Model with
+ :meth:`save_protobuf`.
+ The files "model.pb" and "model_init.pb" are needed.
+
+ Returns:
+ Caffe2Model: the caffe2 model loaded from this directory.
+ """
+ predict_net = caffe2_pb2.NetDef()
+ with PathManager.open(os.path.join(dir, "model.pb"), "rb") as f:
+ predict_net.ParseFromString(f.read())
+
+ init_net = caffe2_pb2.NetDef()
+ with PathManager.open(os.path.join(dir, "model_init.pb"), "rb") as f:
+ init_net.ParseFromString(f.read())
+
+ return Caffe2Model(predict_net, init_net)
+
+ def __call__(self, inputs):
+ """
+ An interface that wraps around a Caffe2 model and mimics detectron2's models'
+ input/output format. See details about the format at :doc:`/tutorials/models`.
+ This is used to compare the outputs of caffe2 model with its original torch model.
+
+ Due to the extra conversion between Pytorch/Caffe2, this method is not meant for
+ benchmark. Because of the conversion, this method also has dependency
+ on detectron2 in order to convert to detectron2's output format.
+ """
+ if self._predictor is None:
+ self._predictor = ProtobufDetectionModel(self._predict_net, self._init_net)
+ return self._predictor(inputs)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/c10.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/c10.py
new file mode 100644
index 0000000000000000000000000000000000000000..c657db1eecb3a80622bee34d8ea58b9c3ff913aa
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/c10.py
@@ -0,0 +1,557 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import math
+from typing import Dict
+import torch
+import torch.nn.functional as F
+
+from custom_detectron2.layers import ShapeSpec, cat
+from custom_detectron2.layers.roi_align_rotated import ROIAlignRotated
+from custom_detectron2.modeling import poolers
+from custom_detectron2.modeling.proposal_generator import rpn
+from custom_detectron2.modeling.roi_heads.mask_head import mask_rcnn_inference
+from custom_detectron2.structures import Boxes, ImageList, Instances, Keypoints, RotatedBoxes
+
+from .shared import alias, to_device
+
+
+"""
+This file contains caffe2-compatible implementation of several detectron2 components.
+"""
+
+
+class Caffe2Boxes(Boxes):
+ """
+ Representing a list of detectron2.structures.Boxes from minibatch, each box
+ is represented by a 5d vector (batch index + 4 coordinates), or a 6d vector
+ (batch index + 5 coordinates) for RotatedBoxes.
+ """
+
+ def __init__(self, tensor):
+ assert isinstance(tensor, torch.Tensor)
+ assert tensor.dim() == 2 and tensor.size(-1) in [4, 5, 6], tensor.size()
+ # TODO: make tensor immutable when dim is Nx5 for Boxes,
+ # and Nx6 for RotatedBoxes?
+ self.tensor = tensor
+
+
+# TODO clean up this class, maybe just extend Instances
+class InstancesList(object):
+ """
+ Tensor representation of a list of Instances object for a batch of images.
+
+ When dealing with a batch of images with Caffe2 ops, a list of bboxes
+ (instances) are usually represented by single Tensor with size
+ (sigma(Ni), 5) or (sigma(Ni), 4) plus a batch split Tensor. This class is
+ for providing common functions to convert between these two representations.
+ """
+
+ def __init__(self, im_info, indices, extra_fields=None):
+ # [N, 3] -> (H, W, Scale)
+ self.im_info = im_info
+ # [N,] -> indice of batch to which the instance belongs
+ self.indices = indices
+ # [N, ...]
+ self.batch_extra_fields = extra_fields or {}
+
+ self.image_size = self.im_info
+
+ def get_fields(self):
+ """like `get_fields` in the Instances object,
+ but return each field in tensor representations"""
+ ret = {}
+ for k, v in self.batch_extra_fields.items():
+ # if isinstance(v, torch.Tensor):
+ # tensor_rep = v
+ # elif isinstance(v, (Boxes, Keypoints)):
+ # tensor_rep = v.tensor
+ # else:
+ # raise ValueError("Can't find tensor representation for: {}".format())
+ ret[k] = v
+ return ret
+
+ def has(self, name):
+ return name in self.batch_extra_fields
+
+ def set(self, name, value):
+ # len(tensor) is a bad practice that generates ONNX constants during tracing.
+ # Although not a problem for the `assert` statement below, torch ONNX exporter
+ # still raises a misleading warning as it does not this call comes from `assert`
+ if isinstance(value, Boxes):
+ data_len = value.tensor.shape[0]
+ elif isinstance(value, torch.Tensor):
+ data_len = value.shape[0]
+ else:
+ data_len = len(value)
+ if len(self.batch_extra_fields):
+ assert (
+ len(self) == data_len
+ ), "Adding a field of length {} to a Instances of length {}".format(data_len, len(self))
+ self.batch_extra_fields[name] = value
+
+ def __getattr__(self, name):
+ if name not in self.batch_extra_fields:
+ raise AttributeError("Cannot find field '{}' in the given Instances!".format(name))
+ return self.batch_extra_fields[name]
+
+ def __len__(self):
+ return len(self.indices)
+
+ def flatten(self):
+ ret = []
+ for _, v in self.batch_extra_fields.items():
+ if isinstance(v, (Boxes, Keypoints)):
+ ret.append(v.tensor)
+ else:
+ ret.append(v)
+ return ret
+
+ @staticmethod
+ def to_d2_instances_list(instances_list):
+ """
+ Convert InstancesList to List[Instances]. The input `instances_list` can
+ also be a List[Instances], in this case this method is a non-op.
+ """
+ if not isinstance(instances_list, InstancesList):
+ assert all(isinstance(x, Instances) for x in instances_list)
+ return instances_list
+
+ ret = []
+ for i, info in enumerate(instances_list.im_info):
+ instances = Instances(torch.Size([int(info[0].item()), int(info[1].item())]))
+
+ ids = instances_list.indices == i
+ for k, v in instances_list.batch_extra_fields.items():
+ if isinstance(v, torch.Tensor):
+ instances.set(k, v[ids])
+ continue
+ elif isinstance(v, Boxes):
+ instances.set(k, v[ids, -4:])
+ continue
+
+ target_type, tensor_source = v
+ assert isinstance(tensor_source, torch.Tensor)
+ assert tensor_source.shape[0] == instances_list.indices.shape[0]
+ tensor_source = tensor_source[ids]
+
+ if issubclass(target_type, Boxes):
+ instances.set(k, Boxes(tensor_source[:, -4:]))
+ elif issubclass(target_type, Keypoints):
+ instances.set(k, Keypoints(tensor_source))
+ elif issubclass(target_type, torch.Tensor):
+ instances.set(k, tensor_source)
+ else:
+ raise ValueError("Can't handle targe type: {}".format(target_type))
+
+ ret.append(instances)
+ return ret
+
+
+class Caffe2Compatible(object):
+ """
+ A model can inherit this class to indicate that it can be traced and deployed with caffe2.
+ """
+
+ def _get_tensor_mode(self):
+ return self._tensor_mode
+
+ def _set_tensor_mode(self, v):
+ self._tensor_mode = v
+
+ tensor_mode = property(_get_tensor_mode, _set_tensor_mode)
+ """
+ If true, the model expects C2-style tensor only inputs/outputs format.
+ """
+
+
+class Caffe2RPN(Caffe2Compatible, rpn.RPN):
+ @classmethod
+ def from_config(cls, cfg, input_shape: Dict[str, ShapeSpec]):
+ ret = super(Caffe2Compatible, cls).from_config(cfg, input_shape)
+ assert tuple(cfg.MODEL.RPN.BBOX_REG_WEIGHTS) == (1.0, 1.0, 1.0, 1.0) or tuple(
+ cfg.MODEL.RPN.BBOX_REG_WEIGHTS
+ ) == (1.0, 1.0, 1.0, 1.0, 1.0)
+ return ret
+
+ def _generate_proposals(
+ self, images, objectness_logits_pred, anchor_deltas_pred, gt_instances=None
+ ):
+ assert isinstance(images, ImageList)
+ if self.tensor_mode:
+ im_info = images.image_sizes
+ else:
+ im_info = torch.tensor([[im_sz[0], im_sz[1], 1.0] for im_sz in images.image_sizes]).to(
+ images.tensor.device
+ )
+ assert isinstance(im_info, torch.Tensor)
+
+ rpn_rois_list = []
+ rpn_roi_probs_list = []
+ for scores, bbox_deltas, cell_anchors_tensor, feat_stride in zip(
+ objectness_logits_pred,
+ anchor_deltas_pred,
+ [b for (n, b) in self.anchor_generator.cell_anchors.named_buffers()],
+ self.anchor_generator.strides,
+ ):
+ scores = scores.detach()
+ bbox_deltas = bbox_deltas.detach()
+
+ rpn_rois, rpn_roi_probs = torch.ops._caffe2.GenerateProposals(
+ scores,
+ bbox_deltas,
+ im_info,
+ cell_anchors_tensor,
+ spatial_scale=1.0 / feat_stride,
+ pre_nms_topN=self.pre_nms_topk[self.training],
+ post_nms_topN=self.post_nms_topk[self.training],
+ nms_thresh=self.nms_thresh,
+ min_size=self.min_box_size,
+ # correct_transform_coords=True, # deprecated argument
+ angle_bound_on=True, # Default
+ angle_bound_lo=-180,
+ angle_bound_hi=180,
+ clip_angle_thresh=1.0, # Default
+ legacy_plus_one=False,
+ )
+ rpn_rois_list.append(rpn_rois)
+ rpn_roi_probs_list.append(rpn_roi_probs)
+
+ # For FPN in D2, in RPN all proposals from different levels are concated
+ # together, ranked and picked by top post_nms_topk. Then in ROIPooler
+ # it calculates level_assignments and calls the RoIAlign from
+ # the corresponding level.
+
+ if len(objectness_logits_pred) == 1:
+ rpn_rois = rpn_rois_list[0]
+ rpn_roi_probs = rpn_roi_probs_list[0]
+ else:
+ assert len(rpn_rois_list) == len(rpn_roi_probs_list)
+ rpn_post_nms_topN = self.post_nms_topk[self.training]
+
+ device = rpn_rois_list[0].device
+ input_list = [to_device(x, "cpu") for x in (rpn_rois_list + rpn_roi_probs_list)]
+
+ # TODO remove this after confirming rpn_max_level/rpn_min_level
+ # is not needed in CollectRpnProposals.
+ feature_strides = list(self.anchor_generator.strides)
+ rpn_min_level = int(math.log2(feature_strides[0]))
+ rpn_max_level = int(math.log2(feature_strides[-1]))
+ assert (rpn_max_level - rpn_min_level + 1) == len(
+ rpn_rois_list
+ ), "CollectRpnProposals requires continuous levels"
+
+ rpn_rois = torch.ops._caffe2.CollectRpnProposals(
+ input_list,
+ # NOTE: in current implementation, rpn_max_level and rpn_min_level
+ # are not needed, only the subtraction of two matters and it
+ # can be infer from the number of inputs. Keep them now for
+ # consistency.
+ rpn_max_level=2 + len(rpn_rois_list) - 1,
+ rpn_min_level=2,
+ rpn_post_nms_topN=rpn_post_nms_topN,
+ )
+ rpn_rois = to_device(rpn_rois, device)
+ rpn_roi_probs = []
+
+ proposals = self.c2_postprocess(im_info, rpn_rois, rpn_roi_probs, self.tensor_mode)
+ return proposals, {}
+
+ def forward(self, images, features, gt_instances=None):
+ assert not self.training
+ features = [features[f] for f in self.in_features]
+ objectness_logits_pred, anchor_deltas_pred = self.rpn_head(features)
+ return self._generate_proposals(
+ images,
+ objectness_logits_pred,
+ anchor_deltas_pred,
+ gt_instances,
+ )
+
+ @staticmethod
+ def c2_postprocess(im_info, rpn_rois, rpn_roi_probs, tensor_mode):
+ proposals = InstancesList(
+ im_info=im_info,
+ indices=rpn_rois[:, 0],
+ extra_fields={
+ "proposal_boxes": Caffe2Boxes(rpn_rois),
+ "objectness_logits": (torch.Tensor, rpn_roi_probs),
+ },
+ )
+ if not tensor_mode:
+ proposals = InstancesList.to_d2_instances_list(proposals)
+ else:
+ proposals = [proposals]
+ return proposals
+
+
+class Caffe2ROIPooler(Caffe2Compatible, poolers.ROIPooler):
+ @staticmethod
+ def c2_preprocess(box_lists):
+ assert all(isinstance(x, Boxes) for x in box_lists)
+ if all(isinstance(x, Caffe2Boxes) for x in box_lists):
+ # input is pure-tensor based
+ assert len(box_lists) == 1
+ pooler_fmt_boxes = box_lists[0].tensor
+ else:
+ pooler_fmt_boxes = poolers.convert_boxes_to_pooler_format(box_lists)
+ return pooler_fmt_boxes
+
+ def forward(self, x, box_lists):
+ assert not self.training
+
+ pooler_fmt_boxes = self.c2_preprocess(box_lists)
+ num_level_assignments = len(self.level_poolers)
+
+ if num_level_assignments == 1:
+ if isinstance(self.level_poolers[0], ROIAlignRotated):
+ c2_roi_align = torch.ops._caffe2.RoIAlignRotated
+ aligned = True
+ else:
+ c2_roi_align = torch.ops._caffe2.RoIAlign
+ aligned = self.level_poolers[0].aligned
+
+ x0 = x[0]
+ if x0.is_quantized:
+ x0 = x0.dequantize()
+
+ out = c2_roi_align(
+ x0,
+ pooler_fmt_boxes,
+ order="NCHW",
+ spatial_scale=float(self.level_poolers[0].spatial_scale),
+ pooled_h=int(self.output_size[0]),
+ pooled_w=int(self.output_size[1]),
+ sampling_ratio=int(self.level_poolers[0].sampling_ratio),
+ aligned=aligned,
+ )
+ return out
+
+ device = pooler_fmt_boxes.device
+ assert (
+ self.max_level - self.min_level + 1 == 4
+ ), "Currently DistributeFpnProposals only support 4 levels"
+ fpn_outputs = torch.ops._caffe2.DistributeFpnProposals(
+ to_device(pooler_fmt_boxes, "cpu"),
+ roi_canonical_scale=self.canonical_box_size,
+ roi_canonical_level=self.canonical_level,
+ roi_max_level=self.max_level,
+ roi_min_level=self.min_level,
+ legacy_plus_one=False,
+ )
+ fpn_outputs = [to_device(x, device) for x in fpn_outputs]
+
+ rois_fpn_list = fpn_outputs[:-1]
+ rois_idx_restore_int32 = fpn_outputs[-1]
+
+ roi_feat_fpn_list = []
+ for roi_fpn, x_level, pooler in zip(rois_fpn_list, x, self.level_poolers):
+ if isinstance(pooler, ROIAlignRotated):
+ c2_roi_align = torch.ops._caffe2.RoIAlignRotated
+ aligned = True
+ else:
+ c2_roi_align = torch.ops._caffe2.RoIAlign
+ aligned = bool(pooler.aligned)
+
+ if x_level.is_quantized:
+ x_level = x_level.dequantize()
+
+ roi_feat_fpn = c2_roi_align(
+ x_level,
+ roi_fpn,
+ order="NCHW",
+ spatial_scale=float(pooler.spatial_scale),
+ pooled_h=int(self.output_size[0]),
+ pooled_w=int(self.output_size[1]),
+ sampling_ratio=int(pooler.sampling_ratio),
+ aligned=aligned,
+ )
+ roi_feat_fpn_list.append(roi_feat_fpn)
+
+ roi_feat_shuffled = cat(roi_feat_fpn_list, dim=0)
+ assert roi_feat_shuffled.numel() > 0 and rois_idx_restore_int32.numel() > 0, (
+ "Caffe2 export requires tracing with a model checkpoint + input that can produce valid"
+ " detections. But no detections were obtained with the given checkpoint and input!"
+ )
+ roi_feat = torch.ops._caffe2.BatchPermutation(roi_feat_shuffled, rois_idx_restore_int32)
+ return roi_feat
+
+
+class Caffe2FastRCNNOutputsInference:
+ def __init__(self, tensor_mode):
+ self.tensor_mode = tensor_mode # whether the output is caffe2 tensor mode
+
+ def __call__(self, box_predictor, predictions, proposals):
+ """equivalent to FastRCNNOutputLayers.inference"""
+ num_classes = box_predictor.num_classes
+ score_thresh = box_predictor.test_score_thresh
+ nms_thresh = box_predictor.test_nms_thresh
+ topk_per_image = box_predictor.test_topk_per_image
+ is_rotated = len(box_predictor.box2box_transform.weights) == 5
+
+ if is_rotated:
+ box_dim = 5
+ assert box_predictor.box2box_transform.weights[4] == 1, (
+ "The weights for Rotated BBoxTransform in C2 have only 4 dimensions,"
+ + " thus enforcing the angle weight to be 1 for now"
+ )
+ box2box_transform_weights = box_predictor.box2box_transform.weights[:4]
+ else:
+ box_dim = 4
+ box2box_transform_weights = box_predictor.box2box_transform.weights
+
+ class_logits, box_regression = predictions
+ if num_classes + 1 == class_logits.shape[1]:
+ class_prob = F.softmax(class_logits, -1)
+ else:
+ assert num_classes == class_logits.shape[1]
+ class_prob = F.sigmoid(class_logits)
+ # BoxWithNMSLimit will infer num_classes from the shape of the class_prob
+ # So append a zero column as placeholder for the background class
+ class_prob = torch.cat((class_prob, torch.zeros(class_prob.shape[0], 1)), dim=1)
+
+ assert box_regression.shape[1] % box_dim == 0
+ cls_agnostic_bbox_reg = box_regression.shape[1] // box_dim == 1
+
+ input_tensor_mode = proposals[0].proposal_boxes.tensor.shape[1] == box_dim + 1
+
+ proposal_boxes = proposals[0].proposal_boxes
+ if isinstance(proposal_boxes, Caffe2Boxes):
+ rois = Caffe2Boxes.cat([p.proposal_boxes for p in proposals])
+ elif isinstance(proposal_boxes, RotatedBoxes):
+ rois = RotatedBoxes.cat([p.proposal_boxes for p in proposals])
+ elif isinstance(proposal_boxes, Boxes):
+ rois = Boxes.cat([p.proposal_boxes for p in proposals])
+ else:
+ raise NotImplementedError(
+ 'Expected proposals[0].proposal_boxes to be type "Boxes", '
+ f"instead got {type(proposal_boxes)}"
+ )
+
+ device, dtype = rois.tensor.device, rois.tensor.dtype
+ if input_tensor_mode:
+ im_info = proposals[0].image_size
+ rois = rois.tensor
+ else:
+ im_info = torch.tensor(
+ [[sz[0], sz[1], 1.0] for sz in [x.image_size for x in proposals]]
+ )
+ batch_ids = cat(
+ [
+ torch.full((b, 1), i, dtype=dtype, device=device)
+ for i, b in enumerate(len(p) for p in proposals)
+ ],
+ dim=0,
+ )
+ rois = torch.cat([batch_ids, rois.tensor], dim=1)
+
+ roi_pred_bbox, roi_batch_splits = torch.ops._caffe2.BBoxTransform(
+ to_device(rois, "cpu"),
+ to_device(box_regression, "cpu"),
+ to_device(im_info, "cpu"),
+ weights=box2box_transform_weights,
+ apply_scale=True,
+ rotated=is_rotated,
+ angle_bound_on=True,
+ angle_bound_lo=-180,
+ angle_bound_hi=180,
+ clip_angle_thresh=1.0,
+ legacy_plus_one=False,
+ )
+ roi_pred_bbox = to_device(roi_pred_bbox, device)
+ roi_batch_splits = to_device(roi_batch_splits, device)
+
+ nms_outputs = torch.ops._caffe2.BoxWithNMSLimit(
+ to_device(class_prob, "cpu"),
+ to_device(roi_pred_bbox, "cpu"),
+ to_device(roi_batch_splits, "cpu"),
+ score_thresh=float(score_thresh),
+ nms=float(nms_thresh),
+ detections_per_im=int(topk_per_image),
+ soft_nms_enabled=False,
+ soft_nms_method="linear",
+ soft_nms_sigma=0.5,
+ soft_nms_min_score_thres=0.001,
+ rotated=is_rotated,
+ cls_agnostic_bbox_reg=cls_agnostic_bbox_reg,
+ input_boxes_include_bg_cls=False,
+ output_classes_include_bg_cls=False,
+ legacy_plus_one=False,
+ )
+ roi_score_nms = to_device(nms_outputs[0], device)
+ roi_bbox_nms = to_device(nms_outputs[1], device)
+ roi_class_nms = to_device(nms_outputs[2], device)
+ roi_batch_splits_nms = to_device(nms_outputs[3], device)
+ roi_keeps_nms = to_device(nms_outputs[4], device)
+ roi_keeps_size_nms = to_device(nms_outputs[5], device)
+ if not self.tensor_mode:
+ roi_class_nms = roi_class_nms.to(torch.int64)
+
+ roi_batch_ids = cat(
+ [
+ torch.full((b, 1), i, dtype=dtype, device=device)
+ for i, b in enumerate(int(x.item()) for x in roi_batch_splits_nms)
+ ],
+ dim=0,
+ )
+
+ roi_class_nms = alias(roi_class_nms, "class_nms")
+ roi_score_nms = alias(roi_score_nms, "score_nms")
+ roi_bbox_nms = alias(roi_bbox_nms, "bbox_nms")
+ roi_batch_splits_nms = alias(roi_batch_splits_nms, "batch_splits_nms")
+ roi_keeps_nms = alias(roi_keeps_nms, "keeps_nms")
+ roi_keeps_size_nms = alias(roi_keeps_size_nms, "keeps_size_nms")
+
+ results = InstancesList(
+ im_info=im_info,
+ indices=roi_batch_ids[:, 0],
+ extra_fields={
+ "pred_boxes": Caffe2Boxes(roi_bbox_nms),
+ "scores": roi_score_nms,
+ "pred_classes": roi_class_nms,
+ },
+ )
+
+ if not self.tensor_mode:
+ results = InstancesList.to_d2_instances_list(results)
+ batch_splits = roi_batch_splits_nms.int().tolist()
+ kept_indices = list(roi_keeps_nms.to(torch.int64).split(batch_splits))
+ else:
+ results = [results]
+ kept_indices = [roi_keeps_nms]
+
+ return results, kept_indices
+
+
+class Caffe2MaskRCNNInference:
+ def __call__(self, pred_mask_logits, pred_instances):
+ """equivalent to mask_head.mask_rcnn_inference"""
+ if all(isinstance(x, InstancesList) for x in pred_instances):
+ assert len(pred_instances) == 1
+ mask_probs_pred = pred_mask_logits.sigmoid()
+ mask_probs_pred = alias(mask_probs_pred, "mask_fcn_probs")
+ pred_instances[0].set("pred_masks", mask_probs_pred)
+ else:
+ mask_rcnn_inference(pred_mask_logits, pred_instances)
+
+
+class Caffe2KeypointRCNNInference:
+ def __init__(self, use_heatmap_max_keypoint):
+ self.use_heatmap_max_keypoint = use_heatmap_max_keypoint
+
+ def __call__(self, pred_keypoint_logits, pred_instances):
+ # just return the keypoint heatmap for now,
+ # there will be option to call HeatmapMaxKeypointOp
+ output = alias(pred_keypoint_logits, "kps_score")
+ if all(isinstance(x, InstancesList) for x in pred_instances):
+ assert len(pred_instances) == 1
+ if self.use_heatmap_max_keypoint:
+ device = output.device
+ output = torch.ops._caffe2.HeatmapMaxKeypoint(
+ to_device(output, "cpu"),
+ pred_instances[0].pred_boxes.tensor,
+ should_output_softmax=True, # worth make it configerable?
+ )
+ output = to_device(output, device)
+ output = alias(output, "keypoints_out")
+ pred_instances[0].set("pred_keypoints", output)
+ return pred_keypoint_logits
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/caffe2_export.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/caffe2_export.py
new file mode 100644
index 0000000000000000000000000000000000000000..d609c27c7deb396352967dbcbc79b1e00f2a2de1
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/caffe2_export.py
@@ -0,0 +1,203 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import copy
+import io
+import logging
+import numpy as np
+from typing import List
+import onnx
+import onnx.optimizer
+import torch
+from caffe2.proto import caffe2_pb2
+from caffe2.python import core
+from caffe2.python.onnx.backend import Caffe2Backend
+from tabulate import tabulate
+from termcolor import colored
+from torch.onnx import OperatorExportTypes
+
+from .shared import (
+ ScopedWS,
+ construct_init_net_from_params,
+ fuse_alias_placeholder,
+ fuse_copy_between_cpu_and_gpu,
+ get_params_from_init_net,
+ group_norm_replace_aten_with_caffe2,
+ infer_device_type,
+ remove_dead_end_ops,
+ remove_reshape_for_fc,
+ save_graph,
+)
+
+logger = logging.getLogger(__name__)
+
+
+def export_onnx_model(model, inputs):
+ """
+ Trace and export a model to onnx format.
+
+ Args:
+ model (nn.Module):
+ inputs (tuple[args]): the model will be called by `model(*inputs)`
+
+ Returns:
+ an onnx model
+ """
+ assert isinstance(model, torch.nn.Module)
+
+ # make sure all modules are in eval mode, onnx may change the training state
+ # of the module if the states are not consistent
+ def _check_eval(module):
+ assert not module.training
+
+ model.apply(_check_eval)
+
+ # Export the model to ONNX
+ with torch.no_grad():
+ with io.BytesIO() as f:
+ torch.onnx.export(
+ model,
+ inputs,
+ f,
+ operator_export_type=OperatorExportTypes.ONNX_ATEN_FALLBACK,
+ # verbose=True, # NOTE: uncomment this for debugging
+ # export_params=True,
+ )
+ onnx_model = onnx.load_from_string(f.getvalue())
+
+ return onnx_model
+
+
+def _op_stats(net_def):
+ type_count = {}
+ for t in [op.type for op in net_def.op]:
+ type_count[t] = type_count.get(t, 0) + 1
+ type_count_list = sorted(type_count.items(), key=lambda kv: kv[0]) # alphabet
+ type_count_list = sorted(type_count_list, key=lambda kv: -kv[1]) # count
+ return "\n".join("{:>4}x {}".format(count, name) for name, count in type_count_list)
+
+
+def _assign_device_option(
+ predict_net: caffe2_pb2.NetDef, init_net: caffe2_pb2.NetDef, tensor_inputs: List[torch.Tensor]
+):
+ """
+ ONNX exported network doesn't have concept of device, assign necessary
+ device option for each op in order to make it runable on GPU runtime.
+ """
+
+ def _get_device_type(torch_tensor):
+ assert torch_tensor.device.type in ["cpu", "cuda"]
+ assert torch_tensor.device.index == 0
+ return torch_tensor.device.type
+
+ def _assign_op_device_option(net_proto, net_ssa, blob_device_types):
+ for op, ssa_i in zip(net_proto.op, net_ssa):
+ if op.type in ["CopyCPUToGPU", "CopyGPUToCPU"]:
+ op.device_option.CopyFrom(core.DeviceOption(caffe2_pb2.CUDA, 0))
+ else:
+ devices = [blob_device_types[b] for b in ssa_i[0] + ssa_i[1]]
+ assert all(d == devices[0] for d in devices)
+ if devices[0] == "cuda":
+ op.device_option.CopyFrom(core.DeviceOption(caffe2_pb2.CUDA, 0))
+
+ # update ops in predict_net
+ predict_net_input_device_types = {
+ (name, 0): _get_device_type(tensor)
+ for name, tensor in zip(predict_net.external_input, tensor_inputs)
+ }
+ predict_net_device_types = infer_device_type(
+ predict_net, known_status=predict_net_input_device_types, device_name_style="pytorch"
+ )
+ predict_net_ssa, _ = core.get_ssa(predict_net)
+ _assign_op_device_option(predict_net, predict_net_ssa, predict_net_device_types)
+
+ # update ops in init_net
+ init_net_ssa, versions = core.get_ssa(init_net)
+ init_net_output_device_types = {
+ (name, versions[name]): predict_net_device_types[(name, 0)]
+ for name in init_net.external_output
+ }
+ init_net_device_types = infer_device_type(
+ init_net, known_status=init_net_output_device_types, device_name_style="pytorch"
+ )
+ _assign_op_device_option(init_net, init_net_ssa, init_net_device_types)
+
+
+def export_caffe2_detection_model(model: torch.nn.Module, tensor_inputs: List[torch.Tensor]):
+ """
+ Export a caffe2-compatible Detectron2 model to caffe2 format via ONNX.
+
+ Arg:
+ model: a caffe2-compatible version of detectron2 model, defined in caffe2_modeling.py
+ tensor_inputs: a list of tensors that caffe2 model takes as input.
+ """
+ model = copy.deepcopy(model)
+ assert isinstance(model, torch.nn.Module)
+ assert hasattr(model, "encode_additional_info")
+
+ # Export via ONNX
+ logger.info(
+ "Exporting a {} model via ONNX ...".format(type(model).__name__)
+ + " Some warnings from ONNX are expected and are usually not to worry about."
+ )
+ onnx_model = export_onnx_model(model, (tensor_inputs,))
+ # Convert ONNX model to Caffe2 protobuf
+ init_net, predict_net = Caffe2Backend.onnx_graph_to_caffe2_net(onnx_model)
+ ops_table = [[op.type, op.input, op.output] for op in predict_net.op]
+ table = tabulate(ops_table, headers=["type", "input", "output"], tablefmt="pipe")
+ logger.info(
+ "ONNX export Done. Exported predict_net (before optimizations):\n" + colored(table, "cyan")
+ )
+
+ # Apply protobuf optimization
+ fuse_alias_placeholder(predict_net, init_net)
+ if any(t.device.type != "cpu" for t in tensor_inputs):
+ fuse_copy_between_cpu_and_gpu(predict_net)
+ remove_dead_end_ops(init_net)
+ _assign_device_option(predict_net, init_net, tensor_inputs)
+ params, device_options = get_params_from_init_net(init_net)
+ predict_net, params = remove_reshape_for_fc(predict_net, params)
+ init_net = construct_init_net_from_params(params, device_options)
+ group_norm_replace_aten_with_caffe2(predict_net)
+
+ # Record necessary information for running the pb model in Detectron2 system.
+ model.encode_additional_info(predict_net, init_net)
+
+ logger.info("Operators used in predict_net: \n{}".format(_op_stats(predict_net)))
+ logger.info("Operators used in init_net: \n{}".format(_op_stats(init_net)))
+
+ return predict_net, init_net
+
+
+def run_and_save_graph(predict_net, init_net, tensor_inputs, graph_save_path):
+ """
+ Run the caffe2 model on given inputs, recording the shape and draw the graph.
+
+ predict_net/init_net: caffe2 model.
+ tensor_inputs: a list of tensors that caffe2 model takes as input.
+ graph_save_path: path for saving graph of exported model.
+ """
+
+ logger.info("Saving graph of ONNX exported model to {} ...".format(graph_save_path))
+ save_graph(predict_net, graph_save_path, op_only=False)
+
+ # Run the exported Caffe2 net
+ logger.info("Running ONNX exported model ...")
+ with ScopedWS("__ws_tmp__", True) as ws:
+ ws.RunNetOnce(init_net)
+ initialized_blobs = set(ws.Blobs())
+ uninitialized = [inp for inp in predict_net.external_input if inp not in initialized_blobs]
+ for name, blob in zip(uninitialized, tensor_inputs):
+ ws.FeedBlob(name, blob)
+
+ try:
+ ws.RunNetOnce(predict_net)
+ except RuntimeError as e:
+ logger.warning("Encountered RuntimeError: \n{}".format(str(e)))
+
+ ws_blobs = {b: ws.FetchBlob(b) for b in ws.Blobs()}
+ blob_sizes = {b: ws_blobs[b].shape for b in ws_blobs if isinstance(ws_blobs[b], np.ndarray)}
+
+ logger.info("Saving graph with blob shapes to {} ...".format(graph_save_path))
+ save_graph(predict_net, graph_save_path, op_only=False, blob_sizes=blob_sizes)
+
+ return ws_blobs
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/caffe2_inference.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/caffe2_inference.py
new file mode 100644
index 0000000000000000000000000000000000000000..deb886c0417285ed1d5ad85eb941fa1ac757cdab
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/caffe2_inference.py
@@ -0,0 +1,161 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import logging
+import numpy as np
+from itertools import count
+import torch
+from caffe2.proto import caffe2_pb2
+from caffe2.python import core
+
+from .caffe2_modeling import META_ARCH_CAFFE2_EXPORT_TYPE_MAP, convert_batched_inputs_to_c2_format
+from .shared import ScopedWS, get_pb_arg_vali, get_pb_arg_vals, infer_device_type
+
+logger = logging.getLogger(__name__)
+
+
+# ===== ref: mobile-vision predictor's 'Caffe2Wrapper' class ======
+class ProtobufModel(torch.nn.Module):
+ """
+ Wrapper of a caffe2's protobuf model.
+ It works just like nn.Module, but running caffe2 under the hood.
+ Input/Output are tuple[tensor] that match the caffe2 net's external_input/output.
+ """
+
+ _ids = count(0)
+
+ def __init__(self, predict_net, init_net):
+ logger.info(f"Initializing ProtobufModel for: {predict_net.name} ...")
+ super().__init__()
+ assert isinstance(predict_net, caffe2_pb2.NetDef)
+ assert isinstance(init_net, caffe2_pb2.NetDef)
+ # create unique temporary workspace for each instance
+ self.ws_name = "__tmp_ProtobufModel_{}__".format(next(self._ids))
+ self.net = core.Net(predict_net)
+
+ logger.info("Running init_net once to fill the parameters ...")
+ with ScopedWS(self.ws_name, is_reset=True, is_cleanup=False) as ws:
+ ws.RunNetOnce(init_net)
+ uninitialized_external_input = []
+ for blob in self.net.Proto().external_input:
+ if blob not in ws.Blobs():
+ uninitialized_external_input.append(blob)
+ ws.CreateBlob(blob)
+ ws.CreateNet(self.net)
+
+ self._error_msgs = set()
+ self._input_blobs = uninitialized_external_input
+
+ def _infer_output_devices(self, inputs):
+ """
+ Returns:
+ list[str]: list of device for each external output
+ """
+
+ def _get_device_type(torch_tensor):
+ assert torch_tensor.device.type in ["cpu", "cuda"]
+ assert torch_tensor.device.index == 0
+ return torch_tensor.device.type
+
+ predict_net = self.net.Proto()
+ input_device_types = {
+ (name, 0): _get_device_type(tensor) for name, tensor in zip(self._input_blobs, inputs)
+ }
+ device_type_map = infer_device_type(
+ predict_net, known_status=input_device_types, device_name_style="pytorch"
+ )
+ ssa, versions = core.get_ssa(predict_net)
+ versioned_outputs = [(name, versions[name]) for name in predict_net.external_output]
+ output_devices = [device_type_map[outp] for outp in versioned_outputs]
+ return output_devices
+
+ def forward(self, inputs):
+ """
+ Args:
+ inputs (tuple[torch.Tensor])
+
+ Returns:
+ tuple[torch.Tensor]
+ """
+ assert len(inputs) == len(self._input_blobs), (
+ f"Length of inputs ({len(inputs)}) "
+ f"doesn't match the required input blobs: {self._input_blobs}"
+ )
+
+ with ScopedWS(self.ws_name, is_reset=False, is_cleanup=False) as ws:
+ for b, tensor in zip(self._input_blobs, inputs):
+ ws.FeedBlob(b, tensor)
+
+ try:
+ ws.RunNet(self.net.Proto().name)
+ except RuntimeError as e:
+ if not str(e) in self._error_msgs:
+ self._error_msgs.add(str(e))
+ logger.warning("Encountered new RuntimeError: \n{}".format(str(e)))
+ logger.warning("Catch the error and use partial results.")
+
+ c2_outputs = [ws.FetchBlob(b) for b in self.net.Proto().external_output]
+ # Remove outputs of current run, this is necessary in order to
+ # prevent fetching the result from previous run if the model fails
+ # in the middle.
+ for b in self.net.Proto().external_output:
+ # Needs to create uninitialized blob to make the net runable.
+ # This is "equivalent" to: ws.RemoveBlob(b) then ws.CreateBlob(b),
+ # but there'no such API.
+ ws.FeedBlob(b, f"{b}, a C++ native class of type nullptr (uninitialized).")
+
+ # Cast output to torch.Tensor on the desired device
+ output_devices = (
+ self._infer_output_devices(inputs)
+ if any(t.device.type != "cpu" for t in inputs)
+ else ["cpu" for _ in self.net.Proto().external_output]
+ )
+
+ outputs = []
+ for name, c2_output, device in zip(
+ self.net.Proto().external_output, c2_outputs, output_devices
+ ):
+ if not isinstance(c2_output, np.ndarray):
+ raise RuntimeError(
+ "Invalid output for blob {}, received: {}".format(name, c2_output)
+ )
+ outputs.append(torch.tensor(c2_output).to(device=device))
+ return tuple(outputs)
+
+
+class ProtobufDetectionModel(torch.nn.Module):
+ """
+ A class works just like a pytorch meta arch in terms of inference, but running
+ caffe2 model under the hood.
+ """
+
+ def __init__(self, predict_net, init_net, *, convert_outputs=None):
+ """
+ Args:
+ predict_net, init_net (core.Net): caffe2 nets
+ convert_outptus (callable): a function that converts caffe2
+ outputs to the same format of the original pytorch model.
+ By default, use the one defined in the caffe2 meta_arch.
+ """
+ super().__init__()
+ self.protobuf_model = ProtobufModel(predict_net, init_net)
+ self.size_divisibility = get_pb_arg_vali(predict_net, "size_divisibility", 0)
+ self.device = get_pb_arg_vals(predict_net, "device", b"cpu").decode("ascii")
+
+ if convert_outputs is None:
+ meta_arch = get_pb_arg_vals(predict_net, "meta_architecture", b"GeneralizedRCNN")
+ meta_arch = META_ARCH_CAFFE2_EXPORT_TYPE_MAP[meta_arch.decode("ascii")]
+ self._convert_outputs = meta_arch.get_outputs_converter(predict_net, init_net)
+ else:
+ self._convert_outputs = convert_outputs
+
+ def _convert_inputs(self, batched_inputs):
+ # currently all models convert inputs in the same way
+ return convert_batched_inputs_to_c2_format(
+ batched_inputs, self.size_divisibility, self.device
+ )
+
+ def forward(self, batched_inputs):
+ c2_inputs = self._convert_inputs(batched_inputs)
+ c2_results = self.protobuf_model(c2_inputs)
+ c2_results = dict(zip(self.protobuf_model.net.Proto().external_output, c2_results))
+ return self._convert_outputs(batched_inputs, c2_inputs, c2_results)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/caffe2_modeling.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/caffe2_modeling.py
new file mode 100644
index 0000000000000000000000000000000000000000..050751370255a8986bf75d02da38536f9abe9065
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/caffe2_modeling.py
@@ -0,0 +1,419 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import functools
+import io
+import struct
+import types
+import torch
+
+from custom_detectron2.modeling import meta_arch
+from custom_detectron2.modeling.box_regression import Box2BoxTransform
+from custom_detectron2.modeling.roi_heads import keypoint_head
+from custom_detectron2.structures import Boxes, ImageList, Instances, RotatedBoxes
+
+from .c10 import Caffe2Compatible
+from .caffe2_patch import ROIHeadsPatcher, patch_generalized_rcnn
+from .shared import (
+ alias,
+ check_set_pb_arg,
+ get_pb_arg_floats,
+ get_pb_arg_valf,
+ get_pb_arg_vali,
+ get_pb_arg_vals,
+ mock_torch_nn_functional_interpolate,
+)
+
+
+def assemble_rcnn_outputs_by_name(image_sizes, tensor_outputs, force_mask_on=False):
+ """
+ A function to assemble caffe2 model's outputs (i.e. Dict[str, Tensor])
+ to detectron2's format (i.e. list of Instances instance).
+ This only works when the model follows the Caffe2 detectron's naming convention.
+
+ Args:
+ image_sizes (List[List[int, int]]): [H, W] of every image.
+ tensor_outputs (Dict[str, Tensor]): external_output to its tensor.
+
+ force_mask_on (Bool): if true, the it make sure there'll be pred_masks even
+ if the mask is not found from tensor_outputs (usually due to model crash)
+ """
+
+ results = [Instances(image_size) for image_size in image_sizes]
+
+ batch_splits = tensor_outputs.get("batch_splits", None)
+ if batch_splits:
+ raise NotImplementedError()
+ assert len(image_sizes) == 1
+ result = results[0]
+
+ bbox_nms = tensor_outputs["bbox_nms"]
+ score_nms = tensor_outputs["score_nms"]
+ class_nms = tensor_outputs["class_nms"]
+ # Detection will always success because Conv support 0-batch
+ assert bbox_nms is not None
+ assert score_nms is not None
+ assert class_nms is not None
+ if bbox_nms.shape[1] == 5:
+ result.pred_boxes = RotatedBoxes(bbox_nms)
+ else:
+ result.pred_boxes = Boxes(bbox_nms)
+ result.scores = score_nms
+ result.pred_classes = class_nms.to(torch.int64)
+
+ mask_fcn_probs = tensor_outputs.get("mask_fcn_probs", None)
+ if mask_fcn_probs is not None:
+ # finish the mask pred
+ mask_probs_pred = mask_fcn_probs
+ num_masks = mask_probs_pred.shape[0]
+ class_pred = result.pred_classes
+ indices = torch.arange(num_masks, device=class_pred.device)
+ mask_probs_pred = mask_probs_pred[indices, class_pred][:, None]
+ result.pred_masks = mask_probs_pred
+ elif force_mask_on:
+ # NOTE: there's no way to know the height/width of mask here, it won't be
+ # used anyway when batch size is 0, so just set them to 0.
+ result.pred_masks = torch.zeros([0, 1, 0, 0], dtype=torch.uint8)
+
+ keypoints_out = tensor_outputs.get("keypoints_out", None)
+ kps_score = tensor_outputs.get("kps_score", None)
+ if keypoints_out is not None:
+ # keypoints_out: [N, 4, #kypoints], where 4 is in order of (x, y, score, prob)
+ keypoints_tensor = keypoints_out
+ # NOTE: it's possible that prob is not calculated if "should_output_softmax"
+ # is set to False in HeatmapMaxKeypoint, so just using raw score, seems
+ # it doesn't affect mAP. TODO: check more carefully.
+ keypoint_xyp = keypoints_tensor.transpose(1, 2)[:, :, [0, 1, 2]]
+ result.pred_keypoints = keypoint_xyp
+ elif kps_score is not None:
+ # keypoint heatmap to sparse data structure
+ pred_keypoint_logits = kps_score
+ keypoint_head.keypoint_rcnn_inference(pred_keypoint_logits, [result])
+
+ return results
+
+
+def _cast_to_f32(f64):
+ return struct.unpack("f", struct.pack("f", f64))[0]
+
+
+def set_caffe2_compatible_tensor_mode(model, enable=True):
+ def _fn(m):
+ if isinstance(m, Caffe2Compatible):
+ m.tensor_mode = enable
+
+ model.apply(_fn)
+
+
+def convert_batched_inputs_to_c2_format(batched_inputs, size_divisibility, device):
+ """
+ See get_caffe2_inputs() below.
+ """
+ assert all(isinstance(x, dict) for x in batched_inputs)
+ assert all(x["image"].dim() == 3 for x in batched_inputs)
+
+ images = [x["image"] for x in batched_inputs]
+ images = ImageList.from_tensors(images, size_divisibility)
+
+ im_info = []
+ for input_per_image, image_size in zip(batched_inputs, images.image_sizes):
+ target_height = input_per_image.get("height", image_size[0])
+ target_width = input_per_image.get("width", image_size[1]) # noqa
+ # NOTE: The scale inside im_info is kept as convention and for providing
+ # post-processing information if further processing is needed. For
+ # current Caffe2 model definitions that don't include post-processing inside
+ # the model, this number is not used.
+ # NOTE: There can be a slight difference between width and height
+ # scales, using a single number can results in numerical difference
+ # compared with D2's post-processing.
+ scale = target_height / image_size[0]
+ im_info.append([image_size[0], image_size[1], scale])
+ im_info = torch.Tensor(im_info)
+
+ return images.tensor.to(device), im_info.to(device)
+
+
+class Caffe2MetaArch(Caffe2Compatible, torch.nn.Module):
+ """
+ Base class for caffe2-compatible implementation of a meta architecture.
+ The forward is traceable and its traced graph can be converted to caffe2
+ graph through ONNX.
+ """
+
+ def __init__(self, cfg, torch_model):
+ """
+ Args:
+ cfg (CfgNode):
+ torch_model (nn.Module): the detectron2 model (meta_arch) to be
+ converted.
+ """
+ super().__init__()
+ self._wrapped_model = torch_model
+ self.eval()
+ set_caffe2_compatible_tensor_mode(self, True)
+
+ def get_caffe2_inputs(self, batched_inputs):
+ """
+ Convert pytorch-style structured inputs to caffe2-style inputs that
+ are tuples of tensors.
+
+ Args:
+ batched_inputs (list[dict]): inputs to a detectron2 model
+ in its standard format. Each dict has "image" (CHW tensor), and optionally
+ "height" and "width".
+
+ Returns:
+ tuple[Tensor]:
+ tuple of tensors that will be the inputs to the
+ :meth:`forward` method. For existing models, the first
+ is an NCHW tensor (padded and batched); the second is
+ a im_info Nx3 tensor, where the rows are
+ (height, width, unused legacy parameter)
+ """
+ return convert_batched_inputs_to_c2_format(
+ batched_inputs,
+ self._wrapped_model.backbone.size_divisibility,
+ self._wrapped_model.device,
+ )
+
+ def encode_additional_info(self, predict_net, init_net):
+ """
+ Save extra metadata that will be used by inference in the output protobuf.
+ """
+ pass
+
+ def forward(self, inputs):
+ """
+ Run the forward in caffe2-style. It has to use caffe2-compatible ops
+ and the method will be used for tracing.
+
+ Args:
+ inputs (tuple[Tensor]): inputs defined by :meth:`get_caffe2_input`.
+ They will be the inputs of the converted caffe2 graph.
+
+ Returns:
+ tuple[Tensor]: output tensors. They will be the outputs of the
+ converted caffe2 graph.
+ """
+ raise NotImplementedError
+
+ def _caffe2_preprocess_image(self, inputs):
+ """
+ Caffe2 implementation of preprocess_image, which is called inside each MetaArch's forward.
+ It normalizes the input images, and the final caffe2 graph assumes the
+ inputs have been batched already.
+ """
+ data, im_info = inputs
+ data = alias(data, "data")
+ im_info = alias(im_info, "im_info")
+ mean, std = self._wrapped_model.pixel_mean, self._wrapped_model.pixel_std
+ normalized_data = (data - mean) / std
+ normalized_data = alias(normalized_data, "normalized_data")
+
+ # Pack (data, im_info) into ImageList which is recognized by self.inference.
+ images = ImageList(tensor=normalized_data, image_sizes=im_info)
+ return images
+
+ @staticmethod
+ def get_outputs_converter(predict_net, init_net):
+ """
+ Creates a function that converts outputs of the caffe2 model to
+ detectron2's standard format.
+ The function uses information in `predict_net` and `init_net` that are
+ available at inferene time. Therefore the function logic can be used in inference.
+
+ The returned function has the following signature:
+
+ def convert(batched_inputs, c2_inputs, c2_results) -> detectron2_outputs
+
+ Where
+
+ * batched_inputs (list[dict]): the original input format of the meta arch
+ * c2_inputs (tuple[Tensor]): the caffe2 inputs.
+ * c2_results (dict[str, Tensor]): the caffe2 output format,
+ corresponding to the outputs of the :meth:`forward` function.
+ * detectron2_outputs: the original output format of the meta arch.
+
+ This function can be used to compare the outputs of the original meta arch and
+ the converted caffe2 graph.
+
+ Returns:
+ callable: a callable of the above signature.
+ """
+ raise NotImplementedError
+
+
+class Caffe2GeneralizedRCNN(Caffe2MetaArch):
+ def __init__(self, cfg, torch_model):
+ assert isinstance(torch_model, meta_arch.GeneralizedRCNN)
+ torch_model = patch_generalized_rcnn(torch_model)
+ super().__init__(cfg, torch_model)
+
+ try:
+ use_heatmap_max_keypoint = cfg.EXPORT_CAFFE2.USE_HEATMAP_MAX_KEYPOINT
+ except AttributeError:
+ use_heatmap_max_keypoint = False
+ self.roi_heads_patcher = ROIHeadsPatcher(
+ self._wrapped_model.roi_heads, use_heatmap_max_keypoint
+ )
+
+ def encode_additional_info(self, predict_net, init_net):
+ size_divisibility = self._wrapped_model.backbone.size_divisibility
+ check_set_pb_arg(predict_net, "size_divisibility", "i", size_divisibility)
+ check_set_pb_arg(
+ predict_net, "device", "s", str.encode(str(self._wrapped_model.device), "ascii")
+ )
+ check_set_pb_arg(predict_net, "meta_architecture", "s", b"GeneralizedRCNN")
+
+ @mock_torch_nn_functional_interpolate()
+ def forward(self, inputs):
+ if not self.tensor_mode:
+ return self._wrapped_model.inference(inputs)
+ images = self._caffe2_preprocess_image(inputs)
+ features = self._wrapped_model.backbone(images.tensor)
+ proposals, _ = self._wrapped_model.proposal_generator(images, features)
+ with self.roi_heads_patcher.mock_roi_heads():
+ detector_results, _ = self._wrapped_model.roi_heads(images, features, proposals)
+ return tuple(detector_results[0].flatten())
+
+ @staticmethod
+ def get_outputs_converter(predict_net, init_net):
+ def f(batched_inputs, c2_inputs, c2_results):
+ _, im_info = c2_inputs
+ image_sizes = [[int(im[0]), int(im[1])] for im in im_info]
+ results = assemble_rcnn_outputs_by_name(image_sizes, c2_results)
+ return meta_arch.GeneralizedRCNN._postprocess(results, batched_inputs, image_sizes)
+
+ return f
+
+
+class Caffe2RetinaNet(Caffe2MetaArch):
+ def __init__(self, cfg, torch_model):
+ assert isinstance(torch_model, meta_arch.RetinaNet)
+ super().__init__(cfg, torch_model)
+
+ @mock_torch_nn_functional_interpolate()
+ def forward(self, inputs):
+ assert self.tensor_mode
+ images = self._caffe2_preprocess_image(inputs)
+
+ # explicitly return the images sizes to avoid removing "im_info" by ONNX
+ # since it's not used in the forward path
+ return_tensors = [images.image_sizes]
+
+ features = self._wrapped_model.backbone(images.tensor)
+ features = [features[f] for f in self._wrapped_model.head_in_features]
+ for i, feature_i in enumerate(features):
+ features[i] = alias(feature_i, "feature_{}".format(i), is_backward=True)
+ return_tensors.append(features[i])
+
+ pred_logits, pred_anchor_deltas = self._wrapped_model.head(features)
+ for i, (box_cls_i, box_delta_i) in enumerate(zip(pred_logits, pred_anchor_deltas)):
+ return_tensors.append(alias(box_cls_i, "box_cls_{}".format(i)))
+ return_tensors.append(alias(box_delta_i, "box_delta_{}".format(i)))
+
+ return tuple(return_tensors)
+
+ def encode_additional_info(self, predict_net, init_net):
+ size_divisibility = self._wrapped_model.backbone.size_divisibility
+ check_set_pb_arg(predict_net, "size_divisibility", "i", size_divisibility)
+ check_set_pb_arg(
+ predict_net, "device", "s", str.encode(str(self._wrapped_model.device), "ascii")
+ )
+ check_set_pb_arg(predict_net, "meta_architecture", "s", b"RetinaNet")
+
+ # Inference parameters:
+ check_set_pb_arg(
+ predict_net, "score_threshold", "f", _cast_to_f32(self._wrapped_model.test_score_thresh)
+ )
+ check_set_pb_arg(
+ predict_net, "topk_candidates", "i", self._wrapped_model.test_topk_candidates
+ )
+ check_set_pb_arg(
+ predict_net, "nms_threshold", "f", _cast_to_f32(self._wrapped_model.test_nms_thresh)
+ )
+ check_set_pb_arg(
+ predict_net,
+ "max_detections_per_image",
+ "i",
+ self._wrapped_model.max_detections_per_image,
+ )
+
+ check_set_pb_arg(
+ predict_net,
+ "bbox_reg_weights",
+ "floats",
+ [_cast_to_f32(w) for w in self._wrapped_model.box2box_transform.weights],
+ )
+ self._encode_anchor_generator_cfg(predict_net)
+
+ def _encode_anchor_generator_cfg(self, predict_net):
+ # serialize anchor_generator for future use
+ serialized_anchor_generator = io.BytesIO()
+ torch.save(self._wrapped_model.anchor_generator, serialized_anchor_generator)
+ # Ideally we can put anchor generating inside the model, then we don't
+ # need to store this information.
+ bytes = serialized_anchor_generator.getvalue()
+ check_set_pb_arg(predict_net, "serialized_anchor_generator", "s", bytes)
+
+ @staticmethod
+ def get_outputs_converter(predict_net, init_net):
+ self = types.SimpleNamespace()
+ serialized_anchor_generator = io.BytesIO(
+ get_pb_arg_vals(predict_net, "serialized_anchor_generator", None)
+ )
+ self.anchor_generator = torch.load(serialized_anchor_generator)
+ bbox_reg_weights = get_pb_arg_floats(predict_net, "bbox_reg_weights", None)
+ self.box2box_transform = Box2BoxTransform(weights=tuple(bbox_reg_weights))
+ self.test_score_thresh = get_pb_arg_valf(predict_net, "score_threshold", None)
+ self.test_topk_candidates = get_pb_arg_vali(predict_net, "topk_candidates", None)
+ self.test_nms_thresh = get_pb_arg_valf(predict_net, "nms_threshold", None)
+ self.max_detections_per_image = get_pb_arg_vali(
+ predict_net, "max_detections_per_image", None
+ )
+
+ # hack to reuse inference code from RetinaNet
+ for meth in [
+ "forward_inference",
+ "inference_single_image",
+ "_transpose_dense_predictions",
+ "_decode_multi_level_predictions",
+ "_decode_per_level_predictions",
+ ]:
+ setattr(self, meth, functools.partial(getattr(meta_arch.RetinaNet, meth), self))
+
+ def f(batched_inputs, c2_inputs, c2_results):
+ _, im_info = c2_inputs
+ image_sizes = [[int(im[0]), int(im[1])] for im in im_info]
+ dummy_images = ImageList(
+ torch.randn(
+ (
+ len(im_info),
+ 3,
+ )
+ + tuple(image_sizes[0])
+ ),
+ image_sizes,
+ )
+
+ num_features = len([x for x in c2_results.keys() if x.startswith("box_cls_")])
+ pred_logits = [c2_results["box_cls_{}".format(i)] for i in range(num_features)]
+ pred_anchor_deltas = [c2_results["box_delta_{}".format(i)] for i in range(num_features)]
+
+ # For each feature level, feature should have the same batch size and
+ # spatial dimension as the box_cls and box_delta.
+ dummy_features = [x.clone()[:, 0:0, :, :] for x in pred_logits]
+ # self.num_classess can be inferred
+ self.num_classes = pred_logits[0].shape[1] // (pred_anchor_deltas[0].shape[1] // 4)
+
+ results = self.forward_inference(
+ dummy_images, dummy_features, [pred_logits, pred_anchor_deltas]
+ )
+ return meta_arch.GeneralizedRCNN._postprocess(results, batched_inputs, image_sizes)
+
+ return f
+
+
+META_ARCH_CAFFE2_EXPORT_TYPE_MAP = {
+ "GeneralizedRCNN": Caffe2GeneralizedRCNN,
+ "RetinaNet": Caffe2RetinaNet,
+}
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/caffe2_patch.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/caffe2_patch.py
new file mode 100644
index 0000000000000000000000000000000000000000..40d50429be5666006a760a3add98981a3d9b78c4
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/caffe2_patch.py
@@ -0,0 +1,152 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import contextlib
+from unittest import mock
+import torch
+
+from custom_detectron2.modeling import poolers
+from custom_detectron2.modeling.proposal_generator import rpn
+from custom_detectron2.modeling.roi_heads import keypoint_head, mask_head
+from custom_detectron2.modeling.roi_heads.fast_rcnn import FastRCNNOutputLayers
+
+from .c10 import (
+ Caffe2Compatible,
+ Caffe2FastRCNNOutputsInference,
+ Caffe2KeypointRCNNInference,
+ Caffe2MaskRCNNInference,
+ Caffe2ROIPooler,
+ Caffe2RPN,
+)
+
+
+class GenericMixin(object):
+ pass
+
+
+class Caffe2CompatibleConverter(object):
+ """
+ A GenericUpdater which implements the `create_from` interface, by modifying
+ module object and assign it with another class replaceCls.
+ """
+
+ def __init__(self, replaceCls):
+ self.replaceCls = replaceCls
+
+ def create_from(self, module):
+ # update module's class to the new class
+ assert isinstance(module, torch.nn.Module)
+ if issubclass(self.replaceCls, GenericMixin):
+ # replaceCls should act as mixin, create a new class on-the-fly
+ new_class = type(
+ "{}MixedWith{}".format(self.replaceCls.__name__, module.__class__.__name__),
+ (self.replaceCls, module.__class__),
+ {}, # {"new_method": lambda self: ...},
+ )
+ module.__class__ = new_class
+ else:
+ # replaceCls is complete class, this allow arbitrary class swap
+ module.__class__ = self.replaceCls
+
+ # initialize Caffe2Compatible
+ if isinstance(module, Caffe2Compatible):
+ module.tensor_mode = False
+
+ return module
+
+
+def patch(model, target, updater, *args, **kwargs):
+ """
+ recursively (post-order) update all modules with the target type and its
+ subclasses, make a initialization/composition/inheritance/... via the
+ updater.create_from.
+ """
+ for name, module in model.named_children():
+ model._modules[name] = patch(module, target, updater, *args, **kwargs)
+ if isinstance(model, target):
+ return updater.create_from(model, *args, **kwargs)
+ return model
+
+
+def patch_generalized_rcnn(model):
+ ccc = Caffe2CompatibleConverter
+ model = patch(model, rpn.RPN, ccc(Caffe2RPN))
+ model = patch(model, poolers.ROIPooler, ccc(Caffe2ROIPooler))
+
+ return model
+
+
+@contextlib.contextmanager
+def mock_fastrcnn_outputs_inference(
+ tensor_mode, check=True, box_predictor_type=FastRCNNOutputLayers
+):
+ with mock.patch.object(
+ box_predictor_type,
+ "inference",
+ autospec=True,
+ side_effect=Caffe2FastRCNNOutputsInference(tensor_mode),
+ ) as mocked_func:
+ yield
+ if check:
+ assert mocked_func.call_count > 0
+
+
+@contextlib.contextmanager
+def mock_mask_rcnn_inference(tensor_mode, patched_module, check=True):
+ with mock.patch(
+ "{}.mask_rcnn_inference".format(patched_module), side_effect=Caffe2MaskRCNNInference()
+ ) as mocked_func:
+ yield
+ if check:
+ assert mocked_func.call_count > 0
+
+
+@contextlib.contextmanager
+def mock_keypoint_rcnn_inference(tensor_mode, patched_module, use_heatmap_max_keypoint, check=True):
+ with mock.patch(
+ "{}.keypoint_rcnn_inference".format(patched_module),
+ side_effect=Caffe2KeypointRCNNInference(use_heatmap_max_keypoint),
+ ) as mocked_func:
+ yield
+ if check:
+ assert mocked_func.call_count > 0
+
+
+class ROIHeadsPatcher:
+ def __init__(self, heads, use_heatmap_max_keypoint):
+ self.heads = heads
+ self.use_heatmap_max_keypoint = use_heatmap_max_keypoint
+
+ @contextlib.contextmanager
+ def mock_roi_heads(self, tensor_mode=True):
+ """
+ Patching several inference functions inside ROIHeads and its subclasses
+
+ Args:
+ tensor_mode (bool): whether the inputs/outputs are caffe2's tensor
+ format or not. Default to True.
+ """
+ # NOTE: this requries the `keypoint_rcnn_inference` and `mask_rcnn_inference`
+ # are called inside the same file as BaseXxxHead due to using mock.patch.
+ kpt_heads_mod = keypoint_head.BaseKeypointRCNNHead.__module__
+ mask_head_mod = mask_head.BaseMaskRCNNHead.__module__
+
+ mock_ctx_managers = [
+ mock_fastrcnn_outputs_inference(
+ tensor_mode=tensor_mode,
+ check=True,
+ box_predictor_type=type(self.heads.box_predictor),
+ )
+ ]
+ if getattr(self.heads, "keypoint_on", False):
+ mock_ctx_managers += [
+ mock_keypoint_rcnn_inference(
+ tensor_mode, kpt_heads_mod, self.use_heatmap_max_keypoint
+ )
+ ]
+ if getattr(self.heads, "mask_on", False):
+ mock_ctx_managers += [mock_mask_rcnn_inference(tensor_mode, mask_head_mod)]
+
+ with contextlib.ExitStack() as stack: # python 3.3+
+ for mgr in mock_ctx_managers:
+ stack.enter_context(mgr)
+ yield
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/flatten.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/flatten.py
new file mode 100644
index 0000000000000000000000000000000000000000..36c757b82ff1b2a106725c14ae959f08f035b6ba
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/flatten.py
@@ -0,0 +1,330 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import collections
+from dataclasses import dataclass
+from typing import Callable, List, Optional, Tuple
+import torch
+from torch import nn
+
+from custom_detectron2.structures import Boxes, Instances, ROIMasks
+from custom_detectron2.utils.registry import _convert_target_to_string, locate
+
+from .torchscript_patch import patch_builtin_len
+
+
+@dataclass
+class Schema:
+ """
+ A Schema defines how to flatten a possibly hierarchical object into tuple of
+ primitive objects, so it can be used as inputs/outputs of PyTorch's tracing.
+
+ PyTorch does not support tracing a function that produces rich output
+ structures (e.g. dict, Instances, Boxes). To trace such a function, we
+ flatten the rich object into tuple of tensors, and return this tuple of tensors
+ instead. Meanwhile, we also need to know how to "rebuild" the original object
+ from the flattened results, so we can evaluate the flattened results.
+ A Schema defines how to flatten an object, and while flattening it, it records
+ necessary schemas so that the object can be rebuilt using the flattened outputs.
+
+ The flattened object and the schema object is returned by ``.flatten`` classmethod.
+ Then the original object can be rebuilt with the ``__call__`` method of schema.
+
+ A Schema is a dataclass that can be serialized easily.
+ """
+
+ # inspired by FetchMapper in tensorflow/python/client/session.py
+
+ @classmethod
+ def flatten(cls, obj):
+ raise NotImplementedError
+
+ def __call__(self, values):
+ raise NotImplementedError
+
+ @staticmethod
+ def _concat(values):
+ ret = ()
+ sizes = []
+ for v in values:
+ assert isinstance(v, tuple), "Flattened results must be a tuple"
+ ret = ret + v
+ sizes.append(len(v))
+ return ret, sizes
+
+ @staticmethod
+ def _split(values, sizes):
+ if len(sizes):
+ expected_len = sum(sizes)
+ assert (
+ len(values) == expected_len
+ ), f"Values has length {len(values)} but expect length {expected_len}."
+ ret = []
+ for k in range(len(sizes)):
+ begin, end = sum(sizes[:k]), sum(sizes[: k + 1])
+ ret.append(values[begin:end])
+ return ret
+
+
+@dataclass
+class ListSchema(Schema):
+ schemas: List[Schema] # the schemas that define how to flatten each element in the list
+ sizes: List[int] # the flattened length of each element
+
+ def __call__(self, values):
+ values = self._split(values, self.sizes)
+ if len(values) != len(self.schemas):
+ raise ValueError(
+ f"Values has length {len(values)} but schemas " f"has length {len(self.schemas)}!"
+ )
+ values = [m(v) for m, v in zip(self.schemas, values)]
+ return list(values)
+
+ @classmethod
+ def flatten(cls, obj):
+ res = [flatten_to_tuple(k) for k in obj]
+ values, sizes = cls._concat([k[0] for k in res])
+ return values, cls([k[1] for k in res], sizes)
+
+
+@dataclass
+class TupleSchema(ListSchema):
+ def __call__(self, values):
+ return tuple(super().__call__(values))
+
+
+@dataclass
+class IdentitySchema(Schema):
+ def __call__(self, values):
+ return values[0]
+
+ @classmethod
+ def flatten(cls, obj):
+ return (obj,), cls()
+
+
+@dataclass
+class DictSchema(ListSchema):
+ keys: List[str]
+
+ def __call__(self, values):
+ values = super().__call__(values)
+ return dict(zip(self.keys, values))
+
+ @classmethod
+ def flatten(cls, obj):
+ for k in obj.keys():
+ if not isinstance(k, str):
+ raise KeyError("Only support flattening dictionaries if keys are str.")
+ keys = sorted(obj.keys())
+ values = [obj[k] for k in keys]
+ ret, schema = ListSchema.flatten(values)
+ return ret, cls(schema.schemas, schema.sizes, keys)
+
+
+@dataclass
+class InstancesSchema(DictSchema):
+ def __call__(self, values):
+ image_size, fields = values[-1], values[:-1]
+ fields = super().__call__(fields)
+ return Instances(image_size, **fields)
+
+ @classmethod
+ def flatten(cls, obj):
+ ret, schema = super().flatten(obj.get_fields())
+ size = obj.image_size
+ if not isinstance(size, torch.Tensor):
+ size = torch.tensor(size)
+ return ret + (size,), schema
+
+
+@dataclass
+class TensorWrapSchema(Schema):
+ """
+ For classes that are simple wrapper of tensors, e.g.
+ Boxes, RotatedBoxes, BitMasks
+ """
+
+ class_name: str
+
+ def __call__(self, values):
+ return locate(self.class_name)(values[0])
+
+ @classmethod
+ def flatten(cls, obj):
+ return (obj.tensor,), cls(_convert_target_to_string(type(obj)))
+
+
+# if more custom structures needed in the future, can allow
+# passing in extra schemas for custom types
+def flatten_to_tuple(obj):
+ """
+ Flatten an object so it can be used for PyTorch tracing.
+ Also returns how to rebuild the original object from the flattened outputs.
+
+ Returns:
+ res (tuple): the flattened results that can be used as tracing outputs
+ schema: an object with a ``__call__`` method such that ``schema(res) == obj``.
+ It is a pure dataclass that can be serialized.
+ """
+ schemas = [
+ ((str, bytes), IdentitySchema),
+ (list, ListSchema),
+ (tuple, TupleSchema),
+ (collections.abc.Mapping, DictSchema),
+ (Instances, InstancesSchema),
+ ((Boxes, ROIMasks), TensorWrapSchema),
+ ]
+ for klass, schema in schemas:
+ if isinstance(obj, klass):
+ F = schema
+ break
+ else:
+ F = IdentitySchema
+
+ return F.flatten(obj)
+
+
+class TracingAdapter(nn.Module):
+ """
+ A model may take rich input/output format (e.g. dict or custom classes),
+ but `torch.jit.trace` requires tuple of tensors as input/output.
+ This adapter flattens input/output format of a model so it becomes traceable.
+
+ It also records the necessary schema to rebuild model's inputs/outputs from flattened
+ inputs/outputs.
+
+ Example:
+ ::
+ outputs = model(inputs) # inputs/outputs may be rich structure
+ adapter = TracingAdapter(model, inputs)
+
+ # can now trace the model, with adapter.flattened_inputs, or another
+ # tuple of tensors with the same length and meaning
+ traced = torch.jit.trace(adapter, adapter.flattened_inputs)
+
+ # traced model can only produce flattened outputs (tuple of tensors)
+ flattened_outputs = traced(*adapter.flattened_inputs)
+ # adapter knows the schema to convert it back (new_outputs == outputs)
+ new_outputs = adapter.outputs_schema(flattened_outputs)
+ """
+
+ flattened_inputs: Tuple[torch.Tensor] = None
+ """
+ Flattened version of inputs given to this class's constructor.
+ """
+
+ inputs_schema: Schema = None
+ """
+ Schema of the inputs given to this class's constructor.
+ """
+
+ outputs_schema: Schema = None
+ """
+ Schema of the output produced by calling the given model with inputs.
+ """
+
+ def __init__(
+ self,
+ model: nn.Module,
+ inputs,
+ inference_func: Optional[Callable] = None,
+ allow_non_tensor: bool = False,
+ ):
+ """
+ Args:
+ model: an nn.Module
+ inputs: An input argument or a tuple of input arguments used to call model.
+ After flattening, it has to only consist of tensors.
+ inference_func: a callable that takes (model, *inputs), calls the
+ model with inputs, and return outputs. By default it
+ is ``lambda model, *inputs: model(*inputs)``. Can be override
+ if you need to call the model differently.
+ allow_non_tensor: allow inputs/outputs to contain non-tensor objects.
+ This option will filter out non-tensor objects to make the
+ model traceable, but ``inputs_schema``/``outputs_schema`` cannot be
+ used anymore because inputs/outputs cannot be rebuilt from pure tensors.
+ This is useful when you're only interested in the single trace of
+ execution (e.g. for flop count), but not interested in
+ generalizing the traced graph to new inputs.
+ """
+ super().__init__()
+ if isinstance(model, (nn.parallel.distributed.DistributedDataParallel, nn.DataParallel)):
+ model = model.module
+ self.model = model
+ if not isinstance(inputs, tuple):
+ inputs = (inputs,)
+ self.inputs = inputs
+ self.allow_non_tensor = allow_non_tensor
+
+ if inference_func is None:
+ inference_func = lambda model, *inputs: model(*inputs) # noqa
+ self.inference_func = inference_func
+
+ self.flattened_inputs, self.inputs_schema = flatten_to_tuple(inputs)
+
+ if all(isinstance(x, torch.Tensor) for x in self.flattened_inputs):
+ return
+ if self.allow_non_tensor:
+ self.flattened_inputs = tuple(
+ [x for x in self.flattened_inputs if isinstance(x, torch.Tensor)]
+ )
+ self.inputs_schema = None
+ else:
+ for input in self.flattened_inputs:
+ if not isinstance(input, torch.Tensor):
+ raise ValueError(
+ "Inputs for tracing must only contain tensors. "
+ f"Got a {type(input)} instead."
+ )
+
+ def forward(self, *args: torch.Tensor):
+ with torch.no_grad(), patch_builtin_len():
+ if self.inputs_schema is not None:
+ inputs_orig_format = self.inputs_schema(args)
+ else:
+ if len(args) != len(self.flattened_inputs) or any(
+ x is not y for x, y in zip(args, self.flattened_inputs)
+ ):
+ raise ValueError(
+ "TracingAdapter does not contain valid inputs_schema."
+ " So it cannot generalize to other inputs and must be"
+ " traced with `.flattened_inputs`."
+ )
+ inputs_orig_format = self.inputs
+
+ outputs = self.inference_func(self.model, *inputs_orig_format)
+ flattened_outputs, schema = flatten_to_tuple(outputs)
+
+ flattened_output_tensors = tuple(
+ [x for x in flattened_outputs if isinstance(x, torch.Tensor)]
+ )
+ if len(flattened_output_tensors) < len(flattened_outputs):
+ if self.allow_non_tensor:
+ flattened_outputs = flattened_output_tensors
+ self.outputs_schema = None
+ else:
+ raise ValueError(
+ "Model cannot be traced because some model outputs "
+ "cannot flatten to tensors."
+ )
+ else: # schema is valid
+ if self.outputs_schema is None:
+ self.outputs_schema = schema
+ else:
+ assert self.outputs_schema == schema, (
+ "Model should always return outputs with the same "
+ "structure so it can be traced!"
+ )
+ return flattened_outputs
+
+ def _create_wrapper(self, traced_model):
+ """
+ Return a function that has an input/output interface the same as the
+ original model, but it calls the given traced model under the hood.
+ """
+
+ def forward(*args):
+ flattened_inputs, _ = flatten_to_tuple(args)
+ flattened_outputs = traced_model(*flattened_inputs)
+ return self.outputs_schema(flattened_outputs)
+
+ return forward
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/shared.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/shared.py
new file mode 100644
index 0000000000000000000000000000000000000000..53ba9335e26819f9381115eba17bbbe3816b469c
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/shared.py
@@ -0,0 +1,1039 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import collections
+import copy
+import functools
+import logging
+import numpy as np
+import os
+from typing import Any, Callable, Dict, List, Optional, Tuple, Union
+from unittest import mock
+import caffe2.python.utils as putils
+import torch
+import torch.nn.functional as F
+from caffe2.proto import caffe2_pb2
+from caffe2.python import core, net_drawer, workspace
+from torch.nn.functional import interpolate as interp
+
+logger = logging.getLogger(__name__)
+
+
+# ==== torch/utils_toffee/cast.py =======================================
+
+
+def to_device(t, device_str):
+ """
+ This function is a replacement of .to(another_device) such that it allows the
+ casting to be traced properly by explicitly calling the underlying copy ops.
+ It also avoids introducing unncessary op when casting to the same device.
+ """
+ src = t.device
+ dst = torch.device(device_str)
+
+ if src == dst:
+ return t
+ elif src.type == "cuda" and dst.type == "cpu":
+ return torch.ops._caffe2.CopyGPUToCPU(t)
+ elif src.type == "cpu" and dst.type == "cuda":
+ return torch.ops._caffe2.CopyCPUToGPU(t)
+ else:
+ raise RuntimeError("Can't cast tensor from device {} to device {}".format(src, dst))
+
+
+# ==== torch/utils_toffee/interpolate.py =======================================
+
+
+# Note: borrowed from vision/detection/fair/detectron/detectron/modeling/detector.py
+def BilinearInterpolation(tensor_in, up_scale):
+ assert up_scale % 2 == 0, "Scale should be even"
+
+ def upsample_filt(size):
+ factor = (size + 1) // 2
+ if size % 2 == 1:
+ center = factor - 1
+ else:
+ center = factor - 0.5
+
+ og = np.ogrid[:size, :size]
+ return (1 - abs(og[0] - center) / factor) * (1 - abs(og[1] - center) / factor)
+
+ kernel_size = int(up_scale) * 2
+ bil_filt = upsample_filt(kernel_size)
+
+ dim = int(tensor_in.shape[1])
+ kernel = np.zeros((dim, dim, kernel_size, kernel_size), dtype=np.float32)
+ kernel[range(dim), range(dim), :, :] = bil_filt
+
+ tensor_out = F.conv_transpose2d(
+ tensor_in,
+ weight=to_device(torch.Tensor(kernel), tensor_in.device),
+ bias=None,
+ stride=int(up_scale),
+ padding=int(up_scale / 2),
+ )
+
+ return tensor_out
+
+
+# NOTE: ONNX is incompatible with traced torch.nn.functional.interpolate if
+# using dynamic `scale_factor` rather than static `size`. (T43166860)
+# NOTE: Caffe2 Int8 conversion might not be able to quantize `size` properly.
+def onnx_compatibale_interpolate(
+ input, size=None, scale_factor=None, mode="nearest", align_corners=None
+):
+ # NOTE: The input dimensions are interpreted in the form:
+ # `mini-batch x channels x [optional depth] x [optional height] x width`.
+ if size is None and scale_factor is not None:
+ if input.dim() == 4:
+ if isinstance(scale_factor, (int, float)):
+ height_scale, width_scale = (scale_factor, scale_factor)
+ else:
+ assert isinstance(scale_factor, (tuple, list))
+ assert len(scale_factor) == 2
+ height_scale, width_scale = scale_factor
+
+ assert not align_corners, "No matching C2 op for align_corners == True"
+ if mode == "nearest":
+ return torch.ops._caffe2.ResizeNearest(
+ input, order="NCHW", width_scale=width_scale, height_scale=height_scale
+ )
+ elif mode == "bilinear":
+ logger.warning(
+ "Use F.conv_transpose2d for bilinear interpolate"
+ " because there's no such C2 op, this may cause significant"
+ " slowdown and the boundary pixels won't be as same as"
+ " using F.interpolate due to padding."
+ )
+ assert height_scale == width_scale
+ return BilinearInterpolation(input, up_scale=height_scale)
+ logger.warning("Output size is not static, it might cause ONNX conversion issue")
+
+ return interp(input, size, scale_factor, mode, align_corners)
+
+
+def mock_torch_nn_functional_interpolate():
+ def decorator(func):
+ @functools.wraps(func)
+ def _mock_torch_nn_functional_interpolate(*args, **kwargs):
+ if torch.onnx.is_in_onnx_export():
+ with mock.patch(
+ "torch.nn.functional.interpolate", side_effect=onnx_compatibale_interpolate
+ ):
+ return func(*args, **kwargs)
+ else:
+ return func(*args, **kwargs)
+
+ return _mock_torch_nn_functional_interpolate
+
+ return decorator
+
+
+# ==== torch/utils_caffe2/ws_utils.py ==========================================
+
+
+class ScopedWS(object):
+ def __init__(self, ws_name, is_reset, is_cleanup=False):
+ self.ws_name = ws_name
+ self.is_reset = is_reset
+ self.is_cleanup = is_cleanup
+ self.org_ws = ""
+
+ def __enter__(self):
+ self.org_ws = workspace.CurrentWorkspace()
+ if self.ws_name is not None:
+ workspace.SwitchWorkspace(self.ws_name, True)
+ if self.is_reset:
+ workspace.ResetWorkspace()
+
+ return workspace
+
+ def __exit__(self, *args):
+ if self.is_cleanup:
+ workspace.ResetWorkspace()
+ if self.ws_name is not None:
+ workspace.SwitchWorkspace(self.org_ws)
+
+
+def fetch_any_blob(name):
+ bb = None
+ try:
+ bb = workspace.FetchBlob(name)
+ except TypeError:
+ bb = workspace.FetchInt8Blob(name)
+ except Exception as e:
+ logger.error("Get blob {} error: {}".format(name, e))
+
+ return bb
+
+
+# ==== torch/utils_caffe2/protobuf.py ==========================================
+
+
+def get_pb_arg(pb, arg_name):
+ for x in pb.arg:
+ if x.name == arg_name:
+ return x
+ return None
+
+
+def get_pb_arg_valf(pb, arg_name, default_val):
+ arg = get_pb_arg(pb, arg_name)
+ return arg.f if arg is not None else default_val
+
+
+def get_pb_arg_floats(pb, arg_name, default_val):
+ arg = get_pb_arg(pb, arg_name)
+ return list(map(float, arg.floats)) if arg is not None else default_val
+
+
+def get_pb_arg_ints(pb, arg_name, default_val):
+ arg = get_pb_arg(pb, arg_name)
+ return list(map(int, arg.ints)) if arg is not None else default_val
+
+
+def get_pb_arg_vali(pb, arg_name, default_val):
+ arg = get_pb_arg(pb, arg_name)
+ return arg.i if arg is not None else default_val
+
+
+def get_pb_arg_vals(pb, arg_name, default_val):
+ arg = get_pb_arg(pb, arg_name)
+ return arg.s if arg is not None else default_val
+
+
+def get_pb_arg_valstrings(pb, arg_name, default_val):
+ arg = get_pb_arg(pb, arg_name)
+ return list(arg.strings) if arg is not None else default_val
+
+
+def check_set_pb_arg(pb, arg_name, arg_attr, arg_value, allow_override=False):
+ arg = get_pb_arg(pb, arg_name)
+ if arg is None:
+ arg = putils.MakeArgument(arg_name, arg_value)
+ assert hasattr(arg, arg_attr)
+ pb.arg.extend([arg])
+ if allow_override and getattr(arg, arg_attr) != arg_value:
+ logger.warning(
+ "Override argument {}: {} -> {}".format(arg_name, getattr(arg, arg_attr), arg_value)
+ )
+ setattr(arg, arg_attr, arg_value)
+ else:
+ assert arg is not None
+ assert getattr(arg, arg_attr) == arg_value, "Existing value {}, new value {}".format(
+ getattr(arg, arg_attr), arg_value
+ )
+
+
+def _create_const_fill_op_from_numpy(name, tensor, device_option=None):
+ assert type(tensor) == np.ndarray
+ kTypeNameMapper = {
+ np.dtype("float32"): "GivenTensorFill",
+ np.dtype("int32"): "GivenTensorIntFill",
+ np.dtype("int64"): "GivenTensorInt64Fill",
+ np.dtype("uint8"): "GivenTensorStringFill",
+ }
+
+ args_dict = {}
+ if tensor.dtype == np.dtype("uint8"):
+ args_dict.update({"values": [str(tensor.data)], "shape": [1]})
+ else:
+ args_dict.update({"values": tensor, "shape": tensor.shape})
+
+ if device_option is not None:
+ args_dict["device_option"] = device_option
+
+ return core.CreateOperator(kTypeNameMapper[tensor.dtype], [], [name], **args_dict)
+
+
+def _create_const_fill_op_from_c2_int8_tensor(name, int8_tensor):
+ assert type(int8_tensor) == workspace.Int8Tensor
+ kTypeNameMapper = {
+ np.dtype("int32"): "Int8GivenIntTensorFill",
+ np.dtype("uint8"): "Int8GivenTensorFill",
+ }
+
+ tensor = int8_tensor.data
+ assert tensor.dtype in [np.dtype("uint8"), np.dtype("int32")]
+ values = tensor.tobytes() if tensor.dtype == np.dtype("uint8") else tensor
+
+ return core.CreateOperator(
+ kTypeNameMapper[tensor.dtype],
+ [],
+ [name],
+ values=values,
+ shape=tensor.shape,
+ Y_scale=int8_tensor.scale,
+ Y_zero_point=int8_tensor.zero_point,
+ )
+
+
+def create_const_fill_op(
+ name: str,
+ blob: Union[np.ndarray, workspace.Int8Tensor],
+ device_option: Optional[caffe2_pb2.DeviceOption] = None,
+) -> caffe2_pb2.OperatorDef:
+ """
+ Given a blob object, return the Caffe2 operator that creates this blob
+ as constant. Currently support NumPy tensor and Caffe2 Int8Tensor.
+ """
+
+ tensor_type = type(blob)
+ assert tensor_type in [
+ np.ndarray,
+ workspace.Int8Tensor,
+ ], 'Error when creating const fill op for "{}", unsupported blob type: {}'.format(
+ name, type(blob)
+ )
+
+ if tensor_type == np.ndarray:
+ return _create_const_fill_op_from_numpy(name, blob, device_option)
+ elif tensor_type == workspace.Int8Tensor:
+ assert device_option is None
+ return _create_const_fill_op_from_c2_int8_tensor(name, blob)
+
+
+def construct_init_net_from_params(
+ params: Dict[str, Any], device_options: Optional[Dict[str, caffe2_pb2.DeviceOption]] = None
+) -> caffe2_pb2.NetDef:
+ """
+ Construct the init_net from params dictionary
+ """
+ init_net = caffe2_pb2.NetDef()
+ device_options = device_options or {}
+ for name, blob in params.items():
+ if isinstance(blob, str):
+ logger.warning(
+ (
+ "Blob {} with type {} is not supported in generating init net,"
+ " skipped.".format(name, type(blob))
+ )
+ )
+ continue
+ init_net.op.extend(
+ [create_const_fill_op(name, blob, device_option=device_options.get(name, None))]
+ )
+ init_net.external_output.append(name)
+ return init_net
+
+
+def get_producer_map(ssa):
+ """
+ Return dict from versioned blob to (i, j),
+ where i is index of producer op, j is the index of output of that op.
+ """
+ producer_map = {}
+ for i in range(len(ssa)):
+ outputs = ssa[i][1]
+ for j, outp in enumerate(outputs):
+ producer_map[outp] = (i, j)
+ return producer_map
+
+
+def get_consumer_map(ssa):
+ """
+ Return dict from versioned blob to list of (i, j),
+ where i is index of consumer op, j is the index of input of that op.
+ """
+ consumer_map = collections.defaultdict(list)
+ for i in range(len(ssa)):
+ inputs = ssa[i][0]
+ for j, inp in enumerate(inputs):
+ consumer_map[inp].append((i, j))
+ return consumer_map
+
+
+def get_params_from_init_net(
+ init_net: caffe2_pb2.NetDef,
+) -> [Dict[str, Any], Dict[str, caffe2_pb2.DeviceOption]]:
+ """
+ Take the output blobs from init_net by running it.
+ Outputs:
+ params: dict from blob name to numpy array
+ device_options: dict from blob name to the device option of its creating op
+ """
+ # NOTE: this assumes that the params is determined by producer op with the
+ # only exception be CopyGPUToCPU which is CUDA op but returns CPU tensor.
+ def _get_device_option(producer_op):
+ if producer_op.type == "CopyGPUToCPU":
+ return caffe2_pb2.DeviceOption()
+ else:
+ return producer_op.device_option
+
+ with ScopedWS("__get_params_from_init_net__", is_reset=True, is_cleanup=True) as ws:
+ ws.RunNetOnce(init_net)
+ params = {b: fetch_any_blob(b) for b in init_net.external_output}
+ ssa, versions = core.get_ssa(init_net)
+ producer_map = get_producer_map(ssa)
+ device_options = {
+ b: _get_device_option(init_net.op[producer_map[(b, versions[b])][0]])
+ for b in init_net.external_output
+ }
+ return params, device_options
+
+
+def _updater_raise(op, input_types, output_types):
+ raise RuntimeError(
+ "Failed to apply updater for op {} given input_types {} and"
+ " output_types {}".format(op, input_types, output_types)
+ )
+
+
+def _generic_status_identifier(
+ predict_net: caffe2_pb2.NetDef,
+ status_updater: Callable,
+ known_status: Dict[Tuple[str, int], Any],
+) -> Dict[Tuple[str, int], Any]:
+ """
+ Statically infer the status of each blob, the status can be such as device type
+ (CPU/GPU), layout (NCHW/NHWC), data type (float32/int8), etc. "Blob" here
+ is versioned blob (Tuple[str, int]) in the format compatible with ssa.
+ Inputs:
+ predict_net: the caffe2 network
+ status_updater: a callable, given an op and the status of its input/output,
+ it returns the updated status of input/output. `None` is used for
+ representing unknown status.
+ known_status: a dict containing known status, used as initialization.
+ Outputs:
+ A dict mapping from versioned blob to its status
+ """
+ ssa, versions = core.get_ssa(predict_net)
+ versioned_ext_input = [(b, 0) for b in predict_net.external_input]
+ versioned_ext_output = [(b, versions[b]) for b in predict_net.external_output]
+ all_versioned_blobs = set().union(*[set(x[0] + x[1]) for x in ssa])
+
+ allowed_vbs = all_versioned_blobs.union(versioned_ext_input).union(versioned_ext_output)
+ assert all(k in allowed_vbs for k in known_status)
+ assert all(v is not None for v in known_status.values())
+ _known_status = copy.deepcopy(known_status)
+
+ def _check_and_update(key, value):
+ assert value is not None
+ if key in _known_status:
+ if not _known_status[key] == value:
+ raise RuntimeError(
+ "Confilict status for {}, existing status {}, new status {}".format(
+ key, _known_status[key], value
+ )
+ )
+ _known_status[key] = value
+
+ def _update_i(op, ssa_i):
+ versioned_inputs = ssa_i[0]
+ versioned_outputs = ssa_i[1]
+
+ inputs_status = [_known_status.get(b, None) for b in versioned_inputs]
+ outputs_status = [_known_status.get(b, None) for b in versioned_outputs]
+
+ new_inputs_status, new_outputs_status = status_updater(op, inputs_status, outputs_status)
+
+ for versioned_blob, status in zip(
+ versioned_inputs + versioned_outputs, new_inputs_status + new_outputs_status
+ ):
+ if status is not None:
+ _check_and_update(versioned_blob, status)
+
+ for op, ssa_i in zip(predict_net.op, ssa):
+ _update_i(op, ssa_i)
+ for op, ssa_i in zip(reversed(predict_net.op), reversed(ssa)):
+ _update_i(op, ssa_i)
+
+ # NOTE: This strictly checks all the blob from predict_net must be assgined
+ # a known status. However sometimes it's impossible (eg. having deadend op),
+ # we may relax this constraint if
+ for k in all_versioned_blobs:
+ if k not in _known_status:
+ raise NotImplementedError(
+ "Can not infer the status for {}. Currently only support the case where"
+ " a single forward and backward pass can identify status for all blobs.".format(k)
+ )
+
+ return _known_status
+
+
+def infer_device_type(
+ predict_net: caffe2_pb2.NetDef,
+ known_status: Dict[Tuple[str, int], Any],
+ device_name_style: str = "caffe2",
+) -> Dict[Tuple[str, int], str]:
+ """Return the device type ("cpu" or "gpu"/"cuda") of each (versioned) blob"""
+
+ assert device_name_style in ["caffe2", "pytorch"]
+ _CPU_STR = "cpu"
+ _GPU_STR = "gpu" if device_name_style == "caffe2" else "cuda"
+
+ def _copy_cpu_to_gpu_updater(op, input_types, output_types):
+ if input_types[0] == _GPU_STR or output_types[0] == _CPU_STR:
+ _updater_raise(op, input_types, output_types)
+ return ([_CPU_STR], [_GPU_STR])
+
+ def _copy_gpu_to_cpu_updater(op, input_types, output_types):
+ if input_types[0] == _CPU_STR or output_types[0] == _GPU_STR:
+ _updater_raise(op, input_types, output_types)
+ return ([_GPU_STR], [_CPU_STR])
+
+ def _other_ops_updater(op, input_types, output_types):
+ non_none_types = [x for x in input_types + output_types if x is not None]
+ if len(non_none_types) > 0:
+ the_type = non_none_types[0]
+ if not all(x == the_type for x in non_none_types):
+ _updater_raise(op, input_types, output_types)
+ else:
+ the_type = None
+ return ([the_type for _ in op.input], [the_type for _ in op.output])
+
+ def _device_updater(op, *args, **kwargs):
+ return {
+ "CopyCPUToGPU": _copy_cpu_to_gpu_updater,
+ "CopyGPUToCPU": _copy_gpu_to_cpu_updater,
+ }.get(op.type, _other_ops_updater)(op, *args, **kwargs)
+
+ return _generic_status_identifier(predict_net, _device_updater, known_status)
+
+
+# ==== torch/utils_caffe2/vis.py ===============================================
+
+
+def _modify_blob_names(ops, blob_rename_f):
+ ret = []
+
+ def _replace_list(blob_list, replaced_list):
+ del blob_list[:]
+ blob_list.extend(replaced_list)
+
+ for x in ops:
+ cur = copy.deepcopy(x)
+ _replace_list(cur.input, list(map(blob_rename_f, cur.input)))
+ _replace_list(cur.output, list(map(blob_rename_f, cur.output)))
+ ret.append(cur)
+
+ return ret
+
+
+def _rename_blob(name, blob_sizes, blob_ranges):
+ def _list_to_str(bsize):
+ ret = ", ".join([str(x) for x in bsize])
+ ret = "[" + ret + "]"
+ return ret
+
+ ret = name
+ if blob_sizes is not None and name in blob_sizes:
+ ret += "\n" + _list_to_str(blob_sizes[name])
+ if blob_ranges is not None and name in blob_ranges:
+ ret += "\n" + _list_to_str(blob_ranges[name])
+
+ return ret
+
+
+# graph_name could not contain word 'graph'
+def save_graph(net, file_name, graph_name="net", op_only=True, blob_sizes=None, blob_ranges=None):
+ blob_rename_f = functools.partial(_rename_blob, blob_sizes=blob_sizes, blob_ranges=blob_ranges)
+ return save_graph_base(net, file_name, graph_name, op_only, blob_rename_f)
+
+
+def save_graph_base(net, file_name, graph_name="net", op_only=True, blob_rename_func=None):
+ graph = None
+ ops = net.op
+ if blob_rename_func is not None:
+ ops = _modify_blob_names(ops, blob_rename_func)
+ if not op_only:
+ graph = net_drawer.GetPydotGraph(ops, graph_name, rankdir="TB")
+ else:
+ graph = net_drawer.GetPydotGraphMinimal(
+ ops, graph_name, rankdir="TB", minimal_dependency=True
+ )
+
+ try:
+ par_dir = os.path.dirname(file_name)
+ if not os.path.exists(par_dir):
+ os.makedirs(par_dir)
+
+ format = os.path.splitext(os.path.basename(file_name))[-1]
+ if format == ".png":
+ graph.write_png(file_name)
+ elif format == ".pdf":
+ graph.write_pdf(file_name)
+ elif format == ".svg":
+ graph.write_svg(file_name)
+ else:
+ print("Incorrect format {}".format(format))
+ except Exception as e:
+ print("Error when writing graph to image {}".format(e))
+
+ return graph
+
+
+# ==== torch/utils_toffee/aten_to_caffe2.py ====================================
+
+
+def group_norm_replace_aten_with_caffe2(predict_net: caffe2_pb2.NetDef):
+ """
+ For ONNX exported model, GroupNorm will be represented as ATen op,
+ this can be a drop in replacement from ATen to GroupNorm
+ """
+ count = 0
+ for op in predict_net.op:
+ if op.type == "ATen":
+ op_name = get_pb_arg_vals(op, "operator", None) # return byte in py3
+ if op_name and op_name.decode() == "group_norm":
+ op.arg.remove(get_pb_arg(op, "operator"))
+
+ if get_pb_arg_vali(op, "cudnn_enabled", None):
+ op.arg.remove(get_pb_arg(op, "cudnn_enabled"))
+
+ num_groups = get_pb_arg_vali(op, "num_groups", None)
+ if num_groups is not None:
+ op.arg.remove(get_pb_arg(op, "num_groups"))
+ check_set_pb_arg(op, "group", "i", num_groups)
+
+ op.type = "GroupNorm"
+ count += 1
+ if count > 1:
+ logger.info("Replaced {} ATen operator to GroupNormOp".format(count))
+
+
+# ==== torch/utils_toffee/alias.py =============================================
+
+
+def alias(x, name, is_backward=False):
+ if not torch.onnx.is_in_onnx_export():
+ return x
+ assert isinstance(x, torch.Tensor)
+ return torch.ops._caffe2.AliasWithName(x, name, is_backward=is_backward)
+
+
+def fuse_alias_placeholder(predict_net, init_net):
+ """Remove AliasWithName placeholder and rename the input/output of it"""
+ # First we finish all the re-naming
+ for i, op in enumerate(predict_net.op):
+ if op.type == "AliasWithName":
+ assert len(op.input) == 1
+ assert len(op.output) == 1
+ name = get_pb_arg_vals(op, "name", None).decode()
+ is_backward = bool(get_pb_arg_vali(op, "is_backward", 0))
+ rename_op_input(predict_net, init_net, i, 0, name, from_producer=is_backward)
+ rename_op_output(predict_net, i, 0, name)
+
+ # Remove AliasWithName, should be very safe since it's a non-op
+ new_ops = []
+ for op in predict_net.op:
+ if op.type != "AliasWithName":
+ new_ops.append(op)
+ else:
+ # safety check
+ assert op.input == op.output
+ assert op.input[0] == op.arg[0].s.decode()
+ del predict_net.op[:]
+ predict_net.op.extend(new_ops)
+
+
+# ==== torch/utils_caffe2/graph_transform.py ===================================
+
+
+class IllegalGraphTransformError(ValueError):
+ """When a graph transform function call can't be executed."""
+
+
+def _rename_versioned_blob_in_proto(
+ proto: caffe2_pb2.NetDef,
+ old_name: str,
+ new_name: str,
+ version: int,
+ ssa: List[Tuple[List[Tuple[str, int]], List[Tuple[str, int]]]],
+ start_versions: Dict[str, int],
+ end_versions: Dict[str, int],
+):
+ """In given proto, rename all blobs with matched version"""
+ # Operater list
+ for op, i_th_ssa in zip(proto.op, ssa):
+ versioned_inputs, versioned_outputs = i_th_ssa
+ for i in range(len(op.input)):
+ if versioned_inputs[i] == (old_name, version):
+ op.input[i] = new_name
+ for i in range(len(op.output)):
+ if versioned_outputs[i] == (old_name, version):
+ op.output[i] = new_name
+ # external_input
+ if start_versions.get(old_name, 0) == version:
+ for i in range(len(proto.external_input)):
+ if proto.external_input[i] == old_name:
+ proto.external_input[i] = new_name
+ # external_output
+ if end_versions.get(old_name, 0) == version:
+ for i in range(len(proto.external_output)):
+ if proto.external_output[i] == old_name:
+ proto.external_output[i] = new_name
+
+
+def rename_op_input(
+ predict_net: caffe2_pb2.NetDef,
+ init_net: caffe2_pb2.NetDef,
+ op_id: int,
+ input_id: int,
+ new_name: str,
+ from_producer: bool = False,
+):
+ """
+ Rename the op_id-th operator in predict_net, change it's input_id-th input's
+ name to the new_name. It also does automatic re-route and change
+ external_input and init_net if necessary.
+ - It requires the input is only consumed by this op.
+ - This function modifies predict_net and init_net in-place.
+ - When from_producer is enable, this also updates other operators that consumes
+ the same input. Be cautious because may trigger unintended behavior.
+ """
+ assert isinstance(predict_net, caffe2_pb2.NetDef)
+ assert isinstance(init_net, caffe2_pb2.NetDef)
+
+ init_net_ssa, init_net_versions = core.get_ssa(init_net)
+ predict_net_ssa, predict_net_versions = core.get_ssa(
+ predict_net, copy.deepcopy(init_net_versions)
+ )
+
+ versioned_inputs, versioned_outputs = predict_net_ssa[op_id]
+ old_name, version = versioned_inputs[input_id]
+
+ if from_producer:
+ producer_map = get_producer_map(predict_net_ssa)
+ if not (old_name, version) in producer_map:
+ raise NotImplementedError(
+ "Can't find producer, the input {} is probably from"
+ " init_net, this is not supported yet.".format(old_name)
+ )
+ producer = producer_map[(old_name, version)]
+ rename_op_output(predict_net, producer[0], producer[1], new_name)
+ return
+
+ def contain_targets(op_ssa):
+ return (old_name, version) in op_ssa[0]
+
+ is_consumer = [contain_targets(op_ssa) for op_ssa in predict_net_ssa]
+ if sum(is_consumer) > 1:
+ raise IllegalGraphTransformError(
+ (
+ "Input '{}' of operator(#{}) are consumed by other ops, please use"
+ + " rename_op_output on the producer instead. Offending op: \n{}"
+ ).format(old_name, op_id, predict_net.op[op_id])
+ )
+
+ # update init_net
+ _rename_versioned_blob_in_proto(
+ init_net, old_name, new_name, version, init_net_ssa, {}, init_net_versions
+ )
+ # update predict_net
+ _rename_versioned_blob_in_proto(
+ predict_net,
+ old_name,
+ new_name,
+ version,
+ predict_net_ssa,
+ init_net_versions,
+ predict_net_versions,
+ )
+
+
+def rename_op_output(predict_net: caffe2_pb2.NetDef, op_id: int, output_id: int, new_name: str):
+ """
+ Rename the op_id-th operator in predict_net, change it's output_id-th input's
+ name to the new_name. It also does automatic re-route and change
+ external_output and if necessary.
+ - It allows multiple consumers of its output.
+ - This function modifies predict_net in-place, doesn't need init_net.
+ """
+ assert isinstance(predict_net, caffe2_pb2.NetDef)
+
+ ssa, blob_versions = core.get_ssa(predict_net)
+
+ versioned_inputs, versioned_outputs = ssa[op_id]
+ old_name, version = versioned_outputs[output_id]
+
+ # update predict_net
+ _rename_versioned_blob_in_proto(
+ predict_net, old_name, new_name, version, ssa, {}, blob_versions
+ )
+
+
+def get_sub_graph_external_input_output(
+ predict_net: caffe2_pb2.NetDef, sub_graph_op_indices: List[int]
+) -> Tuple[List[Tuple[str, int]], List[Tuple[str, int]]]:
+ """
+ Return the list of external input/output of sub-graph,
+ each element is tuple of the name and corresponding version in predict_net.
+
+ external input/output is defined the same way as caffe2 NetDef.
+ """
+ ssa, versions = core.get_ssa(predict_net)
+
+ all_inputs = []
+ all_outputs = []
+ for op_id in sub_graph_op_indices:
+ all_inputs += [inp for inp in ssa[op_id][0] if inp not in all_inputs]
+ all_outputs += list(ssa[op_id][1]) # ssa output won't repeat
+
+ # for versioned blobs, external inputs are just those blob in all_inputs
+ # but not in all_outputs
+ ext_inputs = [inp for inp in all_inputs if inp not in all_outputs]
+
+ # external outputs are essentially outputs of this subgraph that are used
+ # outside of this sub-graph (including predict_net.external_output)
+ all_other_inputs = sum(
+ (ssa[i][0] for i in range(len(ssa)) if i not in sub_graph_op_indices),
+ [(outp, versions[outp]) for outp in predict_net.external_output],
+ )
+ ext_outputs = [outp for outp in all_outputs if outp in set(all_other_inputs)]
+
+ return ext_inputs, ext_outputs
+
+
+class DiGraph:
+ """A DAG representation of caffe2 graph, each vertice is a versioned blob."""
+
+ def __init__(self):
+ self.vertices = set()
+ self.graph = collections.defaultdict(list)
+
+ def add_edge(self, u, v):
+ self.graph[u].append(v)
+ self.vertices.add(u)
+ self.vertices.add(v)
+
+ # grab from https://www.geeksforgeeks.org/find-paths-given-source-destination/
+ def get_all_paths(self, s, d):
+ visited = {k: False for k in self.vertices}
+ path = []
+ all_paths = []
+
+ def _get_all_paths_util(graph, u, d, visited, path):
+ visited[u] = True
+ path.append(u)
+ if u == d:
+ all_paths.append(copy.deepcopy(path))
+ else:
+ for i in graph[u]:
+ if not visited[i]:
+ _get_all_paths_util(graph, i, d, visited, path)
+ path.pop()
+ visited[u] = False
+
+ _get_all_paths_util(self.graph, s, d, visited, path)
+ return all_paths
+
+ @staticmethod
+ def from_ssa(ssa):
+ graph = DiGraph()
+ for op_id in range(len(ssa)):
+ for inp in ssa[op_id][0]:
+ for outp in ssa[op_id][1]:
+ graph.add_edge(inp, outp)
+ return graph
+
+
+def _get_dependency_chain(ssa, versioned_target, versioned_source):
+ """
+ Return the index list of relevant operator to produce target blob from source blob,
+ if there's no dependency, return empty list.
+ """
+
+ # finding all paths between nodes can be O(N!), thus we can only search
+ # in the subgraph using the op starting from the first consumer of source blob
+ # to the producer of the target blob.
+ consumer_map = get_consumer_map(ssa)
+ producer_map = get_producer_map(ssa)
+ start_op = min(x[0] for x in consumer_map[versioned_source]) - 15
+ end_op = (
+ producer_map[versioned_target][0] + 15 if versioned_target in producer_map else start_op
+ )
+ sub_graph_ssa = ssa[start_op : end_op + 1]
+ if len(sub_graph_ssa) > 30:
+ logger.warning(
+ "Subgraph bebetween {} and {} is large (from op#{} to op#{}), it"
+ " might take non-trival time to find all paths between them.".format(
+ versioned_source, versioned_target, start_op, end_op
+ )
+ )
+
+ dag = DiGraph.from_ssa(sub_graph_ssa)
+ paths = dag.get_all_paths(versioned_source, versioned_target) # include two ends
+ ops_in_paths = [[producer_map[blob][0] for blob in path[1:]] for path in paths]
+ return sorted(set().union(*[set(ops) for ops in ops_in_paths]))
+
+
+def identify_reshape_sub_graph(predict_net: caffe2_pb2.NetDef) -> List[List[int]]:
+ """
+ Idenfity the reshape sub-graph in a protobuf.
+ The reshape sub-graph is defined as matching the following pattern:
+
+ (input_blob) -> Op_1 -> ... -> Op_N -> (new_shape) -─┐
+ └-------------------------------------------> Reshape -> (output_blob)
+
+ Return:
+ List of sub-graphs, each sub-graph is represented as a list of indices
+ of the relavent ops, [Op_1, Op_2, ..., Op_N, Reshape]
+ """
+
+ ssa, _ = core.get_ssa(predict_net)
+
+ ret = []
+ for i, op in enumerate(predict_net.op):
+ if op.type == "Reshape":
+ assert len(op.input) == 2
+ input_ssa = ssa[i][0]
+ data_source = input_ssa[0]
+ shape_source = input_ssa[1]
+ op_indices = _get_dependency_chain(ssa, shape_source, data_source)
+ ret.append(op_indices + [i])
+ return ret
+
+
+def remove_reshape_for_fc(predict_net, params):
+ """
+ In PyTorch nn.Linear has to take 2D tensor, this often leads to reshape
+ a 4D tensor to 2D by calling .view(). However this (dynamic) reshaping
+ doesn't work well with ONNX and Int8 tools, and cause using extra
+ ops (eg. ExpandDims) that might not be available on mobile.
+ Luckily Caffe2 supports 4D tensor for FC, so we can remove those reshape
+ after exporting ONNX model.
+ """
+ from caffe2.python import core
+
+ # find all reshape sub-graph that can be removed, which is now all Reshape
+ # sub-graph whose output is only consumed by FC.
+ # TODO: to make it safer, we may need the actually value to better determine
+ # if a Reshape before FC is removable.
+ reshape_sub_graphs = identify_reshape_sub_graph(predict_net)
+ sub_graphs_to_remove = []
+ for reshape_sub_graph in reshape_sub_graphs:
+ reshape_op_id = reshape_sub_graph[-1]
+ assert predict_net.op[reshape_op_id].type == "Reshape"
+ ssa, _ = core.get_ssa(predict_net)
+ reshape_output = ssa[reshape_op_id][1][0]
+ consumers = [i for i in range(len(ssa)) if reshape_output in ssa[i][0]]
+ if all(predict_net.op[consumer].type == "FC" for consumer in consumers):
+ # safety check if the sub-graph is isolated, for this reshape sub-graph,
+ # it means it has one non-param external input and one external output.
+ ext_inputs, ext_outputs = get_sub_graph_external_input_output(
+ predict_net, reshape_sub_graph
+ )
+ non_params_ext_inputs = [inp for inp in ext_inputs if inp[1] != 0]
+ if len(non_params_ext_inputs) == 1 and len(ext_outputs) == 1:
+ sub_graphs_to_remove.append(reshape_sub_graph)
+
+ # perform removing subgraph by:
+ # 1: rename the Reshape's output to its input, then the graph can be
+ # seen as in-place itentify, meaning whose external input/output are the same.
+ # 2: simply remove those ops.
+ remove_op_ids = []
+ params_to_remove = []
+ for sub_graph in sub_graphs_to_remove:
+ logger.info(
+ "Remove Reshape sub-graph:\n{}".format(
+ "".join(["(#{:>4})\n{}".format(i, predict_net.op[i]) for i in sub_graph])
+ )
+ )
+ reshape_op_id = sub_graph[-1]
+ new_reshap_output = predict_net.op[reshape_op_id].input[0]
+ rename_op_output(predict_net, reshape_op_id, 0, new_reshap_output)
+ ext_inputs, ext_outputs = get_sub_graph_external_input_output(predict_net, sub_graph)
+ non_params_ext_inputs = [inp for inp in ext_inputs if inp[1] != 0]
+ params_ext_inputs = [inp for inp in ext_inputs if inp[1] == 0]
+ assert len(non_params_ext_inputs) == 1 and len(ext_outputs) == 1
+ assert ext_outputs[0][0] == non_params_ext_inputs[0][0]
+ assert ext_outputs[0][1] == non_params_ext_inputs[0][1] + 1
+ remove_op_ids.extend(sub_graph)
+ params_to_remove.extend(params_ext_inputs)
+
+ predict_net = copy.deepcopy(predict_net)
+ new_ops = [op for i, op in enumerate(predict_net.op) if i not in remove_op_ids]
+ del predict_net.op[:]
+ predict_net.op.extend(new_ops)
+ for versioned_params in params_to_remove:
+ name = versioned_params[0]
+ logger.info("Remove params: {} from init_net and predict_net.external_input".format(name))
+ del params[name]
+ predict_net.external_input.remove(name)
+
+ return predict_net, params
+
+
+def fuse_copy_between_cpu_and_gpu(predict_net: caffe2_pb2.NetDef):
+ """
+ In-place fuse extra copy ops between cpu/gpu for the following case:
+ a -CopyAToB-> b -CopyBToA> c1 -NextOp1-> d1
+ -CopyBToA> c2 -NextOp2-> d2
+ The fused network will look like:
+ a -NextOp1-> d1
+ -NextOp2-> d2
+ """
+
+ _COPY_OPS = ["CopyCPUToGPU", "CopyGPUToCPU"]
+
+ def _fuse_once(predict_net):
+ ssa, blob_versions = core.get_ssa(predict_net)
+ consumer_map = get_consumer_map(ssa)
+ versioned_external_output = [
+ (name, blob_versions[name]) for name in predict_net.external_output
+ ]
+
+ for op_id, op in enumerate(predict_net.op):
+ if op.type in _COPY_OPS:
+ fw_copy_versioned_output = ssa[op_id][1][0]
+ consumer_ids = [x[0] for x in consumer_map[fw_copy_versioned_output]]
+ reverse_op_type = _COPY_OPS[1 - _COPY_OPS.index(op.type)]
+
+ is_fusable = (
+ len(consumer_ids) > 0
+ and fw_copy_versioned_output not in versioned_external_output
+ and all(
+ predict_net.op[_op_id].type == reverse_op_type
+ and ssa[_op_id][1][0] not in versioned_external_output
+ for _op_id in consumer_ids
+ )
+ )
+
+ if is_fusable:
+ for rv_copy_op_id in consumer_ids:
+ # making each NextOp uses "a" directly and removing Copy ops
+ rs_copy_versioned_output = ssa[rv_copy_op_id][1][0]
+ next_op_id, inp_id = consumer_map[rs_copy_versioned_output][0]
+ predict_net.op[next_op_id].input[inp_id] = op.input[0]
+ # remove CopyOps
+ new_ops = [
+ op
+ for i, op in enumerate(predict_net.op)
+ if i != op_id and i not in consumer_ids
+ ]
+ del predict_net.op[:]
+ predict_net.op.extend(new_ops)
+ return True
+
+ return False
+
+ # _fuse_once returns False is nothing can be fused
+ while _fuse_once(predict_net):
+ pass
+
+
+def remove_dead_end_ops(net_def: caffe2_pb2.NetDef):
+ """remove ops if its output is not used or not in external_output"""
+ ssa, versions = core.get_ssa(net_def)
+ versioned_external_output = [(name, versions[name]) for name in net_def.external_output]
+ consumer_map = get_consumer_map(ssa)
+ removed_op_ids = set()
+
+ def _is_dead_end(versioned_blob):
+ return not (
+ versioned_blob in versioned_external_output
+ or (
+ len(consumer_map[versioned_blob]) > 0
+ and all(x[0] not in removed_op_ids for x in consumer_map[versioned_blob])
+ )
+ )
+
+ for i, ssa_i in reversed(list(enumerate(ssa))):
+ versioned_outputs = ssa_i[1]
+ if all(_is_dead_end(outp) for outp in versioned_outputs):
+ removed_op_ids.add(i)
+
+ # simply removing those deadend ops should have no effect to external_output
+ new_ops = [op for i, op in enumerate(net_def.op) if i not in removed_op_ids]
+ del net_def.op[:]
+ net_def.op.extend(new_ops)
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/torchscript.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/torchscript.py
new file mode 100644
index 0000000000000000000000000000000000000000..19965ad967cf9686a10728c98dcccf9e0fb7c447
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/torchscript.py
@@ -0,0 +1,132 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import os
+import torch
+
+from custom_detectron2.utils.file_io import PathManager
+
+from .torchscript_patch import freeze_training_mode, patch_instances
+
+__all__ = ["scripting_with_instances", "dump_torchscript_IR"]
+
+
+def scripting_with_instances(model, fields):
+ """
+ Run :func:`torch.jit.script` on a model that uses the :class:`Instances` class. Since
+ attributes of :class:`Instances` are "dynamically" added in eager mode,it is difficult
+ for scripting to support it out of the box. This function is made to support scripting
+ a model that uses :class:`Instances`. It does the following:
+
+ 1. Create a scriptable ``new_Instances`` class which behaves similarly to ``Instances``,
+ but with all attributes been "static".
+ The attributes need to be statically declared in the ``fields`` argument.
+ 2. Register ``new_Instances``, and force scripting compiler to
+ use it when trying to compile ``Instances``.
+
+ After this function, the process will be reverted. User should be able to script another model
+ using different fields.
+
+ Example:
+ Assume that ``Instances`` in the model consist of two attributes named
+ ``proposal_boxes`` and ``objectness_logits`` with type :class:`Boxes` and
+ :class:`Tensor` respectively during inference. You can call this function like:
+ ::
+ fields = {"proposal_boxes": Boxes, "objectness_logits": torch.Tensor}
+ torchscipt_model = scripting_with_instances(model, fields)
+
+ Note:
+ It only support models in evaluation mode.
+
+ Args:
+ model (nn.Module): The input model to be exported by scripting.
+ fields (Dict[str, type]): Attribute names and corresponding type that
+ ``Instances`` will use in the model. Note that all attributes used in ``Instances``
+ need to be added, regardless of whether they are inputs/outputs of the model.
+ Data type not defined in detectron2 is not supported for now.
+
+ Returns:
+ torch.jit.ScriptModule: the model in torchscript format
+ """
+ assert (
+ not model.training
+ ), "Currently we only support exporting models in evaluation mode to torchscript"
+
+ with freeze_training_mode(model), patch_instances(fields):
+ scripted_model = torch.jit.script(model)
+ return scripted_model
+
+
+# alias for old name
+export_torchscript_with_instances = scripting_with_instances
+
+
+def dump_torchscript_IR(model, dir):
+ """
+ Dump IR of a TracedModule/ScriptModule/Function in various format (code, graph,
+ inlined graph). Useful for debugging.
+
+ Args:
+ model (TracedModule/ScriptModule/ScriptFUnction): traced or scripted module
+ dir (str): output directory to dump files.
+ """
+ dir = os.path.expanduser(dir)
+ PathManager.mkdirs(dir)
+
+ def _get_script_mod(mod):
+ if isinstance(mod, torch.jit.TracedModule):
+ return mod._actual_script_module
+ return mod
+
+ # Dump pretty-printed code: https://pytorch.org/docs/stable/jit.html#inspecting-code
+ with PathManager.open(os.path.join(dir, "model_ts_code.txt"), "w") as f:
+
+ def get_code(mod):
+ # Try a few ways to get code using private attributes.
+ try:
+ # This contains more information than just `mod.code`
+ return _get_script_mod(mod)._c.code
+ except AttributeError:
+ pass
+ try:
+ return mod.code
+ except AttributeError:
+ return None
+
+ def dump_code(prefix, mod):
+ code = get_code(mod)
+ name = prefix or "root model"
+ if code is None:
+ f.write(f"Could not found code for {name} (type={mod.original_name})\n")
+ f.write("\n")
+ else:
+ f.write(f"\nCode for {name}, type={mod.original_name}:\n")
+ f.write(code)
+ f.write("\n")
+ f.write("-" * 80)
+
+ for name, m in mod.named_children():
+ dump_code(prefix + "." + name, m)
+
+ if isinstance(model, torch.jit.ScriptFunction):
+ f.write(get_code(model))
+ else:
+ dump_code("", model)
+
+ def _get_graph(model):
+ try:
+ # Recursively dump IR of all modules
+ return _get_script_mod(model)._c.dump_to_str(True, False, False)
+ except AttributeError:
+ return model.graph.str()
+
+ with PathManager.open(os.path.join(dir, "model_ts_IR.txt"), "w") as f:
+ f.write(_get_graph(model))
+
+ # Dump IR of the entire graph (all submodules inlined)
+ with PathManager.open(os.path.join(dir, "model_ts_IR_inlined.txt"), "w") as f:
+ f.write(str(model.inlined_graph))
+
+ if not isinstance(model, torch.jit.ScriptFunction):
+ # Dump the model structure in pytorch style
+ with PathManager.open(os.path.join(dir, "model.txt"), "w") as f:
+ f.write(str(model))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/torchscript_patch.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/torchscript_patch.py
new file mode 100644
index 0000000000000000000000000000000000000000..9c031d75579addc30ddbc9c7f46280724078c328
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/export/torchscript_patch.py
@@ -0,0 +1,406 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import os
+import sys
+import tempfile
+from contextlib import ExitStack, contextmanager
+from copy import deepcopy
+from unittest import mock
+import torch
+from torch import nn
+
+# need some explicit imports due to https://github.com/pytorch/pytorch/issues/38964
+import custom_detectron2 # noqa F401
+from custom_detectron2.structures import Boxes, Instances
+from custom_detectron2.utils.env import _import_file
+
+_counter = 0
+
+
+def _clear_jit_cache():
+ from torch.jit._recursive import concrete_type_store
+ from torch.jit._state import _jit_caching_layer
+
+ concrete_type_store.type_store.clear() # for modules
+ _jit_caching_layer.clear() # for free functions
+
+
+def _add_instances_conversion_methods(newInstances):
+ """
+ Add from_instances methods to the scripted Instances class.
+ """
+ cls_name = newInstances.__name__
+
+ @torch.jit.unused
+ def from_instances(instances: Instances):
+ """
+ Create scripted Instances from original Instances
+ """
+ fields = instances.get_fields()
+ image_size = instances.image_size
+ ret = newInstances(image_size)
+ for name, val in fields.items():
+ assert hasattr(ret, f"_{name}"), f"No attribute named {name} in {cls_name}"
+ setattr(ret, name, deepcopy(val))
+ return ret
+
+ newInstances.from_instances = from_instances
+
+
+@contextmanager
+def patch_instances(fields):
+ """
+ A contextmanager, under which the Instances class in detectron2 is replaced
+ by a statically-typed scriptable class, defined by `fields`.
+ See more in `scripting_with_instances`.
+ """
+
+ with tempfile.TemporaryDirectory(prefix="detectron2") as dir, tempfile.NamedTemporaryFile(
+ mode="w", encoding="utf-8", suffix=".py", dir=dir, delete=False
+ ) as f:
+ try:
+ # Objects that use Instances should not reuse previously-compiled
+ # results in cache, because `Instances` could be a new class each time.
+ _clear_jit_cache()
+
+ cls_name, s = _gen_instance_module(fields)
+ f.write(s)
+ f.flush()
+ f.close()
+
+ module = _import(f.name)
+ new_instances = getattr(module, cls_name)
+ _ = torch.jit.script(new_instances)
+ # let torchscript think Instances was scripted already
+ Instances.__torch_script_class__ = True
+ # let torchscript find new_instances when looking for the jit type of Instances
+ Instances._jit_override_qualname = torch._jit_internal._qualified_name(new_instances)
+
+ _add_instances_conversion_methods(new_instances)
+ yield new_instances
+ finally:
+ try:
+ del Instances.__torch_script_class__
+ del Instances._jit_override_qualname
+ except AttributeError:
+ pass
+ sys.modules.pop(module.__name__)
+
+
+def _gen_instance_class(fields):
+ """
+ Args:
+ fields (dict[name: type])
+ """
+
+ class _FieldType:
+ def __init__(self, name, type_):
+ assert isinstance(name, str), f"Field name must be str, got {name}"
+ self.name = name
+ self.type_ = type_
+ self.annotation = f"{type_.__module__}.{type_.__name__}"
+
+ fields = [_FieldType(k, v) for k, v in fields.items()]
+
+ def indent(level, s):
+ return " " * 4 * level + s
+
+ lines = []
+
+ global _counter
+ _counter += 1
+
+ cls_name = "ScriptedInstances{}".format(_counter)
+
+ field_names = tuple(x.name for x in fields)
+ extra_args = ", ".join([f"{f.name}: Optional[{f.annotation}] = None" for f in fields])
+ lines.append(
+ f"""
+class {cls_name}:
+ def __init__(self, image_size: Tuple[int, int], {extra_args}):
+ self.image_size = image_size
+ self._field_names = {field_names}
+"""
+ )
+
+ for f in fields:
+ lines.append(
+ indent(2, f"self._{f.name} = torch.jit.annotate(Optional[{f.annotation}], {f.name})")
+ )
+
+ for f in fields:
+ lines.append(
+ f"""
+ @property
+ def {f.name}(self) -> {f.annotation}:
+ # has to use a local for type refinement
+ # https://pytorch.org/docs/stable/jit_language_reference.html#optional-type-refinement
+ t = self._{f.name}
+ assert t is not None, "{f.name} is None and cannot be accessed!"
+ return t
+
+ @{f.name}.setter
+ def {f.name}(self, value: {f.annotation}) -> None:
+ self._{f.name} = value
+"""
+ )
+
+ # support method `__len__`
+ lines.append(
+ """
+ def __len__(self) -> int:
+"""
+ )
+ for f in fields:
+ lines.append(
+ f"""
+ t = self._{f.name}
+ if t is not None:
+ return len(t)
+"""
+ )
+ lines.append(
+ """
+ raise NotImplementedError("Empty Instances does not support __len__!")
+"""
+ )
+
+ # support method `has`
+ lines.append(
+ """
+ def has(self, name: str) -> bool:
+"""
+ )
+ for f in fields:
+ lines.append(
+ f"""
+ if name == "{f.name}":
+ return self._{f.name} is not None
+"""
+ )
+ lines.append(
+ """
+ return False
+"""
+ )
+
+ # support method `to`
+ none_args = ", None" * len(fields)
+ lines.append(
+ f"""
+ def to(self, device: torch.device) -> "{cls_name}":
+ ret = {cls_name}(self.image_size{none_args})
+"""
+ )
+ for f in fields:
+ if hasattr(f.type_, "to"):
+ lines.append(
+ f"""
+ t = self._{f.name}
+ if t is not None:
+ ret._{f.name} = t.to(device)
+"""
+ )
+ else:
+ # For now, ignore fields that cannot be moved to devices.
+ # Maybe can support other tensor-like classes (e.g. __torch_function__)
+ pass
+ lines.append(
+ """
+ return ret
+"""
+ )
+
+ # support method `getitem`
+ none_args = ", None" * len(fields)
+ lines.append(
+ f"""
+ def __getitem__(self, item) -> "{cls_name}":
+ ret = {cls_name}(self.image_size{none_args})
+"""
+ )
+ for f in fields:
+ lines.append(
+ f"""
+ t = self._{f.name}
+ if t is not None:
+ ret._{f.name} = t[item]
+"""
+ )
+ lines.append(
+ """
+ return ret
+"""
+ )
+
+ # support method `cat`
+ # this version does not contain checks that all instances have same size and fields
+ none_args = ", None" * len(fields)
+ lines.append(
+ f"""
+ def cat(self, instances: List["{cls_name}"]) -> "{cls_name}":
+ ret = {cls_name}(self.image_size{none_args})
+"""
+ )
+ for f in fields:
+ lines.append(
+ f"""
+ t = self._{f.name}
+ if t is not None:
+ values: List[{f.annotation}] = [x.{f.name} for x in instances]
+ if torch.jit.isinstance(t, torch.Tensor):
+ ret._{f.name} = torch.cat(values, dim=0)
+ else:
+ ret._{f.name} = t.cat(values)
+"""
+ )
+ lines.append(
+ """
+ return ret"""
+ )
+
+ # support method `get_fields()`
+ lines.append(
+ """
+ def get_fields(self) -> Dict[str, Tensor]:
+ ret = {}
+ """
+ )
+ for f in fields:
+ if f.type_ == Boxes:
+ stmt = "t.tensor"
+ elif f.type_ == torch.Tensor:
+ stmt = "t"
+ else:
+ stmt = f'assert False, "unsupported type {str(f.type_)}"'
+ lines.append(
+ f"""
+ t = self._{f.name}
+ if t is not None:
+ ret["{f.name}"] = {stmt}
+ """
+ )
+ lines.append(
+ """
+ return ret"""
+ )
+ return cls_name, os.linesep.join(lines)
+
+
+def _gen_instance_module(fields):
+ # TODO: find a more automatic way to enable import of other classes
+ s = """
+from copy import deepcopy
+import torch
+from torch import Tensor
+import typing
+from typing import *
+
+import custom_detectron2
+from custom_detectron2.structures import Boxes, Instances
+
+"""
+
+ cls_name, cls_def = _gen_instance_class(fields)
+ s += cls_def
+ return cls_name, s
+
+
+def _import(path):
+ return _import_file(
+ "{}{}".format(sys.modules[__name__].__name__, _counter), path, make_importable=True
+ )
+
+
+@contextmanager
+def patch_builtin_len(modules=()):
+ """
+ Patch the builtin len() function of a few detectron2 modules
+ to use __len__ instead, because __len__ does not convert values to
+ integers and therefore is friendly to tracing.
+
+ Args:
+ modules (list[stsr]): names of extra modules to patch len(), in
+ addition to those in detectron2.
+ """
+
+ def _new_len(obj):
+ return obj.__len__()
+
+ with ExitStack() as stack:
+ MODULES = [
+ "detectron2.modeling.roi_heads.fast_rcnn",
+ "detectron2.modeling.roi_heads.mask_head",
+ "detectron2.modeling.roi_heads.keypoint_head",
+ ] + list(modules)
+ ctxs = [stack.enter_context(mock.patch(mod + ".len")) for mod in MODULES]
+ for m in ctxs:
+ m.side_effect = _new_len
+ yield
+
+
+def patch_nonscriptable_classes():
+ """
+ Apply patches on a few nonscriptable detectron2 classes.
+ Should not have side-effects on eager usage.
+ """
+ # __prepare_scriptable__ can also be added to models for easier maintenance.
+ # But it complicates the clean model code.
+
+ from custom_detectron2.modeling.backbone import ResNet, FPN
+
+ # Due to https://github.com/pytorch/pytorch/issues/36061,
+ # we change backbone to use ModuleList for scripting.
+ # (note: this changes param names in state_dict)
+
+ def prepare_resnet(self):
+ ret = deepcopy(self)
+ ret.stages = nn.ModuleList(ret.stages)
+ for k in self.stage_names:
+ delattr(ret, k)
+ return ret
+
+ ResNet.__prepare_scriptable__ = prepare_resnet
+
+ def prepare_fpn(self):
+ ret = deepcopy(self)
+ ret.lateral_convs = nn.ModuleList(ret.lateral_convs)
+ ret.output_convs = nn.ModuleList(ret.output_convs)
+ for name, _ in self.named_children():
+ if name.startswith("fpn_"):
+ delattr(ret, name)
+ return ret
+
+ FPN.__prepare_scriptable__ = prepare_fpn
+
+ # Annotate some attributes to be constants for the purpose of scripting,
+ # even though they are not constants in eager mode.
+ from custom_detectron2.modeling.roi_heads import StandardROIHeads
+
+ if hasattr(StandardROIHeads, "__annotations__"):
+ # copy first to avoid editing annotations of base class
+ StandardROIHeads.__annotations__ = deepcopy(StandardROIHeads.__annotations__)
+ StandardROIHeads.__annotations__["mask_on"] = torch.jit.Final[bool]
+ StandardROIHeads.__annotations__["keypoint_on"] = torch.jit.Final[bool]
+
+
+# These patches are not supposed to have side-effects.
+patch_nonscriptable_classes()
+
+
+@contextmanager
+def freeze_training_mode(model):
+ """
+ A context manager that annotates the "training" attribute of every submodule
+ to constant, so that the training codepath in these modules can be
+ meta-compiled away. Upon exiting, the annotations are reverted.
+ """
+ classes = {type(x) for x in model.modules()}
+ # __constants__ is the old way to annotate constants and not compatible
+ # with __annotations__ .
+ classes = {x for x in classes if not hasattr(x, "__constants__")}
+ for cls in classes:
+ cls.__annotations__["training"] = torch.jit.Final[bool]
+ yield
+ for cls in classes:
+ cls.__annotations__["training"] = bool
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/__init__.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..761a3d1c7afa049e9779ee9fc4d299e9aae38cad
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/__init__.py
@@ -0,0 +1,26 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+from .batch_norm import FrozenBatchNorm2d, get_norm, NaiveSyncBatchNorm, CycleBatchNormList
+from .deform_conv import DeformConv, ModulatedDeformConv
+from .mask_ops import paste_masks_in_image
+from .nms import batched_nms, batched_nms_rotated, nms, nms_rotated
+from .roi_align import ROIAlign, roi_align
+from .roi_align_rotated import ROIAlignRotated, roi_align_rotated
+from .shape_spec import ShapeSpec
+from .wrappers import (
+ BatchNorm2d,
+ Conv2d,
+ ConvTranspose2d,
+ cat,
+ interpolate,
+ Linear,
+ nonzero_tuple,
+ cross_entropy,
+ empty_input_loss_func_wrapper,
+ shapes_to_tensor,
+ move_device_like,
+)
+from .blocks import CNNBlockBase, DepthwiseSeparableConv2d
+from .aspp import ASPP
+from .losses import ciou_loss, diou_loss
+
+__all__ = [k for k in globals().keys() if not k.startswith("_")]
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/aspp.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/aspp.py
new file mode 100644
index 0000000000000000000000000000000000000000..14861aa9ede4fea6a69a49f189bcab997b558148
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/aspp.py
@@ -0,0 +1,144 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+from copy import deepcopy
+import fvcore.nn.weight_init as weight_init
+import torch
+from torch import nn
+from torch.nn import functional as F
+
+from .batch_norm import get_norm
+from .blocks import DepthwiseSeparableConv2d
+from .wrappers import Conv2d
+
+
+class ASPP(nn.Module):
+ """
+ Atrous Spatial Pyramid Pooling (ASPP).
+ """
+
+ def __init__(
+ self,
+ in_channels,
+ out_channels,
+ dilations,
+ *,
+ norm,
+ activation,
+ pool_kernel_size=None,
+ dropout: float = 0.0,
+ use_depthwise_separable_conv=False,
+ ):
+ """
+ Args:
+ in_channels (int): number of input channels for ASPP.
+ out_channels (int): number of output channels.
+ dilations (list): a list of 3 dilations in ASPP.
+ norm (str or callable): normalization for all conv layers.
+ See :func:`layers.get_norm` for supported format. norm is
+ applied to all conv layers except the conv following
+ global average pooling.
+ activation (callable): activation function.
+ pool_kernel_size (tuple, list): the average pooling size (kh, kw)
+ for image pooling layer in ASPP. If set to None, it always
+ performs global average pooling. If not None, it must be
+ divisible by the shape of inputs in forward(). It is recommended
+ to use a fixed input feature size in training, and set this
+ option to match this size, so that it performs global average
+ pooling in training, and the size of the pooling window stays
+ consistent in inference.
+ dropout (float): apply dropout on the output of ASPP. It is used in
+ the official DeepLab implementation with a rate of 0.1:
+ https://github.com/tensorflow/models/blob/21b73d22f3ed05b650e85ac50849408dd36de32e/research/deeplab/model.py#L532 # noqa
+ use_depthwise_separable_conv (bool): use DepthwiseSeparableConv2d
+ for 3x3 convs in ASPP, proposed in :paper:`DeepLabV3+`.
+ """
+ super(ASPP, self).__init__()
+ assert len(dilations) == 3, "ASPP expects 3 dilations, got {}".format(len(dilations))
+ self.pool_kernel_size = pool_kernel_size
+ self.dropout = dropout
+ use_bias = norm == ""
+ self.convs = nn.ModuleList()
+ # conv 1x1
+ self.convs.append(
+ Conv2d(
+ in_channels,
+ out_channels,
+ kernel_size=1,
+ bias=use_bias,
+ norm=get_norm(norm, out_channels),
+ activation=deepcopy(activation),
+ )
+ )
+ weight_init.c2_xavier_fill(self.convs[-1])
+ # atrous convs
+ for dilation in dilations:
+ if use_depthwise_separable_conv:
+ self.convs.append(
+ DepthwiseSeparableConv2d(
+ in_channels,
+ out_channels,
+ kernel_size=3,
+ padding=dilation,
+ dilation=dilation,
+ norm1=norm,
+ activation1=deepcopy(activation),
+ norm2=norm,
+ activation2=deepcopy(activation),
+ )
+ )
+ else:
+ self.convs.append(
+ Conv2d(
+ in_channels,
+ out_channels,
+ kernel_size=3,
+ padding=dilation,
+ dilation=dilation,
+ bias=use_bias,
+ norm=get_norm(norm, out_channels),
+ activation=deepcopy(activation),
+ )
+ )
+ weight_init.c2_xavier_fill(self.convs[-1])
+ # image pooling
+ # We do not add BatchNorm because the spatial resolution is 1x1,
+ # the original TF implementation has BatchNorm.
+ if pool_kernel_size is None:
+ image_pooling = nn.Sequential(
+ nn.AdaptiveAvgPool2d(1),
+ Conv2d(in_channels, out_channels, 1, bias=True, activation=deepcopy(activation)),
+ )
+ else:
+ image_pooling = nn.Sequential(
+ nn.AvgPool2d(kernel_size=pool_kernel_size, stride=1),
+ Conv2d(in_channels, out_channels, 1, bias=True, activation=deepcopy(activation)),
+ )
+ weight_init.c2_xavier_fill(image_pooling[1])
+ self.convs.append(image_pooling)
+
+ self.project = Conv2d(
+ 5 * out_channels,
+ out_channels,
+ kernel_size=1,
+ bias=use_bias,
+ norm=get_norm(norm, out_channels),
+ activation=deepcopy(activation),
+ )
+ weight_init.c2_xavier_fill(self.project)
+
+ def forward(self, x):
+ size = x.shape[-2:]
+ if self.pool_kernel_size is not None:
+ if size[0] % self.pool_kernel_size[0] or size[1] % self.pool_kernel_size[1]:
+ raise ValueError(
+ "`pool_kernel_size` must be divisible by the shape of inputs. "
+ "Input size: {} `pool_kernel_size`: {}".format(size, self.pool_kernel_size)
+ )
+ res = []
+ for conv in self.convs:
+ res.append(conv(x))
+ res[-1] = F.interpolate(res[-1], size=size, mode="bilinear", align_corners=False)
+ res = torch.cat(res, dim=1)
+ res = self.project(res)
+ res = F.dropout(res, self.dropout, training=self.training) if self.dropout > 0 else res
+ return res
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/batch_norm.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/batch_norm.py
new file mode 100644
index 0000000000000000000000000000000000000000..24899c56420a3e8db3793f580566ee9d8d44f84c
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/batch_norm.py
@@ -0,0 +1,300 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+import torch
+import torch.distributed as dist
+from fvcore.nn.distributed import differentiable_all_reduce
+from torch import nn
+from torch.nn import functional as F
+
+from custom_detectron2.utils import comm, env
+
+from .wrappers import BatchNorm2d
+
+
+class FrozenBatchNorm2d(nn.Module):
+ """
+ BatchNorm2d where the batch statistics and the affine parameters are fixed.
+
+ It contains non-trainable buffers called
+ "weight" and "bias", "running_mean", "running_var",
+ initialized to perform identity transformation.
+
+ The pre-trained backbone models from Caffe2 only contain "weight" and "bias",
+ which are computed from the original four parameters of BN.
+ The affine transform `x * weight + bias` will perform the equivalent
+ computation of `(x - running_mean) / sqrt(running_var) * weight + bias`.
+ When loading a backbone model from Caffe2, "running_mean" and "running_var"
+ will be left unchanged as identity transformation.
+
+ Other pre-trained backbone models may contain all 4 parameters.
+
+ The forward is implemented by `F.batch_norm(..., training=False)`.
+ """
+
+ _version = 3
+
+ def __init__(self, num_features, eps=1e-5):
+ super().__init__()
+ self.num_features = num_features
+ self.eps = eps
+ self.register_buffer("weight", torch.ones(num_features))
+ self.register_buffer("bias", torch.zeros(num_features))
+ self.register_buffer("running_mean", torch.zeros(num_features))
+ self.register_buffer("running_var", torch.ones(num_features) - eps)
+
+ def forward(self, x):
+ if x.requires_grad:
+ # When gradients are needed, F.batch_norm will use extra memory
+ # because its backward op computes gradients for weight/bias as well.
+ scale = self.weight * (self.running_var + self.eps).rsqrt()
+ bias = self.bias - self.running_mean * scale
+ scale = scale.reshape(1, -1, 1, 1)
+ bias = bias.reshape(1, -1, 1, 1)
+ out_dtype = x.dtype # may be half
+ return x * scale.to(out_dtype) + bias.to(out_dtype)
+ else:
+ # When gradients are not needed, F.batch_norm is a single fused op
+ # and provide more optimization opportunities.
+ return F.batch_norm(
+ x,
+ self.running_mean,
+ self.running_var,
+ self.weight,
+ self.bias,
+ training=False,
+ eps=self.eps,
+ )
+
+ def _load_from_state_dict(
+ self, state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs
+ ):
+ version = local_metadata.get("version", None)
+
+ if version is None or version < 2:
+ # No running_mean/var in early versions
+ # This will silent the warnings
+ if prefix + "running_mean" not in state_dict:
+ state_dict[prefix + "running_mean"] = torch.zeros_like(self.running_mean)
+ if prefix + "running_var" not in state_dict:
+ state_dict[prefix + "running_var"] = torch.ones_like(self.running_var)
+
+ super()._load_from_state_dict(
+ state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs
+ )
+
+ def __repr__(self):
+ return "FrozenBatchNorm2d(num_features={}, eps={})".format(self.num_features, self.eps)
+
+ @classmethod
+ def convert_frozen_batchnorm(cls, module):
+ """
+ Convert all BatchNorm/SyncBatchNorm in module into FrozenBatchNorm.
+
+ Args:
+ module (torch.nn.Module):
+
+ Returns:
+ If module is BatchNorm/SyncBatchNorm, returns a new module.
+ Otherwise, in-place convert module and return it.
+
+ Similar to convert_sync_batchnorm in
+ https://github.com/pytorch/pytorch/blob/master/torch/nn/modules/batchnorm.py
+ """
+ bn_module = nn.modules.batchnorm
+ bn_module = (bn_module.BatchNorm2d, bn_module.SyncBatchNorm)
+ res = module
+ if isinstance(module, bn_module):
+ res = cls(module.num_features)
+ if module.affine:
+ res.weight.data = module.weight.data.clone().detach()
+ res.bias.data = module.bias.data.clone().detach()
+ res.running_mean.data = module.running_mean.data
+ res.running_var.data = module.running_var.data
+ res.eps = module.eps
+ else:
+ for name, child in module.named_children():
+ new_child = cls.convert_frozen_batchnorm(child)
+ if new_child is not child:
+ res.add_module(name, new_child)
+ return res
+
+
+def get_norm(norm, out_channels):
+ """
+ Args:
+ norm (str or callable): either one of BN, SyncBN, FrozenBN, GN;
+ or a callable that takes a channel number and returns
+ the normalization layer as a nn.Module.
+
+ Returns:
+ nn.Module or None: the normalization layer
+ """
+ if norm is None:
+ return None
+ if isinstance(norm, str):
+ if len(norm) == 0:
+ return None
+ norm = {
+ "BN": BatchNorm2d,
+ # Fixed in https://github.com/pytorch/pytorch/pull/36382
+ "SyncBN": NaiveSyncBatchNorm if env.TORCH_VERSION <= (1, 5) else nn.SyncBatchNorm,
+ "FrozenBN": FrozenBatchNorm2d,
+ "GN": lambda channels: nn.GroupNorm(32, channels),
+ # for debugging:
+ "nnSyncBN": nn.SyncBatchNorm,
+ "naiveSyncBN": NaiveSyncBatchNorm,
+ # expose stats_mode N as an option to caller, required for zero-len inputs
+ "naiveSyncBN_N": lambda channels: NaiveSyncBatchNorm(channels, stats_mode="N"),
+ "LN": lambda channels: LayerNorm(channels),
+ }[norm]
+ return norm(out_channels)
+
+
+class NaiveSyncBatchNorm(BatchNorm2d):
+ """
+ In PyTorch<=1.5, ``nn.SyncBatchNorm`` has incorrect gradient
+ when the batch size on each worker is different.
+ (e.g., when scale augmentation is used, or when it is applied to mask head).
+
+ This is a slower but correct alternative to `nn.SyncBatchNorm`.
+
+ Note:
+ There isn't a single definition of Sync BatchNorm.
+
+ When ``stats_mode==""``, this module computes overall statistics by using
+ statistics of each worker with equal weight. The result is true statistics
+ of all samples (as if they are all on one worker) only when all workers
+ have the same (N, H, W). This mode does not support inputs with zero batch size.
+
+ When ``stats_mode=="N"``, this module computes overall statistics by weighting
+ the statistics of each worker by their ``N``. The result is true statistics
+ of all samples (as if they are all on one worker) only when all workers
+ have the same (H, W). It is slower than ``stats_mode==""``.
+
+ Even though the result of this module may not be the true statistics of all samples,
+ it may still be reasonable because it might be preferrable to assign equal weights
+ to all workers, regardless of their (H, W) dimension, instead of putting larger weight
+ on larger images. From preliminary experiments, little difference is found between such
+ a simplified implementation and an accurate computation of overall mean & variance.
+ """
+
+ def __init__(self, *args, stats_mode="", **kwargs):
+ super().__init__(*args, **kwargs)
+ assert stats_mode in ["", "N"]
+ self._stats_mode = stats_mode
+
+ def forward(self, input):
+ if comm.get_world_size() == 1 or not self.training:
+ return super().forward(input)
+
+ B, C = input.shape[0], input.shape[1]
+
+ half_input = input.dtype == torch.float16
+ if half_input:
+ # fp16 does not have good enough numerics for the reduction here
+ input = input.float()
+ mean = torch.mean(input, dim=[0, 2, 3])
+ meansqr = torch.mean(input * input, dim=[0, 2, 3])
+
+ if self._stats_mode == "":
+ assert B > 0, 'SyncBatchNorm(stats_mode="") does not support zero batch size.'
+ vec = torch.cat([mean, meansqr], dim=0)
+ vec = differentiable_all_reduce(vec) * (1.0 / dist.get_world_size())
+ mean, meansqr = torch.split(vec, C)
+ momentum = self.momentum
+ else:
+ if B == 0:
+ vec = torch.zeros([2 * C + 1], device=mean.device, dtype=mean.dtype)
+ vec = vec + input.sum() # make sure there is gradient w.r.t input
+ else:
+ vec = torch.cat(
+ [mean, meansqr, torch.ones([1], device=mean.device, dtype=mean.dtype)], dim=0
+ )
+ vec = differentiable_all_reduce(vec * B)
+
+ total_batch = vec[-1].detach()
+ momentum = total_batch.clamp(max=1) * self.momentum # no update if total_batch is 0
+ mean, meansqr, _ = torch.split(vec / total_batch.clamp(min=1), C) # avoid div-by-zero
+
+ var = meansqr - mean * mean
+ invstd = torch.rsqrt(var + self.eps)
+ scale = self.weight * invstd
+ bias = self.bias - mean * scale
+ scale = scale.reshape(1, -1, 1, 1)
+ bias = bias.reshape(1, -1, 1, 1)
+
+ self.running_mean += momentum * (mean.detach() - self.running_mean)
+ self.running_var += momentum * (var.detach() - self.running_var)
+ ret = input * scale + bias
+ if half_input:
+ ret = ret.half()
+ return ret
+
+
+class CycleBatchNormList(nn.ModuleList):
+ """
+ Implement domain-specific BatchNorm by cycling.
+
+ When a BatchNorm layer is used for multiple input domains or input
+ features, it might need to maintain a separate test-time statistics
+ for each domain. See Sec 5.2 in :paper:`rethinking-batchnorm`.
+
+ This module implements it by using N separate BN layers
+ and it cycles through them every time a forward() is called.
+
+ NOTE: The caller of this module MUST guarantee to always call
+ this module by multiple of N times. Otherwise its test-time statistics
+ will be incorrect.
+ """
+
+ def __init__(self, length: int, bn_class=nn.BatchNorm2d, **kwargs):
+ """
+ Args:
+ length: number of BatchNorm layers to cycle.
+ bn_class: the BatchNorm class to use
+ kwargs: arguments of the BatchNorm class, such as num_features.
+ """
+ self._affine = kwargs.pop("affine", True)
+ super().__init__([bn_class(**kwargs, affine=False) for k in range(length)])
+ if self._affine:
+ # shared affine, domain-specific BN
+ channels = self[0].num_features
+ self.weight = nn.Parameter(torch.ones(channels))
+ self.bias = nn.Parameter(torch.zeros(channels))
+ self._pos = 0
+
+ def forward(self, x):
+ ret = self[self._pos](x)
+ self._pos = (self._pos + 1) % len(self)
+
+ if self._affine:
+ w = self.weight.reshape(1, -1, 1, 1)
+ b = self.bias.reshape(1, -1, 1, 1)
+ return ret * w + b
+ else:
+ return ret
+
+ def extra_repr(self):
+ return f"affine={self._affine}"
+
+
+class LayerNorm(nn.Module):
+ """
+ A LayerNorm variant, popularized by Transformers, that performs point-wise mean and
+ variance normalization over the channel dimension for inputs that have shape
+ (batch_size, channels, height, width).
+ https://github.com/facebookresearch/ConvNeXt/blob/d1fa8f6fef0a165b27399986cc2bdacc92777e40/models/convnext.py#L119 # noqa B950
+ """
+
+ def __init__(self, normalized_shape, eps=1e-6):
+ super().__init__()
+ self.weight = nn.Parameter(torch.ones(normalized_shape))
+ self.bias = nn.Parameter(torch.zeros(normalized_shape))
+ self.eps = eps
+ self.normalized_shape = (normalized_shape,)
+
+ def forward(self, x):
+ u = x.mean(1, keepdim=True)
+ s = (x - u).pow(2).mean(1, keepdim=True)
+ x = (x - u) / torch.sqrt(s + self.eps)
+ x = self.weight[:, None, None] * x + self.bias[:, None, None]
+ return x
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/blocks.py b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/blocks.py
new file mode 100644
index 0000000000000000000000000000000000000000..1995a4bf7339e8deb7eaaffda4f819dda55e7ac7
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/blocks.py
@@ -0,0 +1,111 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) Facebook, Inc. and its affiliates.
+
+import fvcore.nn.weight_init as weight_init
+from torch import nn
+
+from .batch_norm import FrozenBatchNorm2d, get_norm
+from .wrappers import Conv2d
+
+
+"""
+CNN building blocks.
+"""
+
+
+class CNNBlockBase(nn.Module):
+ """
+ A CNN block is assumed to have input channels, output channels and a stride.
+ The input and output of `forward()` method must be NCHW tensors.
+ The method can perform arbitrary computation but must match the given
+ channels and stride specification.
+
+ Attribute:
+ in_channels (int):
+ out_channels (int):
+ stride (int):
+ """
+
+ def __init__(self, in_channels, out_channels, stride):
+ """
+ The `__init__` method of any subclass should also contain these arguments.
+
+ Args:
+ in_channels (int):
+ out_channels (int):
+ stride (int):
+ """
+ super().__init__()
+ self.in_channels = in_channels
+ self.out_channels = out_channels
+ self.stride = stride
+
+ def freeze(self):
+ """
+ Make this block not trainable.
+ This method sets all parameters to `requires_grad=False`,
+ and convert all BatchNorm layers to FrozenBatchNorm
+
+ Returns:
+ the block itself
+ """
+ for p in self.parameters():
+ p.requires_grad = False
+ FrozenBatchNorm2d.convert_frozen_batchnorm(self)
+ return self
+
+
+class DepthwiseSeparableConv2d(nn.Module):
+ """
+ A kxk depthwise convolution + a 1x1 convolution.
+
+ In :paper:`xception`, norm & activation are applied on the second conv.
+ :paper:`mobilenet` uses norm & activation on both convs.
+ """
+
+ def __init__(
+ self,
+ in_channels,
+ out_channels,
+ kernel_size=3,
+ padding=1,
+ dilation=1,
+ *,
+ norm1=None,
+ activation1=None,
+ norm2=None,
+ activation2=None,
+ ):
+ """
+ Args:
+ norm1, norm2 (str or callable): normalization for the two conv layers.
+ activation1, activation2 (callable(Tensor) -> Tensor): activation
+ function for the two conv layers.
+ """
+ super().__init__()
+ self.depthwise = Conv2d(
+ in_channels,
+ in_channels,
+ kernel_size=kernel_size,
+ padding=padding,
+ dilation=dilation,
+ groups=in_channels,
+ bias=not norm1,
+ norm=get_norm(norm1, in_channels),
+ activation=activation1,
+ )
+ self.pointwise = Conv2d(
+ in_channels,
+ out_channels,
+ kernel_size=1,
+ bias=not norm2,
+ norm=get_norm(norm2, out_channels),
+ activation=activation2,
+ )
+
+ # default initialization
+ weight_init.c2_msra_fill(self.depthwise)
+ weight_init.c2_msra_fill(self.pointwise)
+
+ def forward(self, x):
+ return self.pointwise(self.depthwise(x))
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/README.md b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..778ed3da0bae89820831bcd8a72ff7b9cad8d4dd
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/README.md
@@ -0,0 +1,7 @@
+
+
+To add a new Op:
+
+1. Create a new directory
+2. Implement new ops there
+3. Delcare its Python interface in `vision.cpp`.
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/ROIAlignRotated/ROIAlignRotated.h b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/ROIAlignRotated/ROIAlignRotated.h
new file mode 100644
index 0000000000000000000000000000000000000000..03f4211003f42f601f0cfcf4a690f5da4a0a1f67
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/ROIAlignRotated/ROIAlignRotated.h
@@ -0,0 +1,115 @@
+// Copyright (c) Facebook, Inc. and its affiliates.
+#pragma once
+#include
+
+namespace detectron2 {
+
+at::Tensor ROIAlignRotated_forward_cpu(
+ const at::Tensor& input,
+ const at::Tensor& rois,
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio);
+
+at::Tensor ROIAlignRotated_backward_cpu(
+ const at::Tensor& grad,
+ const at::Tensor& rois,
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int batch_size,
+ const int channels,
+ const int height,
+ const int width,
+ const int sampling_ratio);
+
+#if defined(WITH_CUDA) || defined(WITH_HIP)
+at::Tensor ROIAlignRotated_forward_cuda(
+ const at::Tensor& input,
+ const at::Tensor& rois,
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio);
+
+at::Tensor ROIAlignRotated_backward_cuda(
+ const at::Tensor& grad,
+ const at::Tensor& rois,
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int batch_size,
+ const int channels,
+ const int height,
+ const int width,
+ const int sampling_ratio);
+#endif
+
+// Interface for Python
+inline at::Tensor ROIAlignRotated_forward(
+ const at::Tensor& input,
+ const at::Tensor& rois,
+ const double spatial_scale,
+ const int64_t pooled_height,
+ const int64_t pooled_width,
+ const int64_t sampling_ratio) {
+ if (input.is_cuda()) {
+#if defined(WITH_CUDA) || defined(WITH_HIP)
+ return ROIAlignRotated_forward_cuda(
+ input,
+ rois,
+ spatial_scale,
+ pooled_height,
+ pooled_width,
+ sampling_ratio);
+#else
+ AT_ERROR("Detectron2 is not compiled with GPU support!");
+#endif
+ }
+ return ROIAlignRotated_forward_cpu(
+ input, rois, spatial_scale, pooled_height, pooled_width, sampling_ratio);
+}
+
+inline at::Tensor ROIAlignRotated_backward(
+ const at::Tensor& grad,
+ const at::Tensor& rois,
+ const double spatial_scale,
+ const int64_t pooled_height,
+ const int64_t pooled_width,
+ const int64_t batch_size,
+ const int64_t channels,
+ const int64_t height,
+ const int64_t width,
+ const int64_t sampling_ratio) {
+ if (grad.is_cuda()) {
+#if defined(WITH_CUDA) || defined(WITH_HIP)
+ return ROIAlignRotated_backward_cuda(
+ grad,
+ rois,
+ spatial_scale,
+ pooled_height,
+ pooled_width,
+ batch_size,
+ channels,
+ height,
+ width,
+ sampling_ratio);
+#else
+ AT_ERROR("Detectron2 is not compiled with GPU support!");
+#endif
+ }
+ return ROIAlignRotated_backward_cpu(
+ grad,
+ rois,
+ spatial_scale,
+ pooled_height,
+ pooled_width,
+ batch_size,
+ channels,
+ height,
+ width,
+ sampling_ratio);
+}
+
+} // namespace detectron2
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/ROIAlignRotated/ROIAlignRotated_cpu.cpp b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/ROIAlignRotated/ROIAlignRotated_cpu.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2a3d3056cc71a4acaafb570739a9dd247a7eb1ed
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/ROIAlignRotated/ROIAlignRotated_cpu.cpp
@@ -0,0 +1,522 @@
+// Copyright (c) Facebook, Inc. and its affiliates.
+#include
+#include "ROIAlignRotated.h"
+
+// Note: this implementation originates from the Caffe2 ROIAlignRotated Op
+// and PyTorch ROIAlign (non-rotated) Op implementations.
+// The key difference between this implementation and those ones is
+// we don't do "legacy offset" in this version, as there aren't many previous
+// works, if any, using the "legacy" ROIAlignRotated Op.
+// This would make the interface a bit cleaner.
+
+namespace detectron2 {
+
+namespace {
+template
+struct PreCalc {
+ int pos1;
+ int pos2;
+ int pos3;
+ int pos4;
+ T w1;
+ T w2;
+ T w3;
+ T w4;
+};
+
+template
+void pre_calc_for_bilinear_interpolate(
+ const int height,
+ const int width,
+ const int pooled_height,
+ const int pooled_width,
+ const int iy_upper,
+ const int ix_upper,
+ T roi_start_h,
+ T roi_start_w,
+ T bin_size_h,
+ T bin_size_w,
+ int roi_bin_grid_h,
+ int roi_bin_grid_w,
+ T roi_center_h,
+ T roi_center_w,
+ T cos_theta,
+ T sin_theta,
+ std::vector>& pre_calc) {
+ int pre_calc_index = 0;
+ for (int ph = 0; ph < pooled_height; ph++) {
+ for (int pw = 0; pw < pooled_width; pw++) {
+ for (int iy = 0; iy < iy_upper; iy++) {
+ const T yy = roi_start_h + ph * bin_size_h +
+ static_cast(iy + .5f) * bin_size_h /
+ static_cast(roi_bin_grid_h); // e.g., 0.5, 1.5
+ for (int ix = 0; ix < ix_upper; ix++) {
+ const T xx = roi_start_w + pw * bin_size_w +
+ static_cast(ix + .5f) * bin_size_w /
+ static_cast(roi_bin_grid_w);
+
+ // Rotate by theta around the center and translate
+ // In image space, (y, x) is the order for Right Handed System,
+ // and this is essentially multiplying the point by a rotation matrix
+ // to rotate it counterclockwise through angle theta.
+ T y = yy * cos_theta - xx * sin_theta + roi_center_h;
+ T x = yy * sin_theta + xx * cos_theta + roi_center_w;
+ // deal with: inverse elements are out of feature map boundary
+ if (y < -1.0 || y > height || x < -1.0 || x > width) {
+ // empty
+ PreCalc pc;
+ pc.pos1 = 0;
+ pc.pos2 = 0;
+ pc.pos3 = 0;
+ pc.pos4 = 0;
+ pc.w1 = 0;
+ pc.w2 = 0;
+ pc.w3 = 0;
+ pc.w4 = 0;
+ pre_calc[pre_calc_index] = pc;
+ pre_calc_index += 1;
+ continue;
+ }
+
+ if (y < 0) {
+ y = 0;
+ }
+ if (x < 0) {
+ x = 0;
+ }
+
+ int y_low = (int)y;
+ int x_low = (int)x;
+ int y_high;
+ int x_high;
+
+ if (y_low >= height - 1) {
+ y_high = y_low = height - 1;
+ y = (T)y_low;
+ } else {
+ y_high = y_low + 1;
+ }
+
+ if (x_low >= width - 1) {
+ x_high = x_low = width - 1;
+ x = (T)x_low;
+ } else {
+ x_high = x_low + 1;
+ }
+
+ T ly = y - y_low;
+ T lx = x - x_low;
+ T hy = 1. - ly, hx = 1. - lx;
+ T w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;
+
+ // save weights and indices
+ PreCalc pc;
+ pc.pos1 = y_low * width + x_low;
+ pc.pos2 = y_low * width + x_high;
+ pc.pos3 = y_high * width + x_low;
+ pc.pos4 = y_high * width + x_high;
+ pc.w1 = w1;
+ pc.w2 = w2;
+ pc.w3 = w3;
+ pc.w4 = w4;
+ pre_calc[pre_calc_index] = pc;
+
+ pre_calc_index += 1;
+ }
+ }
+ }
+ }
+}
+
+template
+void bilinear_interpolate_gradient(
+ const int height,
+ const int width,
+ T y,
+ T x,
+ T& w1,
+ T& w2,
+ T& w3,
+ T& w4,
+ int& x_low,
+ int& x_high,
+ int& y_low,
+ int& y_high) {
+ // deal with cases that inverse elements are out of feature map boundary
+ if (y < -1.0 || y > height || x < -1.0 || x > width) {
+ // empty
+ w1 = w2 = w3 = w4 = 0.;
+ x_low = x_high = y_low = y_high = -1;
+ return;
+ }
+
+ if (y < 0) {
+ y = 0;
+ }
+
+ if (x < 0) {
+ x = 0;
+ }
+
+ y_low = (int)y;
+ x_low = (int)x;
+
+ if (y_low >= height - 1) {
+ y_high = y_low = height - 1;
+ y = (T)y_low;
+ } else {
+ y_high = y_low + 1;
+ }
+
+ if (x_low >= width - 1) {
+ x_high = x_low = width - 1;
+ x = (T)x_low;
+ } else {
+ x_high = x_low + 1;
+ }
+
+ T ly = y - y_low;
+ T lx = x - x_low;
+ T hy = 1. - ly, hx = 1. - lx;
+
+ // reference in forward
+ // T v1 = input[y_low * width + x_low];
+ // T v2 = input[y_low * width + x_high];
+ // T v3 = input[y_high * width + x_low];
+ // T v4 = input[y_high * width + x_high];
+ // T val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4);
+
+ w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;
+
+ return;
+}
+
+template
+inline void add(T* address, const T& val) {
+ *address += val;
+}
+
+} // namespace
+
+template
+void ROIAlignRotatedForward(
+ const int nthreads,
+ const T* input,
+ const T& spatial_scale,
+ const int channels,
+ const int height,
+ const int width,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio,
+ const T* rois,
+ T* output) {
+ int n_rois = nthreads / channels / pooled_width / pooled_height;
+ // (n, c, ph, pw) is an element in the pooled output
+ // can be parallelized using omp
+ // #pragma omp parallel for num_threads(32)
+ for (int n = 0; n < n_rois; n++) {
+ int index_n = n * channels * pooled_width * pooled_height;
+
+ const T* current_roi = rois + n * 6;
+ int roi_batch_ind = current_roi[0];
+
+ // Do not use rounding; this implementation detail is critical
+ // ROIAlignRotated supports align == true, i.e., continuous coordinate
+ // by default, thus the 0.5 offset
+ T offset = (T)0.5;
+ T roi_center_w = current_roi[1] * spatial_scale - offset;
+ T roi_center_h = current_roi[2] * spatial_scale - offset;
+ T roi_width = current_roi[3] * spatial_scale;
+ T roi_height = current_roi[4] * spatial_scale;
+ T theta = current_roi[5] * M_PI / 180.0;
+ T cos_theta = cos(theta);
+ T sin_theta = sin(theta);
+
+ AT_ASSERTM(
+ roi_width >= 0 && roi_height >= 0,
+ "ROIs in ROIAlignRotated do not have non-negative size!");
+
+ T bin_size_h = static_cast(roi_height) / static_cast(pooled_height);
+ T bin_size_w = static_cast(roi_width) / static_cast(pooled_width);
+
+ // We use roi_bin_grid to sample the grid and mimic integral
+ int roi_bin_grid_h = (sampling_ratio > 0)
+ ? sampling_ratio
+ : ceil(roi_height / pooled_height); // e.g., = 2
+ int roi_bin_grid_w =
+ (sampling_ratio > 0) ? sampling_ratio : ceil(roi_width / pooled_width);
+
+ // We do average (integral) pooling inside a bin
+ const T count = std::max(roi_bin_grid_h * roi_bin_grid_w, 1); // e.g. = 4
+
+ // we want to precalculate indices and weights shared by all channels,
+ // this is the key point of optimization
+ std::vector> pre_calc(
+ roi_bin_grid_h * roi_bin_grid_w * pooled_width * pooled_height);
+
+ // roi_start_h and roi_start_w are computed wrt the center of RoI (x, y).
+ // Appropriate translation needs to be applied after.
+ T roi_start_h = -roi_height / 2.0;
+ T roi_start_w = -roi_width / 2.0;
+
+ pre_calc_for_bilinear_interpolate(
+ height,
+ width,
+ pooled_height,
+ pooled_width,
+ roi_bin_grid_h,
+ roi_bin_grid_w,
+ roi_start_h,
+ roi_start_w,
+ bin_size_h,
+ bin_size_w,
+ roi_bin_grid_h,
+ roi_bin_grid_w,
+ roi_center_h,
+ roi_center_w,
+ cos_theta,
+ sin_theta,
+ pre_calc);
+
+ for (int c = 0; c < channels; c++) {
+ int index_n_c = index_n + c * pooled_width * pooled_height;
+ const T* offset_input =
+ input + (roi_batch_ind * channels + c) * height * width;
+ int pre_calc_index = 0;
+
+ for (int ph = 0; ph < pooled_height; ph++) {
+ for (int pw = 0; pw < pooled_width; pw++) {
+ int index = index_n_c + ph * pooled_width + pw;
+
+ T output_val = 0.;
+ for (int iy = 0; iy < roi_bin_grid_h; iy++) {
+ for (int ix = 0; ix < roi_bin_grid_w; ix++) {
+ PreCalc pc = pre_calc[pre_calc_index];
+ output_val += pc.w1 * offset_input[pc.pos1] +
+ pc.w2 * offset_input[pc.pos2] +
+ pc.w3 * offset_input[pc.pos3] + pc.w4 * offset_input[pc.pos4];
+
+ pre_calc_index += 1;
+ }
+ }
+ output_val /= count;
+
+ output[index] = output_val;
+ } // for pw
+ } // for ph
+ } // for c
+ } // for n
+}
+
+template
+void ROIAlignRotatedBackward(
+ const int nthreads,
+ // may not be contiguous. should index using n_stride, etc
+ const T* grad_output,
+ const T& spatial_scale,
+ const int channels,
+ const int height,
+ const int width,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio,
+ T* grad_input,
+ const T* rois,
+ const int n_stride,
+ const int c_stride,
+ const int h_stride,
+ const int w_stride) {
+ for (int index = 0; index < nthreads; index++) {
+ // (n, c, ph, pw) is an element in the pooled output
+ int pw = index % pooled_width;
+ int ph = (index / pooled_width) % pooled_height;
+ int c = (index / pooled_width / pooled_height) % channels;
+ int n = index / pooled_width / pooled_height / channels;
+
+ const T* current_roi = rois + n * 6;
+ int roi_batch_ind = current_roi[0];
+
+ // Do not use rounding; this implementation detail is critical
+ // ROIAlignRotated supports align == true, i.e., continuous coordinate
+ // by default, thus the 0.5 offset
+ T offset = (T)0.5;
+ T roi_center_w = current_roi[1] * spatial_scale - offset;
+ T roi_center_h = current_roi[2] * spatial_scale - offset;
+ T roi_width = current_roi[3] * spatial_scale;
+ T roi_height = current_roi[4] * spatial_scale;
+ T theta = current_roi[5] * M_PI / 180.0;
+ T cos_theta = cos(theta);
+ T sin_theta = sin(theta);
+
+ AT_ASSERTM(
+ roi_width >= 0 && roi_height >= 0,
+ "ROIs in ROIAlignRotated do not have non-negative size!");
+
+ T bin_size_h = static_cast(roi_height) / static_cast(pooled_height);
+ T bin_size_w = static_cast(roi_width) / static_cast(pooled_width);
+
+ T* offset_grad_input =
+ grad_input + ((roi_batch_ind * channels + c) * height * width);
+
+ int output_offset = n * n_stride + c * c_stride;
+ const T* offset_grad_output = grad_output + output_offset;
+ const T grad_output_this_bin =
+ offset_grad_output[ph * h_stride + pw * w_stride];
+
+ // We use roi_bin_grid to sample the grid and mimic integral
+ int roi_bin_grid_h = (sampling_ratio > 0)
+ ? sampling_ratio
+ : ceil(roi_height / pooled_height); // e.g., = 2
+ int roi_bin_grid_w =
+ (sampling_ratio > 0) ? sampling_ratio : ceil(roi_width / pooled_width);
+
+ // roi_start_h and roi_start_w are computed wrt the center of RoI (x, y).
+ // Appropriate translation needs to be applied after.
+ T roi_start_h = -roi_height / 2.0;
+ T roi_start_w = -roi_width / 2.0;
+
+ // We do average (integral) pooling inside a bin
+ const T count = roi_bin_grid_h * roi_bin_grid_w; // e.g. = 4
+
+ for (int iy = 0; iy < roi_bin_grid_h; iy++) {
+ const T yy = roi_start_h + ph * bin_size_h +
+ static_cast(iy + .5f) * bin_size_h /
+ static_cast(roi_bin_grid_h); // e.g., 0.5, 1.5
+ for (int ix = 0; ix < roi_bin_grid_w; ix++) {
+ const T xx = roi_start_w + pw * bin_size_w +
+ static_cast(ix + .5f) * bin_size_w /
+ static_cast(roi_bin_grid_w);
+
+ // Rotate by theta around the center and translate
+ T y = yy * cos_theta - xx * sin_theta + roi_center_h;
+ T x = yy * sin_theta + xx * cos_theta + roi_center_w;
+
+ T w1, w2, w3, w4;
+ int x_low, x_high, y_low, y_high;
+
+ bilinear_interpolate_gradient(
+ height, width, y, x, w1, w2, w3, w4, x_low, x_high, y_low, y_high);
+
+ T g1 = grad_output_this_bin * w1 / count;
+ T g2 = grad_output_this_bin * w2 / count;
+ T g3 = grad_output_this_bin * w3 / count;
+ T g4 = grad_output_this_bin * w4 / count;
+
+ if (x_low >= 0 && x_high >= 0 && y_low >= 0 && y_high >= 0) {
+ // atomic add is not needed for now since it is single threaded
+ add(offset_grad_input + y_low * width + x_low, static_cast(g1));
+ add(offset_grad_input + y_low * width + x_high, static_cast(g2));
+ add(offset_grad_input + y_high * width + x_low, static_cast(g3));
+ add(offset_grad_input + y_high * width + x_high, static_cast(g4));
+ } // if
+ } // ix
+ } // iy
+ } // for
+} // ROIAlignRotatedBackward
+
+at::Tensor ROIAlignRotated_forward_cpu(
+ const at::Tensor& input,
+ const at::Tensor& rois,
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio) {
+ AT_ASSERTM(input.device().is_cpu(), "input must be a CPU tensor");
+ AT_ASSERTM(rois.device().is_cpu(), "rois must be a CPU tensor");
+
+ at::TensorArg input_t{input, "input", 1}, rois_t{rois, "rois", 2};
+
+ at::CheckedFrom c = "ROIAlign_forward_cpu";
+ at::checkAllSameType(c, {input_t, rois_t});
+
+ auto num_rois = rois.size(0);
+ auto channels = input.size(1);
+ auto height = input.size(2);
+ auto width = input.size(3);
+
+ at::Tensor output = at::zeros(
+ {num_rois, channels, pooled_height, pooled_width}, input.options());
+
+ auto output_size = num_rois * pooled_height * pooled_width * channels;
+
+ if (output.numel() == 0) {
+ return output;
+ }
+
+ auto input_ = input.contiguous(), rois_ = rois.contiguous();
+ AT_DISPATCH_FLOATING_TYPES_AND_HALF(
+ input.scalar_type(), "ROIAlignRotated_forward", [&] {
+ ROIAlignRotatedForward(
+ output_size,
+ input_.data_ptr(),
+ spatial_scale,
+ channels,
+ height,
+ width,
+ pooled_height,
+ pooled_width,
+ sampling_ratio,
+ rois_.data_ptr(),
+ output.data_ptr());
+ });
+ return output;
+}
+
+at::Tensor ROIAlignRotated_backward_cpu(
+ const at::Tensor& grad,
+ const at::Tensor& rois,
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int batch_size,
+ const int channels,
+ const int height,
+ const int width,
+ const int sampling_ratio) {
+ AT_ASSERTM(grad.device().is_cpu(), "grad must be a CPU tensor");
+ AT_ASSERTM(rois.device().is_cpu(), "rois must be a CPU tensor");
+
+ at::TensorArg grad_t{grad, "grad", 1}, rois_t{rois, "rois", 2};
+
+ at::CheckedFrom c = "ROIAlignRotated_backward_cpu";
+ at::checkAllSameType(c, {grad_t, rois_t});
+
+ at::Tensor grad_input =
+ at::zeros({batch_size, channels, height, width}, grad.options());
+
+ // handle possibly empty gradients
+ if (grad.numel() == 0) {
+ return grad_input;
+ }
+
+ // get stride values to ensure indexing into gradients is correct.
+ int n_stride = grad.stride(0);
+ int c_stride = grad.stride(1);
+ int h_stride = grad.stride(2);
+ int w_stride = grad.stride(3);
+
+ auto rois_ = rois.contiguous();
+ AT_DISPATCH_FLOATING_TYPES_AND_HALF(
+ grad.scalar_type(), "ROIAlignRotated_forward", [&] {
+ ROIAlignRotatedBackward(
+ grad.numel(),
+ grad.data_ptr(),
+ spatial_scale,
+ channels,
+ height,
+ width,
+ pooled_height,
+ pooled_width,
+ sampling_ratio,
+ grad_input.data_ptr(),
+ rois_.data_ptr(),
+ n_stride,
+ c_stride,
+ h_stride,
+ w_stride);
+ });
+ return grad_input;
+}
+
+} // namespace detectron2
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/ROIAlignRotated/ROIAlignRotated_cuda.cu b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/ROIAlignRotated/ROIAlignRotated_cuda.cu
new file mode 100644
index 0000000000000000000000000000000000000000..fca186519143b168a912c880a4cf495a0a5a9322
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/ROIAlignRotated/ROIAlignRotated_cuda.cu
@@ -0,0 +1,443 @@
+// Copyright (c) Facebook, Inc. and its affiliates.
+#include
+#include
+#include
+#include
+
+// TODO make it in a common file
+#define CUDA_1D_KERNEL_LOOP(i, n) \
+ for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < n; \
+ i += blockDim.x * gridDim.x)
+
+// Note: this implementation originates from the Caffe2 ROIAlignRotated Op
+// and PyTorch ROIAlign (non-rotated) Op implementations.
+// The key difference between this implementation and those ones is
+// we don't do "legacy offset" in this version, as there aren't many previous
+// works, if any, using the "legacy" ROIAlignRotated Op.
+// This would make the interface a bit cleaner.
+
+namespace detectron2 {
+
+namespace {
+
+template
+__device__ T bilinear_interpolate(
+ const T* input,
+ const int height,
+ const int width,
+ T y,
+ T x) {
+ // deal with cases that inverse elements are out of feature map boundary
+ if (y < -1.0 || y > height || x < -1.0 || x > width) {
+ // empty
+ return 0;
+ }
+
+ if (y < 0) {
+ y = 0;
+ }
+
+ if (x < 0) {
+ x = 0;
+ }
+
+ int y_low = (int)y;
+ int x_low = (int)x;
+ int y_high;
+ int x_high;
+
+ if (y_low >= height - 1) {
+ y_high = y_low = height - 1;
+ y = (T)y_low;
+ } else {
+ y_high = y_low + 1;
+ }
+
+ if (x_low >= width - 1) {
+ x_high = x_low = width - 1;
+ x = (T)x_low;
+ } else {
+ x_high = x_low + 1;
+ }
+
+ T ly = y - y_low;
+ T lx = x - x_low;
+ T hy = 1. - ly, hx = 1. - lx;
+ // do bilinear interpolation
+ T v1 = input[y_low * width + x_low];
+ T v2 = input[y_low * width + x_high];
+ T v3 = input[y_high * width + x_low];
+ T v4 = input[y_high * width + x_high];
+ T w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;
+
+ T val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4);
+
+ return val;
+}
+
+template
+__device__ void bilinear_interpolate_gradient(
+ const int height,
+ const int width,
+ T y,
+ T x,
+ T& w1,
+ T& w2,
+ T& w3,
+ T& w4,
+ int& x_low,
+ int& x_high,
+ int& y_low,
+ int& y_high) {
+ // deal with cases that inverse elements are out of feature map boundary
+ if (y < -1.0 || y > height || x < -1.0 || x > width) {
+ // empty
+ w1 = w2 = w3 = w4 = 0.;
+ x_low = x_high = y_low = y_high = -1;
+ return;
+ }
+
+ if (y < 0) {
+ y = 0;
+ }
+
+ if (x < 0) {
+ x = 0;
+ }
+
+ y_low = (int)y;
+ x_low = (int)x;
+
+ if (y_low >= height - 1) {
+ y_high = y_low = height - 1;
+ y = (T)y_low;
+ } else {
+ y_high = y_low + 1;
+ }
+
+ if (x_low >= width - 1) {
+ x_high = x_low = width - 1;
+ x = (T)x_low;
+ } else {
+ x_high = x_low + 1;
+ }
+
+ T ly = y - y_low;
+ T lx = x - x_low;
+ T hy = 1. - ly, hx = 1. - lx;
+
+ // reference in forward
+ // T v1 = input[y_low * width + x_low];
+ // T v2 = input[y_low * width + x_high];
+ // T v3 = input[y_high * width + x_low];
+ // T v4 = input[y_high * width + x_high];
+ // T val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4);
+
+ w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;
+
+ return;
+}
+
+} // namespace
+
+template
+__global__ void RoIAlignRotatedForward(
+ const int nthreads,
+ const T* input,
+ const T spatial_scale,
+ const int channels,
+ const int height,
+ const int width,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio,
+ const T* rois,
+ T* top_data) {
+ CUDA_1D_KERNEL_LOOP(index, nthreads) {
+ // (n, c, ph, pw) is an element in the pooled output
+ int pw = index % pooled_width;
+ int ph = (index / pooled_width) % pooled_height;
+ int c = (index / pooled_width / pooled_height) % channels;
+ int n = index / pooled_width / pooled_height / channels;
+
+ const T* current_roi = rois + n * 6;
+ int roi_batch_ind = current_roi[0];
+
+ // Do not use rounding; this implementation detail is critical
+ // ROIAlignRotated supports align == true, i.e., continuous coordinate
+ // by default, thus the 0.5 offset
+ T offset = (T)0.5;
+ T roi_center_w = current_roi[1] * spatial_scale - offset;
+ T roi_center_h = current_roi[2] * spatial_scale - offset;
+ T roi_width = current_roi[3] * spatial_scale;
+ T roi_height = current_roi[4] * spatial_scale;
+ T theta = current_roi[5] * M_PI / 180.0;
+ T cos_theta = cos(theta);
+ T sin_theta = sin(theta);
+
+ T bin_size_h = static_cast(roi_height) / static_cast(pooled_height);
+ T bin_size_w = static_cast(roi_width) / static_cast(pooled_width);
+
+ const T* offset_input =
+ input + (roi_batch_ind * channels + c) * height * width;
+
+ // We use roi_bin_grid to sample the grid and mimic integral
+ int roi_bin_grid_h = (sampling_ratio > 0)
+ ? sampling_ratio
+ : ceil(roi_height / pooled_height); // e.g., = 2
+ int roi_bin_grid_w =
+ (sampling_ratio > 0) ? sampling_ratio : ceil(roi_width / pooled_width);
+
+ // roi_start_h and roi_start_w are computed wrt the center of RoI (x, y).
+ // Appropriate translation needs to be applied after.
+ T roi_start_h = -roi_height / 2.0;
+ T roi_start_w = -roi_width / 2.0;
+
+ // We do average (inte gral) pooling inside a bin
+ const T count = max(roi_bin_grid_h * roi_bin_grid_w, 1); // e.g. = 4
+
+ T output_val = 0.;
+ for (int iy = 0; iy < roi_bin_grid_h; iy++) // e.g., iy = 0, 1
+ {
+ const T yy = roi_start_h + ph * bin_size_h +
+ static_cast(iy + .5f) * bin_size_h /
+ static_cast(roi_bin_grid_h); // e.g., 0.5, 1.5
+ for (int ix = 0; ix < roi_bin_grid_w; ix++) {
+ const T xx = roi_start_w + pw * bin_size_w +
+ static_cast(ix + .5f) * bin_size_w /
+ static_cast(roi_bin_grid_w);
+
+ // Rotate by theta around the center and translate
+ T y = yy * cos_theta - xx * sin_theta + roi_center_h;
+ T x = yy * sin_theta + xx * cos_theta + roi_center_w;
+
+ T val = bilinear_interpolate(offset_input, height, width, y, x);
+ output_val += val;
+ }
+ }
+ output_val /= count;
+
+ top_data[index] = output_val;
+ }
+}
+
+template
+__global__ void RoIAlignRotatedBackwardFeature(
+ const int nthreads,
+ const T* top_diff,
+ const int num_rois,
+ const T spatial_scale,
+ const int channels,
+ const int height,
+ const int width,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio,
+ T* bottom_diff,
+ const T* rois) {
+ CUDA_1D_KERNEL_LOOP(index, nthreads) {
+ // (n, c, ph, pw) is an element in the pooled output
+ int pw = index % pooled_width;
+ int ph = (index / pooled_width) % pooled_height;
+ int c = (index / pooled_width / pooled_height) % channels;
+ int n = index / pooled_width / pooled_height / channels;
+
+ const T* current_roi = rois + n * 6;
+ int roi_batch_ind = current_roi[0];
+
+ // Do not use rounding; this implementation detail is critical
+ // ROIAlignRotated supports align == true, i.e., continuous coordinate
+ // by default, thus the 0.5 offset
+ T offset = (T)0.5;
+ T roi_center_w = current_roi[1] * spatial_scale - offset;
+ T roi_center_h = current_roi[2] * spatial_scale - offset;
+ T roi_width = current_roi[3] * spatial_scale;
+ T roi_height = current_roi[4] * spatial_scale;
+ T theta = current_roi[5] * M_PI / 180.0;
+ T cos_theta = cos(theta);
+ T sin_theta = sin(theta);
+
+ T bin_size_h = static_cast(roi_height) / static_cast(pooled_height);
+ T bin_size_w = static_cast(roi_width) / static_cast(pooled_width);
+
+ T* offset_bottom_diff =
+ bottom_diff + (roi_batch_ind * channels + c) * height * width;
+
+ int top_offset = (n * channels + c) * pooled_height * pooled_width;
+ const T* offset_top_diff = top_diff + top_offset;
+ const T top_diff_this_bin = offset_top_diff[ph * pooled_width + pw];
+
+ // We use roi_bin_grid to sample the grid and mimic integral
+ int roi_bin_grid_h = (sampling_ratio > 0)
+ ? sampling_ratio
+ : ceil(roi_height / pooled_height); // e.g., = 2
+ int roi_bin_grid_w =
+ (sampling_ratio > 0) ? sampling_ratio : ceil(roi_width / pooled_width);
+
+ // roi_start_h and roi_start_w are computed wrt the center of RoI (x, y).
+ // Appropriate translation needs to be applied after.
+ T roi_start_h = -roi_height / 2.0;
+ T roi_start_w = -roi_width / 2.0;
+
+ // We do average (integral) pooling inside a bin
+ const T count = roi_bin_grid_h * roi_bin_grid_w; // e.g. = 4
+
+ for (int iy = 0; iy < roi_bin_grid_h; iy++) // e.g., iy = 0, 1
+ {
+ const T yy = roi_start_h + ph * bin_size_h +
+ static_cast(iy + .5f) * bin_size_h /
+ static_cast(roi_bin_grid_h); // e.g., 0.5, 1.5
+ for (int ix = 0; ix < roi_bin_grid_w; ix++) {
+ const T xx = roi_start_w + pw * bin_size_w +
+ static_cast(ix + .5f) * bin_size_w /
+ static_cast(roi_bin_grid_w);
+
+ // Rotate by theta around the center and translate
+ T y = yy * cos_theta - xx * sin_theta + roi_center_h;
+ T x = yy * sin_theta + xx * cos_theta + roi_center_w;
+
+ T w1, w2, w3, w4;
+ int x_low, x_high, y_low, y_high;
+
+ bilinear_interpolate_gradient(
+ height, width, y, x, w1, w2, w3, w4, x_low, x_high, y_low, y_high);
+
+ T g1 = top_diff_this_bin * w1 / count;
+ T g2 = top_diff_this_bin * w2 / count;
+ T g3 = top_diff_this_bin * w3 / count;
+ T g4 = top_diff_this_bin * w4 / count;
+
+ if (x_low >= 0 && x_high >= 0 && y_low >= 0 && y_high >= 0) {
+ atomicAdd(
+ offset_bottom_diff + y_low * width + x_low, static_cast(g1));
+ atomicAdd(
+ offset_bottom_diff + y_low * width + x_high, static_cast(g2));
+ atomicAdd(
+ offset_bottom_diff + y_high * width + x_low, static_cast(g3));
+ atomicAdd(
+ offset_bottom_diff + y_high * width + x_high, static_cast(g4));
+ } // if
+ } // ix
+ } // iy
+ } // CUDA_1D_KERNEL_LOOP
+} // RoIAlignRotatedBackward
+
+at::Tensor ROIAlignRotated_forward_cuda(
+ const at::Tensor& input,
+ const at::Tensor& rois,
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int sampling_ratio) {
+ AT_ASSERTM(input.device().is_cuda(), "input must be a CUDA tensor");
+ AT_ASSERTM(rois.device().is_cuda(), "rois must be a CUDA tensor");
+ at::TensorArg input_t{input, "input", 1}, rois_t{rois, "rois", 2};
+
+ at::CheckedFrom c = "ROIAlignRotated_forward_cuda";
+ at::checkAllSameGPU(c, {input_t, rois_t});
+ at::checkAllSameType(c, {input_t, rois_t});
+ at::cuda::CUDAGuard device_guard(input.device());
+
+ auto num_rois = rois.size(0);
+ auto channels = input.size(1);
+ auto height = input.size(2);
+ auto width = input.size(3);
+
+ auto output = at::empty(
+ {num_rois, channels, pooled_height, pooled_width}, input.options());
+ auto output_size = num_rois * pooled_height * pooled_width * channels;
+ cudaStream_t stream = at::cuda::getCurrentCUDAStream();
+
+ dim3 grid(std::min(
+ at::cuda::ATenCeilDiv(
+ static_cast(output_size), static_cast(512)),
+ static_cast(4096)));
+ dim3 block(512);
+
+ if (output.numel() == 0) {
+ AT_CUDA_CHECK(cudaGetLastError());
+ return output;
+ }
+
+ auto input_ = input.contiguous(), rois_ = rois.contiguous();
+ AT_DISPATCH_FLOATING_TYPES(
+ input.scalar_type(), "ROIAlignRotated_forward", [&] {
+ RoIAlignRotatedForward<<>>(
+ output_size,
+ input_.data_ptr(),
+ spatial_scale,
+ channels,
+ height,
+ width,
+ pooled_height,
+ pooled_width,
+ sampling_ratio,
+ rois_.data_ptr(),
+ output.data_ptr());
+ });
+ cudaDeviceSynchronize();
+ AT_CUDA_CHECK(cudaGetLastError());
+ return output;
+}
+
+// TODO remove the dependency on input and use instead its sizes -> save memory
+at::Tensor ROIAlignRotated_backward_cuda(
+ const at::Tensor& grad,
+ const at::Tensor& rois,
+ const float spatial_scale,
+ const int pooled_height,
+ const int pooled_width,
+ const int batch_size,
+ const int channels,
+ const int height,
+ const int width,
+ const int sampling_ratio) {
+ AT_ASSERTM(grad.device().is_cuda(), "grad must be a CUDA tensor");
+ AT_ASSERTM(rois.device().is_cuda(), "rois must be a CUDA tensor");
+
+ at::TensorArg grad_t{grad, "grad", 1}, rois_t{rois, "rois", 2};
+ at::CheckedFrom c = "ROIAlign_backward_cuda";
+ at::checkAllSameGPU(c, {grad_t, rois_t});
+ at::checkAllSameType(c, {grad_t, rois_t});
+ at::cuda::CUDAGuard device_guard(grad.device());
+
+ auto num_rois = rois.size(0);
+ auto grad_input =
+ at::zeros({batch_size, channels, height, width}, grad.options());
+
+ cudaStream_t stream = at::cuda::getCurrentCUDAStream();
+
+ dim3 grid(std::min(
+ at::cuda::ATenCeilDiv(
+ static_cast(grad.numel()), static_cast(512)),
+ static_cast(4096)));
+ dim3 block(512);
+
+ // handle possibly empty gradients
+ if (grad.numel() == 0) {
+ AT_CUDA_CHECK(cudaGetLastError());
+ return grad_input;
+ }
+
+ auto grad_ = grad.contiguous(), rois_ = rois.contiguous();
+ AT_DISPATCH_FLOATING_TYPES(
+ grad.scalar_type(), "ROIAlignRotated_backward", [&] {
+ RoIAlignRotatedBackwardFeature<<>>(
+ grad.numel(),
+ grad_.data_ptr(),
+ num_rois,
+ spatial_scale,
+ channels,
+ height,
+ width,
+ pooled_height,
+ pooled_width,
+ sampling_ratio,
+ grad_input.data_ptr(),
+ rois_.data_ptr());
+ });
+ AT_CUDA_CHECK(cudaGetLastError());
+ return grad_input;
+}
+
+} // namespace detectron2
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/box_iou_rotated/box_iou_rotated.h b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/box_iou_rotated/box_iou_rotated.h
new file mode 100644
index 0000000000000000000000000000000000000000..3bf383b8ed9b358b5313d433a9682c294dfb77e4
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/box_iou_rotated/box_iou_rotated.h
@@ -0,0 +1,35 @@
+// Copyright (c) Facebook, Inc. and its affiliates.
+#pragma once
+#include
+
+namespace detectron2 {
+
+at::Tensor box_iou_rotated_cpu(
+ const at::Tensor& boxes1,
+ const at::Tensor& boxes2);
+
+#if defined(WITH_CUDA) || defined(WITH_HIP)
+at::Tensor box_iou_rotated_cuda(
+ const at::Tensor& boxes1,
+ const at::Tensor& boxes2);
+#endif
+
+// Interface for Python
+// inline is needed to prevent multiple function definitions when this header is
+// included by different cpps
+inline at::Tensor box_iou_rotated(
+ const at::Tensor& boxes1,
+ const at::Tensor& boxes2) {
+ assert(boxes1.device().is_cuda() == boxes2.device().is_cuda());
+ if (boxes1.device().is_cuda()) {
+#if defined(WITH_CUDA) || defined(WITH_HIP)
+ return box_iou_rotated_cuda(boxes1.contiguous(), boxes2.contiguous());
+#else
+ AT_ERROR("Detectron2 is not compiled with GPU support!");
+#endif
+ }
+
+ return box_iou_rotated_cpu(boxes1.contiguous(), boxes2.contiguous());
+}
+
+} // namespace detectron2
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/box_iou_rotated/box_iou_rotated_cpu.cpp b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/box_iou_rotated/box_iou_rotated_cpu.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c843487b5fa4e8077dd27402ec99009266ddda8d
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/box_iou_rotated/box_iou_rotated_cpu.cpp
@@ -0,0 +1,39 @@
+// Copyright (c) Facebook, Inc. and its affiliates.
+#include "box_iou_rotated.h"
+#include "box_iou_rotated_utils.h"
+
+namespace detectron2 {
+
+template
+void box_iou_rotated_cpu_kernel(
+ const at::Tensor& boxes1,
+ const at::Tensor& boxes2,
+ at::Tensor& ious) {
+ auto num_boxes1 = boxes1.size(0);
+ auto num_boxes2 = boxes2.size(0);
+
+ for (int i = 0; i < num_boxes1; i++) {
+ for (int j = 0; j < num_boxes2; j++) {
+ ious[i * num_boxes2 + j] = single_box_iou_rotated(
+ boxes1[i].data_ptr(), boxes2[j].data_ptr());
+ }
+ }
+}
+
+at::Tensor box_iou_rotated_cpu(
+ // input must be contiguous:
+ const at::Tensor& boxes1,
+ const at::Tensor& boxes2) {
+ auto num_boxes1 = boxes1.size(0);
+ auto num_boxes2 = boxes2.size(0);
+ at::Tensor ious =
+ at::empty({num_boxes1 * num_boxes2}, boxes1.options().dtype(at::kFloat));
+
+ box_iou_rotated_cpu_kernel(boxes1, boxes2, ious);
+
+ // reshape from 1d array to 2d array
+ auto shape = std::vector{num_boxes1, num_boxes2};
+ return ious.reshape(shape);
+}
+
+} // namespace detectron2
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/box_iou_rotated/box_iou_rotated_cuda.cu b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/box_iou_rotated/box_iou_rotated_cuda.cu
new file mode 100644
index 0000000000000000000000000000000000000000..952710e53041187907fbd113f8d0d0fa24134a86
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/box_iou_rotated/box_iou_rotated_cuda.cu
@@ -0,0 +1,130 @@
+// Copyright (c) Facebook, Inc. and its affiliates.
+#include
+#include
+#include
+#include
+#include "box_iou_rotated_utils.h"
+
+namespace detectron2 {
+
+// 2D block with 32 * 16 = 512 threads per block
+const int BLOCK_DIM_X = 32;
+const int BLOCK_DIM_Y = 16;
+
+template
+__global__ void box_iou_rotated_cuda_kernel(
+ const int n_boxes1,
+ const int n_boxes2,
+ const T* dev_boxes1,
+ const T* dev_boxes2,
+ T* dev_ious) {
+ const int row_start = blockIdx.x * blockDim.x;
+ const int col_start = blockIdx.y * blockDim.y;
+
+ const int row_size = min(n_boxes1 - row_start, blockDim.x);
+ const int col_size = min(n_boxes2 - col_start, blockDim.y);
+
+ __shared__ float block_boxes1[BLOCK_DIM_X * 5];
+ __shared__ float block_boxes2[BLOCK_DIM_Y * 5];
+
+ // It's safe to copy using threadIdx.x since BLOCK_DIM_X >= BLOCK_DIM_Y
+ if (threadIdx.x < row_size && threadIdx.y == 0) {
+ block_boxes1[threadIdx.x * 5 + 0] =
+ dev_boxes1[(row_start + threadIdx.x) * 5 + 0];
+ block_boxes1[threadIdx.x * 5 + 1] =
+ dev_boxes1[(row_start + threadIdx.x) * 5 + 1];
+ block_boxes1[threadIdx.x * 5 + 2] =
+ dev_boxes1[(row_start + threadIdx.x) * 5 + 2];
+ block_boxes1[threadIdx.x * 5 + 3] =
+ dev_boxes1[(row_start + threadIdx.x) * 5 + 3];
+ block_boxes1[threadIdx.x * 5 + 4] =
+ dev_boxes1[(row_start + threadIdx.x) * 5 + 4];
+ }
+
+ if (threadIdx.x < col_size && threadIdx.y == 0) {
+ block_boxes2[threadIdx.x * 5 + 0] =
+ dev_boxes2[(col_start + threadIdx.x) * 5 + 0];
+ block_boxes2[threadIdx.x * 5 + 1] =
+ dev_boxes2[(col_start + threadIdx.x) * 5 + 1];
+ block_boxes2[threadIdx.x * 5 + 2] =
+ dev_boxes2[(col_start + threadIdx.x) * 5 + 2];
+ block_boxes2[threadIdx.x * 5 + 3] =
+ dev_boxes2[(col_start + threadIdx.x) * 5 + 3];
+ block_boxes2[threadIdx.x * 5 + 4] =
+ dev_boxes2[(col_start + threadIdx.x) * 5 + 4];
+ }
+ __syncthreads();
+
+ if (threadIdx.x < row_size && threadIdx.y < col_size) {
+ int offset = (row_start + threadIdx.x) * n_boxes2 + col_start + threadIdx.y;
+ dev_ious[offset] = single_box_iou_rotated(
+ block_boxes1 + threadIdx.x * 5, block_boxes2 + threadIdx.y * 5);
+ }
+}
+
+at::Tensor box_iou_rotated_cuda(
+ // input must be contiguous
+ const at::Tensor& boxes1,
+ const at::Tensor& boxes2) {
+ using scalar_t = float;
+ AT_ASSERTM(
+ boxes1.scalar_type() == at::kFloat, "boxes1 must be a float tensor");
+ AT_ASSERTM(
+ boxes2.scalar_type() == at::kFloat, "boxes2 must be a float tensor");
+ AT_ASSERTM(boxes1.is_cuda(), "boxes1 must be a CUDA tensor");
+ AT_ASSERTM(boxes2.is_cuda(), "boxes2 must be a CUDA tensor");
+ at::cuda::CUDAGuard device_guard(boxes1.device());
+
+ auto num_boxes1 = boxes1.size(0);
+ auto num_boxes2 = boxes2.size(0);
+
+ at::Tensor ious =
+ at::empty({num_boxes1 * num_boxes2}, boxes1.options().dtype(at::kFloat));
+
+ bool transpose = false;
+ if (num_boxes1 > 0 && num_boxes2 > 0) {
+ scalar_t *data1 = boxes1.data_ptr(),
+ *data2 = boxes2.data_ptr();
+
+ if (num_boxes2 > 65535 * BLOCK_DIM_Y) {
+ AT_ASSERTM(
+ num_boxes1 <= 65535 * BLOCK_DIM_Y,
+ "Too many boxes for box_iou_rotated_cuda!");
+ // x dim is allowed to be large, but y dim cannot,
+ // so we transpose the two to avoid "invalid configuration argument"
+ // error. We assume one of them is small. Otherwise the result is hard to
+ // fit in memory anyway.
+ std::swap(num_boxes1, num_boxes2);
+ std::swap(data1, data2);
+ transpose = true;
+ }
+
+ const int blocks_x =
+ at::cuda::ATenCeilDiv(static_cast(num_boxes1), BLOCK_DIM_X);
+ const int blocks_y =
+ at::cuda::ATenCeilDiv(static_cast(num_boxes2), BLOCK_DIM_Y);
+
+ dim3 blocks(blocks_x, blocks_y);
+ dim3 threads(BLOCK_DIM_X, BLOCK_DIM_Y);
+ cudaStream_t stream = at::cuda::getCurrentCUDAStream();
+
+ box_iou_rotated_cuda_kernel<<>>(
+ num_boxes1,
+ num_boxes2,
+ data1,
+ data2,
+ (scalar_t*)ious.data_ptr());
+
+ AT_CUDA_CHECK(cudaGetLastError());
+ }
+
+ // reshape from 1d array to 2d array
+ auto shape = std::vector{num_boxes1, num_boxes2};
+ if (transpose) {
+ return ious.view(shape).t();
+ } else {
+ return ious.view(shape);
+ }
+}
+
+} // namespace detectron2
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/box_iou_rotated/box_iou_rotated_utils.h b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/box_iou_rotated/box_iou_rotated_utils.h
new file mode 100644
index 0000000000000000000000000000000000000000..b54a5dde2ca11a74d29c4d8adb7fe1634f5baf9c
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/box_iou_rotated/box_iou_rotated_utils.h
@@ -0,0 +1,370 @@
+// Copyright (c) Facebook, Inc. and its affiliates.
+#pragma once
+
+#include
+#include
+
+#if defined(__CUDACC__) || __HCC__ == 1 || __HIP__ == 1
+// Designates functions callable from the host (CPU) and the device (GPU)
+#define HOST_DEVICE __host__ __device__
+#define HOST_DEVICE_INLINE HOST_DEVICE __forceinline__
+#else
+#include
+#define HOST_DEVICE
+#define HOST_DEVICE_INLINE HOST_DEVICE inline
+#endif
+
+namespace detectron2 {
+
+namespace {
+
+template
+struct RotatedBox {
+ T x_ctr, y_ctr, w, h, a;
+};
+
+template
+struct Point {
+ T x, y;
+ HOST_DEVICE_INLINE Point(const T& px = 0, const T& py = 0) : x(px), y(py) {}
+ HOST_DEVICE_INLINE Point operator+(const Point& p) const {
+ return Point(x + p.x, y + p.y);
+ }
+ HOST_DEVICE_INLINE Point& operator+=(const Point& p) {
+ x += p.x;
+ y += p.y;
+ return *this;
+ }
+ HOST_DEVICE_INLINE Point operator-(const Point& p) const {
+ return Point(x - p.x, y - p.y);
+ }
+ HOST_DEVICE_INLINE Point operator*(const T coeff) const {
+ return Point(x * coeff, y * coeff);
+ }
+};
+
+template
+HOST_DEVICE_INLINE T dot_2d(const Point& A, const Point& B) {
+ return A.x * B.x + A.y * B.y;
+}
+
+// R: result type. can be different from input type
+template
+HOST_DEVICE_INLINE R cross_2d(const Point& A, const Point& B) {
+ return static_cast(A.x) * static_cast(B.y) -
+ static_cast(B.x) * static_cast(A.y);
+}
+
+template
+HOST_DEVICE_INLINE void get_rotated_vertices(
+ const RotatedBox& box,
+ Point (&pts)[4]) {
+ // M_PI / 180. == 0.01745329251
+ double theta = box.a * 0.01745329251;
+ T cosTheta2 = (T)cos(theta) * 0.5f;
+ T sinTheta2 = (T)sin(theta) * 0.5f;
+
+ // y: top --> down; x: left --> right
+ pts[0].x = box.x_ctr + sinTheta2 * box.h + cosTheta2 * box.w;
+ pts[0].y = box.y_ctr + cosTheta2 * box.h - sinTheta2 * box.w;
+ pts[1].x = box.x_ctr - sinTheta2 * box.h + cosTheta2 * box.w;
+ pts[1].y = box.y_ctr - cosTheta2 * box.h - sinTheta2 * box.w;
+ pts[2].x = 2 * box.x_ctr - pts[0].x;
+ pts[2].y = 2 * box.y_ctr - pts[0].y;
+ pts[3].x = 2 * box.x_ctr - pts[1].x;
+ pts[3].y = 2 * box.y_ctr - pts[1].y;
+}
+
+template
+HOST_DEVICE_INLINE int get_intersection_points(
+ const Point (&pts1)[4],
+ const Point (&pts2)[4],
+ Point (&intersections)[24]) {
+ // Line vector
+ // A line from p1 to p2 is: p1 + (p2-p1)*t, t=[0,1]
+ Point vec1[4], vec2[4];
+ for (int i = 0; i < 4; i++) {
+ vec1[i] = pts1[(i + 1) % 4] - pts1[i];
+ vec2[i] = pts2[(i + 1) % 4] - pts2[i];
+ }
+
+ // When computing the intersection area, it doesn't hurt if we have
+ // more (duplicated/approximate) intersections/vertices than needed,
+ // while it can cause drastic difference if we miss an intersection/vertex.
+ // Therefore, we add an epsilon to relax the comparisons between
+ // the float point numbers that decide the intersection points.
+ double EPS = 1e-5;
+
+ // Line test - test all line combos for intersection
+ int num = 0; // number of intersections
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ // Solve for 2x2 Ax=b
+ T det = cross_2d(vec2[j], vec1[i]);
+
+ // This takes care of parallel lines
+ if (fabs(det) <= 1e-14) {
+ continue;
+ }
+
+ auto vec12 = pts2[j] - pts1[i];
+
+ T t1 = cross_2d(vec2[j], vec12) / det;
+ T t2 = cross_2d(vec1[i], vec12) / det;
+
+ if (t1 > -EPS && t1 < 1.0f + EPS && t2 > -EPS && t2 < 1.0f + EPS) {
+ intersections[num++] = pts1[i] + vec1[i] * t1;
+ }
+ }
+ }
+
+ // Check for vertices of rect1 inside rect2
+ {
+ const auto& AB = vec2[0];
+ const auto& DA = vec2[3];
+ auto ABdotAB = dot_2d(AB, AB);
+ auto ADdotAD = dot_2d(DA, DA);
+ for (int i = 0; i < 4; i++) {
+ // assume ABCD is the rectangle, and P is the point to be judged
+ // P is inside ABCD iff. P's projection on AB lies within AB
+ // and P's projection on AD lies within AD
+
+ auto AP = pts1[i] - pts2[0];
+
+ auto APdotAB = dot_2d(AP, AB);
+ auto APdotAD = -dot_2d(AP, DA);
+
+ if ((APdotAB > -EPS) && (APdotAD > -EPS) && (APdotAB < ABdotAB + EPS) &&
+ (APdotAD < ADdotAD + EPS)) {
+ intersections[num++] = pts1[i];
+ }
+ }
+ }
+
+ // Reverse the check - check for vertices of rect2 inside rect1
+ {
+ const auto& AB = vec1[0];
+ const auto& DA = vec1[3];
+ auto ABdotAB = dot_2d(AB, AB);
+ auto ADdotAD = dot_2d(DA, DA);
+ for (int i = 0; i < 4; i++) {
+ auto AP = pts2[i] - pts1[0];
+
+ auto APdotAB = dot_2d(AP, AB);
+ auto APdotAD = -dot_2d(AP, DA);
+
+ if ((APdotAB > -EPS) && (APdotAD > -EPS) && (APdotAB < ABdotAB + EPS) &&
+ (APdotAD < ADdotAD + EPS)) {
+ intersections[num++] = pts2[i];
+ }
+ }
+ }
+
+ return num;
+}
+
+template
+HOST_DEVICE_INLINE int convex_hull_graham(
+ const Point (&p)[24],
+ const int& num_in,
+ Point (&q)[24],
+ bool shift_to_zero = false) {
+ assert(num_in >= 2);
+
+ // Step 1:
+ // Find point with minimum y
+ // if more than 1 points have the same minimum y,
+ // pick the one with the minimum x.
+ int t = 0;
+ for (int i = 1; i < num_in; i++) {
+ if (p[i].y < p[t].y || (p[i].y == p[t].y && p[i].x < p[t].x)) {
+ t = i;
+ }
+ }
+ auto& start = p[t]; // starting point
+
+ // Step 2:
+ // Subtract starting point from every points (for sorting in the next step)
+ for (int i = 0; i < num_in; i++) {
+ q[i] = p[i] - start;
+ }
+
+ // Swap the starting point to position 0
+ auto tmp = q[0];
+ q[0] = q[t];
+ q[t] = tmp;
+
+ // Step 3:
+ // Sort point 1 ~ num_in according to their relative cross-product values
+ // (essentially sorting according to angles)
+ // If the angles are the same, sort according to their distance to origin
+ T dist[24];
+#if defined(__CUDACC__) || __HCC__ == 1 || __HIP__ == 1
+ // compute distance to origin before sort, and sort them together with the
+ // points
+ for (int i = 0; i < num_in; i++) {
+ dist[i] = dot_2d(q[i], q[i]);
+ }
+
+ // CUDA version
+ // In the future, we can potentially use thrust
+ // for sorting here to improve speed (though not guaranteed)
+ for (int i = 1; i < num_in - 1; i++) {
+ for (int j = i + 1; j < num_in; j++) {
+ T crossProduct = cross_2d(q[i], q[j]);
+ if ((crossProduct < -1e-6) ||
+ (fabs(crossProduct) < 1e-6 && dist[i] > dist[j])) {
+ auto q_tmp = q[i];
+ q[i] = q[j];
+ q[j] = q_tmp;
+ auto dist_tmp = dist[i];
+ dist[i] = dist[j];
+ dist[j] = dist_tmp;
+ }
+ }
+ }
+#else
+ // CPU version
+ std::sort(
+ q + 1, q + num_in, [](const Point& A, const Point& B) -> bool {
+ T temp = cross_2d(A, B);
+ if (fabs(temp) < 1e-6) {
+ return dot_2d(A, A) < dot_2d(B, B);
+ } else {
+ return temp > 0;
+ }
+ });
+ // compute distance to origin after sort, since the points are now different.
+ for (int i = 0; i < num_in; i++) {
+ dist[i] = dot_2d(q[i], q[i]);
+ }
+#endif
+
+ // Step 4:
+ // Make sure there are at least 2 points (that don't overlap with each other)
+ // in the stack
+ int k; // index of the non-overlapped second point
+ for (k = 1; k < num_in; k++) {
+ if (dist[k] > 1e-8) {
+ break;
+ }
+ }
+ if (k == num_in) {
+ // We reach the end, which means the convex hull is just one point
+ q[0] = p[t];
+ return 1;
+ }
+ q[1] = q[k];
+ int m = 2; // 2 points in the stack
+ // Step 5:
+ // Finally we can start the scanning process.
+ // When a non-convex relationship between the 3 points is found
+ // (either concave shape or duplicated points),
+ // we pop the previous point from the stack
+ // until the 3-point relationship is convex again, or
+ // until the stack only contains two points
+ for (int i = k + 1; i < num_in; i++) {
+ while (m > 1) {
+ auto q1 = q[i] - q[m - 2], q2 = q[m - 1] - q[m - 2];
+ // cross_2d() uses FMA and therefore computes round(round(q1.x*q2.y) -
+ // q2.x*q1.y) So it may not return 0 even when q1==q2. Therefore we
+ // compare round(q1.x*q2.y) and round(q2.x*q1.y) directly. (round means
+ // round to nearest floating point).
+ if (q1.x * q2.y >= q2.x * q1.y)
+ m--;
+ else
+ break;
+ }
+ // Using double also helps, but float can solve the issue for now.
+ // while (m > 1 && cross_2d(q[i] - q[m - 2], q[m - 1] - q[m - 2])
+ // >= 0) {
+ // m--;
+ // }
+ q[m++] = q[i];
+ }
+
+ // Step 6 (Optional):
+ // In general sense we need the original coordinates, so we
+ // need to shift the points back (reverting Step 2)
+ // But if we're only interested in getting the area/perimeter of the shape
+ // We can simply return.
+ if (!shift_to_zero) {
+ for (int i = 0; i < m; i++) {
+ q[i] += start;
+ }
+ }
+
+ return m;
+}
+
+template
+HOST_DEVICE_INLINE T polygon_area(const Point (&q)[24], const int& m) {
+ if (m <= 2) {
+ return 0;
+ }
+
+ T area = 0;
+ for (int i = 1; i < m - 1; i++) {
+ area += fabs(cross_2d(q[i] - q[0], q[i + 1] - q[0]));
+ }
+
+ return area / 2.0;
+}
+
+template
+HOST_DEVICE_INLINE T rotated_boxes_intersection(
+ const RotatedBox& box1,
+ const RotatedBox& box2) {
+ // There are up to 4 x 4 + 4 + 4 = 24 intersections (including dups) returned
+ // from rotated_rect_intersection_pts
+ Point intersectPts[24], orderedPts[24];
+
+ Point pts1[4];
+ Point pts2[4];
+ get_rotated_vertices(box1, pts1);
+ get_rotated_vertices(box2, pts2);
+
+ int num = get_intersection_points(pts1, pts2, intersectPts);
+
+ if (num <= 2) {
+ return 0.0;
+ }
+
+ // Convex Hull to order the intersection points in clockwise order and find
+ // the contour area.
+ int num_convex = convex_hull_graham(intersectPts, num, orderedPts, true);
+ return polygon_area(orderedPts, num_convex);
+}
+
+} // namespace
+
+template
+HOST_DEVICE_INLINE T
+single_box_iou_rotated(T const* const box1_raw, T const* const box2_raw) {
+ // shift center to the middle point to achieve higher precision in result
+ RotatedBox box1, box2;
+ auto center_shift_x = (box1_raw[0] + box2_raw[0]) / 2.0;
+ auto center_shift_y = (box1_raw[1] + box2_raw[1]) / 2.0;
+ box1.x_ctr = box1_raw[0] - center_shift_x;
+ box1.y_ctr = box1_raw[1] - center_shift_y;
+ box1.w = box1_raw[2];
+ box1.h = box1_raw[3];
+ box1.a = box1_raw[4];
+ box2.x_ctr = box2_raw[0] - center_shift_x;
+ box2.y_ctr = box2_raw[1] - center_shift_y;
+ box2.w = box2_raw[2];
+ box2.h = box2_raw[3];
+ box2.a = box2_raw[4];
+
+ T area1 = box1.w * box1.h;
+ T area2 = box2.w * box2.h;
+ if (area1 < 1e-14 || area2 < 1e-14) {
+ return 0.f;
+ }
+
+ T intersection = rotated_boxes_intersection(box1, box2);
+ T iou = intersection / (area1 + area2 - intersection);
+ return iou;
+}
+
+} // namespace detectron2
diff --git a/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/cocoeval/cocoeval.cpp b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/cocoeval/cocoeval.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0a5b7b907c06720fefc77b0dfd921b8ec3ecf2be
--- /dev/null
+++ b/custom_nodes/comfyui_controlnet_aux/src/custom_detectron2/layers/csrc/cocoeval/cocoeval.cpp
@@ -0,0 +1,507 @@
+// Copyright (c) Facebook, Inc. and its affiliates.
+#include "cocoeval.h"
+#include
+#include
+#include
+#include
+
+using namespace pybind11::literals;
+
+namespace detectron2 {
+
+namespace COCOeval {
+
+// Sort detections from highest score to lowest, such that
+// detection_instances[detection_sorted_indices[t]] >=
+// detection_instances[detection_sorted_indices[t+1]]. Use stable_sort to match
+// original COCO API
+void SortInstancesByDetectionScore(
+ const std::vector& detection_instances,
+ std::vector* detection_sorted_indices) {
+ detection_sorted_indices->resize(detection_instances.size());
+ std::iota(
+ detection_sorted_indices->begin(), detection_sorted_indices->end(), 0);
+ std::stable_sort(
+ detection_sorted_indices->begin(),
+ detection_sorted_indices->end(),
+ [&detection_instances](size_t j1, size_t j2) {
+ return detection_instances[j1].score > detection_instances[j2].score;
+ });
+}
+
+// Partition the ground truth objects based on whether or not to ignore them
+// based on area
+void SortInstancesByIgnore(
+ const std::array& area_range,
+ const std::vector& ground_truth_instances,
+ std::vector* ground_truth_sorted_indices,
+ std::vector* ignores) {
+ ignores->clear();
+ ignores->reserve(ground_truth_instances.size());
+ for (auto o : ground_truth_instances) {
+ ignores->push_back(
+ o.ignore || o.area < area_range[0] || o.area > area_range[1]);
+ }
+
+ ground_truth_sorted_indices->resize(ground_truth_instances.size());
+ std::iota(
+ ground_truth_sorted_indices->begin(),
+ ground_truth_sorted_indices->end(),
+ 0);
+ std::stable_sort(
+ ground_truth_sorted_indices->begin(),
+ ground_truth_sorted_indices->end(),
+ [&ignores](size_t j1, size_t j2) {
+ return (int)(*ignores)[j1] < (int)(*ignores)[j2];
+ });
+}
+
+// For each IOU threshold, greedily match each detected instance to a ground
+// truth instance (if possible) and store the results
+void MatchDetectionsToGroundTruth(
+ const std::vector& detection_instances,
+ const std::vector& detection_sorted_indices,
+ const std::vector& ground_truth_instances,
+ const std::vector& ground_truth_sorted_indices,
+ const std::vector& ignores,
+ const std::vector>& ious,
+ const std::vector& iou_thresholds,
+ const std::array& area_range,
+ ImageEvaluation* results) {
+ // Initialize memory to store return data matches and ignore
+ const int num_iou_thresholds = iou_thresholds.size();
+ const int num_ground_truth = ground_truth_sorted_indices.size();
+ const int num_detections = detection_sorted_indices.size();
+ std::vector ground_truth_matches(
+ num_iou_thresholds * num_ground_truth, 0);
+ std::vector& detection_matches = results->detection_matches;
+ std::vector& detection_ignores = results->detection_ignores;
+ std::vector& ground_truth_ignores = results->ground_truth_ignores;
+ detection_matches.resize(num_iou_thresholds * num_detections, 0);
+ detection_ignores.resize(num_iou_thresholds * num_detections, false);
+ ground_truth_ignores.resize(num_ground_truth);
+ for (auto g = 0; g < num_ground_truth; ++g) {
+ ground_truth_ignores[g] = ignores[ground_truth_sorted_indices[g]];
+ }
+
+ for (auto t = 0; t < num_iou_thresholds; ++t) {
+ for (auto d = 0; d < num_detections; ++d) {
+ // information about best match so far (match=-1 -> unmatched)
+ double best_iou = std::min(iou_thresholds[t], 1 - 1e-10);
+ int match = -1;
+ for (auto g = 0; g < num_ground_truth; ++g) {
+ // if this ground truth instance is already matched and not a
+ // crowd, it cannot be matched to another detection
+ if (ground_truth_matches[t * num_ground_truth + g] > 0 &&
+ !ground_truth_instances[ground_truth_sorted_indices[g]].is_crowd) {
+ continue;
+ }
+
+ // if detected instance matched to a regular ground truth
+ // instance, we can break on the first ground truth instance
+ // tagged as ignore (because they are sorted by the ignore tag)
+ if (match >= 0 && !ground_truth_ignores[match] &&
+ ground_truth_ignores[g]) {
+ break;
+ }
+
+ // if IOU overlap is the best so far, store the match appropriately
+ if (ious[d][ground_truth_sorted_indices[g]] >= best_iou) {
+ best_iou = ious[d][ground_truth_sorted_indices[g]];
+ match = g;
+ }
+ }
+ // if match was made, store id of match for both detection and
+ // ground truth
+ if (match >= 0) {
+ detection_ignores[t * num_detections + d] = ground_truth_ignores[match];
+ detection_matches[t * num_detections + d] =
+ ground_truth_instances[ground_truth_sorted_indices[match]].id;
+ ground_truth_matches[t * num_ground_truth + match] =
+ detection_instances[detection_sorted_indices[d]].id;
+ }
+
+ // set unmatched detections outside of area range to ignore
+ const InstanceAnnotation& detection =
+ detection_instances[detection_sorted_indices[d]];
+ detection_ignores[t * num_detections + d] =
+ detection_ignores[t * num_detections + d] ||
+ (detection_matches[t * num_detections + d] == 0 &&
+ (detection.area < area_range[0] || detection.area > area_range[1]));
+ }
+ }
+
+ // store detection score results
+ results->detection_scores.resize(detection_sorted_indices.size());
+ for (size_t d = 0; d < detection_sorted_indices.size(); ++d) {
+ results->detection_scores[d] =
+ detection_instances[detection_sorted_indices[d]].score;
+ }
+}
+
+std::vector EvaluateImages(
+ const std::vector>& area_ranges,
+ int max_detections,
+ const std::vector& iou_thresholds,
+ const ImageCategoryInstances>& image_category_ious,
+ const ImageCategoryInstances&
+ image_category_ground_truth_instances,
+ const ImageCategoryInstances