Compare commits
76
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d29c41809f | ||
|
|
667dead8c1 | ||
|
|
08cdc7358b | ||
|
|
81485bcf4c | ||
|
|
f6a8c65094 | ||
|
|
37bc51713f | ||
|
|
004a9534e4 | ||
|
|
ca710caaab | ||
|
|
c657cf152a | ||
|
|
9678d685c2 | ||
|
|
51cace6f1c | ||
|
|
c0d54d8a88 | ||
|
|
74b2856499 | ||
|
|
25ae8d522f | ||
|
|
50d42c30b5 | ||
|
|
89e24b7529 | ||
|
|
6db6ca54c0 | ||
|
|
69b7c486a9 | ||
|
|
b09b295009 | ||
|
|
a15d3362d8 | ||
|
|
019713040c | ||
|
|
bc075b1a4f | ||
|
|
452d8cfc00 | ||
|
|
17716ddb52 | ||
|
|
56f4f01f02 | ||
|
|
f64c98714b | ||
|
|
30974e69c4 | ||
|
|
ec29936d16 | ||
|
|
0bd92419de | ||
|
|
ec6831fd68 | ||
|
|
c876e477e8 | ||
|
|
0de0e4232c | ||
|
|
61ba155cf1 | ||
|
|
09ae2eaa9b | ||
|
|
eab0b95df5 | ||
|
|
badc1c0cb4 | ||
|
|
cf9bae0718 | ||
|
|
c90d6c8d0f | ||
|
|
7aa30a9633 | ||
|
|
69af3ec749 | ||
|
|
2ba2fec053 | ||
|
|
ad0c179ec7 | ||
|
|
cadf604de5 | ||
|
|
ccd472ea9a | ||
|
|
d84435ef62 | ||
|
|
f1fb2a046d | ||
|
|
d150ca3adf | ||
|
|
78444b5010 | ||
|
|
4b6ba88916 | ||
|
|
8f8cac507d | ||
|
|
68fb465fe8 | ||
|
|
55db53ac6b | ||
|
|
2b947baada | ||
|
|
bf54f25f07 | ||
|
|
5320c1c7d2 | ||
|
|
34e64e7a6a | ||
|
|
9b2d22a9a1 | ||
|
|
877103b7d6 | ||
|
|
b940df0d5c | ||
|
|
cc2ae35891 | ||
|
|
bc558bf42b | ||
|
|
5d84ac9e88 | ||
|
|
2b7a79090c | ||
|
|
e255ff46fb | ||
|
|
1b88905a62 | ||
|
|
0916454bce | ||
|
|
ea7a88be49 | ||
|
|
ee446d1f90 | ||
|
|
6be70a0618 | ||
|
|
51a7845ec5 | ||
|
|
186b601e65 | ||
|
|
34de5e338f | ||
|
|
0dec9404fa | ||
|
|
9c578996b0 | ||
|
|
a967cc96ab | ||
|
|
9c5fa36a51 |
@@ -0,0 +1,21 @@
|
||||
name: Publish to Comfy registry
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "pyproject.toml"
|
||||
|
||||
jobs:
|
||||
publish-node:
|
||||
name: Publish Custom Node to registry
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Publish Custom Node
|
||||
uses: Comfy-Org/publish-node-action@main
|
||||
with:
|
||||
## Add your own personal access token to your Github Repository secrets and reference it here.
|
||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
||||
@@ -2,6 +2,7 @@
|
||||
This repository offers various extension nodes for ComfyUI. Nodes here have different characteristics compared to those in the ComfyUI Impact Pack. The Impact Pack has become too large now...
|
||||
|
||||
## Notice:
|
||||
* V0.73 The Variation Seed feature is added to Regional Prompt nodes, and it is only compatible with versions Impact Pack V5.10 and above.
|
||||
* V0.69 incompatible with the outdated **ComfyUI IPAdapter Plus**. (A version dated March 24th or later is required.)
|
||||
* V0.64 add sigma_factor to RegionalPrompt... nodes required Impact Pack V4.76 or later.
|
||||
* V0.62 support faceid in Regional IPAdapter
|
||||
@@ -10,39 +11,50 @@ This repository offers various extension nodes for ComfyUI. Nodes here have diff
|
||||
* WARN: If you use version **0.12 to 0.12.2** without a GlobalSeed node, your workflow's seed may have been erased. Please update immediately.
|
||||
|
||||
## Nodes
|
||||
* Lora Block Weight - This is a node that provides functionality related to Lora block weight.
|
||||
* This provides similar functionality to [sd-webui-lora-block-weight](https://github.com/hako-mikan/sd-webui-lora-block-weight)
|
||||
* `Lora Loader (Block Weight)`: When loading Lora, the block weight vector is applied.
|
||||
* In the block vector, you can use numbers, R, A, a, B, and b.
|
||||
* R is determined sequentially based on a random seed, while A and B represent the values of the A and B parameters, respectively. a and b are half of the values of A and B, respectively.
|
||||
* `XY Input: Lora Block Weight`: This is a node in the [Efficiency Nodes](https://github.com/LucianoCirino/efficiency-nodes-comfyui)' XY Plot that allows you to use Lora block weight.
|
||||
* You must ensure that X and Y connections are made, and dependencies should be connected to the XY Plot.
|
||||
* Note: To use this feature, update `Efficient Nodes` to a version released after September 3rd.
|
||||
### Lora Block Weight - This is a node that provides functionality related to Lora block weight.
|
||||
* This provides similar functionality to [sd-webui-lora-block-weight](https://github.com/hako-mikan/sd-webui-lora-block-weight)
|
||||
* `LoRA Loader (Block Weight)`: When loading Lora, the block weight vector is applied.
|
||||
* In the block vector, you can use numbers, R, A, a, B, and b.
|
||||
* R is determined sequentially based on a random seed, while A and B represent the values of the A and B parameters, respectively. a and b are half of the values of A and B, respectively.
|
||||
* `XY Input: LoRA Block Weight`: This is a node in the [Efficiency Nodes](https://github.com/LucianoCirino/efficiency-nodes-comfyui)' XY Plot that allows you to use Lora block weight.
|
||||
* You must ensure that X and Y connections are made, and dependencies should be connected to the XY Plot.
|
||||
* Note: To use this feature, update `Efficient Nodes` to a version released after September 3rd.
|
||||
* Make LoRA Block Weight: Instead of directly applying the LoRA Block Weight to the MODEL, it is generated in a separate LBW_MODEL form
|
||||
* Apply LoRA Block Weight: Apply LBW_MODEL to MODEL and CLIP
|
||||
* Save LoRA Block Weight: Save LBW_MODEL as a .lbw.safetensors file
|
||||
* Load LoRA Block Weight: Load LBW_MODEL from .lbw.safetensors file
|
||||
|
||||
* SEGS Supports nodes - This is a node that supports ApplyControlNet (SEGS) from the Impact Pack.
|
||||
* `OpenPose Preprocessor Provider (SEGS)`: OpenPose preprocessor is applied for the purpose of using OpenPose ControlNet in SEGS.
|
||||
* You need to install [ControlNet Auxiliary Preprocessors](https://github.com/Fannovel16/comfyui_controlnet_aux) to use this.
|
||||
* `Canny Preprocessor Provider (SEGS)`: Canny preprocessor is applied for the purpose of using Canny ControlNet in SEGS.
|
||||
* `DW Preprocessor Provider (SEGS)`, `MiDaS Depth Map Preprocessor Provider (SEGS)`, `LeReS Depth Map Preprocessor Provider (SEGS)`,
|
||||
`MediaPipe FaceMesh Preprocessor Provider (SEGS)`, `HED Preprocessor Provider (SEGS)`, `Fake Scribble Preprocessor (SEGS)`,
|
||||
`AnimeLineArt Preprocessor Provider (SEGS)`, `Manga2Anime LineArt Preprocessor Provider (SEGS)`, `LineArt Preprocessor Provider (SEGS)`,
|
||||
`Color Preprocessor Provider (SEGS)`, `Inpaint Preprocessor Provider (SEGS)`, `Tile Preprocessor Provider (SEGS)`, `MeshGraphormer Depth Map Preprocessor Provider (SEGS)`
|
||||
* `MediaPipeFaceMeshDetectorProvider`: This node provides `BBOX_DETECTOR` and `SEGM_DETECTOR` that can be used in Impact Pack's Detector using the `MediaPipe-FaceMesh Preprocessor` of ControlNet Auxiliary Preprocessors.
|
||||
### SEGS Supports nodes - This is a node that supports ApplyControlNet (SEGS) from the Impact Pack.
|
||||
* `OpenPose Preprocessor Provider (SEGS)`: OpenPose preprocessor is applied for the purpose of using OpenPose ControlNet in SEGS.
|
||||
* You need to install [ControlNet Auxiliary Preprocessors](https://github.com/Fannovel16/comfyui_controlnet_aux) to use this.
|
||||
* `Canny Preprocessor Provider (SEGS)`: Canny preprocessor is applied for the purpose of using Canny ControlNet in SEGS.
|
||||
* `DW Preprocessor Provider (SEGS)`, `MiDaS Depth Map Preprocessor Provider (SEGS)`, `LeReS Depth Map Preprocessor Provider (SEGS)`,
|
||||
`MediaPipe FaceMesh Preprocessor Provider (SEGS)`, `HED Preprocessor Provider (SEGS)`, `Fake Scribble Preprocessor (SEGS)`,
|
||||
`AnimeLineArt Preprocessor Provider (SEGS)`, `Manga2Anime LineArt Preprocessor Provider (SEGS)`, `LineArt Preprocessor Provider (SEGS)`,
|
||||
`Color Preprocessor Provider (SEGS)`, `Inpaint Preprocessor Provider (SEGS)`, `Tile Preprocessor Provider (SEGS)`, `MeshGraphormer Depth Map Preprocessor Provider (SEGS)`
|
||||
* `MediaPipeFaceMeshDetectorProvider`: This node provides `BBOX_DETECTOR` and `SEGM_DETECTOR` that can be used in Impact Pack's Detector using the `MediaPipe-FaceMesh Preprocessor` of ControlNet Auxiliary Preprocessors.
|
||||
|
||||
* A1111 Compatibility support - These nodes assists in replicating the creation of A1111 in ComfyUI exactly.
|
||||
* `KSampler (Inspire)`: ComfyUI uses the CPU for generating random noise, while A1111 uses the GPU. One of the three factors that significantly impact reproducing A1111's results in ComfyUI can be addressed using `KSampler (Inspire)`.
|
||||
* Other point #1 : Please make sure you haven't forgotten to include 'embedding:' in the embedding used in the prompt, like 'embedding:easynegative.'
|
||||
* Other point #2 : ComfyUI and A1111 have different interpretations of weighting. To align them, you need to use [BlenderNeko/Advanced CLIP Text Encode](https://github.com/BlenderNeko/ComfyUI_ADV_CLIP_emb).
|
||||
* `KSamplerAdvanced (Inspire)`: Inspire Pack version of `KSampler (Advanced)`.
|
||||
* Common Parameters
|
||||
* `batch_seed_mode` determines how seeds are applied to batch latents:
|
||||
* `comfy`: This method applies the noise to batch latents all at once. This is advantageous to prevent duplicate images from being generated due to seed duplication when creating images.
|
||||
* `incremental`: Similar to the A1111 case, this method incrementally increases the seed and applies noise sequentially for each batch. This approach is beneficial for straightforward reproduction using only the seed.
|
||||
* `variation_strength`: In each batch, the variation strength starts from the set `variation_strength` and increases by `xxx`.
|
||||
* `variation_seed` and `variation_strength` - Initial noise generated by the seed is transformed to the shape of `variation_seed` by `variation_strength`. If `variation_strength` is 0, it only relies on the influence of the seed, and if `variation_strength` is 1.0, it is solely influenced by `variation_seed`.
|
||||
* These parameters are used when you want to maintain the composition of an image generated by the seed but wish to introduce slight changes.
|
||||
### A1111 Compatibility support - These nodes assists in replicating the creation of A1111 in ComfyUI exactly.
|
||||
* `KSampler (Inspire)`: ComfyUI uses the CPU for generating random noise, while A1111 uses the GPU. One of the three factors that significantly impact reproducing A1111's results in ComfyUI can be addressed using `KSampler (Inspire)`.
|
||||
* Other point #1 : Please make sure you haven't forgotten to include 'embedding:' in the embedding used in the prompt, like 'embedding:easynegative.'
|
||||
* Other point #2 : ComfyUI and A1111 have different interpretations of weighting. To align them, you need to use [BlenderNeko/Advanced CLIP Text Encode](https://github.com/BlenderNeko/ComfyUI_ADV_CLIP_emb).
|
||||
* `KSamplerAdvanced (Inspire)`: Inspire Pack version of `KSampler (Advanced)`.
|
||||
* `RandomNoise (inspire)`: Inspire Pack version of `RandomNoise`.
|
||||
* Common Parameters
|
||||
* `batch_seed_mode` determines how seeds are applied to batch latents:
|
||||
* `comfy`: This method applies the noise to batch latents all at once. This is advantageous to prevent duplicate images from being generated due to seed duplication when creating images.
|
||||
* `incremental`: Similar to the A1111 case, this method incrementally increases the seed and applies noise sequentially for each batch. This approach is beneficial for straightforward reproduction using only the seed.
|
||||
* `variation_strength`: In each batch, the variation strength starts from the set `variation_strength` and increases by `xxx`.
|
||||
* `variation_seed` and `variation_strength` - Initial noise generated by the seed is transformed to the shape of `variation_seed` by `variation_strength`. If `variation_strength` is 0, it only relies on the influence of the seed, and if `variation_strength` is 1.0, it is solely influenced by `variation_seed`.
|
||||
* These parameters are used when you want to maintain the composition of an image generated by the seed but wish to introduce slight changes.
|
||||
|
||||
* Prompt Support - These are nodes for supporting prompt processing.
|
||||
### Sampler nodes
|
||||
* `KSampler Progress (Inspire)` - In KSampler, the sampling process generates latent batches. By using `Video Combine` node from [ComfyUI-VideoHelperSuite](https://github.com/Kosinkadink/ComfyUI-VideoHelperSuite), you can create a video from the progress.
|
||||
* `Scheduled CFGGuider (Inspire)` - This is a CFGGuider that adjusts the schedule from from_cfg to to_cfg using linear, log, and exp methods.
|
||||
* `Scheduled PerpNeg CFGGuider (Inspire)` - This is a PerpNeg CFGGuider that adjusts the schedule from from_cfg to to_cfg using linear, log, and exp methods.
|
||||
|
||||
|
||||
### Prompt Support - These are nodes for supporting prompt processing.
|
||||
* `Load Prompts From Dir (Inspire)`: It sequentially reads prompts files from the specified directory. The output it returns is ZIPPED_PROMPT.
|
||||
* Specify the directories located under `ComfyUI-Inspire-Pack/prompts/`
|
||||
* One prompts file can have multiple prompts separated by `---`.
|
||||
@@ -71,12 +83,13 @@ This repository offers various extension nodes for ComfyUI. Nodes here have diff
|
||||
* In the `seed_prompt`, the first seed is considered the initial seed, and the reflection rate is omitted, always defaulting to 1.0.
|
||||
* Each prompt is separated by a comma, and from the second seed onwards, it should follow the format `seed:strength`.
|
||||
* Pressing the "Add to prompt" button will append `additional_seed:additional_strength` to the prompt.
|
||||
* `Composite Noise (Inspire)`: This node overwrites a specific area on top of the destination noise with the source noise.
|
||||
* `Random Generator for List (Inspire)`: When connecting the list output to the signal input, this node generates random values for all items in the list.
|
||||
* `Make Basic Pipe (Inspire)`: This is a node that creates a BASIC_PIPE using Wildcard Encode. The `Add select to` determines whether the selected item from the `Select to...` combo will be input as positive wildcard text or negative wildcard text.
|
||||
* `Remove ControlNet (Inspire)`, `Remove ControlNet [RegionalPrompts] (Inspire)`: Remove ControlNet from CONDITIONING or REGIONAL_PROMPTS.
|
||||
* `Remove ControlNet [RegionalPrompts] (Inspire)` requires Impact Pack V4.73.1 or above.
|
||||
|
||||
* Regional Nodes - These node simplifies the application of prompts by region.
|
||||
### Regional Nodes - These node simplifies the application of prompts by region.
|
||||
* Regional Sampler - These nodes assists in the easy utilization of the regional sampler in the `Impact Pack`.
|
||||
* `Regional Prompt Simple (Inspire)`: This node takes `mask` and `basic_pipe` as inputs and simplifies the creation of `REGIONAL_PROMPTS`.
|
||||
* `Regional Prompt By Color Mask (Inspire)`: Similar to `Regional Prompt Simple (Inspire)`, this function accepts a color mask image as input and defines the region using the color value that will be used as the mask, instead of directly receiving the mask.
|
||||
@@ -91,8 +104,18 @@ This repository offers various extension nodes for ComfyUI. Nodes here have diff
|
||||
* Regional Seed Explorer - These nodes restrict the variation through a seed prompt, applying it only to the masked areas.
|
||||
* `Regional Seed Explorer By Mask (Inspire)`
|
||||
* `Regional Seed Explorer By Color Mask (Inspire)`
|
||||
* `Regional CFG (Inspire)` - By applying a mask as a multiplier to the configured cfg, it allows different areas to have different cfg settings.
|
||||
* `Color Mask To Depth Mask (Inspire)` - Convert the color map from the spec text into a mask with depth values ranging from 0.0 to 1.0.
|
||||
* The range of the mask value is limited to 0.0 to 1.0.
|
||||
* base_value: Sets the value of the base mask.
|
||||
* dilation: Dilation applied to each mask layer before flattening.
|
||||
* flatten_method: The method of flattening the mask layers.
|
||||
* The layers are flattened including the base layer set by base_value.
|
||||
* override: Each pixel is overwritten by the non-zero value of the upper layer.
|
||||
* sum: Each pixel is flattened by summing the values of all layers.
|
||||
* max: Each pixel is flattened by taking the maximum value from all layers.
|
||||
|
||||
* Image Util
|
||||
### Image Util
|
||||
* `Load Image Batch From Dir (Inspire)`: This is almost same as `LoadImagesFromDirectory` of [ComfyUI-Advanced-Controlnet](https://github.com/Kosinkadink/ComfyUI-Advanced-ControlNet). This is just a modified version. Just note that this node forcibly normalizes the size of the loaded image to match the size of the first image, even if they are not the same size, to create a batch image.
|
||||
* `Load Image List From Dir (Inspire)`: This is almost same as `Load Image Batch From Dir (Inspire)`. However, note that this node loads data in a list format, not as a batch, so it returns images at their original size without normalizing the size.
|
||||
* `Load Image (Inspire)`: This node is similar to LoadImage, but the loaded image information is stored in the workflow. The image itself is stored in the workflow, making it easier to reproduce image generation on other computers.
|
||||
@@ -102,10 +125,8 @@ This repository offers various extension nodes for ComfyUI. Nodes here have diff
|
||||
* `ImageBatchSplitter //Inspire`, `LatentBatchSplitter //Inspire`: The script divides a batch of images/latents into individual images/latents, each with a quantity equal to the specified `split_count`. An additional output slot is added for each `split_count`. If the number of images/latents exceeds the `split_count`, the remaining ones are returned as the "remained" output.
|
||||
* `Color Map To Masks (Inspire)`: From the color_map, it extracts the top max_count number of colors and creates masks. min_pixels represents the minimum number of pixels for each color.
|
||||
* `Select Nth Mask (Inspire)`: Extracts the nth mask from the mask batch.
|
||||
|
||||
* KSampler Progress - In KSampler, the sampling process generates latent batches. By using `Video Combine` node from [ComfyUI-VideoHelperSuite](https://github.com/Kosinkadink/ComfyUI-VideoHelperSuite), you can create a video from the progress.
|
||||
|
||||
* Backend Cache - Nodes for storing arbitrary data from the backend in a cache and sharing it across multiple workflows.
|
||||
### Backend Cache - Nodes for storing arbitrary data from the backend in a cache and sharing it across multiple workflows.
|
||||
* `Cache Backend Data (Inspire)`: Stores any backend data in the cache using a string key. Tags are for quick reference.
|
||||
* `Retrieve Backend Data (Inspire)`: Retrieves cached backend data using a string key.
|
||||
* `Remove Backend Data (Inspire)`: Removes cached backend data.
|
||||
@@ -122,18 +143,20 @@ This repository offers various extension nodes for ComfyUI. Nodes here have diff
|
||||
* This node resolves the issue of reloading checkpoints during workflow switching.
|
||||
* `Stable Cascade Checkpoint Loader (Inspire)`: This node provides a feature that allows you to load the `stage_b` and `stage_c` checkpoints of Stable Cascade at once, and it also provides a backend caching feature, optionally.
|
||||
|
||||
* Conditioning - Nodes for conditionings
|
||||
### Conditioning - Nodes for conditionings
|
||||
* `Concat Conditionings with Multiplier (Inspire)`: Concatenating an arbitrary number of Conditionings while applying a multiplier for each Conditioning. The multiplier depends on `comfy_PoP`, so [comfy_PoP](https://github.com/picturesonpictures/comfy_PoP) must be installed.
|
||||
* `Conditioning Upscale (Inspire)`: When upscaling an image, it helps to expand the conditioning area according to the upscale factor. Taken from [ComfyUI_Dave_CustomNode](https://github.com/Davemane42/ComfyUI_Dave_CustomNode)
|
||||
* `Conditioning Stretch (Inspire)`: When upscaling an image, it helps to expand the conditioning area by specifying the original resolution and the new resolution to be applied. Taken from [ComfyUI_Dave_CustomNode](https://github.com/Davemane42/ComfyUI_Dave_CustomNode)
|
||||
|
||||
* Models - Nodes for models
|
||||
### Models - Nodes for models
|
||||
* `IPAdapter Model Helper (Inspire)`: This provides presets that allow for easy loading of the IPAdapter related models. However, it is essential for the model's name to be accurate.
|
||||
* You can download the appropriate model through ComfyUI-Manager.
|
||||
|
||||
* Util - Utilities
|
||||
### Util - Utilities
|
||||
* `Float Range (Inspire)`: Create a float list that increases the value by `step` from `start` to `stop`. A list as large as the maximum limit is created, and when `ensure_end` is enabled, the last value of the list becomes the stop value.
|
||||
* `ToIPAdapterPipe (Inspire)`, `FromIPAdapterPipe (Inspire)`: These nodes assists in conveniently using the bundled ipadapter_model, clip_vision, and model required for applying IPAdapter.
|
||||
* `List Counter (Inspire)`: When each item in the list traverses through this node, it increments a counter by one, generating an integer value.
|
||||
|
||||
* `RGB Hex To HSV (Inspire)`: Convert an RGB hex string like `#FFD500` to HSV:
|
||||
|
||||
## Credits
|
||||
|
||||
@@ -149,4 +172,8 @@ Kosinkadink/[ComfyUI-Advanced-Controlnet](https://github.com/Kosinkadink/ComfyUI
|
||||
|
||||
Trung0246/[ComfyUI-0246](https://github.com/Trung0246/ComfyUI-0246) - Nice bypass hack!
|
||||
|
||||
cubiq/[ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) - IPAdapter related nodes depend on this extension.
|
||||
cubiq/[ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) - IPAdapter related nodes depend on this extension.
|
||||
|
||||
Davemane42/[ComfyUI_Dave_CustomNode](https://github.com/Davemane42/ComfyUI_Dave_CustomNode) - Original author of ConditioningStretch, ConditioningUpscale
|
||||
|
||||
BlenderNeko/[ComfyUI_Noise](https://github.com/BlenderNeko/ComfyUI_Noise) - slerp code for noise variation
|
||||
|
||||
+3
-2
@@ -7,7 +7,7 @@
|
||||
|
||||
import importlib
|
||||
|
||||
version_code = [0, 69, 2]
|
||||
version_code = [1, 5, 1]
|
||||
version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
|
||||
print(f"### Loading: ComfyUI-Inspire-Pack ({version_str})")
|
||||
|
||||
@@ -23,7 +23,8 @@ node_list = [
|
||||
"backend_support",
|
||||
"list_nodes",
|
||||
"conditioning_nodes",
|
||||
"model_nodes"
|
||||
"model_nodes",
|
||||
"util_nodes"
|
||||
]
|
||||
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
|
||||
+100
-15
@@ -7,12 +7,61 @@ import math
|
||||
from .libs import common
|
||||
|
||||
|
||||
class Inspire_RandomNoise:
|
||||
def __init__(self, seed, mode, incremental_seed_mode, variation_seed, variation_strength, variation_method="linear"):
|
||||
device = comfy.model_management.get_torch_device()
|
||||
self.seed = seed
|
||||
self.noise_device = "cpu" if mode == "CPU" else device
|
||||
self.incremental_seed_mode = incremental_seed_mode
|
||||
self.variation_seed = variation_seed
|
||||
self.variation_strength = variation_strength
|
||||
self.variation_method = variation_method
|
||||
|
||||
def generate_noise(self, input_latent):
|
||||
latent_image = input_latent["samples"]
|
||||
batch_inds = input_latent["batch_index"] if "batch_index" in input_latent else None
|
||||
noise = utils.prepare_noise(latent_image, self.seed, batch_inds, self.noise_device, self.incremental_seed_mode,
|
||||
variation_seed=self.variation_seed, variation_strength=self.variation_strength, variation_method=self.variation_method)
|
||||
return noise.cpu()
|
||||
|
||||
|
||||
class RandomNoise:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"noise_mode": (["GPU(=A1111)", "CPU"],),
|
||||
"batch_seed_mode": (["incremental", "comfy", "variation str inc:0.01", "variation str inc:0.05"],),
|
||||
"variation_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"variation_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
},
|
||||
"optional":
|
||||
{"variation_method": (["linear", "slerp"],), }
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("NOISE",)
|
||||
FUNCTION = "get_noise"
|
||||
CATEGORY = "InspirePack/a1111_compat"
|
||||
|
||||
def get_noise(self, noise_seed, noise_mode, batch_seed_mode, variation_seed, variation_strength, variation_method="linear"):
|
||||
return (Inspire_RandomNoise(noise_seed, noise_mode, batch_seed_mode, variation_seed, variation_strength, variation_method=variation_method),)
|
||||
|
||||
|
||||
def inspire_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent, denoise=1.0,
|
||||
noise_mode="CPU", disable_noise=False, start_step=None, last_step=None, force_full_denoise=False,
|
||||
incremental_seed_mode="comfy", variation_seed=None, variation_strength=None, noise=None, callback=None):
|
||||
incremental_seed_mode="comfy", variation_seed=None, variation_strength=None, noise=None, callback=None, variation_method="linear",
|
||||
scheduler_func=None):
|
||||
device = comfy.model_management.get_torch_device()
|
||||
noise_device = "cpu" if noise_mode == "CPU" else device
|
||||
latent_image = latent["samples"]
|
||||
if hasattr(comfy.sample, 'fix_empty_latent_channels'):
|
||||
latent_image = comfy.sample.fix_empty_latent_channels(model, latent_image)
|
||||
|
||||
latent = latent.copy()
|
||||
|
||||
if noise is not None and latent_image.shape[1] != noise.shape[1]:
|
||||
print("[Inspire Pack] inspire_ksampler: The type of latent input for noise generation does not match the model's latent type. When using the SD3 model, you must use the SD3 Empty Latent.")
|
||||
raise Exception("The type of latent input for noise generation does not match the model's latent type. When using the SD3 model, you must use the SD3 Empty Latent.")
|
||||
|
||||
if noise is None:
|
||||
if disable_noise:
|
||||
@@ -20,7 +69,8 @@ def inspire_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive,
|
||||
noise = torch.zeros(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout, device=noise_device)
|
||||
else:
|
||||
batch_inds = latent["batch_index"] if "batch_index" in latent else None
|
||||
noise = utils.prepare_noise(latent_image, seed, batch_inds, noise_device, incremental_seed_mode, variation_seed=variation_seed, variation_strength=variation_strength)
|
||||
noise = utils.prepare_noise(latent_image, seed, batch_inds, noise_device, incremental_seed_mode,
|
||||
variation_seed=variation_seed, variation_strength=variation_strength, variation_method=variation_method)
|
||||
|
||||
if start_step is None:
|
||||
if denoise == 1.0:
|
||||
@@ -30,9 +80,21 @@ def inspire_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive,
|
||||
start_step = advanced_steps - steps
|
||||
steps = advanced_steps
|
||||
|
||||
samples = common.impact_sampling(
|
||||
model=model, add_noise=not disable_noise, seed=seed, steps=steps, cfg=cfg, sampler_name=sampler_name, scheduler=scheduler, positive=positive, negative=negative,
|
||||
latent_image=latent, start_at_step=start_step, end_at_step=last_step, return_with_leftover_noise=not force_full_denoise, noise=noise, callback=callback)
|
||||
try:
|
||||
samples = common.impact_sampling(
|
||||
model=model, add_noise=not disable_noise, seed=seed, steps=steps, cfg=cfg, sampler_name=sampler_name, scheduler=scheduler, positive=positive, negative=negative,
|
||||
latent_image=latent, start_at_step=start_step, end_at_step=last_step, return_with_leftover_noise=not force_full_denoise, noise=noise, callback=callback,
|
||||
scheduler_func=scheduler_func)
|
||||
except Exception as e:
|
||||
if "unexpected keyword argument 'scheduler_func'" in str(e):
|
||||
print(f"[Inspire Pack] Impact Pack is outdated. (Cannot use GITS scheduler.)")
|
||||
|
||||
samples = common.impact_sampling(
|
||||
model=model, add_noise=not disable_noise, seed=seed, steps=steps, cfg=cfg, sampler_name=sampler_name, scheduler=scheduler, positive=positive, negative=negative,
|
||||
latent_image=latent, start_at_step=start_step, end_at_step=last_step, return_with_leftover_noise=not force_full_denoise, noise=noise, callback=callback)
|
||||
else:
|
||||
raise e
|
||||
|
||||
return samples, noise
|
||||
|
||||
|
||||
@@ -54,7 +116,12 @@ class KSampler_inspire:
|
||||
"batch_seed_mode": (["incremental", "comfy", "variation str inc:0.01", "variation str inc:0.05"],),
|
||||
"variation_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"variation_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
}
|
||||
},
|
||||
"optional":
|
||||
{
|
||||
"variation_method": (["linear", "slerp"],),
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LATENT",)
|
||||
@@ -62,8 +129,12 @@ class KSampler_inspire:
|
||||
|
||||
CATEGORY = "InspirePack/a1111_compat"
|
||||
|
||||
def doit(self, model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise, noise_mode, batch_seed_mode="comfy", variation_seed=None, variation_strength=None):
|
||||
return (inspire_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise, noise_mode, incremental_seed_mode=batch_seed_mode, variation_seed=variation_seed, variation_strength=variation_strength)[0],)
|
||||
@staticmethod
|
||||
def doit(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise, noise_mode,
|
||||
batch_seed_mode="comfy", variation_seed=None, variation_strength=None, variation_method="linear", scheduler_func_opt=None):
|
||||
return (inspire_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise, noise_mode,
|
||||
incremental_seed_mode=batch_seed_mode, variation_seed=variation_seed, variation_strength=variation_strength, variation_method=variation_method,
|
||||
scheduler_func=scheduler_func_opt)[0], )
|
||||
|
||||
|
||||
class KSamplerAdvanced_inspire:
|
||||
@@ -90,7 +161,9 @@ class KSamplerAdvanced_inspire:
|
||||
},
|
||||
"optional":
|
||||
{
|
||||
"variation_method": (["linear", "slerp"],),
|
||||
"noise_opt": ("NOISE",),
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +173,8 @@ class KSamplerAdvanced_inspire:
|
||||
CATEGORY = "InspirePack/a1111_compat"
|
||||
|
||||
@staticmethod
|
||||
def sample(model, add_noise, noise_seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, start_at_step, end_at_step, noise_mode, return_with_leftover_noise, denoise=1.0, batch_seed_mode="comfy", variation_seed=None, variation_strength=None, noise_opt=None, callback=None):
|
||||
def sample(model, add_noise, noise_seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, start_at_step, end_at_step, noise_mode, return_with_leftover_noise,
|
||||
denoise=1.0, batch_seed_mode="comfy", variation_seed=None, variation_strength=None, noise_opt=None, callback=None, variation_method="linear", scheduler_func_opt=None):
|
||||
force_full_denoise = True
|
||||
|
||||
if return_with_leftover_noise:
|
||||
@@ -114,7 +188,8 @@ class KSamplerAdvanced_inspire:
|
||||
return inspire_ksampler(model, noise_seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image,
|
||||
denoise=denoise, disable_noise=disable_noise, start_step=start_at_step, last_step=end_at_step,
|
||||
force_full_denoise=force_full_denoise, noise_mode=noise_mode, incremental_seed_mode=batch_seed_mode,
|
||||
variation_seed=variation_seed, variation_strength=variation_strength, noise=noise_opt, callback=callback)
|
||||
variation_seed=variation_seed, variation_strength=variation_strength, noise=noise_opt, callback=callback, variation_method=variation_method,
|
||||
scheduler_func=scheduler_func_opt)
|
||||
|
||||
def doit(self, *args, **kwargs):
|
||||
return (self.sample(*args, **kwargs)[0],)
|
||||
@@ -136,7 +211,11 @@ class KSampler_inspire_pipe:
|
||||
"batch_seed_mode": (["incremental", "comfy", "variation str inc:0.01", "variation str inc:0.05"],),
|
||||
"variation_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"variation_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
}
|
||||
},
|
||||
"optional":
|
||||
{
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LATENT", "VAE")
|
||||
@@ -144,9 +223,11 @@ class KSampler_inspire_pipe:
|
||||
|
||||
CATEGORY = "InspirePack/a1111_compat"
|
||||
|
||||
def sample(self, basic_pipe, seed, steps, cfg, sampler_name, scheduler, latent_image, denoise, noise_mode, batch_seed_mode="comfy", variation_seed=None, variation_strength=None):
|
||||
def sample(self, basic_pipe, seed, steps, cfg, sampler_name, scheduler, latent_image, denoise, noise_mode, batch_seed_mode="comfy",
|
||||
variation_seed=None, variation_strength=None, scheduler_func_opt=None):
|
||||
model, clip, vae, positive, negative = basic_pipe
|
||||
latent = inspire_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise, noise_mode, incremental_seed_mode=batch_seed_mode, variation_seed=variation_seed, variation_strength=variation_strength)[0]
|
||||
latent = inspire_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise, noise_mode, incremental_seed_mode=batch_seed_mode,
|
||||
variation_seed=variation_seed, variation_strength=variation_strength, scheduler_func=scheduler_func_opt)[0]
|
||||
return latent, vae
|
||||
|
||||
|
||||
@@ -173,6 +254,7 @@ class KSamplerAdvanced_inspire_pipe:
|
||||
"optional":
|
||||
{
|
||||
"noise_opt": ("NOISE",),
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +263,8 @@ class KSamplerAdvanced_inspire_pipe:
|
||||
|
||||
CATEGORY = "InspirePack/a1111_compat"
|
||||
|
||||
def sample(self, basic_pipe, add_noise, noise_seed, steps, cfg, sampler_name, scheduler, latent_image, start_at_step, end_at_step, noise_mode, return_with_leftover_noise, denoise=1.0, batch_seed_mode="comfy", variation_seed=None, variation_strength=None, noise_opt=None):
|
||||
def sample(self, basic_pipe, add_noise, noise_seed, steps, cfg, sampler_name, scheduler, latent_image, start_at_step, end_at_step, noise_mode, return_with_leftover_noise,
|
||||
denoise=1.0, batch_seed_mode="comfy", variation_seed=None, variation_strength=None, noise_opt=None, scheduler_func_opt=None):
|
||||
model, clip, vae, positive, negative = basic_pipe
|
||||
latent = KSamplerAdvanced_inspire().sample(model=model, add_noise=add_noise, noise_seed=noise_seed,
|
||||
steps=steps, cfg=cfg, sampler_name=sampler_name, scheduler=scheduler,
|
||||
@@ -189,7 +272,7 @@ class KSamplerAdvanced_inspire_pipe:
|
||||
start_at_step=start_at_step, end_at_step=end_at_step,
|
||||
noise_mode=noise_mode, return_with_leftover_noise=return_with_leftover_noise,
|
||||
denoise=denoise, batch_seed_mode=batch_seed_mode, variation_seed=variation_seed,
|
||||
variation_strength=variation_strength, noise_opt=noise_opt)[0]
|
||||
variation_strength=variation_strength, noise_opt=noise_opt, scheduler_func_opt=scheduler_func_opt)[0]
|
||||
return latent, vae
|
||||
|
||||
|
||||
@@ -338,6 +421,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"KSamplerAdvanced //Inspire": KSamplerAdvanced_inspire,
|
||||
"KSamplerPipe //Inspire": KSampler_inspire_pipe,
|
||||
"KSamplerAdvancedPipe //Inspire": KSamplerAdvanced_inspire_pipe,
|
||||
"RandomNoise //Inspire": RandomNoise,
|
||||
"HyperTile //Inspire": HyperTileInspire
|
||||
}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
@@ -345,5 +429,6 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"KSamplerAdvanced //Inspire": "KSamplerAdvanced (inspire)",
|
||||
"KSamplerPipe //Inspire": "KSampler [pipe] (inspire)",
|
||||
"KSamplerAdvancedPipe //Inspire": "KSamplerAdvanced [pipe] (inspire)",
|
||||
"RandomNoise //Inspire": "RandomNoise (inspire)",
|
||||
"HyperTile //Inspire": "HyperTile (Inspire)"
|
||||
}
|
||||
|
||||
+103
-13
@@ -2,25 +2,33 @@ import torch
|
||||
import nodes
|
||||
import inspect
|
||||
from .libs import utils
|
||||
from nodes import MAX_RESOLUTION
|
||||
|
||||
|
||||
class ConcatConditioningsWithMultiplier:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
flex_inputs = {}
|
||||
|
||||
stack = inspect.stack()
|
||||
if stack[1].function == 'get_input_data':
|
||||
if stack[1].function == 'get_input_info':
|
||||
# bypass validation
|
||||
for x in range(0, 100):
|
||||
flex_inputs[f"multiplier{x}"] = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01})
|
||||
else:
|
||||
flex_inputs["multiplier1"] = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01})
|
||||
class AllContainer:
|
||||
def __contains__(self, item):
|
||||
return True
|
||||
|
||||
def __getitem__(self, key):
|
||||
# Return a default value appropriate for your use case
|
||||
# Adjust the return value as needed
|
||||
return "FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}
|
||||
|
||||
return {
|
||||
"required": {"conditioning1": ("CONDITIONING",), },
|
||||
"optional": AllContainer()
|
||||
}
|
||||
|
||||
return {
|
||||
"required": {"conditioning1": ("CONDITIONING",), },
|
||||
"optional": flex_inputs
|
||||
}
|
||||
"required": {"conditioning1": ("CONDITIONING",), },
|
||||
"optional": {"multiplier1": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), },
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("CONDITIONING",)
|
||||
FUNCTION = "doit"
|
||||
@@ -47,7 +55,7 @@ class ConcatConditioningsWithMultiplier:
|
||||
if len(conditioning_from) > 1:
|
||||
print(f"Warning: ConcatConditioningsWithMultiplier {k} contains more than 1 cond, only the first one will actually be applied to conditioning1.")
|
||||
|
||||
mkey = 'multiplier'+k[12:]
|
||||
mkey = 'multiplier' + k[12:]
|
||||
multiplier = float(kwargs[mkey])
|
||||
conditioning_from = obj.multiply_conditioning_strength(conditioning=conditioning_from, multiplier=multiplier)[0]
|
||||
cond_from = conditioning_from[0][0]
|
||||
@@ -61,16 +69,98 @@ class ConcatConditioningsWithMultiplier:
|
||||
conditioning_to = out
|
||||
|
||||
if out is None:
|
||||
return (kwargs['conditioning1'], )
|
||||
return (kwargs['conditioning1'],)
|
||||
else:
|
||||
return (out,)
|
||||
|
||||
|
||||
# CREDIT for ConditioningStretch, ConditioningUpscale: Davemane42
|
||||
# Imported to support archived custom nodes.
|
||||
# original code: https://github.com/Davemane42/ComfyUI_Dave_CustomNode/blob/main/MultiAreaConditioning.py
|
||||
class ConditioningStretch:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"conditioning": ("CONDITIONING",),
|
||||
"resolutionX": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 64}),
|
||||
"resolutionY": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 64}),
|
||||
"newWidth": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 64}),
|
||||
"newHeight": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 64}),
|
||||
# "scalar": ("INT", {"default": 2, "min": 1, "max": 100, "step": 0.5}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("CONDITIONING",)
|
||||
CATEGORY = "InspirePack/conditioning"
|
||||
|
||||
FUNCTION = 'upscale'
|
||||
|
||||
@staticmethod
|
||||
def upscale(conditioning, resolutionX, resolutionY, newWidth, newHeight, scalar=1):
|
||||
c = []
|
||||
for t in conditioning:
|
||||
|
||||
n = [t[0], t[1].copy()]
|
||||
if 'area' in n[1]:
|
||||
newWidth *= scalar
|
||||
newHeight *= scalar
|
||||
|
||||
x = ((n[1]['area'][3] * 8) * newWidth / resolutionX) // 8
|
||||
y = ((n[1]['area'][2] * 8) * newHeight / resolutionY) // 8
|
||||
w = ((n[1]['area'][1] * 8) * newWidth / resolutionX) // 8
|
||||
h = ((n[1]['area'][0] * 8) * newHeight / resolutionY) // 8
|
||||
|
||||
n[1]['area'] = tuple(map(lambda x: (((int(x) + 7) >> 3) << 3), [h, w, y, x]))
|
||||
|
||||
c.append(n)
|
||||
|
||||
return (c,)
|
||||
|
||||
|
||||
class ConditioningUpscale:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"conditioning": ("CONDITIONING",),
|
||||
"scalar": ("INT", {"default": 2, "min": 1, "max": 100, "step": 0.5}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("CONDITIONING",)
|
||||
CATEGORY = "InspirePack/conditioning"
|
||||
|
||||
FUNCTION = 'upscale'
|
||||
|
||||
@staticmethod
|
||||
def upscale(conditioning, scalar):
|
||||
c = []
|
||||
for t in conditioning:
|
||||
|
||||
n = [t[0], t[1].copy()]
|
||||
if 'area' in n[1]:
|
||||
n[1]['area'] = tuple(map(lambda x: ((x * scalar + 7) >> 3) << 3, n[1]['area']))
|
||||
|
||||
c.append(n)
|
||||
|
||||
return (c,)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"ConcatConditioningsWithMultiplier //Inspire": ConcatConditioningsWithMultiplier,
|
||||
"ConditioningUpscale //Inspire": ConditioningUpscale,
|
||||
"ConditioningStretch //Inspire": ConditioningStretch,
|
||||
}
|
||||
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ConcatConditioningsWithMultiplier //Inspire": "Concat Conditionings with Multiplier (Inspire)",
|
||||
"ConditioningUpscale //Inspire": "Conditioning Upscale (Inspire)",
|
||||
"ConditioningStretch //Inspire": "Conditioning Stretch (Inspire)",
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ class LoadImagesFromDirBatch:
|
||||
},
|
||||
"optional": {
|
||||
"image_load_cap": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"start_index": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"start_index": ("INT", {"default": 0, "min": -1, "step": 1}),
|
||||
"load_always": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
}
|
||||
}
|
||||
@@ -94,10 +94,10 @@ class LoadImagesFromDirBatch:
|
||||
image2 = comfy.utils.common_upscale(image2.movedim(-1, 1), image1.shape[2], image1.shape[1], "bilinear", "center").movedim(1, -1)
|
||||
image1 = torch.cat((image1, image2), dim=0)
|
||||
|
||||
for mask2 in masks[1:]:
|
||||
for mask2 in masks:
|
||||
if has_non_empty_mask:
|
||||
if image1.shape[1:3] != mask2.shape:
|
||||
mask2 = torch.nn.functional.interpolate(mask2.unsqueeze(0).unsqueeze(0), size=(image1.shape[2], image1.shape[1]), mode='bilinear', align_corners=False)
|
||||
mask2 = torch.nn.functional.interpolate(mask2.unsqueeze(0).unsqueeze(0), size=(image1.shape[1], image1.shape[2]), mode='bilinear', align_corners=False)
|
||||
mask2 = mask2.squeeze(0)
|
||||
else:
|
||||
mask2 = mask2.unsqueeze(0)
|
||||
@@ -126,8 +126,9 @@ class LoadImagesFromDirList:
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "MASK")
|
||||
OUTPUT_IS_LIST = (True, True)
|
||||
RETURN_TYPES = ("IMAGE", "MASK", "STRING")
|
||||
RETURN_NAMES = ("IMAGE", "MASK", "FILE PATH")
|
||||
OUTPUT_IS_LIST = (True, True, True)
|
||||
|
||||
FUNCTION = "load_images"
|
||||
|
||||
@@ -159,6 +160,7 @@ class LoadImagesFromDirList:
|
||||
|
||||
images = []
|
||||
masks = []
|
||||
file_paths = []
|
||||
|
||||
limit_images = False
|
||||
if image_load_cap > 0:
|
||||
@@ -184,9 +186,10 @@ class LoadImagesFromDirList:
|
||||
|
||||
images.append(image)
|
||||
masks.append(mask)
|
||||
file_paths.append(str(image_path))
|
||||
image_count += 1
|
||||
|
||||
return images, masks
|
||||
return (images, masks, file_paths)
|
||||
|
||||
|
||||
class LoadImageInspire:
|
||||
|
||||
@@ -331,15 +331,17 @@ def populate_wildcards(json_data):
|
||||
mbp_updated_widget_values[k] = inputs['positive_populated_text'], inputs['negative_populated_text']
|
||||
|
||||
if 'extra_data' in json_data and 'extra_pnginfo' in json_data['extra_data']:
|
||||
for node in json_data['extra_data']['extra_pnginfo']['workflow']['nodes']:
|
||||
key = str(node['id'])
|
||||
if key in updated_widget_values:
|
||||
node['widgets_values'][3] = updated_widget_values[key]
|
||||
node['widgets_values'][4] = False
|
||||
if key in mbp_updated_widget_values:
|
||||
node['widgets_values'][7] = mbp_updated_widget_values[key][0]
|
||||
node['widgets_values'][8] = mbp_updated_widget_values[key][1]
|
||||
node['widgets_values'][5] = False
|
||||
extra_pnginfo = json_data['extra_data']['extra_pnginfo']
|
||||
if 'workflow' in extra_pnginfo and 'nodes' in extra_pnginfo['workflow']:
|
||||
for node in extra_pnginfo['workflow']['nodes']:
|
||||
key = str(node['id'])
|
||||
if key in updated_widget_values:
|
||||
node['widgets_values'][3] = updated_widget_values[key]
|
||||
node['widgets_values'][4] = False
|
||||
if key in mbp_updated_widget_values:
|
||||
node['widgets_values'][7] = mbp_updated_widget_values[key][0]
|
||||
node['widgets_values'][8] = mbp_updated_widget_values[key][1]
|
||||
node['widgets_values'][5] = False
|
||||
|
||||
|
||||
def force_reset_useless_params(json_data):
|
||||
|
||||
@@ -2,7 +2,7 @@ import comfy
|
||||
import nodes
|
||||
from . import utils
|
||||
|
||||
SCHEDULERS = comfy.samplers.KSampler.SCHEDULERS + ['AYS SDXL', 'AYS SD1', 'AYS SVD']
|
||||
SCHEDULERS = comfy.samplers.KSampler.SCHEDULERS + ['AYS SDXL', 'AYS SD1', 'AYS SVD', "GITS[coeff=1.2]"]
|
||||
|
||||
|
||||
def impact_sampling(*args, **kwargs):
|
||||
|
||||
+101
-8
@@ -4,9 +4,11 @@ import numpy as np
|
||||
import torch
|
||||
from PIL import Image, ImageDraw
|
||||
import math
|
||||
import cv2
|
||||
import folder_paths
|
||||
|
||||
|
||||
def apply_variation_noise(latent_image, noise_device, variation_seed, variation_strength, mask=None):
|
||||
def apply_variation_noise(latent_image, noise_device, variation_seed, variation_strength, mask=None, variation_method='linear'):
|
||||
latent_size = latent_image.size()
|
||||
latent_size_1batch = [1, latent_size[1], latent_size[2], latent_size[3]]
|
||||
|
||||
@@ -27,12 +29,50 @@ def apply_variation_noise(latent_image, noise_device, variation_seed, variation_
|
||||
result = (1 - variation_strength) * latent_image + variation_strength * variation_noise
|
||||
else:
|
||||
# this seems precision is not enough when variation_strength is 0.0
|
||||
result = (mask == 1).float() * ((1 - variation_strength) * latent_image + variation_strength * variation_noise * mask) + (mask == 0).float() * latent_image
|
||||
mixed_noise = mix_noise(latent_image, variation_noise, variation_strength, variation_method=variation_method)
|
||||
result = (mask == 1).float() * mixed_noise + (mask == 0).float() * latent_image
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def prepare_noise(latent_image, seed, noise_inds=None, noise_device="cpu", incremental_seed_mode="comfy", variation_seed=None, variation_strength=None):
|
||||
# CREDIT: https://github.com/BlenderNeko/ComfyUI_Noise/blob/afb14757216257b12268c91845eac248727a55e2/nodes.py#L68
|
||||
# https://discuss.pytorch.org/t/help-regarding-slerp-function-for-generative-model-sampling/32475/3
|
||||
def slerp(val, low, high):
|
||||
dims = low.shape
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def mix_noise(from_noise, to_noise, strength, variation_method):
|
||||
to_noise = to_noise.to(from_noise.device)
|
||||
|
||||
if variation_method == 'slerp':
|
||||
mixed_noise = slerp(strength, from_noise, to_noise)
|
||||
else:
|
||||
# linear
|
||||
mixed_noise = (1 - strength) * from_noise + strength * to_noise
|
||||
|
||||
# NOTE: Since the variance of the Gaussian noise in mixed_noise has changed, it must be corrected through scaling.
|
||||
scale_factor = math.sqrt((1 - strength) ** 2 + strength ** 2)
|
||||
mixed_noise /= scale_factor
|
||||
|
||||
return mixed_noise
|
||||
|
||||
|
||||
def prepare_noise(latent_image, seed, noise_inds=None, noise_device="cpu", incremental_seed_mode="comfy", variation_seed=None, variation_strength=None, variation_method="linear"):
|
||||
"""
|
||||
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
|
||||
@@ -63,13 +103,10 @@ def prepare_noise(latent_image, seed, noise_inds=None, noise_device="cpu", incre
|
||||
strength += strength_up
|
||||
|
||||
variation_noise = variation_latent.expand(input_latent.size()[0], -1, -1, -1)
|
||||
mixed_noise = (1 - strength) * input_latent + strength * variation_noise
|
||||
|
||||
# NOTE: Since the variance of the Gaussian noise in mixed_noise has changed, it must be corrected through scaling.
|
||||
scale_factor = math.sqrt((1 - strength) ** 2 + strength ** 2)
|
||||
corrected_noise = mixed_noise / scale_factor
|
||||
mixed_noise = mix_noise(input_latent, variation_noise, strength, variation_method)
|
||||
|
||||
return corrected_noise
|
||||
return mixed_noise
|
||||
|
||||
# method: incremental seed batch noise
|
||||
if noise_inds is None and incremental_seed_mode == "incremental":
|
||||
@@ -255,3 +292,59 @@ class TaggedCache:
|
||||
def clear(self):
|
||||
# clear all cache
|
||||
self._data = {}
|
||||
|
||||
|
||||
def make_3d_mask(mask):
|
||||
if len(mask.shape) == 4:
|
||||
return mask.squeeze(0)
|
||||
|
||||
elif len(mask.shape) == 2:
|
||||
return mask.unsqueeze(0)
|
||||
|
||||
return mask
|
||||
|
||||
|
||||
def dilate_mask(mask: torch.Tensor, dilation_factor: float) -> torch.Tensor:
|
||||
"""Dilate a mask using a square kernel with a given dilation factor."""
|
||||
kernel_size = int(dilation_factor * 2) + 1
|
||||
kernel = np.ones((abs(kernel_size), abs(kernel_size)), np.uint8)
|
||||
|
||||
masks = make_3d_mask(mask).numpy()
|
||||
dilated_masks = []
|
||||
for m in masks:
|
||||
if dilation_factor > 0:
|
||||
m2 = cv2.dilate(m, kernel, iterations=1)
|
||||
else:
|
||||
m2 = cv2.erode(m, kernel, iterations=1)
|
||||
|
||||
dilated_masks.append(torch.from_numpy(m2))
|
||||
|
||||
return torch.stack(dilated_masks)
|
||||
|
||||
|
||||
def flatten_non_zero_override(masks: torch.Tensor):
|
||||
"""
|
||||
flatten multiple layer mask tensor to 1 layer mask tensor.
|
||||
Override the lower layer with the tensor from the upper layer, but only override non-zero values.
|
||||
|
||||
:param masks: 3d mask
|
||||
:return: flatten mask
|
||||
"""
|
||||
final_mask = masks[0]
|
||||
|
||||
for i in range(1, masks.size(0)):
|
||||
non_zero_mask = masks[i] != 0
|
||||
final_mask[non_zero_mask] = masks[i][non_zero_mask]
|
||||
|
||||
return final_mask
|
||||
|
||||
|
||||
def add_folder_path_and_extensions(folder_name, full_folder_paths, extensions):
|
||||
for full_folder_path in full_folder_paths:
|
||||
folder_paths.add_model_folder_path(folder_name, full_folder_path)
|
||||
if folder_name in folder_paths.folder_names_and_paths:
|
||||
current_paths, current_extensions = folder_paths.folder_names_and_paths[folder_name]
|
||||
updated_extensions = current_extensions | extensions
|
||||
folder_paths.folder_names_and_paths[folder_name] = (current_paths, updated_extensions)
|
||||
else:
|
||||
folder_paths.folder_names_and_paths[folder_name] = (full_folder_paths, extensions)
|
||||
|
||||
@@ -18,9 +18,14 @@ class FloatRange:
|
||||
CATEGORY = "InspirePack/Util"
|
||||
|
||||
def doit(self, start, stop, step, limit, ensure_end):
|
||||
if start >= stop or step == 0:
|
||||
if start == stop or step == 0:
|
||||
return ([start], )
|
||||
|
||||
reverse = False
|
||||
if start > stop:
|
||||
reverse = True
|
||||
start, stop = stop, start
|
||||
|
||||
res = []
|
||||
x = start
|
||||
last = x
|
||||
@@ -36,6 +41,9 @@ class FloatRange:
|
||||
|
||||
res.append(stop)
|
||||
|
||||
if reverse:
|
||||
res.reverse()
|
||||
|
||||
return (res, )
|
||||
|
||||
|
||||
|
||||
+547
-56
@@ -6,11 +6,20 @@ import torch
|
||||
import numpy as np
|
||||
import nodes
|
||||
import re
|
||||
import json
|
||||
from comfy.cli_args import args
|
||||
from safetensors.torch import safe_open
|
||||
import ast
|
||||
|
||||
|
||||
from server import PromptServer
|
||||
from .libs import utils
|
||||
|
||||
|
||||
model_path = folder_paths.models_dir
|
||||
utils.add_folder_path_and_extensions("lbw_models", [os.path.join(model_path, "lbw_models")], {'.safetensors'})
|
||||
|
||||
|
||||
def is_numeric_string(input_str):
|
||||
return re.match(r'^-?\d+(\.\d+)?$', input_str) is not None
|
||||
|
||||
@@ -34,6 +43,76 @@ def load_lbw_preset(filename):
|
||||
return []
|
||||
|
||||
|
||||
def parse_unet_num(s):
|
||||
if s[1] == '.':
|
||||
return int(s[0])
|
||||
else:
|
||||
return int(s)
|
||||
|
||||
|
||||
class MakeLBW:
|
||||
def __init__(self):
|
||||
self.loaded_lora = None
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
preset = ["Preset"] # 20
|
||||
preset += load_lbw_preset("lbw-preset.txt")
|
||||
preset += load_lbw_preset("lbw-preset.custom.txt")
|
||||
preset = [name for name in preset if not name.startswith('@')]
|
||||
|
||||
lora_names = folder_paths.get_filename_list("loras")
|
||||
lora_dirs = [os.path.dirname(name) for name in lora_names]
|
||||
lora_dirs = ["All"] + list(set(lora_dirs))
|
||||
|
||||
return {"required": {"model": ("MODEL",),
|
||||
"clip": ("CLIP", ),
|
||||
"category_filter": (lora_dirs,),
|
||||
"lora_name": (lora_names, ),
|
||||
"inverse": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False", "tooltip": "Apply the following weights for each block:\nTrue: 1 - weight\nFalse: weight"}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": ""}),
|
||||
"A": ("FLOAT", {"default": 4.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
||||
"B": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
||||
"preset": (preset,),
|
||||
"block_vector": ("STRING", {"multiline": True, "placeholder": "block weight vectors", "default": "1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1", "pysssss.autocomplete": False}),
|
||||
"bypass": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LBW_MODEL", "STRING")
|
||||
RETURN_NAMES = ("lbw_model", "populated_vector")
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/LoraBlockWeight"
|
||||
|
||||
DESCRIPTION = "Instead of directly applying the LoRA Block Weight to the MODEL, it is generated in a separate LBW_MODEL form."
|
||||
|
||||
def __init__(self):
|
||||
self.loaded_lora = None
|
||||
|
||||
def doit(self, model, clip, lora_name, inverse, seed, A, B, preset, block_vector, bypass=False, category_filter=None):
|
||||
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:
|
||||
temp = self.loaded_lora
|
||||
self.loaded_lora = None
|
||||
del temp
|
||||
|
||||
if lora is None:
|
||||
lora = comfy.utils.load_torch_file(lora_path, safe_load=True)
|
||||
self.loaded_lora = (lora_path, lora)
|
||||
|
||||
block_weights, muted_weights, populated_vector = LoraLoaderBlockWeight.load_lbw(model, clip, lora, inverse, seed, A, B, block_vector)
|
||||
lbw_model = {
|
||||
'blocks': block_weights,
|
||||
'muted': muted_weights
|
||||
}
|
||||
return lbw_model, populated_vector
|
||||
|
||||
|
||||
class LoraLoaderBlockWeight:
|
||||
def __init__(self):
|
||||
self.loaded_lora = None
|
||||
@@ -55,8 +134,8 @@ class LoraLoaderBlockWeight:
|
||||
"lora_name": (lora_names, ),
|
||||
"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}),
|
||||
"inverse": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False"}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"inverse": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False", "tooltip": "Apply the following weights for each block:\nTrue: 1 - weight\nFalse: weight"}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": ""}),
|
||||
"A": ("FLOAT", {"default": 4.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
||||
"B": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
||||
"preset": (preset,),
|
||||
@@ -130,11 +209,150 @@ class LoraLoaderBlockWeight:
|
||||
return value
|
||||
|
||||
@staticmethod
|
||||
def load_lora_for_models(model, clip, lora, strength_model, strength_clip, inverse, seed, A, B, block_vector):
|
||||
def block_spec_parser(loaded, spec):
|
||||
if not spec.startswith("%"):
|
||||
return spec
|
||||
else:
|
||||
items = [x.strip() for x in spec[1:].split(',')]
|
||||
|
||||
input_blocks_set = set()
|
||||
middle_blocks_set= set()
|
||||
output_blocks_set = set()
|
||||
double_blocks_set = set()
|
||||
single_blocks_set = set()
|
||||
|
||||
for key, v in loaded.items():
|
||||
if isinstance(key, tuple):
|
||||
k = key[0]
|
||||
else:
|
||||
k = key
|
||||
|
||||
k_unet = k[len("diffusion_model."):]
|
||||
|
||||
if k_unet.startswith("input_blocks."):
|
||||
k_unet_num = k_unet[len("input_blocks."):len("input_blocks.")+2]
|
||||
k_unet_int = parse_unet_num(k_unet_num)
|
||||
input_blocks_set.add(k_unet_int)
|
||||
elif k_unet.startswith("middle_block."):
|
||||
k_unet_num = k_unet[len("middle_block."):len("middle_block.")+2]
|
||||
k_unet_int = parse_unet_num(k_unet_num)
|
||||
middle_blocks_set.add(k_unet_int)
|
||||
elif k_unet.startswith("output_blocks."):
|
||||
k_unet_num = k_unet[len("output_blocks."):len("output_blocks.")+2]
|
||||
k_unet_int = parse_unet_num(k_unet_num)
|
||||
output_blocks_set.add(k_unet_int)
|
||||
elif k_unet.startswith("double_blocks."):
|
||||
k_unet_num = k_unet[len("double_blocks."):len("double_blocks.") + 2]
|
||||
k_unet_int = parse_unet_num(k_unet_num)
|
||||
double_blocks_set.add(k_unet_int)
|
||||
elif k_unet.startswith("single_blocks."):
|
||||
k_unet_num = k_unet[len("single_blocks."):len("single_blocks.") + 2]
|
||||
k_unet_int = parse_unet_num(k_unet_num)
|
||||
single_blocks_set.add(k_unet_int)
|
||||
|
||||
pat1 = re.compile(r"(default|base)=([0-9.]+)")
|
||||
pat2 = re.compile(r"(in|out|mid|double|single)([0-9]+)-([0-9]+)=([0-9.]+)")
|
||||
pat3 = re.compile(r"(in|out|mid|double|single)([0-9]+)=([0-9.]+)")
|
||||
pat4 = re.compile(r"(in|out|mid|double|single)=([0-9.]+)")
|
||||
|
||||
base_spec = None
|
||||
default_spec = 1.0
|
||||
|
||||
for item in items:
|
||||
match = pat1.match(item)
|
||||
if match:
|
||||
if match[1] == 'base':
|
||||
base_spec = match[2]
|
||||
continue
|
||||
|
||||
if match[1] == 'default':
|
||||
default_spec = match[2]
|
||||
continue
|
||||
|
||||
if base_spec is None:
|
||||
base_spec = default_spec
|
||||
|
||||
input_blocks = [default_spec] * len(input_blocks_set)
|
||||
middle_blocks = [default_spec] * len(middle_blocks_set)
|
||||
output_blocks = [default_spec] * len(output_blocks_set)
|
||||
double_blocks = [default_spec] * len(double_blocks_set)
|
||||
single_blocks = [default_spec] * len(single_blocks_set)
|
||||
|
||||
for item in items:
|
||||
match = pat2.match(item)
|
||||
if match:
|
||||
for x in range(int(match[2])-1, int(match[3])):
|
||||
value = float(match[4])
|
||||
|
||||
if x < 0:
|
||||
continue
|
||||
|
||||
if match[1] == 'in' and len(input_blocks) > x:
|
||||
input_blocks[x] = value
|
||||
elif match[1] == 'out' and len(output_blocks) > x:
|
||||
output_blocks[x] = value
|
||||
elif match[1] == 'mid' and len(middle_blocks) > x:
|
||||
middle_blocks[x] = value
|
||||
elif match[1] == 'double' and len(double_blocks) > x:
|
||||
double_blocks[x] = value
|
||||
elif match[1] == 'single' and len(single_blocks) > x:
|
||||
single_blocks[x] = value
|
||||
|
||||
continue
|
||||
|
||||
match = pat3.match(item)
|
||||
if match:
|
||||
value = float(match[3])
|
||||
x = int(match[2]) - 1
|
||||
|
||||
if x < 0:
|
||||
continue
|
||||
|
||||
if match[1] == 'in' and len(input_blocks) > x:
|
||||
input_blocks[x] = value
|
||||
elif match[1] == 'out' and len(output_blocks) > x:
|
||||
output_blocks[x] = value
|
||||
elif match[1] == 'mid' and len(middle_blocks) > x:
|
||||
middle_blocks[x] = value
|
||||
elif match[1] == 'double' and len(double_blocks) > x:
|
||||
double_blocks[x] = value
|
||||
elif match[1] == 'single' and len(single_blocks) > x:
|
||||
single_blocks[x] = value
|
||||
|
||||
continue
|
||||
|
||||
match = pat4.match(item)
|
||||
if match:
|
||||
value = float(match[2])
|
||||
|
||||
if match[1] == 'in':
|
||||
input_blocks = [value] * len(input_blocks)
|
||||
elif match[1] == 'out':
|
||||
output_blocks = [value] * len(output_blocks)
|
||||
elif match[1] == 'mid':
|
||||
middle_blocks = [value] * len(middle_blocks)
|
||||
elif match[1] == 'double':
|
||||
double_blocks = [value] * len(double_blocks)
|
||||
elif match[1] == 'single':
|
||||
single_blocks = [value] * len(single_blocks)
|
||||
|
||||
continue
|
||||
|
||||
# concat specs
|
||||
res = [str(base_spec)]
|
||||
for x in (input_blocks + middle_blocks + output_blocks + double_blocks + single_blocks):
|
||||
res.append(str(x))
|
||||
|
||||
return ",".join(res)
|
||||
|
||||
@staticmethod
|
||||
def load_lbw(model, clip, lora, inverse, seed, A, B, block_vector):
|
||||
key_map = comfy.lora.model_lora_keys_unet(model.model)
|
||||
key_map = comfy.lora.model_lora_keys_clip(clip.cond_stage_model, key_map)
|
||||
loaded = comfy.lora.load_lora(lora, key_map)
|
||||
|
||||
block_vector = LoraLoaderBlockWeight.block_spec_parser(loaded, block_vector)
|
||||
|
||||
block_vector = block_vector.split(":")
|
||||
if len(block_vector) > 1:
|
||||
block_vector = block_vector[1]
|
||||
@@ -142,7 +360,6 @@ class LoraLoaderBlockWeight:
|
||||
block_vector = block_vector[0]
|
||||
|
||||
vector = block_vector.split(",")
|
||||
vector_i = 1
|
||||
|
||||
if not LoraLoaderBlockWeight.validate(vector):
|
||||
preset_dict = load_preset_dict()
|
||||
@@ -151,22 +368,19 @@ class LoraLoaderBlockWeight:
|
||||
else:
|
||||
raise ValueError(f"[LoraLoaderBlockWeight] invalid block_vector '{block_vector}'")
|
||||
|
||||
last_k_unet_num = None
|
||||
new_modelpatcher = model.clone()
|
||||
populated_ratio = strength_model
|
||||
|
||||
def parse_unet_num(s):
|
||||
if s[1] == '.':
|
||||
return int(s[0])
|
||||
else:
|
||||
return int(s)
|
||||
|
||||
# sort: input, middle, output, others
|
||||
input_blocks = []
|
||||
middle_blocks = []
|
||||
output_blocks = []
|
||||
double_blocks = []
|
||||
single_blocks = []
|
||||
others = []
|
||||
for k, v in loaded.items():
|
||||
for key, v in loaded.items():
|
||||
if isinstance(key, tuple):
|
||||
k = key[0]
|
||||
else:
|
||||
k = key
|
||||
|
||||
k_unet = k[len("diffusion_model."):]
|
||||
|
||||
if k_unet.startswith("input_blocks."):
|
||||
@@ -178,18 +392,34 @@ class LoraLoaderBlockWeight:
|
||||
elif k_unet.startswith("output_blocks."):
|
||||
k_unet_num = k_unet[len("output_blocks."):len("output_blocks.")+2]
|
||||
output_blocks.append((k, v, parse_unet_num(k_unet_num), k_unet))
|
||||
elif k_unet.startswith("double_blocks."):
|
||||
k_unet_num = k_unet[len("double_blocks."):len("double_blocks.")+2]
|
||||
double_blocks.append((key, v, parse_unet_num(k_unet_num), k_unet))
|
||||
elif k_unet.startswith("single_blocks."):
|
||||
k_unet_num = k_unet[len("single_blocks."):len("single_blocks.")+2]
|
||||
single_blocks.append((key, v, parse_unet_num(k_unet_num), k_unet))
|
||||
else:
|
||||
others.append((k, v, k_unet))
|
||||
|
||||
input_blocks = sorted(input_blocks, key=lambda x: x[2])
|
||||
middle_blocks = sorted(middle_blocks, key=lambda x: x[2])
|
||||
output_blocks = sorted(output_blocks, key=lambda x: x[2])
|
||||
double_blocks = sorted(double_blocks, key=lambda x: x[2])
|
||||
single_blocks = sorted(single_blocks, key=lambda x: x[2])
|
||||
|
||||
# prepare patch
|
||||
np.random.seed(seed % (2**31))
|
||||
populated_vector_list = []
|
||||
ratios = []
|
||||
for k, v, k_unet_num, k_unet in (input_blocks + middle_blocks + output_blocks):
|
||||
ratio = 1.0
|
||||
vector_i = 1
|
||||
|
||||
last_k_unet_num = None
|
||||
|
||||
block_weights = {}
|
||||
muted_weights = []
|
||||
|
||||
for k, v, k_unet_num, k_unet in (input_blocks + middle_blocks + output_blocks + double_blocks + single_blocks):
|
||||
if last_k_unet_num != k_unet_num and len(vector) > vector_i:
|
||||
ratios = LoraLoaderBlockWeight.convert_vector_value(A, B, vector[vector_i].strip())
|
||||
ratio = ratios.pop(0)
|
||||
@@ -205,6 +435,8 @@ class LoraLoaderBlockWeight:
|
||||
else:
|
||||
if len(ratios) > 0:
|
||||
ratio = ratios.pop(0)
|
||||
else:
|
||||
pass # use last used ratio if no more user specified ratio is given
|
||||
|
||||
if inverse:
|
||||
populated_ratio = 1 - ratio
|
||||
@@ -213,11 +445,10 @@ class LoraLoaderBlockWeight:
|
||||
|
||||
last_k_unet_num = k_unet_num
|
||||
|
||||
new_modelpatcher.add_patches({k: v}, strength_model * populated_ratio)
|
||||
# if inverse:
|
||||
# print(f"\t{k_unet} -> inv({ratio}) ")
|
||||
# else:
|
||||
# print(f"\t{k_unet} -> ({ratio}) ")
|
||||
if populated_ratio != 0:
|
||||
block_weights[k] = v, populated_ratio
|
||||
else:
|
||||
muted_weights.append(k)
|
||||
|
||||
# prepare base patch
|
||||
ratios = LoraLoaderBlockWeight.convert_vector_value(A, B, vector[0].strip())
|
||||
@@ -226,25 +457,43 @@ class LoraLoaderBlockWeight:
|
||||
if inverse:
|
||||
populated_ratio = 1 - ratio
|
||||
else:
|
||||
populated_ratio = 1
|
||||
populated_ratio = ratio
|
||||
|
||||
populated_vector_list.insert(0, LoraLoaderBlockWeight.norm_value(populated_ratio))
|
||||
|
||||
for k, v, k_unet in others:
|
||||
new_modelpatcher.add_patches({k: v}, strength_model * populated_ratio)
|
||||
# if inverse:
|
||||
# print(f"\t{k_unet} -> inv({ratio}) ")
|
||||
# else:
|
||||
# print(f"\t{k_unet} -> ({ratio}) ")
|
||||
if populated_ratio != 0:
|
||||
block_weights[k] = v, populated_ratio
|
||||
else:
|
||||
muted_weights.append(k)
|
||||
|
||||
new_clip = clip.clone()
|
||||
new_clip.add_patches(loaded, strength_clip)
|
||||
populated_vector = ','.join(map(str, populated_vector_list))
|
||||
return (new_modelpatcher, new_clip, populated_vector)
|
||||
return block_weights, muted_weights, populated_vector
|
||||
|
||||
@staticmethod
|
||||
def load_lora_for_models(model, clip, lora, strength_model, strength_clip, inverse, seed, A, B, block_vector):
|
||||
block_weights, muted_weights, populated_vector = LoraLoaderBlockWeight.load_lbw(model, clip, lora, inverse, seed, A, B, block_vector)
|
||||
|
||||
new_modelpatcher = model.clone()
|
||||
new_clip = clip.clone()
|
||||
|
||||
muted_weights = set(muted_weights)
|
||||
|
||||
for k, v in block_weights.items():
|
||||
weights, ratio = v
|
||||
|
||||
if k in muted_weights:
|
||||
pass
|
||||
elif 'text' in k:
|
||||
new_clip.add_patches({k: weights}, strength_clip * ratio)
|
||||
else:
|
||||
new_modelpatcher.add_patches({k: weights}, strength_model * ratio)
|
||||
|
||||
return new_modelpatcher, new_clip, populated_vector
|
||||
|
||||
def doit(self, model, clip, lora_name, strength_model, strength_clip, inverse, seed, A, B, preset, block_vector, bypass=False, category_filter=None):
|
||||
if strength_model == 0 and strength_clip == 0 or bypass:
|
||||
return (model, clip, "")
|
||||
return model, clip, ""
|
||||
|
||||
lora_path = folder_paths.get_full_path("loras", lora_name)
|
||||
lora = None
|
||||
@@ -261,7 +510,48 @@ class LoraLoaderBlockWeight:
|
||||
self.loaded_lora = (lora_path, lora)
|
||||
|
||||
model_lora, clip_lora, populated_vector = LoraLoaderBlockWeight.load_lora_for_models(model, clip, lora, strength_model, strength_clip, inverse, seed, A, B, block_vector)
|
||||
return (model_lora, clip_lora, populated_vector)
|
||||
return model_lora, clip_lora, populated_vector
|
||||
|
||||
|
||||
class ApplyLBW:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"model": ("MODEL", ),
|
||||
"clip": ("CLIP", ),
|
||||
"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}),
|
||||
"lbw_model": ("LBW_MODEL",),
|
||||
}}
|
||||
|
||||
RETURN_TYPES = ("MODEL", "CLIP")
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/LoraBlockWeight"
|
||||
|
||||
DESCRIPTION = "Apply LBW_MODEL to MODEL and CLIP"
|
||||
|
||||
@staticmethod
|
||||
def doit(model, clip, strength_model, strength_clip, lbw_model):
|
||||
block_weights = lbw_model['blocks']
|
||||
muted_weights = lbw_model['muted']
|
||||
|
||||
new_modelpatcher = model.clone()
|
||||
new_clip = clip.clone()
|
||||
|
||||
muted_weights = set(muted_weights)
|
||||
|
||||
for k, v in block_weights.items():
|
||||
weights, ratio = v
|
||||
|
||||
if k in muted_weights:
|
||||
pass
|
||||
elif 'text' in k:
|
||||
new_clip.add_patches({k: weights}, strength_clip * ratio)
|
||||
else:
|
||||
new_modelpatcher.add_patches({k: weights}, strength_model * ratio)
|
||||
|
||||
return new_modelpatcher, new_clip
|
||||
|
||||
|
||||
class XY_Capsule_LoraBlockWeight:
|
||||
@@ -328,6 +618,7 @@ class XY_Capsule_LoraBlockWeight:
|
||||
else:
|
||||
image = torch.abs(weighted_image - reference_image)
|
||||
self.storage[(self.another_capsule.x, self.y)] = image
|
||||
|
||||
elif self.y == 3:
|
||||
import matplotlib.cm as cm
|
||||
# heatmap
|
||||
@@ -336,7 +627,7 @@ class XY_Capsule_LoraBlockWeight:
|
||||
if image == "fail":
|
||||
image = utils.empty_pil_tensor(8,8)
|
||||
latent = utils.empty_latent()
|
||||
return (image, latent)
|
||||
return image, latent
|
||||
else:
|
||||
image = image.clone()
|
||||
|
||||
@@ -368,7 +659,7 @@ class XY_Capsule_LoraBlockWeight:
|
||||
image = heatmap_alpha * heatmap + (1 - heatmap_alpha) * image
|
||||
|
||||
latent = nodes.VAEEncode().encode(vae, image)[0]
|
||||
return (image, latent)
|
||||
return image, latent
|
||||
|
||||
def getLabel(self):
|
||||
return self.label
|
||||
@@ -485,7 +776,7 @@ class XYInput_LoraBlockWeight:
|
||||
XY_Capsule_LoraBlockWeight(0, 2, '', 'diff', storage, common_params),
|
||||
XY_Capsule_LoraBlockWeight(0, 3, '', 'heatmap', storage, common_params)]
|
||||
|
||||
return ((xy_type, x_values), (xy_type, y_values), )
|
||||
return (xy_type, x_values), (xy_type, y_values),
|
||||
|
||||
|
||||
class LoraBlockInfo:
|
||||
@@ -535,8 +826,21 @@ class LoraBlockInfo:
|
||||
text_blocks = []
|
||||
text_blocks_map = {}
|
||||
|
||||
double_block_count = set()
|
||||
double_blocks = []
|
||||
double_blocks_map = {}
|
||||
|
||||
single_block_count = set()
|
||||
single_blocks = []
|
||||
single_blocks_map = {}
|
||||
|
||||
others = []
|
||||
for k, v in loaded.items():
|
||||
for key, v in loaded.items():
|
||||
if isinstance(key, tuple):
|
||||
k = key[0]
|
||||
else:
|
||||
k = key
|
||||
|
||||
k_unet = k[len("diffusion_model."):]
|
||||
|
||||
if k_unet.startswith("input_blocks."):
|
||||
@@ -572,8 +876,30 @@ class LoraBlockInfo:
|
||||
else:
|
||||
output_blocks_map[k_unet_int] = [k_unet]
|
||||
|
||||
elif k_unet.startswith("_model.encoder.layers."):
|
||||
k_unet_num = k_unet[len("_model.encoder.layers."):len("_model.encoder.layers.")+2]
|
||||
elif k_unet.startswith("double_blocks."):
|
||||
k_unet_num = k_unet[len("double_blocks."):len("double_blocks.") + 2]
|
||||
k_unet_int = parse_unet_num(k_unet_num)
|
||||
|
||||
double_block_count.add(k_unet_int)
|
||||
double_blocks.append(k_unet)
|
||||
if k_unet_int in double_blocks_map:
|
||||
double_blocks_map[k_unet_int].append(k_unet)
|
||||
else:
|
||||
double_blocks_map[k_unet_int] = [k_unet]
|
||||
|
||||
elif k_unet.startswith("single_blocks."):
|
||||
k_unet_num = k_unet[len("single_blocks."):len("single_blocks.") + 2]
|
||||
k_unet_int = parse_unet_num(k_unet_num)
|
||||
|
||||
single_block_count.add(k_unet_int)
|
||||
single_blocks.append(k_unet)
|
||||
if k_unet_int in single_blocks_map:
|
||||
single_blocks_map[k_unet_int].append(k_unet)
|
||||
else:
|
||||
single_blocks_map[k_unet_int] = [k_unet]
|
||||
|
||||
elif k_unet.startswith("er.text_model.encoder.layers."):
|
||||
k_unet_num = k_unet[len("er.text_model.encoder.layers."):len("er.text_model.encoder.layers.")+2]
|
||||
k_unet_int = parse_unet_num(k_unet_num)
|
||||
|
||||
text_block_count.add(k_unet_int)
|
||||
@@ -591,29 +917,45 @@ class LoraBlockInfo:
|
||||
input_blocks = sorted(input_blocks)
|
||||
middle_blocks = sorted(middle_blocks)
|
||||
output_blocks = sorted(output_blocks)
|
||||
double_blocks = sorted(double_blocks)
|
||||
single_blocks = sorted(single_blocks)
|
||||
others = sorted(others)
|
||||
|
||||
text += f"\n-------[Input blocks] ({len(input_block_count)}, Subs={len(input_blocks)})-------\n"
|
||||
input_keys = sorted(input_blocks_map.keys())
|
||||
for x in input_keys:
|
||||
text += f" IN{x}: {len(input_blocks_map[x])}\n"
|
||||
if len(input_block_count) > 0:
|
||||
text += f"\n-------[Input blocks] ({len(input_block_count)}, Subs={len(input_blocks)})-------\n"
|
||||
input_keys = sorted(input_blocks_map.keys())
|
||||
for x in input_keys:
|
||||
text += f" IN{x}: {len(input_blocks_map[x])}\n"
|
||||
|
||||
text += f"\n-------[Middle blocks] ({len(middle_block_count)}, Subs={len(middle_blocks)})-------\n"
|
||||
middle_keys = sorted(middle_blocks_map.keys())
|
||||
for x in middle_keys:
|
||||
text += f" MID{x}: {len(middle_blocks_map[x])}\n"
|
||||
if len(middle_block_count) > 0:
|
||||
text += f"\n-------[Middle blocks] ({len(middle_block_count)}, Subs={len(middle_blocks)})-------\n"
|
||||
middle_keys = sorted(middle_blocks_map.keys())
|
||||
for x in middle_keys:
|
||||
text += f" MID{x}: {len(middle_blocks_map[x])}\n"
|
||||
|
||||
text += f"\n-------[Output blocks] ({len(output_block_count)}, Subs={len(output_blocks)})-------\n"
|
||||
output_keys = sorted(output_blocks_map.keys())
|
||||
for x in output_keys:
|
||||
text += f" OUT{x}: {len(output_blocks_map[x])}\n"
|
||||
if len(output_block_count) > 0:
|
||||
text += f"\n-------[Output blocks] ({len(output_block_count)}, Subs={len(output_blocks)})-------\n"
|
||||
output_keys = sorted(output_blocks_map.keys())
|
||||
for x in output_keys:
|
||||
text += f" OUT{x}: {len(output_blocks_map[x])}\n"
|
||||
|
||||
text += f"\n-------[Text blocks] ({len(text_block_count)}, Subs={len(text_blocks)})-------\n"
|
||||
if len(double_block_count) > 0:
|
||||
text += f"\n-------[Double blocks(MMDiT)] ({len(double_block_count)}, Subs={len(double_blocks)})-------\n"
|
||||
double_keys = sorted(double_blocks_map.keys())
|
||||
for x in double_keys:
|
||||
text += f" DOUBLE{x}: {len(double_blocks_map[x])}\n"
|
||||
|
||||
if len(single_block_count) > 0:
|
||||
text += f"\n-------[Single blocks(DiT)] ({len(single_block_count)}, Subs={len(single_blocks)})-------\n"
|
||||
single_keys = sorted(single_blocks_map.keys())
|
||||
for x in single_keys:
|
||||
text += f" SINGLE{x}: {len(single_blocks_map[x])}\n"
|
||||
|
||||
text += f"\n-------[Base blocks] ({len(text_block_count) + len(others)}, Subs={len(text_blocks) + len(others)})-------\n"
|
||||
text_keys = sorted(text_blocks_map.keys())
|
||||
for x in text_keys:
|
||||
text += f" CLIP{x}: {len(text_blocks_map[x])}\n"
|
||||
text += f" TXT_ENC{x}: {len(text_blocks_map[x])}\n"
|
||||
|
||||
text += f"\n-------[Base blocks] ({len(others)})-------\n"
|
||||
for x in others:
|
||||
text += f" {x}\n"
|
||||
|
||||
@@ -629,13 +971,162 @@ class LoraBlockInfo:
|
||||
return {}
|
||||
|
||||
|
||||
class LoadLBW:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
files = folder_paths.get_filename_list('lbw_models')
|
||||
return {"required": {
|
||||
"lbw_model": [sorted(files), ]},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LBW_MODEL",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/LoraBlockWeight"
|
||||
|
||||
DESCRIPTION = "Load LBW_MODEL from .lbw.safetensors file"
|
||||
|
||||
@staticmethod
|
||||
def decode_dict(encoded_dict, tensor_dict):
|
||||
original_dict = {}
|
||||
|
||||
def decode_value(value):
|
||||
if isinstance(value, str) and value.startswith('t') and value[1:].isdigit():
|
||||
return tensor_dict[value]
|
||||
return value
|
||||
|
||||
for k, tuple_value in encoded_dict.items():
|
||||
decoded_tuple = tuple(decode_value(v) for v in tuple_value[0][1])
|
||||
key = ast.literal_eval(k) if isinstance(k, str) and (k.startswith('(') or k.startswith('[')) else k
|
||||
original_dict[key] = ((tuple_value[0][0], decoded_tuple), tuple_value[1])
|
||||
|
||||
return original_dict
|
||||
|
||||
@staticmethod
|
||||
def load(file):
|
||||
tensor_dict = comfy.utils.load_torch_file(file)
|
||||
|
||||
with safe_open(file, framework="pt") as f:
|
||||
metadata = f.metadata()
|
||||
|
||||
encoded_dict = json.loads(metadata.get('blocks', '{}'))
|
||||
muted_blocks = ast.literal_eval(metadata.get('muted_blocks', '[]'))
|
||||
|
||||
decoded_dict = LoadLBW.decode_dict(encoded_dict, tensor_dict)
|
||||
|
||||
lbw_model = {
|
||||
'blocks': decoded_dict,
|
||||
'muted': muted_blocks
|
||||
}
|
||||
|
||||
return lbw_model, metadata
|
||||
|
||||
def doit(self, lbw_model):
|
||||
lbw_path = folder_paths.get_full_path("lbw_models", lbw_model)
|
||||
lbw_model, _ = LoadLBW.load(lbw_path)
|
||||
return (lbw_model,)
|
||||
|
||||
|
||||
class SaveLBW:
|
||||
def __init__(self):
|
||||
self.output_dir = folder_paths.get_folder_paths('lbw_models')[-1]
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "lbw_model": ("LBW_MODEL", ),
|
||||
"filename_prefix": ("STRING", {"default": "ComfyUI"}) },
|
||||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
|
||||
}
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "doit"
|
||||
|
||||
OUTPUT_NODE = True
|
||||
|
||||
CATEGORY = "InspirePack/LoraBlockWeight"
|
||||
|
||||
DESCRIPTION = "Save LBW_MODEL as a .lbw.safetensors file"
|
||||
|
||||
@staticmethod
|
||||
def encode_dict(original_dict):
|
||||
tensor_dict = {}
|
||||
encoded_dict = {}
|
||||
counter = 0
|
||||
|
||||
def generate_unique_id():
|
||||
nonlocal counter
|
||||
counter += 1
|
||||
return f"t{counter}"
|
||||
|
||||
def encode_value(value):
|
||||
if isinstance(value, torch.Tensor):
|
||||
unique_id = generate_unique_id()
|
||||
tensor_dict[unique_id] = value
|
||||
return unique_id
|
||||
return value
|
||||
|
||||
for k, tuple_value in original_dict.items():
|
||||
encoded_tuple = tuple(encode_value(v) for v in tuple_value[0][1])
|
||||
encoded_dict[str(k)] = (tuple_value[0][0], encoded_tuple), tuple_value[1]
|
||||
|
||||
return encoded_dict, tensor_dict
|
||||
|
||||
@staticmethod
|
||||
def save(lbw_model, file, metadata):
|
||||
metadata['format'] = 'Inspire LBW 1.0'
|
||||
weighted_blocks = lbw_model['blocks']
|
||||
metadata['muted_blocks'] = str(lbw_model['muted'])
|
||||
encoded_dict, tensor_dict = SaveLBW.encode_dict(weighted_blocks)
|
||||
metadata['blocks'] = json.dumps(encoded_dict)
|
||||
|
||||
comfy.utils.save_torch_file(tensor_dict, file, metadata=metadata)
|
||||
|
||||
def doit(self, lbw_model, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None):
|
||||
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir)
|
||||
|
||||
# support save metadata for lbw sharing
|
||||
prompt_info = ""
|
||||
if prompt is not None:
|
||||
prompt_info = json.dumps(prompt)
|
||||
|
||||
metadata = {}
|
||||
if not args.disable_metadata:
|
||||
metadata = {"prompt": prompt_info}
|
||||
if extra_pnginfo is not None:
|
||||
for x in extra_pnginfo:
|
||||
metadata[x] = json.dumps(extra_pnginfo[x])
|
||||
|
||||
file = f"{filename}_{counter:05}_.lbw.safetensors"
|
||||
|
||||
results = list()
|
||||
results.append({
|
||||
"filename": file,
|
||||
"subfolder": subfolder,
|
||||
"type": "output"
|
||||
})
|
||||
|
||||
file = os.path.join(full_output_folder, file)
|
||||
|
||||
SaveLBW.save(lbw_model, file, metadata)
|
||||
|
||||
return {}
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"XY Input: Lora Block Weight //Inspire": XYInput_LoraBlockWeight,
|
||||
"LoraLoaderBlockWeight //Inspire": LoraLoaderBlockWeight,
|
||||
"LoraBlockInfo //Inspire": LoraBlockInfo,
|
||||
"MakeLBW //Inspire": MakeLBW,
|
||||
"ApplyLBW //Inspire": ApplyLBW,
|
||||
"SaveLBW //Inspire": SaveLBW,
|
||||
"LoadLBW //Inspire": LoadLBW,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"XY Input: Lora Block Weight //Inspire": "XY Input: Lora Block Weight",
|
||||
"LoraLoaderBlockWeight //Inspire": "Lora Loader (Block Weight)",
|
||||
"LoraBlockInfo //Inspire": "Lora Block Info",
|
||||
"XY Input: Lora Block Weight //Inspire": "XY Input: LoRA Block Weight",
|
||||
"LoraLoaderBlockWeight //Inspire": "LoRA Loader (Block Weight)",
|
||||
"LoraBlockInfo //Inspire": "LoRA Block Info",
|
||||
"MakeLBW //Inspire": "Make LoRA Block Weight",
|
||||
"ApplyLBW //Inspire": "Apply LoRA Block Weight",
|
||||
"SaveLBW //Inspire": "Save LoRA Block Weight",
|
||||
"LoadLBW //Inspire": "Load LoRA Block Weight",
|
||||
}
|
||||
|
||||
+12
-4
@@ -19,6 +19,7 @@ model_preset = {
|
||||
"SDXL ViT-H": ("ip-adapter_sdxl_vit-h", "CLIP-ViT-H-14-laion2B-s32B-b79K", None, False),
|
||||
"SDXL Plus ViT-H": ("ip-adapter-plus_sdxl_vit-h", "CLIP-ViT-H-14-laion2B-s32B-b79K", None, False),
|
||||
"SDXL Plus Face ViT-H": ("ip-adapter-plus-face_sdxl_vit-h", "CLIP-ViT-H-14-laion2B-s32B-b79K", None, False),
|
||||
"Kolors Plus": ("Kolors-IP-Adapter-Plus", "clip-vit-large-patch14-336", None, False),
|
||||
|
||||
# faceid
|
||||
"SD1.5 FaceID": ("ip-adapter-faceid_sd15", "CLIP-ViT-H-14-laion2B-s32B-b79K", "ip-adapter-faceid_sd15_lora", True),
|
||||
@@ -29,6 +30,7 @@ model_preset = {
|
||||
"SDXL FaceID": ("ip-adapter-faceid_sdxl", "CLIP-ViT-H-14-laion2B-s32B-b79K", "ip-adapter-faceid_sdxl_lora", True),
|
||||
"SDXL FaceID Portrait": ("ip-adapter-faceid-portrait_sdxl", "CLIP-ViT-H-14-laion2B-s32B-b79K", None, True),
|
||||
"SDXL FaceID Portrait unnorm": ("ip-adapter-faceid-portrait_sdxl_unnorm", "CLIP-ViT-H-14-laion2B-s32B-b79K", None, True),
|
||||
"Kolors FaceID Plus": ("Kolors-IP-Adapter-FaceID-Plus", "clip-vit-large-patch14-336", None, True),
|
||||
|
||||
# composition
|
||||
"SD1.5 Plus Composition": ("ip-adapter_sd15", "CLIP-ViT-H-14-laion2B-s32B-b79K", None, False),
|
||||
@@ -56,13 +58,16 @@ class IPAdapterModelHelper:
|
||||
return {
|
||||
"required": {
|
||||
"model": ("MODEL",),
|
||||
"clip": ("CLIP",),
|
||||
"preset": (list(model_preset.keys()),),
|
||||
"lora_strength_model": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}),
|
||||
"lora_strength_clip": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}),
|
||||
"insightface_provider": (["CPU", "CUDA", "ROCM"], ),
|
||||
"cache_mode": (["insightface only", "clip_vision only", "all", "none"], {"default": "insightface only"}),
|
||||
},
|
||||
"optional": {
|
||||
"clip": ("CLIP",),
|
||||
"insightface_model_name": (['buffalo_l', 'antelopev2'],),
|
||||
},
|
||||
"hidden": {"unique_id": "UNIQUE_ID"}
|
||||
}
|
||||
|
||||
@@ -72,14 +77,17 @@ class IPAdapterModelHelper:
|
||||
|
||||
CATEGORY = "InspirePack/models"
|
||||
|
||||
def doit(self, model, clip, preset, lora_strength_model, lora_strength_clip, insightface_provider, cache_mode="none", unique_id=None):
|
||||
def doit(self, model, preset, lora_strength_model, lora_strength_clip, insightface_provider, clip=None, cache_mode="none", unique_id=None, insightface_model_name='buffalo_l'):
|
||||
if 'IPAdapter' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/cubiq/ComfyUI_IPAdapter_plus',
|
||||
"To use 'IPAdapterModelHelper' node, 'ComfyUI IPAdapter Plus' extension is required.")
|
||||
raise Exception(f"[ERROR] To use IPAdapterModelHelper, you need to install 'ComfyUI IPAdapter Plus'")
|
||||
|
||||
is_sdxl_preset = 'SDXL' in preset
|
||||
is_sdxl_model = isinstance(clip.tokenizer, sdxl_clip.SDXLTokenizer)
|
||||
if clip is not None:
|
||||
is_sdxl_model = isinstance(clip.tokenizer, sdxl_clip.SDXLTokenizer)
|
||||
else:
|
||||
is_sdxl_model = False
|
||||
|
||||
if is_sdxl_preset != is_sdxl_model:
|
||||
server.PromptServer.instance.send_sync("inspire-node-output-label", {"node_id": unique_id, "output_idx": 1, "label": "IPADAPTER (fail)"})
|
||||
@@ -157,7 +165,7 @@ class IPAdapterModelHelper:
|
||||
if cache_mode in ["insightface only", "all"]:
|
||||
icache_key = 'insightface-' + insightface_provider
|
||||
if icache_key not in backend_support.cache:
|
||||
backend_support.update_cache(icache_key, "insightface", (False, insight_face_loader(insightface_provider)[0]))
|
||||
backend_support.update_cache(icache_key, "insightface", (False, insight_face_loader(provider=insightface_provider, model_name=insightface_model_name)[0]))
|
||||
_, (_, insightface) = backend_support.cache[icache_key]
|
||||
else:
|
||||
insightface = insight_face_loader(insightface_provider)[0]
|
||||
|
||||
+203
-54
@@ -12,6 +12,7 @@ import folder_paths
|
||||
import comfy
|
||||
import traceback
|
||||
import random
|
||||
import hashlib
|
||||
|
||||
from server import PromptServer
|
||||
from .libs import utils, common
|
||||
@@ -37,7 +38,7 @@ try:
|
||||
with open(pb_yaml_path, 'r', encoding="utf-8") as f:
|
||||
prompt_builder_preset = yaml.load(f, Loader=yaml.FullLoader)
|
||||
except Exception as e:
|
||||
print(f"[Inspire Pack] Failed to load 'prompt-builder.yaml'")
|
||||
print(f"[Inspire Pack] Failed to load 'prompt-builder.yaml'\nNOTE: Only files with UTF-8 encoding are supported.")
|
||||
|
||||
|
||||
class LoadPromptsFromDir:
|
||||
@@ -49,7 +50,13 @@ class LoadPromptsFromDir:
|
||||
except Exception:
|
||||
prompt_dirs = []
|
||||
|
||||
return {"required": {"prompt_dir": (prompt_dirs,)}}
|
||||
return {"required": {
|
||||
"prompt_dir": (prompt_dirs,)
|
||||
},
|
||||
"optional": {
|
||||
"reload": ("BOOLEAN", { "default": False, "label_on": "if file changed", "label_off": "if value changed"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("ZIPPED_PROMPT",)
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
@@ -58,7 +65,31 @@ class LoadPromptsFromDir:
|
||||
|
||||
CATEGORY = "InspirePack/Prompt"
|
||||
|
||||
def doit(self, prompt_dir):
|
||||
@staticmethod
|
||||
def IS_CHANGED(prompt_dir, reload=False):
|
||||
if not reload:
|
||||
return prompt_dir
|
||||
else:
|
||||
global prompts_path
|
||||
prompt_dir = os.path.join(prompts_path, prompt_dir)
|
||||
files = [f for f in os.listdir(prompt_dir) if f.endswith(".txt")]
|
||||
|
||||
md5 = hashlib.md5()
|
||||
files.sort()
|
||||
|
||||
for file in files:
|
||||
md5.update(file.encode('utf-8'))
|
||||
with open(os.path.join(prompt_dir, file), 'rb') as f:
|
||||
while True:
|
||||
chunk = f.read(4096)
|
||||
if not chunk:
|
||||
break
|
||||
md5.update(chunk)
|
||||
|
||||
return md5.hexdigest()
|
||||
|
||||
@staticmethod
|
||||
def doit(prompt_dir, reload=False):
|
||||
global prompts_path
|
||||
prompt_dir = os.path.join(prompts_path, prompt_dir)
|
||||
files = [f for f in os.listdir(prompt_dir) if f.endswith(".txt")]
|
||||
@@ -84,7 +115,7 @@ class LoadPromptsFromDir:
|
||||
else:
|
||||
print(f"[WARN] LoadPromptsFromDir: invalid prompt format in '{file_name}'")
|
||||
except Exception as e:
|
||||
print(f"[ERROR] LoadPromptsFromDir: an error occurred while processing '{file_name}': {str(e)}")
|
||||
print(f"[ERROR] LoadPromptsFromDir: an error occurred while processing '{file_name}': {str(e)}\nNOTE: Only files with UTF-8 encoding are supported.")
|
||||
|
||||
return (prompts, )
|
||||
|
||||
@@ -104,7 +135,14 @@ class LoadPromptsFromFile:
|
||||
except Exception:
|
||||
prompt_files = []
|
||||
|
||||
return {"required": {"prompt_file": (prompt_files,)}}
|
||||
return {"required": {
|
||||
"prompt_file": (prompt_files,)
|
||||
},
|
||||
"optional": {
|
||||
"text_data_opt": ("STRING", {"defaultInput": True}),
|
||||
"reload": ("BOOLEAN", {"default": False, "label_on": "if file changed", "label_off": "if value changed"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("ZIPPED_PROMPT",)
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
@@ -113,29 +151,55 @@ class LoadPromptsFromFile:
|
||||
|
||||
CATEGORY = "InspirePack/Prompt"
|
||||
|
||||
def doit(self, prompt_file):
|
||||
@staticmethod
|
||||
def IS_CHANGED(prompt_file, text_data_opt=None, reload=False):
|
||||
md5 = hashlib.md5()
|
||||
|
||||
if text_data_opt is not None:
|
||||
md5.update(text_data_opt)
|
||||
return md5.hexdigest()
|
||||
elif not reload:
|
||||
return prompt_file
|
||||
else:
|
||||
prompt_path = os.path.join(prompts_path, prompt_file)
|
||||
|
||||
with open(prompt_path, 'rb') as f:
|
||||
while True:
|
||||
chunk = f.read(4096)
|
||||
if not chunk:
|
||||
break
|
||||
md5.update(chunk)
|
||||
|
||||
return md5.hexdigest()
|
||||
|
||||
@staticmethod
|
||||
def doit(prompt_file, text_data_opt=None, reload=False):
|
||||
prompt_path = os.path.join(prompts_path, prompt_file)
|
||||
|
||||
prompts = []
|
||||
try:
|
||||
with open(prompt_path, "r", encoding="utf-8") as file:
|
||||
prompt_data = file.read()
|
||||
prompt_list = re.split(r'\n\s*-+\s*\n', prompt_data)
|
||||
if not text_data_opt:
|
||||
with open(prompt_path, "r", encoding="utf-8") as file:
|
||||
prompt_data = file.read()
|
||||
else:
|
||||
prompt_data = text_data_opt
|
||||
|
||||
pattern = r"positive:(.*?)(?:\n*|$)negative:(.*)"
|
||||
prompt_list = re.split(r'\n\s*-+\s*\n', prompt_data)
|
||||
|
||||
for prompt in prompt_list:
|
||||
matches = re.search(pattern, prompt, re.DOTALL)
|
||||
pattern = r"positive:(.*?)(?:\n*|$)negative:(.*)"
|
||||
|
||||
if matches:
|
||||
positive_text = matches.group(1).strip()
|
||||
negative_text = matches.group(2).strip()
|
||||
result_tuple = (positive_text, negative_text, prompt_file)
|
||||
prompts.append(result_tuple)
|
||||
else:
|
||||
print(f"[WARN] LoadPromptsFromFile: invalid prompt format in '{prompt_file}'")
|
||||
for prompt in prompt_list:
|
||||
matches = re.search(pattern, prompt, re.DOTALL)
|
||||
|
||||
if matches:
|
||||
positive_text = matches.group(1).strip()
|
||||
negative_text = matches.group(2).strip()
|
||||
result_tuple = (positive_text, negative_text, prompt_file)
|
||||
prompts.append(result_tuple)
|
||||
else:
|
||||
print(f"[WARN] LoadPromptsFromFile: invalid prompt format in '{prompt_file}'")
|
||||
except Exception as e:
|
||||
print(f"[ERROR] LoadPromptsFromFile: an error occurred while processing '{prompt_file}': {str(e)}")
|
||||
print(f"[ERROR] LoadPromptsFromFile: an error occurred while processing '{prompt_file}': {str(e)}\nNOTE: Only files with UTF-8 encoding are supported.")
|
||||
|
||||
return (prompts, )
|
||||
|
||||
@@ -156,10 +220,11 @@ class LoadSinglePromptFromFile:
|
||||
prompt_files = []
|
||||
|
||||
return {"required": {
|
||||
"prompt_file": (prompt_files,),
|
||||
"index": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
}
|
||||
}
|
||||
"prompt_file": (prompt_files,),
|
||||
"index": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
},
|
||||
"optional": {"text_data_opt": ("STRING", {"defaultInput": True})}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("ZIPPED_PROMPT",)
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
@@ -168,31 +233,36 @@ class LoadSinglePromptFromFile:
|
||||
|
||||
CATEGORY = "InspirePack/Prompt"
|
||||
|
||||
def doit(self, prompt_file, index):
|
||||
@staticmethod
|
||||
def doit(prompt_file, index, text_data_opt=None):
|
||||
prompt_path = os.path.join(prompts_path, prompt_file)
|
||||
|
||||
prompts = []
|
||||
try:
|
||||
with open(prompt_path, "r", encoding="utf-8") as file:
|
||||
prompt_data = file.read()
|
||||
prompt_list = re.split(r'\n\s*-+\s*\n', prompt_data)
|
||||
try:
|
||||
prompt = prompt_list[index]
|
||||
except Exception:
|
||||
prompt = prompt_list[-1]
|
||||
if not text_data_opt:
|
||||
with open(prompt_path, "r", encoding="utf-8") as file:
|
||||
prompt_data = file.read()
|
||||
else:
|
||||
prompt_data = text_data_opt
|
||||
|
||||
prompt_list = re.split(r'\n\s*-+\s*\n', prompt_data)
|
||||
try:
|
||||
prompt = prompt_list[index]
|
||||
except Exception:
|
||||
prompt = prompt_list[-1]
|
||||
|
||||
pattern = r"positive:(.*?)(?:\n*|$)negative:(.*)"
|
||||
matches = re.search(pattern, prompt, re.DOTALL)
|
||||
pattern = r"positive:(.*?)(?:\n*|$)negative:(.*)"
|
||||
matches = re.search(pattern, prompt, re.DOTALL)
|
||||
|
||||
if matches:
|
||||
positive_text = matches.group(1).strip()
|
||||
negative_text = matches.group(2).strip()
|
||||
result_tuple = (positive_text, negative_text, prompt_file)
|
||||
prompts.append(result_tuple)
|
||||
else:
|
||||
print(f"[WARN] LoadPromptsFromFile: invalid prompt format in '{prompt_file}'")
|
||||
if matches:
|
||||
positive_text = matches.group(1).strip()
|
||||
negative_text = matches.group(2).strip()
|
||||
result_tuple = (positive_text, negative_text, prompt_file)
|
||||
prompts.append(result_tuple)
|
||||
else:
|
||||
print(f"[WARN] LoadSinglePromptFromFile: invalid prompt format in '{prompt_file}'")
|
||||
except Exception as e:
|
||||
print(f"[ERROR] LoadPromptsFromFile: an error occurred while processing '{prompt_file}': {str(e)}")
|
||||
print(f"[ERROR] LoadSinglePromptFromFile: an error occurred while processing '{prompt_file}': {str(e)}\nNOTE: Only files with UTF-8 encoding are supported.")
|
||||
|
||||
return (prompts, )
|
||||
|
||||
@@ -235,9 +305,8 @@ class ZipPrompt:
|
||||
return ((positive, negative, name_opt), )
|
||||
|
||||
|
||||
prompt_blacklist = set([
|
||||
'filename_prefix'
|
||||
])
|
||||
prompt_blacklist = set(['filename_prefix'])
|
||||
|
||||
|
||||
class PromptExtractor:
|
||||
@classmethod
|
||||
@@ -451,8 +520,9 @@ class WildcardEncodeInspire:
|
||||
"To use 'Wildcard Encode (Inspire)' node, 'Impact Pack' extension is required.")
|
||||
raise Exception(f"[ERROR] To use 'Wildcard Encode (Inspire)', you need to install 'Impact Pack'")
|
||||
|
||||
model, clip, conditioning = nodes.NODE_CLASS_MAPPINGS['ImpactWildcardEncode'].process_with_loras(wildcard_opt=populated, model=kwargs['model'], clip=kwargs['clip'], clip_encoder=clip_encoder)
|
||||
return (model, clip, conditioning, populated)
|
||||
processed = []
|
||||
model, clip, conditioning = nodes.NODE_CLASS_MAPPINGS['ImpactWildcardEncode'].process_with_loras(wildcard_opt=populated, model=kwargs['model'], clip=kwargs['clip'], seed=kwargs['seed'], clip_encoder=clip_encoder, processed=processed)
|
||||
return (model, clip, conditioning, processed[0])
|
||||
|
||||
|
||||
class MakeBasicPipe:
|
||||
@@ -549,7 +619,12 @@ class SeedExplorer:
|
||||
"additional_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"noise_mode": (["GPU(=A1111)", "CPU"],),
|
||||
"initial_batch_seed_mode": (["incremental", "comfy"],),
|
||||
}
|
||||
},
|
||||
"optional":
|
||||
{
|
||||
"variation_method": (["linear", "slerp"],),
|
||||
"model": ("model",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("NOISE",)
|
||||
@@ -558,7 +633,7 @@ class SeedExplorer:
|
||||
CATEGORY = "InspirePack/Prompt"
|
||||
|
||||
@staticmethod
|
||||
def apply_variation(start_noise, seed_items, noise_device, mask=None):
|
||||
def apply_variation(start_noise, seed_items, noise_device, mask=None, variation_method='linear'):
|
||||
noise = start_noise
|
||||
for x in seed_items:
|
||||
if isinstance(x, str):
|
||||
@@ -571,15 +646,20 @@ class SeedExplorer:
|
||||
variation_seed = int(item[0])
|
||||
variation_strength = float(item[1])
|
||||
|
||||
noise = utils.apply_variation_noise(noise, noise_device, variation_seed, variation_strength, mask=mask)
|
||||
noise = utils.apply_variation_noise(noise, noise_device, variation_seed, variation_strength, mask=mask, variation_method=variation_method)
|
||||
except Exception:
|
||||
print(f"[ERROR] IGNORED: SeedExplorer failed to processing '{x}'")
|
||||
traceback.print_exc()
|
||||
return noise
|
||||
|
||||
def doit(self, latent, seed_prompt, enable_additional, additional_seed, additional_strength, noise_mode,
|
||||
initial_batch_seed_mode):
|
||||
@staticmethod
|
||||
def doit(latent, seed_prompt, enable_additional, additional_seed, additional_strength, noise_mode,
|
||||
initial_batch_seed_mode, variation_method='linear', model=None):
|
||||
latent_image = latent["samples"]
|
||||
|
||||
if hasattr(comfy.sample, 'fix_empty_latent_channels') and model is not None:
|
||||
latent_image = comfy.sample.fix_empty_latent_channels(model, latent_image)
|
||||
|
||||
device = comfy.model_management.get_torch_device()
|
||||
noise_device = "cpu" if noise_mode == "CPU" else device
|
||||
|
||||
@@ -603,7 +683,7 @@ class SeedExplorer:
|
||||
|
||||
noise = utils.prepare_noise(latent_image, hd_seed, None, noise_device, initial_batch_seed_mode)
|
||||
noise = noise.to(device)
|
||||
noise = SeedExplorer.apply_variation(noise, tl, noise_device)
|
||||
noise = SeedExplorer.apply_variation(noise, tl, noise_device, variation_method=variation_method)
|
||||
noise = noise.cpu()
|
||||
|
||||
return (noise,)
|
||||
@@ -617,6 +697,72 @@ class SeedExplorer:
|
||||
return (noise,)
|
||||
|
||||
|
||||
class CompositeNoise:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"destination": ("NOISE",),
|
||||
"source": ("NOISE",),
|
||||
"mode": (["center", "left-top", "right-top", "left-bottom", "right-bottom", "xy"], ),
|
||||
"x": ("INT", {"default": 0, "min": 0, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
||||
"y": ("INT", {"default": 0, "min": 0, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("NOISE",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/Prompt"
|
||||
|
||||
def doit(self, destination, source, mode, x, y):
|
||||
new_tensor = destination.clone()
|
||||
|
||||
if mode == 'center':
|
||||
y1 = (new_tensor.size(2) - source.size(2)) // 2
|
||||
x1 = (new_tensor.size(3) - source.size(3)) // 2
|
||||
elif mode == 'left-top':
|
||||
y1 = 0
|
||||
x1 = 0
|
||||
elif mode == 'right-top':
|
||||
y1 = 0
|
||||
x1 = new_tensor.size(2) - source.size(2)
|
||||
elif mode == 'left-bottom':
|
||||
y1 = new_tensor.size(3) - source.size(3)
|
||||
x1 = 0
|
||||
elif mode == 'right-bottom':
|
||||
y1 = new_tensor.size(3) - source.size(3)
|
||||
x1 = new_tensor.size(2) - source.size(2)
|
||||
else: # mode == 'xy':
|
||||
x1 = max(0, x)
|
||||
y1 = max(0, y)
|
||||
|
||||
# raw coordinates
|
||||
y2 = y1 + source.size(2)
|
||||
x2 = x1 + source.size(3)
|
||||
|
||||
# bounding for destination
|
||||
top = max(0, y1)
|
||||
left = max(0, x1)
|
||||
bottom = min(new_tensor.size(2), y2)
|
||||
right = min(new_tensor.size(3), x2)
|
||||
|
||||
# bounding for source
|
||||
left_gap = left - x1
|
||||
top_gap = top - y1
|
||||
|
||||
width = right - left
|
||||
height = bottom - top
|
||||
|
||||
height = min(height, y1 + source.size(2) - top)
|
||||
width = min(width, x1 + source.size(3) - left)
|
||||
|
||||
# composite
|
||||
new_tensor[:, :, top:top + height, left:left + width] = source[:, :, top_gap:top_gap + height, left_gap:left_gap + width]
|
||||
|
||||
return (new_tensor,)
|
||||
|
||||
|
||||
list_counter_map = {}
|
||||
|
||||
|
||||
@@ -768,7 +914,9 @@ NODE_CLASS_MAPPINGS = {
|
||||
"MakeBasicPipe //Inspire": MakeBasicPipe,
|
||||
"RemoveControlNet //Inspire": RemoveControlNet,
|
||||
"RemoveControlNetFromRegionalPrompts //Inspire": RemoveControlNetFromRegionalPrompts,
|
||||
"CompositeNoise //Inspire": CompositeNoise
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"LoadPromptsFromDir //Inspire": "Load Prompts From Dir (Inspire)",
|
||||
"LoadPromptsFromFile //Inspire": "Load Prompts From File (Inspire)",
|
||||
@@ -787,5 +935,6 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"RandomGeneratorForList //Inspire": "Random Generator for List (Inspire)",
|
||||
"MakeBasicPipe //Inspire": "Make Basic Pipe (Inspire)",
|
||||
"RemoveControlNet //Inspire": "Remove ControlNet (Inspire)",
|
||||
"RemoveControlNetFromRegionalPrompts //Inspire": "Remove ControlNet [RegionalPrompts] (Inspire)"
|
||||
"RemoveControlNetFromRegionalPrompts //Inspire": "Remove ControlNet [RegionalPrompts] (Inspire)",
|
||||
"CompositeNoise //Inspire": "Composite Noise (Inspire)"
|
||||
}
|
||||
|
||||
+185
-27
@@ -3,6 +3,9 @@ import traceback
|
||||
import comfy
|
||||
import nodes
|
||||
import torch
|
||||
import re
|
||||
import webcolors
|
||||
|
||||
from . import prompt_support
|
||||
from .libs import utils, common
|
||||
|
||||
@@ -21,6 +24,12 @@ class RegionalPromptSimple:
|
||||
"controlnet_in_pipe": ("BOOLEAN", {"default": False, "label_on": "Keep", "label_off": "Override"}),
|
||||
"sigma_factor": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||
},
|
||||
"optional": {
|
||||
"variation_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"variation_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"variation_method": (["linear", "slerp"],),
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("REGIONAL_PROMPTS", )
|
||||
@@ -28,7 +37,9 @@ class RegionalPromptSimple:
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
def doit(self, basic_pipe, mask, cfg, sampler_name, scheduler, wildcard_prompt, controlnet_in_pipe=False, sigma_factor=1.0):
|
||||
@staticmethod
|
||||
def doit(basic_pipe, mask, cfg, sampler_name, scheduler, wildcard_prompt,
|
||||
controlnet_in_pipe=False, sigma_factor=1.0, variation_seed=0, variation_strength=0.0, variation_method='linear', scheduler_func_opt=None):
|
||||
if 'RegionalPrompt' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/ltdrdata/ComfyUI-Impact-Pack',
|
||||
"To use 'RegionalPromptSimple' node, 'Impact Pack' extension is required.")
|
||||
@@ -41,7 +52,7 @@ class RegionalPromptSimple:
|
||||
rp = nodes.NODE_CLASS_MAPPINGS['RegionalPrompt']()
|
||||
|
||||
if wildcard_prompt != "":
|
||||
model, clip, new_positive, _ = iwe.doit(model=model, clip=clip, populated_text=wildcard_prompt)
|
||||
model, clip, new_positive, _ = iwe.doit(model=model, clip=clip, populated_text=wildcard_prompt, seed=None)
|
||||
|
||||
if controlnet_in_pipe:
|
||||
prev_cnet = None
|
||||
@@ -60,16 +71,20 @@ class RegionalPromptSimple:
|
||||
|
||||
basic_pipe = model, clip, vae, new_positive, negative
|
||||
|
||||
sampler = kap.doit(cfg, sampler_name, scheduler, basic_pipe, sigma_factor=sigma_factor)[0]
|
||||
regional_prompts = rp.doit(mask, sampler)[0]
|
||||
sampler = kap.doit(cfg, sampler_name, scheduler, basic_pipe, sigma_factor=sigma_factor, scheduler_func_opt=scheduler_func_opt)[0]
|
||||
try:
|
||||
regional_prompts = rp.doit(mask, sampler, variation_seed=variation_seed, variation_strength=variation_strength, variation_method=variation_method)[0]
|
||||
except:
|
||||
raise Exception("[Inspire-Pack] ERROR: Impact Pack is outdated. Update Impact Pack to latest version to use this.")
|
||||
|
||||
return (regional_prompts, )
|
||||
|
||||
|
||||
def color_to_mask(color_mask, mask_color):
|
||||
try:
|
||||
if mask_color.startswith("#"):
|
||||
selected = int(mask_color[1:], 16)
|
||||
if mask_color.startswith("#") or mask_color.isalpha():
|
||||
hex = mask_color[1:] if mask_color.startswith("#") else webcolors.name_to_hex(mask_color)[1:]
|
||||
selected = int(hex, 16)
|
||||
else:
|
||||
selected = int(mask_color, 10)
|
||||
except Exception:
|
||||
@@ -96,6 +111,12 @@ class RegionalPromptColorMask:
|
||||
"controlnet_in_pipe": ("BOOLEAN", {"default": False, "label_on": "Keep", "label_off": "Override"}),
|
||||
"sigma_factor": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||
},
|
||||
"optional": {
|
||||
"variation_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"variation_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"variation_method": (["linear", "slerp"],),
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("REGIONAL_PROMPTS", "MASK")
|
||||
@@ -103,10 +124,13 @@ class RegionalPromptColorMask:
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
def doit(self, basic_pipe, color_mask, mask_color, cfg, sampler_name, scheduler, wildcard_prompt, controlnet_in_pipe=False, sigma_factor=1.0):
|
||||
@staticmethod
|
||||
def doit(basic_pipe, color_mask, mask_color, cfg, sampler_name, scheduler, wildcard_prompt,
|
||||
controlnet_in_pipe=False, sigma_factor=1.0, variation_seed=0, variation_strength=0.0, variation_method="linear", scheduler_func_opt=None):
|
||||
mask = color_to_mask(color_mask, mask_color)
|
||||
rp = RegionalPromptSimple().doit(basic_pipe, mask, cfg, sampler_name, scheduler, wildcard_prompt, controlnet_in_pipe, sigma_factor=sigma_factor)[0]
|
||||
return (rp, mask)
|
||||
rp = RegionalPromptSimple().doit(basic_pipe, mask, cfg, sampler_name, scheduler, wildcard_prompt, controlnet_in_pipe,
|
||||
sigma_factor=sigma_factor, variation_seed=variation_seed, variation_strength=variation_strength, variation_method=variation_method, scheduler_func_opt=scheduler_func_opt)[0]
|
||||
return rp, mask
|
||||
|
||||
|
||||
class RegionalConditioningSimple:
|
||||
@@ -127,7 +151,8 @@ class RegionalConditioningSimple:
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
def doit(self, clip, mask, strength, set_cond_area, prompt):
|
||||
@staticmethod
|
||||
def doit(clip, mask, strength, set_cond_area, prompt):
|
||||
conditioning = nodes.CLIPTextEncode().encode(clip, prompt)[0]
|
||||
conditioning = nodes.ConditioningSetMask().append(conditioning, mask, set_cond_area, strength)[0]
|
||||
return (conditioning, )
|
||||
@@ -145,6 +170,9 @@ class RegionalConditioningColorMask:
|
||||
"set_cond_area": (["default", "mask bounds"],),
|
||||
"prompt": ("STRING", {"multiline": True, "placeholder": "prompt"}),
|
||||
},
|
||||
"optional": {
|
||||
"dilation": ("INT", {"default": 0, "min": -512, "max": 512, "step": 1}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("CONDITIONING", "MASK")
|
||||
@@ -152,12 +180,16 @@ class RegionalConditioningColorMask:
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
def doit(self, clip, color_mask, mask_color, strength, set_cond_area, prompt):
|
||||
@staticmethod
|
||||
def doit(clip, color_mask, mask_color, strength, set_cond_area, prompt, dilation=0):
|
||||
mask = color_to_mask(color_mask, mask_color)
|
||||
|
||||
if dilation != 0:
|
||||
mask = utils.dilate_mask(mask, dilation)
|
||||
|
||||
conditioning = nodes.CLIPTextEncode().encode(clip, prompt)[0]
|
||||
conditioning = nodes.ConditioningSetMask().append(conditioning, mask, set_cond_area, strength)[0]
|
||||
return (conditioning, mask)
|
||||
return conditioning, mask
|
||||
|
||||
|
||||
class ToIPAdapterPipe:
|
||||
@@ -179,7 +211,8 @@ class ToIPAdapterPipe:
|
||||
|
||||
CATEGORY = "InspirePack/Util"
|
||||
|
||||
def doit(self, ipadapter, model, clip_vision, insightface=None):
|
||||
@staticmethod
|
||||
def doit(ipadapter, model, clip_vision, insightface=None):
|
||||
pipe = ipadapter, model, clip_vision, insightface, lambda x: x
|
||||
|
||||
return (pipe,)
|
||||
@@ -244,6 +277,22 @@ class IPAdapterConditioning:
|
||||
return model
|
||||
|
||||
|
||||
IPADAPTER_WEIGHT_TYPES_CACHE = None
|
||||
|
||||
|
||||
def IPADAPTER_WEIGHT_TYPES():
|
||||
global IPADAPTER_WEIGHT_TYPES_CACHE
|
||||
|
||||
if IPADAPTER_WEIGHT_TYPES_CACHE is None:
|
||||
try:
|
||||
IPADAPTER_WEIGHT_TYPES_CACHE = nodes.NODE_CLASS_MAPPINGS['IPAdapterAdvanced']().INPUT_TYPES()['required']['weight_type'][0]
|
||||
except Exception:
|
||||
print(f"[Inspire Pack] IPAdapterPlus is not installed.")
|
||||
IPADAPTER_WEIGHT_TYPES_CACHE = ["IPAdapterPlus is not installed"]
|
||||
|
||||
return IPADAPTER_WEIGHT_TYPES_CACHE
|
||||
|
||||
|
||||
class RegionalIPAdapterMask:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -254,7 +303,7 @@ class RegionalIPAdapterMask:
|
||||
"image": ("IMAGE",),
|
||||
"weight": ("FLOAT", {"default": 0.7, "min": -1, "max": 3, "step": 0.05}),
|
||||
"noise": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"weight_type": (["original", "linear", "channel penalty"],),
|
||||
"weight_type": (IPADAPTER_WEIGHT_TYPES(), ),
|
||||
"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}),
|
||||
@@ -272,7 +321,8 @@ class RegionalIPAdapterMask:
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
def doit(self, mask, image, weight, noise, weight_type, start_at=0.0, end_at=1.0, unfold_batch=False, faceid_v2=False, weight_v2=False, combine_embeds="concat", neg_image=None):
|
||||
@staticmethod
|
||||
def doit(mask, image, weight, noise, weight_type, start_at=0.0, end_at=1.0, unfold_batch=False, faceid_v2=False, weight_v2=False, combine_embeds="concat", neg_image=None):
|
||||
cond = IPAdapterConditioning(mask, weight, weight_type, noise=noise, image=image, neg_image=neg_image, start_at=start_at, end_at=end_at, unfold_batch=unfold_batch, weight_v2=weight_v2, combine_embeds=combine_embeds)
|
||||
return (cond, )
|
||||
|
||||
@@ -288,7 +338,7 @@ class RegionalIPAdapterColorMask:
|
||||
"image": ("IMAGE",),
|
||||
"weight": ("FLOAT", {"default": 0.7, "min": -1, "max": 3, "step": 0.05}),
|
||||
"noise": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"weight_type": (["original", "linear", "channel penalty"], ),
|
||||
"weight_type": (IPADAPTER_WEIGHT_TYPES(), ),
|
||||
"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}),
|
||||
@@ -306,7 +356,8 @@ class RegionalIPAdapterColorMask:
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
def doit(self, color_mask, mask_color, image, weight, noise, weight_type, start_at=0.0, end_at=1.0, unfold_batch=False, faceid_v2=False, weight_v2=False, combine_embeds="concat", neg_image=None):
|
||||
@staticmethod
|
||||
def doit(color_mask, mask_color, image, weight, noise, weight_type, start_at=0.0, end_at=1.0, unfold_batch=False, faceid_v2=False, weight_v2=False, combine_embeds="concat", neg_image=None):
|
||||
mask = color_to_mask(color_mask, mask_color)
|
||||
cond = IPAdapterConditioning(mask, weight, weight_type, noise=noise, image=image, neg_image=neg_image, start_at=start_at, end_at=end_at, unfold_batch=unfold_batch, weight_v2=weight_v2, combine_embeds=combine_embeds)
|
||||
return (cond, mask)
|
||||
@@ -321,7 +372,7 @@ class RegionalIPAdapterEncodedMask:
|
||||
|
||||
"embeds": ("EMBEDS",),
|
||||
"weight": ("FLOAT", {"default": 0.7, "min": -1, "max": 3, "step": 0.05}),
|
||||
"weight_type": (["original", "linear", "channel penalty"],),
|
||||
"weight_type": (IPADAPTER_WEIGHT_TYPES(), ),
|
||||
"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}),
|
||||
@@ -336,7 +387,8 @@ class RegionalIPAdapterEncodedMask:
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
def doit(self, mask, embeds, weight, weight_type, start_at=0.0, end_at=1.0, unfold_batch=False, neg_embeds=None):
|
||||
@staticmethod
|
||||
def doit(mask, embeds, weight, weight_type, start_at=0.0, end_at=1.0, unfold_batch=False, neg_embeds=None):
|
||||
cond = IPAdapterConditioning(mask, weight, weight_type, embeds=embeds, start_at=start_at, end_at=end_at, unfold_batch=unfold_batch, neg_embeds=neg_embeds)
|
||||
return (cond, )
|
||||
|
||||
@@ -351,7 +403,7 @@ class RegionalIPAdapterEncodedColorMask:
|
||||
|
||||
"embeds": ("EMBEDS",),
|
||||
"weight": ("FLOAT", {"default": 0.7, "min": -1, "max": 3, "step": 0.05}),
|
||||
"weight_type": (["original", "linear", "channel penalty"],),
|
||||
"weight_type": (IPADAPTER_WEIGHT_TYPES(), ),
|
||||
"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}),
|
||||
@@ -366,7 +418,8 @@ class RegionalIPAdapterEncodedColorMask:
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
def doit(self, color_mask, mask_color, embeds, weight, weight_type, start_at=0.0, end_at=1.0, unfold_batch=False, neg_embeds=None):
|
||||
@staticmethod
|
||||
def doit(color_mask, mask_color, embeds, weight, weight_type, start_at=0.0, end_at=1.0, unfold_batch=False, neg_embeds=None):
|
||||
mask = color_to_mask(color_mask, mask_color)
|
||||
cond = IPAdapterConditioning(mask, weight, weight_type, embeds=embeds, start_at=start_at, end_at=end_at, unfold_batch=unfold_batch, neg_embeds=neg_embeds)
|
||||
return (cond, mask)
|
||||
@@ -386,7 +439,8 @@ class ApplyRegionalIPAdapters:
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
def doit(self, **kwargs):
|
||||
@staticmethod
|
||||
def doit(**kwargs):
|
||||
ipadapter_pipe = kwargs['ipadapter_pipe']
|
||||
ipadapter, model, clip_vision, insightface, lora_loader = ipadapter_pipe
|
||||
|
||||
@@ -413,6 +467,8 @@ class RegionalSeedExplorerMask:
|
||||
"additional_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"noise_mode": (["GPU(=A1111)", "CPU"],),
|
||||
},
|
||||
"optional":
|
||||
{"variation_method": (["linear", "slerp"],), }
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("NOISE",)
|
||||
@@ -420,7 +476,8 @@ class RegionalSeedExplorerMask:
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
def doit(self, mask, noise, seed_prompt, enable_additional, additional_seed, additional_strength, noise_mode):
|
||||
@staticmethod
|
||||
def doit(mask, noise, seed_prompt, enable_additional, additional_seed, additional_strength, noise_mode, variation_method='linear'):
|
||||
device = comfy.model_management.get_torch_device()
|
||||
noise_device = "cpu" if noise_mode == "CPU" else device
|
||||
|
||||
@@ -442,7 +499,7 @@ class RegionalSeedExplorerMask:
|
||||
if enable_additional:
|
||||
items.append((additional_seed, additional_strength))
|
||||
|
||||
noise = prompt_support.SeedExplorer.apply_variation(noise, items, noise_device, mask)
|
||||
noise = prompt_support.SeedExplorer.apply_variation(noise, items, noise_device, mask, variation_method=variation_method)
|
||||
except Exception:
|
||||
print(f"[ERROR] IGNORED: RegionalSeedExplorerColorMask is failed.")
|
||||
traceback.print_exc()
|
||||
@@ -467,6 +524,8 @@ class RegionalSeedExplorerColorMask:
|
||||
"additional_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"noise_mode": (["GPU(=A1111)", "CPU"],),
|
||||
},
|
||||
"optional":
|
||||
{"variation_method": (["linear", "slerp"],), }
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("NOISE", "MASK")
|
||||
@@ -474,7 +533,8 @@ class RegionalSeedExplorerColorMask:
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
def doit(self, color_mask, mask_color, noise, seed_prompt, enable_additional, additional_seed, additional_strength, noise_mode):
|
||||
@staticmethod
|
||||
def doit(color_mask, mask_color, noise, seed_prompt, enable_additional, additional_seed, additional_strength, noise_mode, variation_method='linear'):
|
||||
device = comfy.model_management.get_torch_device()
|
||||
noise_device = "cpu" if noise_mode == "CPU" else device
|
||||
|
||||
@@ -497,7 +557,7 @@ class RegionalSeedExplorerColorMask:
|
||||
if enable_additional:
|
||||
items.append((additional_seed, additional_strength))
|
||||
|
||||
noise = prompt_support.SeedExplorer.apply_variation(noise, items, noise_device, mask)
|
||||
noise = prompt_support.SeedExplorer.apply_variation(noise, items, noise_device, mask, variation_method=variation_method)
|
||||
except Exception:
|
||||
print(f"[ERROR] IGNORED: RegionalSeedExplorerColorMask is failed.")
|
||||
traceback.print_exc()
|
||||
@@ -508,6 +568,100 @@ class RegionalSeedExplorerColorMask:
|
||||
return (noise, original_mask)
|
||||
|
||||
|
||||
class ColorMaskToDepthMask:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"color_mask": ("IMAGE",),
|
||||
"spec": ("STRING", {"multiline": True, "default": "#FF0000:1.0\n#000000:1.0"}),
|
||||
"base_value": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0}),
|
||||
"dilation": ("INT", {"default": 0, "min": -512, "max": 512, "step": 1}),
|
||||
"flatten_method": (["override", "sum", "max"],),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MASK", )
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
def doit(self, color_mask, spec, base_value, dilation, flatten_method):
|
||||
specs = spec.split('\n')
|
||||
pat = re.compile("(?P<color_code>#[A-F0-9]+):(?P<cfg>[0-9]+(.[0-9]*)?)")
|
||||
|
||||
masks = [torch.ones((1, color_mask.shape[1], color_mask.shape[2])) * base_value]
|
||||
for x in specs:
|
||||
match = pat.match(x)
|
||||
if match:
|
||||
mask = color_to_mask(color_mask=color_mask, mask_color=match['color_code']) * float(match['cfg'])
|
||||
mask = utils.dilate_mask(mask, dilation)
|
||||
masks.append(mask)
|
||||
|
||||
if masks:
|
||||
masks = torch.cat(masks, dim=0)
|
||||
if flatten_method == 'override':
|
||||
masks = utils.flatten_non_zero_override(masks)
|
||||
elif flatten_method == 'max':
|
||||
masks = torch.max(masks, dim=0)[0]
|
||||
else: # flatten_method == 'sum':
|
||||
masks = torch.sum(masks, dim=0)
|
||||
|
||||
masks = torch.clamp(masks, min=0.0, max=1.0)
|
||||
masks = masks.unsqueeze(0)
|
||||
else:
|
||||
masks = torch.tensor([])
|
||||
|
||||
return (masks, )
|
||||
|
||||
|
||||
class RegionalCFG:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {"model": ("MODEL",),
|
||||
"mask": ("MASK",),
|
||||
}}
|
||||
|
||||
RETURN_TYPES = ("MODEL",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
@staticmethod
|
||||
def doit(model, mask):
|
||||
if len(mask.shape) == 2:
|
||||
mask = mask.unsqueeze(0).unsqueeze(0)
|
||||
elif len(mask.shape) == 3:
|
||||
mask = mask.unsqueeze(0)
|
||||
|
||||
size = None
|
||||
|
||||
def regional_cfg(args):
|
||||
nonlocal mask
|
||||
nonlocal size
|
||||
|
||||
x = args['input']
|
||||
|
||||
if mask.device != x.device:
|
||||
mask = mask.to(x.device)
|
||||
|
||||
if size != (x.shape[2], x.shape[3]):
|
||||
size = (x.shape[2], x.shape[3])
|
||||
mask = torch.nn.functional.interpolate(mask, size=size, mode='bilinear', align_corners=False)
|
||||
|
||||
cond_pred = args["cond_denoised"]
|
||||
uncond_pred = args["uncond_denoised"]
|
||||
cond_scale = args["cond_scale"]
|
||||
|
||||
cfg_result = uncond_pred + (cond_pred - uncond_pred) * cond_scale * mask
|
||||
|
||||
return x - cfg_result
|
||||
|
||||
m = model.clone()
|
||||
m.set_model_sampler_cfg_function(regional_cfg)
|
||||
return (m,)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"RegionalPromptSimple //Inspire": RegionalPromptSimple,
|
||||
"RegionalPromptColorMask //Inspire": RegionalPromptColorMask,
|
||||
@@ -522,6 +676,8 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ToIPAdapterPipe //Inspire": ToIPAdapterPipe,
|
||||
"FromIPAdapterPipe //Inspire": FromIPAdapterPipe,
|
||||
"ApplyRegionalIPAdapters //Inspire": ApplyRegionalIPAdapters,
|
||||
"RegionalCFG //Inspire": RegionalCFG,
|
||||
"ColorMaskToDepthMask //Inspire": ColorMaskToDepthMask,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
@@ -537,5 +693,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"RegionalSeedExplorerColorMask //Inspire": "Regional Seed Explorer By Color Mask (Inspire)",
|
||||
"ToIPAdapterPipe //Inspire": "ToIPAdapterPipe (Inspire)",
|
||||
"FromIPAdapterPipe //Inspire": "FromIPAdapterPipe (Inspire)",
|
||||
"ApplyRegionalIPAdapters //Inspire": "Apply Regional IPAdapters (Inspire)"
|
||||
"ApplyRegionalIPAdapters //Inspire": "Apply Regional IPAdapters (Inspire)",
|
||||
"RegionalCFG //Inspire": "Regional CFG (Inspire)",
|
||||
"ColorMaskToDepthMask //Inspire": "Color Mask To Depth Mask (Inspire)",
|
||||
}
|
||||
|
||||
+277
-51
@@ -3,26 +3,32 @@ from . import a1111_compat
|
||||
import comfy
|
||||
from .libs import common
|
||||
from comfy import model_management
|
||||
|
||||
from comfy.samplers import CFGGuider
|
||||
from comfy_extras.nodes_perpneg import Guider_PerpNeg
|
||||
import math
|
||||
|
||||
class KSampler_progress(a1111_compat.KSampler_inspire):
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{"model": ("MODEL",),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"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": (common.SCHEDULERS, ),
|
||||
"positive": ("CONDITIONING", ),
|
||||
"negative": ("CONDITIONING", ),
|
||||
"latent_image": ("LATENT", ),
|
||||
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"noise_mode": (["GPU(=A1111)", "CPU"],),
|
||||
"interval": ("INT", {"default": 1, "min": 1, "max": 10000}),
|
||||
"omit_start_latent": ("BOOLEAN", {"default": True, "label_on": "True", "label_off": "False"}),
|
||||
}
|
||||
return {"required": {
|
||||
"model": ("MODEL",),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"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": (common.SCHEDULERS, ),
|
||||
"positive": ("CONDITIONING", ),
|
||||
"negative": ("CONDITIONING", ),
|
||||
"latent_image": ("LATENT", ),
|
||||
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"noise_mode": (["GPU(=A1111)", "CPU"],),
|
||||
"interval": ("INT", {"default": 1, "min": 1, "max": 10000}),
|
||||
"omit_start_latent": ("BOOLEAN", {"default": True, "label_on": "True", "label_off": "False"}),
|
||||
"omit_final_latent": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False"}),
|
||||
},
|
||||
"optional": {
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
}
|
||||
}
|
||||
|
||||
CATEGORY = "InspirePack/analysis"
|
||||
@@ -30,22 +36,29 @@ class KSampler_progress(a1111_compat.KSampler_inspire):
|
||||
RETURN_TYPES = ("LATENT", "LATENT")
|
||||
RETURN_NAMES = ("latent", "progress_latent")
|
||||
|
||||
def doit(self, model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise, noise_mode, interval, omit_start_latent):
|
||||
@staticmethod
|
||||
def doit(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise, noise_mode,
|
||||
interval, omit_start_latent, omit_final_latent, scheduler_func_opt=None):
|
||||
adv_steps = int(steps / denoise)
|
||||
|
||||
if omit_start_latent:
|
||||
result = []
|
||||
else:
|
||||
result = [latent_image['samples']]
|
||||
|
||||
result = []
|
||||
result = [comfy.sample.fix_empty_latent_channels(model, latent_image['samples']).cpu()]
|
||||
|
||||
def progress_callback(step, x0, x, total_steps):
|
||||
x0 = model.model.process_latent_out(x0)
|
||||
x0 = x0.to(model_management.intermediate_device())
|
||||
result.append(x0)
|
||||
if (total_steps-1) != step and step % interval != 0:
|
||||
return
|
||||
|
||||
latent_image, noise = a1111_compat.KSamplerAdvanced_inspire.sample(model, True, seed, adv_steps, cfg, sampler_name, scheduler, positive, negative, latent_image, (adv_steps-steps), adv_steps, noise_mode, False, callback=progress_callback)
|
||||
x = model.model.process_latent_out(x)
|
||||
x = x.cpu()
|
||||
result.append(x)
|
||||
|
||||
latent_image, noise = a1111_compat.KSamplerAdvanced_inspire.sample(model, True, seed, adv_steps, cfg, sampler_name, scheduler, positive, negative, latent_image, (adv_steps-steps),
|
||||
adv_steps, noise_mode, False, callback=progress_callback, scheduler_func_opt=scheduler_func_opt)
|
||||
|
||||
if not omit_final_latent:
|
||||
result.append(latent_image['samples'].cpu())
|
||||
|
||||
if len(result) > 0:
|
||||
result = torch.cat(result)
|
||||
@@ -59,25 +72,29 @@ class KSampler_progress(a1111_compat.KSampler_inspire):
|
||||
class KSamplerAdvanced_progress(a1111_compat.KSamplerAdvanced_inspire):
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{"model": ("MODEL",),
|
||||
"add_noise": ("BOOLEAN", {"default": True, "label_on": "enable", "label_off": "disable"}),
|
||||
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.5, "round": 0.01}),
|
||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
|
||||
"scheduler": (common.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}),
|
||||
"noise_mode": (["GPU(=A1111)", "CPU"],),
|
||||
"return_with_leftover_noise": ("BOOLEAN", {"default": False, "label_on": "enable", "label_off": "disable"}),
|
||||
"interval": ("INT", {"default": 1, "min": 1, "max": 10000}),
|
||||
"omit_start_latent": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False"}),
|
||||
},
|
||||
"optional": {"prev_progress_latent_opt": ("LATENT",), }
|
||||
return {"required": {
|
||||
"model": ("MODEL",),
|
||||
"add_noise": ("BOOLEAN", {"default": True, "label_on": "enable", "label_off": "disable"}),
|
||||
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.5, "round": 0.01}),
|
||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
|
||||
"scheduler": (common.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}),
|
||||
"noise_mode": (["GPU(=A1111)", "CPU"],),
|
||||
"return_with_leftover_noise": ("BOOLEAN", {"default": False, "label_on": "enable", "label_off": "disable"}),
|
||||
"interval": ("INT", {"default": 1, "min": 1, "max": 10000}),
|
||||
"omit_start_latent": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False"}),
|
||||
"omit_final_latent": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False"}),
|
||||
},
|
||||
"optional": {
|
||||
"prev_progress_latent_opt": ("LATENT",),
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
}
|
||||
}
|
||||
|
||||
FUNCTION = "doit"
|
||||
@@ -87,22 +104,28 @@ class KSamplerAdvanced_progress(a1111_compat.KSamplerAdvanced_inspire):
|
||||
RETURN_TYPES = ("LATENT", "LATENT")
|
||||
RETURN_NAMES = ("latent", "progress_latent")
|
||||
|
||||
def doit(self, model, add_noise, noise_seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, start_at_step, end_at_step,
|
||||
noise_mode, return_with_leftover_noise, interval, omit_start_latent, prev_progress_latent_opt=None):
|
||||
def doit(self, model, add_noise, noise_seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image,
|
||||
start_at_step, end_at_step, noise_mode, return_with_leftover_noise, interval, omit_start_latent, omit_final_latent,
|
||||
prev_progress_latent_opt=None, scheduler_func_opt=None):
|
||||
|
||||
if omit_start_latent:
|
||||
result = []
|
||||
else:
|
||||
result = [latent_image['samples']]
|
||||
|
||||
result = []
|
||||
|
||||
def progress_callback(step, x0, x, total_steps):
|
||||
x0 = model.model.process_latent_out(x0)
|
||||
x0 = x0.to(model_management.intermediate_device())
|
||||
result.append(x0)
|
||||
if (total_steps-1) != step and step % interval != 0:
|
||||
return
|
||||
|
||||
x = model.model.process_latent_out(x)
|
||||
x = x.cpu()
|
||||
result.append(x)
|
||||
|
||||
latent_image, noise = a1111_compat.KSamplerAdvanced_inspire.sample(model, add_noise, noise_seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, start_at_step, end_at_step,
|
||||
noise_mode, False, callback=progress_callback)
|
||||
noise_mode, return_with_leftover_noise, callback=progress_callback, scheduler_func_opt=scheduler_func_opt)
|
||||
|
||||
if not omit_final_latent:
|
||||
result.append(latent_image['samples'].cpu())
|
||||
|
||||
if len(result) > 0:
|
||||
result = torch.cat(result)
|
||||
@@ -116,12 +139,215 @@ class KSamplerAdvanced_progress(a1111_compat.KSamplerAdvanced_inspire):
|
||||
return latent_image, result
|
||||
|
||||
|
||||
def exponential_interpolation(from_cfg, to_cfg, i, steps):
|
||||
if i == steps-1:
|
||||
return to_cfg
|
||||
|
||||
if from_cfg == to_cfg:
|
||||
return from_cfg
|
||||
|
||||
if from_cfg == 0:
|
||||
return to_cfg * (1 - math.exp(-5 * i / steps)) / (1 - math.exp(-5))
|
||||
elif to_cfg == 0:
|
||||
return from_cfg * (math.exp(-5 * i / steps) - math.exp(-5)) / (1 - math.exp(-5))
|
||||
else:
|
||||
log_from = math.log(from_cfg)
|
||||
log_to = math.log(to_cfg)
|
||||
log_value = log_from + (log_to - log_from) * i / steps
|
||||
return math.exp(log_value)
|
||||
|
||||
|
||||
def logarithmic_interpolation(from_cfg, to_cfg, i, steps):
|
||||
if i == 0:
|
||||
return from_cfg
|
||||
|
||||
if i == steps-1:
|
||||
return to_cfg
|
||||
|
||||
log_i = math.log(i + 1)
|
||||
log_steps = math.log(steps + 1)
|
||||
|
||||
t = log_i / log_steps
|
||||
|
||||
return from_cfg + (to_cfg - from_cfg) * t
|
||||
|
||||
|
||||
def cosine_interpolation(from_cfg, to_cfg, i, steps):
|
||||
if (i == 0) or (i == steps-1):
|
||||
return from_cfg
|
||||
|
||||
t = (1.0 + math.cos(math.pi*2*(i/steps))) / 2
|
||||
|
||||
return from_cfg + (to_cfg - from_cfg) * t
|
||||
|
||||
|
||||
class Guider_scheduled(CFGGuider):
|
||||
def __init__(self, model_patcher, sigmas, from_cfg, to_cfg, schedule):
|
||||
super().__init__(model_patcher)
|
||||
self.default_cfg = self.cfg
|
||||
self.sigmas = sigmas
|
||||
self.cfg_sigmas = None
|
||||
self.cfg_sigmas_i = None
|
||||
self.from_cfg = from_cfg
|
||||
self.to_cfg = to_cfg
|
||||
self.schedule = schedule
|
||||
self.last_i = 0
|
||||
self.renew_cfg_sigmas()
|
||||
|
||||
def set_cfg(self, cfg):
|
||||
self.default_cfg = cfg
|
||||
self.renew_cfg_sigmas()
|
||||
|
||||
def renew_cfg_sigmas(self):
|
||||
self.cfg_sigmas = {}
|
||||
self.cfg_sigmas_i = {}
|
||||
i = 0
|
||||
steps = len(self.sigmas) - 1
|
||||
for x in self.sigmas:
|
||||
k = float(x)
|
||||
delta = self.to_cfg - self.from_cfg
|
||||
if self.schedule == 'exp':
|
||||
self.cfg_sigmas[k] = exponential_interpolation(self.from_cfg, self.to_cfg, i, steps), i
|
||||
elif self.schedule == 'log':
|
||||
self.cfg_sigmas[k] = logarithmic_interpolation(self.from_cfg, self.to_cfg, i, steps), i
|
||||
elif self.schedule == 'cos':
|
||||
self.cfg_sigmas[k] = cosine_interpolation(self.from_cfg, self.to_cfg, i, steps), i
|
||||
else:
|
||||
self.cfg_sigmas[k] = self.from_cfg + delta * i / steps, i
|
||||
|
||||
self.cfg_sigmas_i[i] = self.cfg_sigmas[k]
|
||||
i += 1
|
||||
|
||||
def predict_noise(self, x, timestep, model_options={}, seed=None):
|
||||
k = float(timestep[0])
|
||||
|
||||
v = self.cfg_sigmas.get(k)
|
||||
if v is None:
|
||||
# fallback
|
||||
v = self.cfg_sigmas_i[self.last_i+1]
|
||||
self.cfg_sigmas[k] = v
|
||||
|
||||
self.last_i = v[1]
|
||||
self.cfg = v[0]
|
||||
|
||||
return super().predict_noise(x, timestep, model_options, seed)
|
||||
|
||||
|
||||
class Guider_PerpNeg_scheduled(Guider_PerpNeg):
|
||||
def __init__(self, model_patcher, sigmas, from_cfg, to_cfg, schedule, neg_scale):
|
||||
super().__init__(model_patcher)
|
||||
self.default_cfg = self.cfg
|
||||
self.sigmas = sigmas
|
||||
self.cfg_sigmas = None
|
||||
self.cfg_sigmas_i = None
|
||||
self.from_cfg = from_cfg
|
||||
self.to_cfg = to_cfg
|
||||
self.schedule = schedule
|
||||
self.neg_scale = neg_scale
|
||||
self.last_i = 0
|
||||
self.renew_cfg_sigmas()
|
||||
|
||||
def set_cfg(self, cfg):
|
||||
self.default_cfg = cfg
|
||||
self.renew_cfg_sigmas()
|
||||
|
||||
def renew_cfg_sigmas(self):
|
||||
self.cfg_sigmas = {}
|
||||
self.cfg_sigmas_i = {}
|
||||
i = 0
|
||||
steps = len(self.sigmas) - 1
|
||||
for x in self.sigmas:
|
||||
k = float(x)
|
||||
delta = self.to_cfg - self.from_cfg
|
||||
if self.schedule == 'exp':
|
||||
self.cfg_sigmas[k] = exponential_interpolation(self.from_cfg, self.to_cfg, i, steps), i
|
||||
elif self.schedule == 'log':
|
||||
self.cfg_sigmas[k] = logarithmic_interpolation(self.from_cfg, self.to_cfg, i, steps), i
|
||||
elif self.schedule == 'cos':
|
||||
self.cfg_sigmas[k] = cosine_interpolation(self.from_cfg, self.to_cfg, i, steps), i
|
||||
else:
|
||||
self.cfg_sigmas[k] = self.from_cfg + delta * i / steps, i
|
||||
|
||||
self.cfg_sigmas_i[i] = self.cfg_sigmas[k]
|
||||
i += 1
|
||||
|
||||
def predict_noise(self, x, timestep, model_options={}, seed=None):
|
||||
k = float(timestep[0])
|
||||
|
||||
v = self.cfg_sigmas.get(k)
|
||||
if v is None:
|
||||
# fallback
|
||||
v = self.cfg_sigmas_i[self.last_i+1]
|
||||
self.cfg_sigmas[k] = v
|
||||
|
||||
self.last_i = v[1]
|
||||
self.cfg = v[0]
|
||||
|
||||
return super().predict_noise(x, timestep, model_options, seed)
|
||||
|
||||
|
||||
class ScheduledCFGGuider:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"model": ("MODEL", ),
|
||||
"positive": ("CONDITIONING", ),
|
||||
"negative": ("CONDITIONING", ),
|
||||
"sigmas": ("SIGMAS", ),
|
||||
"from_cfg": ("FLOAT", {"default": 6.5, "min": 0.0, "max": 100.0, "step": 0.1, "round": 0.01}),
|
||||
"to_cfg": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.1, "round": 0.01}),
|
||||
"schedule": (["linear", "log", "exp", "cos"], {'default': 'log'})
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("GUIDER", "SIGMAS")
|
||||
|
||||
FUNCTION = "get_guider"
|
||||
CATEGORY = "sampling/custom_sampling/guiders"
|
||||
|
||||
def get_guider(self, model, positive, negative, sigmas, from_cfg, to_cfg, schedule):
|
||||
guider = Guider_scheduled(model, sigmas, from_cfg, to_cfg, schedule)
|
||||
guider.set_conds(positive, negative)
|
||||
return guider, sigmas
|
||||
|
||||
|
||||
class ScheduledPerpNegCFGGuider:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"model": ("MODEL", ),
|
||||
"positive": ("CONDITIONING", ),
|
||||
"negative": ("CONDITIONING", ),
|
||||
"empty_conditioning": ("CONDITIONING", ),
|
||||
"neg_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.01}),
|
||||
"sigmas": ("SIGMAS", ),
|
||||
"from_cfg": ("FLOAT", {"default": 6.5, "min": 0.0, "max": 100.0, "step": 0.1, "round": 0.01}),
|
||||
"to_cfg": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.1, "round": 0.01}),
|
||||
"schedule": (["linear", "log", "exp", "cos"], {'default': 'log'})
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("GUIDER", "SIGMAS")
|
||||
|
||||
FUNCTION = "get_guider"
|
||||
CATEGORY = "sampling/custom_sampling/guiders"
|
||||
|
||||
def get_guider(self, model, positive, negative, empty_conditioning, neg_scale, sigmas, from_cfg, to_cfg, schedule):
|
||||
guider = Guider_PerpNeg_scheduled(model, sigmas, from_cfg, to_cfg, schedule, neg_scale)
|
||||
guider.set_conds(positive, negative, empty_conditioning)
|
||||
return guider, sigmas
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"KSamplerProgress //Inspire": KSampler_progress,
|
||||
"KSamplerAdvancedProgress //Inspire": KSamplerAdvanced_progress,
|
||||
"ScheduledCFGGuider //Inspire": ScheduledCFGGuider,
|
||||
"ScheduledPerpNegCFGGuider //Inspire": ScheduledPerpNegCFGGuider
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"KSamplerProgress //Inspire": "KSampler Progress (Inspire)",
|
||||
"KSamplerAdvancedProgress //Inspire": "KSampler Advanced Progress (Inspire)",
|
||||
"ScheduledCFGGuider //Inspire": "Scheduled CFGGuider (Inspire)",
|
||||
"ScheduledPerpNegCFGGuider //Inspire": "Scheduled PerpNeg CFGGuider (Inspire)"
|
||||
}
|
||||
|
||||
+24
-4
@@ -40,6 +40,9 @@ class MediaPipeFaceMeshDetector:
|
||||
|
||||
resolution = normalize_size_base_64(image.shape[2], image.shape[1])
|
||||
facemesh_image = pre_obj().detect(image, self.max_faces, threshold, resolution=resolution)[0]
|
||||
|
||||
facemesh_image = nodes.ImageScale().upscale(facemesh_image, "bilinear", image.shape[2], image.shape[1], "disabled")[0]
|
||||
|
||||
segs = seg_obj().doit(facemesh_image, crop_factor, not self.is_segm, crop_min_size, drop_size, dilation,
|
||||
self.face, self.mouth, self.left_eyebrow, self.left_eye, self.left_pupil,
|
||||
self.right_eyebrow, self.right_eye, self.right_pupil)[0]
|
||||
@@ -107,6 +110,9 @@ class Color_Preprocessor_wrapper:
|
||||
|
||||
|
||||
class InpaintPreprocessor_wrapper:
|
||||
def __init__(self, black_pixel_for_xinsir_cn):
|
||||
self.black_pixel_for_xinsir_cn = black_pixel_for_xinsir_cn
|
||||
|
||||
def apply(self, image, mask=None):
|
||||
if 'InpaintPreprocessor' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/Fannovel16/comfyui_controlnet_aux',
|
||||
@@ -117,7 +123,16 @@ class InpaintPreprocessor_wrapper:
|
||||
if mask is None:
|
||||
mask = torch.ones((image.shape[1], image.shape[2]), dtype=torch.float32, device="cpu").unsqueeze(0)
|
||||
|
||||
return obj.preprocess(image, mask)[0]
|
||||
try:
|
||||
res = obj.preprocess(image, mask, black_pixel_for_xinsir_cn=self.black_pixel_for_xinsir_cn)[0]
|
||||
except Exception as e:
|
||||
if self.black_pixel_for_xinsir_cn:
|
||||
raise e
|
||||
else:
|
||||
res = obj.preprocess(image, mask)[0]
|
||||
print(f"[Inspire Pack] Installed 'ComfyUI's ControlNet Auxiliary Preprocessors.' is outdated.")
|
||||
|
||||
return res
|
||||
|
||||
|
||||
class TilePreprocessor_wrapper:
|
||||
@@ -544,14 +559,19 @@ class Color_Preprocessor_Provider_for_SEGS:
|
||||
class InpaintPreprocessor_Provider_for_SEGS:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {}}
|
||||
return {
|
||||
"required": {},
|
||||
"optional": {
|
||||
"black_pixel_for_xinsir_cn": ("BOOLEAN", {"default": False, "label_on": "enable", "label_off": "disable"}),
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = ("SEGS_PREPROCESSOR",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/SEGS/ControlNet"
|
||||
|
||||
def doit(self):
|
||||
obj = InpaintPreprocessor_wrapper()
|
||||
def doit(self, black_pixel_for_xinsir_cn=False):
|
||||
obj = InpaintPreprocessor_wrapper(black_pixel_for_xinsir_cn)
|
||||
return (obj, )
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import colorsys
|
||||
|
||||
|
||||
def hex_to_hsv(hex_color):
|
||||
hex_color = hex_color.lstrip('#')
|
||||
r, g, b = tuple(int(hex_color[i:i+2], 16) / 255.0 for i in (0, 2, 4))
|
||||
|
||||
h, s, v = colorsys.rgb_to_hsv(r, g, b)
|
||||
|
||||
hue = h * 360
|
||||
|
||||
saturation = s
|
||||
value = v
|
||||
|
||||
return hue, saturation, value
|
||||
|
||||
|
||||
class RGB_HexToHSV:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"rgb_hex": ("STRING", {"defaultInput": True}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("FLOAT", "FLOAT", "FLOAT")
|
||||
RETURN_NAMES = ("hue", "saturation", "value")
|
||||
FUNCTION = "doit"
|
||||
CATEGORY = "InspirePack/Util"
|
||||
|
||||
def doit(self, rgb_hex):
|
||||
return hex_to_hsv(rgb_hex)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"RGB_HexToHSV //Inspire": RGB_HexToHSV,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"RGB_HexToHSV //Inspire": "RGB Hex To HSV (Inspire)",
|
||||
}
|
||||
+1
-1
@@ -44,7 +44,7 @@ app.registerExtension({
|
||||
|
||||
Object.defineProperty(node, 'imgs', {
|
||||
set(v) {
|
||||
if (!v[0].complete) {
|
||||
if (v && !v[0].complete) {
|
||||
let orig_onload = v[0].onload;
|
||||
v[0].onload = function(v2) {
|
||||
if(orig_onload)
|
||||
|
||||
+57
-15
@@ -158,21 +158,57 @@ export function register_concat_conditionings_with_multiplier_node(nodeType, nod
|
||||
}
|
||||
|
||||
async function ensure_multipliers() {
|
||||
let ncon = get_input_count('conditioning', true);
|
||||
let nmul = get_input_count('multiplier', false) + get_widget_count('multiplier');
|
||||
if(self.ensuring_multipliers) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
self.ensuring_multipliers = true;
|
||||
|
||||
if(ncon == 0 && nmul == 0)
|
||||
ncon = 1;
|
||||
let ncon = get_input_count('conditioning', true);
|
||||
let nmul = get_input_count('multiplier', false) + get_widget_count('multiplier');
|
||||
|
||||
for(let i = nmul+1; i<=ncon; i++) {
|
||||
let config = { min: 0, max: 10, step: 0.1, round: 0.01, precision: 2 };
|
||||
let widget = await self.addWidget("number", `multiplier${i}`, 1.0, function (v) {
|
||||
if (config.round) {
|
||||
self.value = Math.round(v/config.round)*config.round;
|
||||
} else {
|
||||
self.value = v;
|
||||
}
|
||||
}, config);
|
||||
if(ncon == 0 && nmul == 0)
|
||||
ncon = 1;
|
||||
|
||||
for(let i = nmul+1; i<=ncon; i++) {
|
||||
let config = { min: 0, max: 10, step: 0.1, round: 0.01, precision: 2 };
|
||||
|
||||
// NOTE: addWidget trigger calling ensure_multipliers
|
||||
let widget = await self.addWidget("number", `multiplier${i}`, 1.0, function (v) {
|
||||
if (config.round) {
|
||||
self.value = Math.round(v/config.round)*config.round;
|
||||
} else {
|
||||
self.value = v;
|
||||
}
|
||||
}, config);
|
||||
}
|
||||
}
|
||||
finally{
|
||||
self.ensuring_multipliers = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function recover_multipliers() {
|
||||
if(self.recover_multipliers) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
self.recover_multipliers = true;
|
||||
for(let i = 1; i<self.widgets_values.length; i++) {
|
||||
let config = { min: 0, max: 10, step: 0.1, round: 0.01, precision: 2 };
|
||||
|
||||
// NOTE: addWidget trigger calling recover_multipliers
|
||||
let widget = await self.addWidget("number", `multiplier${i+1}`, 1.0, function (v) {
|
||||
if (config.round) {
|
||||
self.value = Math.round(v/config.round)*config.round;
|
||||
} else {
|
||||
self.value = v;
|
||||
}
|
||||
}, config);
|
||||
}
|
||||
}
|
||||
finally{
|
||||
self.recover_multipliers = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,12 +219,18 @@ export function register_concat_conditionings_with_multiplier_node(nodeType, nod
|
||||
}
|
||||
}
|
||||
|
||||
if(!Error().stack.includes('pasteFromClipboard')) {
|
||||
const stackTrace = new Error().stack;
|
||||
if(!stackTrace.includes('loadGraphData') && !stackTrace.includes('pasteFromClipboard')) {
|
||||
await remove_garbage();
|
||||
await ensure_inputs();
|
||||
}
|
||||
|
||||
await ensure_multipliers();
|
||||
if(!stackTrace.includes('loadGraphData')) {
|
||||
await ensure_multipliers();
|
||||
}
|
||||
else {
|
||||
await recover_multipliers();
|
||||
}
|
||||
|
||||
await this.setSize( this.computeSize() );
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ app.registerExtension({
|
||||
name: "Comfy.Inspire.LBW",
|
||||
|
||||
nodeCreated(node, app) {
|
||||
if(node.comfyClass == "LoraLoaderBlockWeight //Inspire") {
|
||||
if(node.comfyClass == "LoraLoaderBlockWeight //Inspire" || node.comfyClass == "MakeLBW //Inspire") {
|
||||
// category filter
|
||||
const lora_names_widget = node.widgets[node.widgets.findIndex(obj => obj.name === 'lora_name')];
|
||||
var full_lora_list = lora_names_widget.options.values;
|
||||
@@ -26,6 +26,12 @@ app.registerExtension({
|
||||
// vector selector
|
||||
let preset_i = 9;
|
||||
let vector_i = 10;
|
||||
|
||||
if(node.comfyClass == "MakeLBW //Inspire") {
|
||||
preset_i = 7;
|
||||
vector_i = 8;
|
||||
}
|
||||
|
||||
node._value = "Preset";
|
||||
|
||||
Object.defineProperty(node.widgets[preset_i], "value", {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
[project]
|
||||
name = "comfyui-inspire-pack"
|
||||
description = "This extension provides various nodes to support Lora Block Weight and the Impact Pack. Provides many easily applicable regional features and applications for Variation Seed."
|
||||
version = "1.5.1"
|
||||
license = { file = "LICENSE" }
|
||||
dependencies = ["matplotlib", "cachetools"]
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://github.com/ltdrdata/ComfyUI-Inspire-Pack"
|
||||
# Used by Comfy Registry https://comfyregistry.org
|
||||
|
||||
[tool.comfy]
|
||||
PublisherId = "drltdata"
|
||||
DisplayName = "ComfyUI Inspire Pack"
|
||||
Icon = ""
|
||||
+4
-1
@@ -1,2 +1,5 @@
|
||||
matplotlib
|
||||
cachetools
|
||||
cachetools
|
||||
numpy<2
|
||||
webcolors
|
||||
opencv-python
|
||||
|
||||
@@ -29,6 +29,16 @@ SDXL-LyC-ALL:1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
|
||||
SDXL-LyC-INALL:1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
SDXL-LyC-MIDALL:1,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0
|
||||
SDXL-LyC-OUTALL:1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1
|
||||
FLUX-DBL-ALL:1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
|
||||
FLUX-DBL-FRONT7:1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
FLUX-DBL-MID6:1,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0
|
||||
FLUX-DBL-TAIL6:1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1
|
||||
FLUX-SINGLE-ALL:1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
|
||||
FLUX-SINGLE-1to10:1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
FLUX-SINGLE-11to20:1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
FLUX-SINGLE-21to30:1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0
|
||||
FLUX-SINGLE-31to37:1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1
|
||||
FLUX-ALL:1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
|
||||
@SD-FULL-TEST:17
|
||||
@SD-BLOCK1-TEST:17,12,1
|
||||
@SD-BLOCK2-TEST:17,12,2
|
||||
@@ -49,4 +59,64 @@ SDXL-LyC-OUTALL:1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1
|
||||
@SD-BLOCK17-TEST:17,12,17
|
||||
@SD-LyC-FULL-TEST:27
|
||||
@SDXL-FULL-TEST:12
|
||||
@SDXL-LyC-FULL-TEST:21
|
||||
@SDXL-LyC-FULL-TEST:21
|
||||
@FLUX-DBL-FULL:19
|
||||
@FLUX-DBL-SGL-FULL:58
|
||||
@FLUX-DBL0-TEST:19,14,2
|
||||
@FLUX-DBL1-TEST:19,14,3
|
||||
@FLUX-DBL2-TEST:19,14,4
|
||||
@FLUX-DBL3-TEST:19,14,5
|
||||
@FLUX-DBL4-TEST:19,14,6
|
||||
@FLUX-DBL5-TEST:19,14,7
|
||||
@FLUX-DBL6-TEST:19,14,8
|
||||
@FLUX-DBL7-TEST:19,14,9
|
||||
@FLUX-DBL8-TEST:19,14,10
|
||||
@FLUX-DBL9-TEST:19,14,11
|
||||
@FLUX-DBL10-TEST:19,14,12
|
||||
@FLUX-DBL11-TEST:19,14,13
|
||||
@FLUX-DBL12-TEST:19,14,14
|
||||
@FLUX-DBL13-TEST:19,14,15
|
||||
@FLUX-DBL14-TEST:19,14,16
|
||||
@FLUX-DBL15-TEST:19,14,17
|
||||
@FLUX-DBL16-TEST:19,14,18
|
||||
@FLUX-DBL17-TEST:19,14,19
|
||||
@FLUX-DBL18-TEST:19,14,20
|
||||
@FLUX-SGL0-TEST:58,6,21
|
||||
@FLUX-SGL1-TEST:58,6,22
|
||||
@FLUX-SGL2-TEST:58,6,23
|
||||
@FLUX-SGL3-TEST:58,6,24
|
||||
@FLUX-SGL4-TEST:58,6,25
|
||||
@FLUX-SGL5-TEST:58,6,26
|
||||
@FLUX-SGL6-TEST:58,6,27
|
||||
@FLUX-SGL7-TEST:58,6,28
|
||||
@FLUX-SGL8-TEST:58,6,29
|
||||
@FLUX-SGL9-TEST:58,6,30
|
||||
@FLUX-SGL10-TEST:58,6,31
|
||||
@FLUX-SGL11-TEST:58,6,32
|
||||
@FLUX-SGL12-TEST:58,6,33
|
||||
@FLUX-SGL13-TEST:58,6,34
|
||||
@FLUX-SGL14-TEST:58,6,35
|
||||
@FLUX-SGL15-TEST:58,6,36
|
||||
@FLUX-SGL16-TEST:58,6,37
|
||||
@FLUX-SGL17-TEST:58,6,38
|
||||
@FLUX-SGL18-TEST:58,6,39
|
||||
@FLUX-SGL19-TEST:58,6,40
|
||||
@FLUX-SGL20-TEST:58,6,41
|
||||
@FLUX-SGL21-TEST:58,6,42
|
||||
@FLUX-SGL22-TEST:58,6,43
|
||||
@FLUX-SGL23-TEST:58,6,44
|
||||
@FLUX-SGL24-TEST:58,6,45
|
||||
@FLUX-SGL25-TEST:58,6,46
|
||||
@FLUX-SGL26-TEST:58,6,47
|
||||
@FLUX-SGL27-TEST:58,6,48
|
||||
@FLUX-SGL28-TEST:58,6,49
|
||||
@FLUX-SGL29-TEST:58,6,50
|
||||
@FLUX-SGL30-TEST:58,6,51
|
||||
@FLUX-SGL31-TEST:58,6,52
|
||||
@FLUX-SGL32-TEST:58,6,53
|
||||
@FLUX-SGL33-TEST:58,6,54
|
||||
@FLUX-SGL34-TEST:58,6,55
|
||||
@FLUX-SGL35-TEST:58,6,56
|
||||
@FLUX-SGL36-TEST:58,6,57
|
||||
@FLUX-SGL37-TEST:58,6,58
|
||||
@FLUX-SGL38-TEST:58,6,59
|
||||
Reference in New Issue
Block a user