Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c1d4eec46 | ||
|
|
d0cb63472a | ||
|
|
e984384068 | ||
|
|
8b35530cfb | ||
|
|
5f73ac55c0 | ||
|
|
33f3af04a8 | ||
|
|
88d3db9495 | ||
|
|
7c740a8fb0 | ||
|
|
b4ce4d9a76 | ||
|
|
dd6d0de968 | ||
|
|
43bca637c2 | ||
|
|
6aee0aa442 | ||
|
|
8a4050ed2a | ||
|
|
dd6ecaf9c2 | ||
|
|
43bb20dc9d |
@@ -6,7 +6,8 @@
|
||||
This custom node helps to conveniently enhance images through Detector, Detailer, Upscaler, Pipe, and more.
|
||||
|
||||
|
||||
## NOTICE
|
||||
## NOTICE
|
||||
* V4.77: Compatibility patch applied. Requires ComfyUI version (Oct. 8th) or later.
|
||||
* V4.73.3: ControlNetApply (SEGS) supports AnimateDiff
|
||||
* V4.20.1: Due to the feature update in `RegionalSampler`, the parameter order has changed, causing malfunctions in previously created `RegionalSamplers`. Please adjust the parameters accordingly.
|
||||
* V4.12: `MASKS` is changed to `MASK`.
|
||||
@@ -40,12 +41,13 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
* While `batch_masks` may not be completely separated, it provides functionality to perform some level of segmentation.
|
||||
* Simple Detector (SEGS) - Operating primarily with `BBOX_DETECTOR`, and with the additional provision of `SAM_MODEL` or `SEGM_DETECTOR`, this node internally generates improved SEGS through mask operations on both *bbox* and *silhouette*. It serves as a convenient tool to simplify a somewhat intricate workflow.
|
||||
|
||||
* ControlNet
|
||||
* ControlNet, IPAdapter
|
||||
* ControlNetApply (SEGS) - To apply ControlNet in SEGS, you need to use the Preprocessor Provider node from the Inspire Pack to utilize this node.
|
||||
* `segs_preprocessor` and `control_image` can be selectively applied. If an `control_image` is given, `segs_preprocessor` will be ignored.
|
||||
* If set to `control_image`, you can preview the cropped cnet image through `SEGSPreview (CNET Image)`. Images generated by `segs_preprocessor` should be verified through the `cnet_images` output of each Detailer.
|
||||
* The `segs_preprocessor` operates by applying preprocessing on-the-fly based on the cropped image during the detailing process, while `control_image` will be cropped and used as input to `ControlNetApply (SEGS)`.
|
||||
* ControlNetClear (SEGS) - Clear applied ControlNet in SEGS
|
||||
* ControlNetClear (SEGS) - Clear applied ControlNet in SEGS
|
||||
* IPAdapterApply (SEGS) - To apply IPAdapter in SEGS, you need to use the Preprocessor Provider node from the Inspire Pack to utilize this node.
|
||||
|
||||
* Bitwise(SEGS & SEGS) - Performs a 'bitwise and' operation between two SEGS.
|
||||
* Bitwise(SEGS - SEGS) - Subtracts one SEGS from another.
|
||||
@@ -91,6 +93,7 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
* SEGS Filter (label) - This node filters SEGS based on the label of the detected areas.
|
||||
* SEGS Filter (ordered) - This node sorts SEGS based on size and position and retrieves SEGs within a certain range.
|
||||
* SEGS Filter (range) - This node retrieves only SEGs from SEGS that have a size and position within a certain range.
|
||||
* SEGS Assign (label) - Assign labels sequentially to SEGS. This node is useful when used with `[LAB]` of FaceDetailer.
|
||||
* SEGSConcat - Concatenate segs1 and segs2. If source shape of segs1 and segs2 are different from segs2 will be ignored.
|
||||
* Picker (SEGS) - Among the input SEGS, you can select a specific SEG through a dialog. If no SEG is selected, it outputs an empty SEGS. Increasing the batch_size of SEGSDetailer can be used for the purpose of selecting from the candidates.
|
||||
* Set Default Image For SEGS - Set a default image for SEGS. SEGS with images set this way do not need to have a fallback image set. When override is set to false, the original image is preserved.
|
||||
@@ -105,6 +108,8 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
* From SEG_ELT - Extract detailed information from SEG_ELT.
|
||||
* Edit SEG_ELT - Modify some of the information in SEG_ELT.
|
||||
* Dilate SEG_ELT - Dilate the mask of SEG_ELT.
|
||||
* From SEG_ELT bbox - Extract coordinate from bbox in SEG_ELT
|
||||
* From SEG_ELT crop_region - Extract coordinate from crop_region in SEG_ELT
|
||||
|
||||
* Mask Manipulation
|
||||
* Dilate Mask - Dilate Mask.
|
||||
@@ -124,8 +129,9 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
* You need to install the [BlenderNeko/ComfyUI_TiledKSampler](https://github.com/BlenderNeko/ComfyUI_TiledKSampler) node extension.
|
||||
|
||||
* PK_HOOK
|
||||
* DenoiseScheduleHookProvider - IterativeUpscale provides a hook that gradually changes the denoise to target_denoise as the step progresses.
|
||||
* CfgScheduleHookProvider - IterativeUpscale provides a hook that gradually changes the cfg to target_cfg as the step progresses.
|
||||
* DenoiseScheduleHookProvider - IterativeUpscale provides a hook that gradually changes the denoise to target_denoise as the iterative-step progresses.
|
||||
* CfgScheduleHookProvider - IterativeUpscale provides a hook that gradually changes the cfg to target_cfg as the iterative-step progresses.
|
||||
* StepsScheduleHookProvider - IterativeUpscale provides a hook that gradually changes the sampling-steps to target_steps as the iterative-step progresses.
|
||||
* NoiseInjectionHookProvider - During each iteration of IterativeUpscale, noise is injected into the latent space while varying the strength according to a schedule.
|
||||
* You need to install the [BlenderNeko/ComfyUI_Noise](https://github.com/BlenderNeko/ComfyUI_Noise) node extension.
|
||||
* The seed serves as the initial value required for generating noise, and it increments by 1 with each iteration as the process unfolds.
|
||||
@@ -157,7 +163,8 @@ This takes latent as input and outputs latent as the result.
|
||||
* You need to install the [BlenderNeko/ComfyUI_TiledKSampler](https://github.com/BlenderNeko/ComfyUI_TiledKSampler) node extension.
|
||||
|
||||
* TwoAdvancedSamplersForMask - TwoSamplersForMask is similar to TwoAdvancedSamplersForMask, but they differ in their operation. TwoSamplersForMask performs sampling in the mask area only after all the samples in the base area are finished. On the other hand, TwoAdvancedSamplersForMask performs sampling in both the base area and the mask area sequentially at each step.
|
||||
* KSamplerAdvancedProvider - This is a wrapper that enables KSampler to be used in TwoAdvancedSamplersForMask.
|
||||
* KSamplerAdvancedProvider - This is a wrapper that enables KSampler to be used in TwoAdvancedSamplersForMask, RegionalSampler.
|
||||
* sigma_factor: By multiplying the denoise schedule by the sigma_factor, you can adjust the amount of denoising based on the configured denoise.
|
||||
|
||||
* TwoSamplersForMaskUpscalerProvider - This is an Upscaler that extends TwoSamplersForMask to be used in Iterative Upscale.
|
||||
* TwoSamplersForMaskUpscalerProviderPipe - pipe version of TwoSamplersForMaskUpscalerProvider.
|
||||
|
||||
+25
-11
@@ -96,17 +96,18 @@ def setup_js():
|
||||
|
||||
setup_js()
|
||||
|
||||
from impact.impact_pack import *
|
||||
from impact.detectors import *
|
||||
from impact.pipe import *
|
||||
from impact.logics import *
|
||||
from impact.util_nodes import *
|
||||
from impact.segs_nodes import *
|
||||
from impact.special_samplers import *
|
||||
from impact.hf_nodes import *
|
||||
from impact.bridge_nodes import *
|
||||
from impact.hook_nodes import *
|
||||
from impact.animatediff_nodes import *
|
||||
from .modules.impact.impact_pack import *
|
||||
from .modules.impact.detectors import *
|
||||
from .modules.impact.pipe import *
|
||||
from .modules.impact.logics import *
|
||||
from .modules.impact.util_nodes import *
|
||||
from .modules.impact.segs_nodes import *
|
||||
from .modules.impact.special_samplers import *
|
||||
from .modules.impact.hf_nodes import *
|
||||
from .modules.impact.bridge_nodes import *
|
||||
from .modules.impact.hook_nodes import *
|
||||
from .modules.impact.animatediff_nodes import *
|
||||
from .modules.impact.segs_upscaler import *
|
||||
|
||||
import threading
|
||||
|
||||
@@ -176,6 +177,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
|
||||
"PixelKSampleHookCombine": PixelKSampleHookCombine,
|
||||
"DenoiseScheduleHookProvider": DenoiseScheduleHookProvider,
|
||||
"StepsScheduleHookProvider": StepsScheduleHookProvider,
|
||||
"CfgScheduleHookProvider": CfgScheduleHookProvider,
|
||||
"NoiseInjectionHookProvider": NoiseInjectionHookProvider,
|
||||
"UnsamplerHookProvider": UnsamplerHookProvider,
|
||||
@@ -216,6 +218,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ImpactControlNetApplySEGS": ControlNetApplySEGS,
|
||||
"ImpactControlNetApplyAdvancedSEGS": ControlNetApplyAdvancedSEGS,
|
||||
"ImpactControlNetClearSEGS": ControlNetClearSEGS,
|
||||
"ImpactIPAdapterApplySEGS": IPAdapterApplySEGS,
|
||||
|
||||
"ImpactDecomposeSEGS": DecomposeSEGS,
|
||||
"ImpactAssembleSEGS": AssembleSEGS,
|
||||
@@ -227,6 +230,8 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ImpactDilateMaskInSEGS": DilateMaskInSEGS,
|
||||
"ImpactGaussianBlurMaskInSEGS": GaussianBlurMaskInSEGS,
|
||||
"ImpactScaleBy_BBOX_SEG_ELT": SEG_ELT_BBOX_ScaleBy,
|
||||
"ImpactFrom_SEG_ELT_bbox": From_SEG_ELT_bbox,
|
||||
"ImpactFrom_SEG_ELT_crop_region": From_SEG_ELT_crop_region,
|
||||
|
||||
"BboxDetectorCombined_v2": BboxDetectorCombined,
|
||||
"SegmDetectorCombined_v2": SegmDetectorCombined,
|
||||
@@ -254,6 +259,8 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ImpactWildcardProcessor": ImpactWildcardProcessor,
|
||||
"ImpactWildcardEncode": ImpactWildcardEncode,
|
||||
|
||||
"SEGSUpscaler": SEGSUpscaler,
|
||||
"SEGSUpscalerPipe": SEGSUpscalerPipe,
|
||||
"SEGSDetailer": SEGSDetailer,
|
||||
"SEGSPaste": SEGSPaste,
|
||||
"SEGSPreview": SEGSPreview,
|
||||
@@ -285,6 +292,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ImpactCombineConditionings": CombineConditionings,
|
||||
"ImpactConcatConditionings": ConcatConditionings,
|
||||
|
||||
"ImpactSEGSLabelAssign": SEGSLabelAssign,
|
||||
"ImpactSEGSLabelFilter": SEGSLabelFilter,
|
||||
"ImpactSEGSRangeFilter": SEGSRangeFilter,
|
||||
"ImpactSEGSOrderedFilter": SEGSOrderedFilter,
|
||||
@@ -337,6 +345,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ImpactSimpleDetectorSEGSPipe": "Simple Detector (SEGS/pipe)",
|
||||
"ImpactControlNetApplySEGS": "ControlNetApply (SEGS)",
|
||||
"ImpactControlNetApplyAdvancedSEGS": "ControlNetApplyAdvanced (SEGS)",
|
||||
"ImpactIPAdapterApplySEGS": "IPAdapterApply (SEGS)",
|
||||
|
||||
"BboxDetectorCombined_v2": "BBOX Detector (combined)",
|
||||
"SegmDetectorCombined_v2": "SEGM Detector (combined)",
|
||||
@@ -357,6 +366,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"DetailerForEachDebugPipe": "DetailerDebug (SEGS/pipe)",
|
||||
"SEGSDetailerForAnimateDiff": "SEGSDetailer For AnimateDiff (SEGS/pipe)",
|
||||
"DetailerForEachPipeForAnimateDiff": "Detailer For AnimateDiff (SEGS/pipe)",
|
||||
"SEGSUpscaler": "Upscaler (SEGS)",
|
||||
"SEGSUpscalerPipe": "Upscaler (SEGS/pipe)",
|
||||
|
||||
"SAMDetectorCombined": "SAMDetector (combined)",
|
||||
"SAMDetectorSegmented": "SAMDetector (segmented)",
|
||||
@@ -384,6 +395,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
|
||||
"ImpactKSamplerBasicPipe": "KSampler (pipe)",
|
||||
"ImpactKSamplerAdvancedBasicPipe": "KSampler (Advanced/pipe)",
|
||||
"ImpactSEGSLabelAssign": "SEGS Assign (label)",
|
||||
"ImpactSEGSLabelFilter": "SEGS Filter (label)",
|
||||
"ImpactSEGSRangeFilter": "SEGS Filter (range)",
|
||||
"ImpactSEGSOrderedFilter": "SEGS Filter (ordered)",
|
||||
@@ -397,6 +409,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ImpactAssembleSEGS": "Assemble (SEGS)",
|
||||
"ImpactFrom_SEG_ELT": "From SEG_ELT",
|
||||
"ImpactEdit_SEG_ELT": "Edit SEG_ELT",
|
||||
"ImpactFrom_SEG_ELT_bbox": "From SEG_ELT bbox",
|
||||
"ImpactFrom_SEG_ELT_crop_region": "From SEG_ELT crop_region",
|
||||
"ImpactDilate_Mask_SEG_ELT": "Dilate Mask (SEG_ELT)",
|
||||
"ImpactScaleBy_BBOX_SEG_ELT": "ScaleBy BBOX (SEG_ELT)",
|
||||
"ImpactDilateMask": "Dilate Mask",
|
||||
|
||||
@@ -65,7 +65,7 @@ class SEGSDetailerForAnimateDiff:
|
||||
else:
|
||||
cropped_image_frames = torch.concat((cropped_image_frames, cropped_image), dim=0)
|
||||
|
||||
cropped_image_frames = cropped_image_frames.numpy()
|
||||
cropped_image_frames = cropped_image_frames.cpu().numpy()
|
||||
enhanced_image_tensor, cnet_images = core.enhance_detail_for_animatediff(cropped_image_frames, model, clip, vae, guide_size, guide_size_for, max_size,
|
||||
seg.bbox, seed, steps, cfg, sampler_name, scheduler,
|
||||
positive, negative, denoise, seg.cropped_mask,
|
||||
@@ -79,7 +79,7 @@ class SEGSDetailerForAnimateDiff:
|
||||
if enhanced_image_tensor is None:
|
||||
new_cropped_image = cropped_image_frames
|
||||
else:
|
||||
new_cropped_image = enhanced_image_tensor.numpy()
|
||||
new_cropped_image = enhanced_image_tensor.cpu().numpy()
|
||||
|
||||
new_seg = SEG(new_cropped_image, seg.cropped_mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, None)
|
||||
new_segs.append(new_seg)
|
||||
|
||||
@@ -2,7 +2,7 @@ import configparser
|
||||
import os
|
||||
|
||||
|
||||
version_code = [4, 74]
|
||||
version_code = [4, 82]
|
||||
version = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
|
||||
|
||||
dependency_version = 20
|
||||
|
||||
+65
-3
@@ -225,6 +225,8 @@ def enhance_detail(image, model, clip, vae, guide_size, guide_size_for_bbox, max
|
||||
cnet_pils = None
|
||||
if control_net_wrapper is not None:
|
||||
positive, negative, cnet_pils = control_net_wrapper.apply(positive, negative, upscaled_image, noise_mask)
|
||||
model, cnet_pils2 = control_net_wrapper.doit_ipadapter(model)
|
||||
cnet_pils.extend(cnet_pils2)
|
||||
|
||||
# prepare mask
|
||||
if noise_mask is not None and inpaint_model:
|
||||
@@ -1491,9 +1493,57 @@ class PixelKSampleUpscaler:
|
||||
return refined_latent
|
||||
|
||||
|
||||
class IPAdapterWrapper:
|
||||
def __init__(self, ipadapter_pipe, weight, noise, weight_type, start_at, end_at, unfold_batch, faceid_v2, weight_v2, reference_image, prev_control_net=None):
|
||||
self.reference_image = reference_image
|
||||
self.ipadapter_pipe = ipadapter_pipe
|
||||
self.weight = weight
|
||||
self.weight_type = weight_type
|
||||
self.noise = noise
|
||||
self.start_at = start_at
|
||||
self.end_at = end_at
|
||||
self.unfold_batch = unfold_batch
|
||||
self.prev_control_net = prev_control_net
|
||||
self.faceid_v2 = faceid_v2
|
||||
self.weight_v2 = weight_v2
|
||||
self.image = reference_image
|
||||
|
||||
# name 'apply_ipadapter' isn't allowed
|
||||
def doit_ipadapter(self, model):
|
||||
cnet_image_list = [self.image]
|
||||
prev_cnet_images = []
|
||||
|
||||
if 'IPAdapterApply' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/cubiq/ComfyUI_IPAdapter_plus',
|
||||
"To use 'IPAdapterApplySEGS' node, 'ComfyUI IPAdapter Plus' extension is required.")
|
||||
raise Exception(f"[ERROR] To use IPAdapterApplySEGS, you need to install 'ComfyUI IPAdapter Plus'")
|
||||
|
||||
obj = nodes.NODE_CLASS_MAPPINGS['IPAdapterApply']
|
||||
|
||||
ipadapter, _, clip_vision, insightface, lora_loader = self.ipadapter_pipe
|
||||
model = lora_loader(model)
|
||||
|
||||
if self.prev_control_net is not None:
|
||||
model, prev_cnet_images = self.prev_control_net.doit_ipadapter(model)
|
||||
|
||||
model = obj().apply_ipadapter(ipadapter, model, self.weight, clip_vision=clip_vision, image=self.image,
|
||||
embeds=None, weight_type=self.weight_type, noise=self.noise,
|
||||
attn_mask=None, start_at=self.start_at, end_at=self.end_at,
|
||||
unfold_batch=self.unfold_batch, insightface=insightface, faceid_v2=self.faceid_v2, weight_v2=self.weight_v2)[0]
|
||||
|
||||
cnet_image_list.extend(prev_cnet_images)
|
||||
|
||||
return model, cnet_image_list
|
||||
|
||||
def apply(self, positive, negative, image, mask=None, use_acn=False):
|
||||
if self.prev_control_net is not None:
|
||||
return self.prev_control_net.apply(positive, negative, image, mask, use_acn=use_acn)
|
||||
else:
|
||||
return positive, negative, []
|
||||
|
||||
|
||||
class ControlNetWrapper:
|
||||
def __init__(self, control_net, strength, preprocessor, prev_control_net=None,
|
||||
original_size=None, crop_region=None, control_image=None):
|
||||
def __init__(self, control_net, strength, preprocessor, prev_control_net=None, original_size=None, crop_region=None, control_image=None):
|
||||
self.control_net = control_net
|
||||
self.strength = strength
|
||||
self.preprocessor = preprocessor
|
||||
@@ -1536,6 +1586,12 @@ class ControlNetWrapper:
|
||||
|
||||
return positive, negative, cnet_image_list
|
||||
|
||||
def doit_ipadapter(self, model):
|
||||
if self.prev_control_net is not None:
|
||||
return self.prev_control_net.doit_ipadapter(model)
|
||||
else:
|
||||
return model, []
|
||||
|
||||
|
||||
class ControlNetAdvancedWrapper:
|
||||
def __init__(self, control_net, strength, start_percent, end_percent, preprocessor, prev_control_net=None,
|
||||
@@ -1553,6 +1609,12 @@ class ControlNetAdvancedWrapper:
|
||||
else:
|
||||
self.control_image = None
|
||||
|
||||
def doit_ipadapter(self, model):
|
||||
if self.prev_control_net is not None:
|
||||
return self.prev_control_net.doit_ipadapter(model)
|
||||
else:
|
||||
return model, []
|
||||
|
||||
def apply(self, positive, negative, image, mask=None, use_acn=False):
|
||||
cnet_image_list = []
|
||||
prev_cnet_images = []
|
||||
@@ -1755,7 +1817,7 @@ def random_mask_raw(mask, bbox, factor):
|
||||
w = x2 - x1
|
||||
h = y2 - y1
|
||||
|
||||
factor = int(min(w, h) * factor / 4)
|
||||
factor = max(6, int(min(w, h) * factor / 4))
|
||||
|
||||
def draw_random_circle(center, radius):
|
||||
i, j = center
|
||||
|
||||
@@ -209,16 +209,20 @@ class SimpleDetectorForEach:
|
||||
if len(image) > 1:
|
||||
raise Exception('[Impact Pack] ERROR: SimpleDetectorForEach does not allow image batches.\nPlease refer to https://github.com/ltdrdata/ComfyUI-extension-tutorials/blob/Main/ComfyUI-Impact-Pack/tutorial/batching-detailer.md for more information.')
|
||||
|
||||
segs = bbox_detector.detect(image, bbox_threshold, bbox_dilation, crop_factor, drop_size, detailer_hook=detailer_hook)
|
||||
if segm_detector_opt is not None and hasattr(segm_detector_opt, 'bbox_detector') and segm_detector_opt.bbox_detector == bbox_detector:
|
||||
# Better segm support for YOLO-World detector
|
||||
segs = segm_detector_opt.detect(image, sub_threshold, sub_dilation, crop_factor, drop_size, detailer_hook=detailer_hook)
|
||||
else:
|
||||
segs = bbox_detector.detect(image, bbox_threshold, bbox_dilation, crop_factor, drop_size, detailer_hook=detailer_hook)
|
||||
|
||||
if sam_model_opt is not None:
|
||||
mask = core.make_sam_mask(sam_model_opt, segs, image, "center-1", sub_dilation,
|
||||
sub_threshold, sub_bbox_expansion, sam_mask_hint_threshold, False)
|
||||
segs = core.segs_bitwise_and_mask(segs, mask)
|
||||
elif segm_detector_opt is not None:
|
||||
segm_segs = segm_detector_opt.detect(image, sub_threshold, sub_dilation, crop_factor, drop_size, detailer_hook=detailer_hook)
|
||||
mask = core.segs_to_combined_mask(segm_segs)
|
||||
segs = core.segs_bitwise_and_mask(segs, mask)
|
||||
if sam_model_opt is not None:
|
||||
mask = core.make_sam_mask(sam_model_opt, segs, image, "center-1", sub_dilation,
|
||||
sub_threshold, sub_bbox_expansion, sam_mask_hint_threshold, False)
|
||||
segs = core.segs_bitwise_and_mask(segs, mask)
|
||||
elif segm_detector_opt is not None:
|
||||
segm_segs = segm_detector_opt.detect(image, sub_threshold, sub_dilation, crop_factor, drop_size, detailer_hook=detailer_hook)
|
||||
mask = core.segs_to_combined_mask(segm_segs)
|
||||
segs = core.segs_bitwise_and_mask(segs, mask)
|
||||
|
||||
segs = core.dilate_segs(segs, post_dilation)
|
||||
|
||||
|
||||
@@ -73,11 +73,11 @@ class PreviewDetailerHookProvider:
|
||||
"hidden": {"unique_id": "UNIQUE_ID"},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DETAILER_HOOK", )
|
||||
RETURN_TYPES = ("DETAILER_HOOK", "UPSCALER_HOOK")
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
def doit(self, quality, unique_id):
|
||||
hook = hooks.PreviewDetailerHook(unique_id, quality)
|
||||
return (hook, )
|
||||
return (hook, hook)
|
||||
|
||||
+40
-13
@@ -109,11 +109,14 @@ class SimpleCfgScheduleHook(PixelKSampleHook):
|
||||
super().__init__()
|
||||
self.target_cfg = target_cfg
|
||||
|
||||
def pre_ksample(self, model, seed, steps, cfg, sampler_name, scheduler, positive, negative, upscaled_latent,
|
||||
denoise):
|
||||
progress = self.cur_step / self.total_step
|
||||
gap = self.target_cfg - cfg
|
||||
current_cfg = cfg + gap * progress
|
||||
def pre_ksample(self, model, seed, steps, cfg, sampler_name, scheduler, positive, negative, upscaled_latent, denoise):
|
||||
if self.total_step > 1:
|
||||
progress = self.cur_step / (self.total_step - 1)
|
||||
gap = self.target_cfg - cfg
|
||||
current_cfg = int(cfg + gap * progress)
|
||||
else:
|
||||
current_cfg = self.target_cfg
|
||||
|
||||
return model, seed, steps, current_cfg, sampler_name, scheduler, positive, negative, upscaled_latent, denoise
|
||||
|
||||
|
||||
@@ -122,14 +125,33 @@ class SimpleDenoiseScheduleHook(PixelKSampleHook):
|
||||
super().__init__()
|
||||
self.target_denoise = target_denoise
|
||||
|
||||
def pre_ksample(self, model, seed, steps, cfg, sampler_name, scheduler, positive, negative, upscaled_latent,
|
||||
denoise):
|
||||
progress = self.cur_step / self.total_step
|
||||
gap = self.target_denoise - denoise
|
||||
current_denoise = denoise + gap * progress
|
||||
def pre_ksample(self, model, seed, steps, cfg, sampler_name, scheduler, positive, negative, upscaled_latent, denoise):
|
||||
if self.total_step > 1:
|
||||
progress = self.cur_step / (self.total_step - 1)
|
||||
gap = self.target_denoise - denoise
|
||||
current_denoise = denoise + gap * progress
|
||||
else:
|
||||
current_denoise = self.target_denoise
|
||||
|
||||
return model, seed, steps, cfg, sampler_name, scheduler, positive, negative, upscaled_latent, current_denoise
|
||||
|
||||
|
||||
class SimpleStepsScheduleHook(PixelKSampleHook):
|
||||
def __init__(self, target_steps):
|
||||
super().__init__()
|
||||
self.target_steps = target_steps
|
||||
|
||||
def pre_ksample(self, model, seed, steps, cfg, sampler_name, scheduler, positive, negative, upscaled_latent, denoise):
|
||||
if self.total_step > 1:
|
||||
progress = self.cur_step / (self.total_step - 1)
|
||||
gap = self.target_steps - steps
|
||||
current_steps = int(steps + gap * progress)
|
||||
else:
|
||||
current_steps = self.target_steps
|
||||
|
||||
return model, seed, current_steps, cfg, sampler_name, scheduler, positive, negative, upscaled_latent, denoise
|
||||
|
||||
|
||||
class DetailerHook(PixelKSampleHook):
|
||||
def cycle_latent(self, latent):
|
||||
return latent
|
||||
@@ -147,9 +169,14 @@ class SimpleDetailerDenoiseSchedulerHook(DetailerHook):
|
||||
self.target_denoise = target_denoise
|
||||
|
||||
def pre_ksample(self, model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent, denoise):
|
||||
progress = self.cur_step / self.total_step
|
||||
gap = self.target_denoise - denoise
|
||||
current_denoise = denoise + gap * progress
|
||||
if self.total_step > 1:
|
||||
progress = self.cur_step / (self.total_step - 1)
|
||||
gap = self.target_denoise - denoise
|
||||
current_denoise = denoise + gap * progress
|
||||
else:
|
||||
# ignore hook if total cycle <= 1
|
||||
current_denoise = denoise
|
||||
|
||||
return model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent, current_denoise
|
||||
|
||||
|
||||
|
||||
@@ -237,8 +237,10 @@ class DetailerForEach:
|
||||
else:
|
||||
cropped_mask = None
|
||||
|
||||
if wildcard_chooser is not None:
|
||||
if wildcard_chooser is not None and wmode != "LAB":
|
||||
seg_seed, wildcard_item = wildcard_chooser.get(seg)
|
||||
elif wildcard_chooser is not None and wmode == "LAB":
|
||||
seg_seed, wildcard_item = None, wildcard_chooser.get(seg)
|
||||
else:
|
||||
seg_seed, wildcard_item = None, None
|
||||
|
||||
@@ -272,7 +274,7 @@ class DetailerForEach:
|
||||
# Convert enhanced_pil_alpha to RGBA mode
|
||||
enhanced_image_alpha = tensor_convert_rgba(enhanced_image)
|
||||
new_seg_image = enhanced_image.numpy() # alpha should not be applied to seg_image
|
||||
|
||||
|
||||
# Apply the mask
|
||||
mask = tensor_resize(mask, *tensor_get_size(enhanced_image))
|
||||
tensor_putalpha(enhanced_image_alpha, mask)
|
||||
@@ -780,6 +782,30 @@ class DenoiseScheduleHookProvider:
|
||||
return (hook, )
|
||||
|
||||
|
||||
class StepsScheduleHookProvider:
|
||||
schedules = ["simple"]
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"schedule_for_iteration": (s.schedules,),
|
||||
"target_steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("PK_HOOK",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Upscale"
|
||||
|
||||
def doit(self, schedule_for_iteration, target_steps):
|
||||
hook = None
|
||||
if schedule_for_iteration == "simple":
|
||||
hook = hooks.SimpleStepsScheduleHook(target_steps)
|
||||
|
||||
return (hook, )
|
||||
|
||||
|
||||
class DetailerHookCombine:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -1104,7 +1130,7 @@ class IterativeLatentUpscale:
|
||||
new_h = h*upscale_factor
|
||||
core.update_node_status(unique_id, f"Final step | x{upscale_factor:.2f}", 1.0)
|
||||
print(f"IterativeLatentUpscale[Final]: {new_w:.1f}x{new_h:.1f} (scale:{upscale_factor:.2f}) ")
|
||||
step_info = steps, steps
|
||||
step_info = steps-1, steps
|
||||
current_latent = upscaler.upscale_shape(step_info, current_latent, new_w, new_h, temp_prefix)
|
||||
|
||||
core.update_node_status(unique_id, "", None)
|
||||
@@ -1451,7 +1477,7 @@ class SegsBitwiseAndMask:
|
||||
|
||||
def doit(self, segs, mask):
|
||||
return (core.segs_bitwise_and_mask(segs, mask), )
|
||||
|
||||
|
||||
|
||||
class SegsBitwiseAndMaskForEach:
|
||||
@classmethod
|
||||
@@ -1626,7 +1652,7 @@ class SubtractMask:
|
||||
"mask2": ("MASK", ),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RETURN_TYPES = ("MASK",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
@@ -1757,7 +1783,7 @@ class ImageReceiver:
|
||||
return hash(image_data)
|
||||
else:
|
||||
return hash(image)
|
||||
|
||||
|
||||
|
||||
from server import PromptServer
|
||||
|
||||
|
||||
@@ -119,18 +119,18 @@ def separated_sample(model, add_noise, seed, steps, cfg, sampler_name, scheduler
|
||||
|
||||
|
||||
def ksampler_wrapper(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise,
|
||||
refiner_ratio=None, refiner_model=None, refiner_clip=None, refiner_positive=None, refiner_negative=None):
|
||||
refiner_ratio=None, refiner_model=None, refiner_clip=None, refiner_positive=None, refiner_negative=None, sigma_factor=1.0):
|
||||
|
||||
if refiner_ratio is None or refiner_model is None or refiner_clip is None or refiner_positive is None or refiner_negative is None:
|
||||
refined_latent = nodes.KSampler().sample(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise)[0]
|
||||
refined_latent = nodes.KSampler().sample(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise * sigma_factor)[0]
|
||||
else:
|
||||
advanced_steps = math.floor(steps / denoise)
|
||||
start_at_step = advanced_steps - steps
|
||||
end_at_step = start_at_step + math.floor(steps * (1.0 - refiner_ratio))
|
||||
|
||||
print(f"pre: {start_at_step} .. {end_at_step} / {advanced_steps}")
|
||||
# print(f"pre: {start_at_step} .. {end_at_step} / {advanced_steps}")
|
||||
temp_latent = separated_sample(model, True, seed, advanced_steps, cfg, sampler_name, scheduler,
|
||||
positive, negative, latent_image, start_at_step, end_at_step, True)
|
||||
positive, negative, latent_image, start_at_step, end_at_step, True, sigma_ratio=sigma_factor)
|
||||
|
||||
if 'noise_mask' in latent_image:
|
||||
# noise_latent = \
|
||||
@@ -141,9 +141,9 @@ def ksampler_wrapper(model, seed, steps, cfg, sampler_name, scheduler, positive,
|
||||
latent_compositor = nodes.NODE_CLASS_MAPPINGS['LatentCompositeMasked']()
|
||||
temp_latent = latent_compositor.composite(latent_image, temp_latent, 0, 0, False, latent_image['noise_mask'])[0]
|
||||
|
||||
print(f"post: {end_at_step} .. {advanced_steps + 1} / {advanced_steps}")
|
||||
# print(f"post: {end_at_step} .. {advanced_steps + 1} / {advanced_steps}")
|
||||
refined_latent = separated_sample(refiner_model, False, seed, advanced_steps, cfg, sampler_name, scheduler,
|
||||
refiner_positive, refiner_negative, temp_latent, end_at_step, advanced_steps + 1, False)
|
||||
refiner_positive, refiner_negative, temp_latent, end_at_step, advanced_steps + 1, False, sigma_ratio=sigma_factor)
|
||||
|
||||
return refined_latent
|
||||
|
||||
@@ -151,18 +151,19 @@ def ksampler_wrapper(model, seed, steps, cfg, sampler_name, scheduler, positive,
|
||||
class KSamplerAdvancedWrapper:
|
||||
params = None
|
||||
|
||||
def __init__(self, model, cfg, sampler_name, scheduler, positive, negative, sampler_opt=None):
|
||||
self.params = model, cfg, sampler_name, scheduler, positive, negative
|
||||
def __init__(self, model, cfg, sampler_name, scheduler, positive, negative, sampler_opt=None, sigma_factor=1.0):
|
||||
self.params = model, cfg, sampler_name, scheduler, positive, negative, sigma_factor
|
||||
self.sampler_opt = sampler_opt
|
||||
|
||||
def clone_with_conditionings(self, positive, negative):
|
||||
model, cfg, sampler_name, scheduler, _, _ = self.params
|
||||
model, cfg, sampler_name, scheduler, _, _, _ = self.params
|
||||
return KSamplerAdvancedWrapper(model, cfg, sampler_name, scheduler, positive, negative, self.sampler_opt)
|
||||
|
||||
def sample_advanced(self, add_noise, seed, steps, latent_image, start_at_step, end_at_step, return_with_leftover_noise, hook=None,
|
||||
recovery_mode="ratio additional", recovery_sampler="AUTO", recovery_sigma_ratio=1.0):
|
||||
|
||||
model, cfg, sampler_name, scheduler, positive, negative = self.params
|
||||
model, cfg, sampler_name, scheduler, positive, negative, sigma_factor = self.params
|
||||
# steps, start_at_step, end_at_step = self.compensate_denoise(steps, start_at_step, end_at_step)
|
||||
|
||||
if hook is not None:
|
||||
model, seed, steps, cfg, sampler_name, scheduler, positive, negative, upscaled_latent = hook.pre_ksample_advanced(model, add_noise, seed, steps, cfg, sampler_name, scheduler,
|
||||
@@ -183,7 +184,7 @@ class KSamplerAdvancedWrapper:
|
||||
if sigma_ratio > 0:
|
||||
latent_image = separated_sample(model, add_noise, seed, steps, cfg, sampler_name, scheduler,
|
||||
positive, negative, latent_image, start_at_step, end_at_step,
|
||||
return_with_leftover_noise, sigma_ratio=sigma_ratio, sampler_opt=self.sampler_opt)
|
||||
return_with_leftover_noise, sigma_ratio=sigma_ratio * sigma_factor, sampler_opt=self.sampler_opt)
|
||||
except ValueError as e:
|
||||
if str(e) == 'sigma_min and sigma_max must not be 0':
|
||||
print(f"\nWARN: sampling skipped - sigma_min and sigma_max are 0")
|
||||
@@ -207,7 +208,7 @@ class KSamplerAdvancedWrapper:
|
||||
try:
|
||||
latent_image = separated_sample(model, add_noise, seed, steps, cfg, recovery_sampler, scheduler,
|
||||
positive, negative, latent_image, start_at_step-compensate, end_at_step,
|
||||
return_with_leftover_noise, sigma_ratio=recovery_sigma_ratio, sampler_opt=self.sampler_opt)
|
||||
return_with_leftover_noise, sigma_ratio=recovery_sigma_ratio * sigma_factor, sampler_opt=self.sampler_opt)
|
||||
except ValueError as e:
|
||||
if str(e) == 'sigma_min and sigma_max must not be 0':
|
||||
print(f"\nWARN: sampling skipped - sigma_min and sigma_max are 0")
|
||||
|
||||
@@ -424,11 +424,11 @@ class ImpactQueueTriggerCountdown:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {
|
||||
"signal": (any_typ,),
|
||||
"count": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"total": ("INT", {"default": 10, "min": 1, "max": 0xffffffffffffffff}),
|
||||
"mode": ("BOOLEAN", {"default": True, "label_on": "Trigger", "label_off": "Don't trigger"}),
|
||||
},
|
||||
"optional": {"signal": (any_typ,),},
|
||||
"hidden": {"unique_id": "UNIQUE_ID"}
|
||||
}
|
||||
|
||||
@@ -439,14 +439,15 @@ class ImpactQueueTriggerCountdown:
|
||||
RETURN_NAMES = ("signal_opt", "count", "total")
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def doit(self, signal, count, total, mode, unique_id):
|
||||
if count < total - 1 and (mode):
|
||||
PromptServer.instance.send_sync("impact-node-feedback",
|
||||
{"node_id": unique_id, "widget_name": "count", "type": "int", "value": count+1})
|
||||
PromptServer.instance.send_sync("impact-add-queue", {})
|
||||
if count >= total - 1:
|
||||
PromptServer.instance.send_sync("impact-node-feedback",
|
||||
{"node_id": unique_id, "widget_name": "count", "type": "int", "value": 0})
|
||||
def doit(self, count, total, mode, unique_id, signal=None):
|
||||
if (mode):
|
||||
if count < total - 1:
|
||||
PromptServer.instance.send_sync("impact-node-feedback",
|
||||
{"node_id": unique_id, "widget_name": "count", "type": "int", "value": count+1})
|
||||
PromptServer.instance.send_sync("impact-add-queue", {})
|
||||
if count >= total - 1:
|
||||
PromptServer.instance.send_sync("impact-node-feedback",
|
||||
{"node_id": unique_id, "widget_name": "count", "type": "int", "value": 0})
|
||||
|
||||
return (signal, count, total)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import folder_paths
|
||||
from impact.core import *
|
||||
import os
|
||||
|
||||
import mmcv
|
||||
from mmdet.apis import (inference_detector, init_detector)
|
||||
|
||||
@@ -5,11 +5,12 @@ import impact.impact_server
|
||||
from nodes import MAX_RESOLUTION
|
||||
|
||||
from impact.utils import *
|
||||
import impact.core as core
|
||||
from impact.core import SEG
|
||||
from . import core
|
||||
from .core import SEG
|
||||
import impact.utils as utils
|
||||
from . import defs
|
||||
|
||||
from . import segs_upscaler
|
||||
import math
|
||||
|
||||
class SEGSDetailer:
|
||||
@classmethod
|
||||
@@ -415,6 +416,44 @@ class SEGSLabelFilter:
|
||||
return SEGSLabelFilter.filter(segs, labels)
|
||||
|
||||
|
||||
class SEGSLabelAssign:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"segs": ("SEGS", ),
|
||||
"labels": ("STRING", {"multiline": True, "placeholder": "List the label to be assigned in order of segs, separated by commas"}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SEGS",)
|
||||
RETURN_NAMES = ("SEGS",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
@staticmethod
|
||||
def assign(segs, labels):
|
||||
labels = [label.strip() for label in labels]
|
||||
|
||||
if len(labels) != len(segs[1]):
|
||||
print(f'Warning (SEGSLabelAssign): length of labels ({len(labels)}) != length of segs ({len(segs[1])})')
|
||||
|
||||
labeled_segs = []
|
||||
|
||||
idx = 0
|
||||
for x in segs[1]:
|
||||
if len(labels) > idx:
|
||||
x = x._replace(label=labels[idx])
|
||||
labeled_segs.append(x)
|
||||
idx += 1
|
||||
|
||||
return ((segs[0], labeled_segs), )
|
||||
|
||||
def doit(self, segs, labels):
|
||||
labels = labels.split(',')
|
||||
return SEGSLabelAssign.assign(segs, labels)
|
||||
|
||||
|
||||
class SEGSOrderedFilter:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -717,6 +756,44 @@ class From_SEG_ELT:
|
||||
return (seg_elt, cropped_image, to_tensor(seg_elt.cropped_mask), seg_elt.crop_region, seg_elt.bbox, seg_elt.control_net_wrapper, seg_elt.confidence, seg_elt.label,)
|
||||
|
||||
|
||||
class From_SEG_ELT_bbox:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"bbox": ("SEG_ELT_bbox", ),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT", "INT", "INT", "INT")
|
||||
RETURN_NAMES = ("left", "top", "right", "bottom")
|
||||
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
def doit(self, bbox):
|
||||
return bbox
|
||||
|
||||
|
||||
class From_SEG_ELT_crop_region:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"crop_region": ("SEG_ELT_crop_region", ),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT", "INT", "INT", "INT")
|
||||
RETURN_NAMES = ("left", "top", "right", "bottom")
|
||||
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
def doit(self, crop_region):
|
||||
return crop_region
|
||||
|
||||
|
||||
class Edit_SEG_ELT:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -1083,6 +1160,55 @@ class MaskToSEGS_for_AnimateDiff:
|
||||
return MaskToSEGS().doit(result_mask, False, crop_factor, False, drop_size, contour_fill)
|
||||
|
||||
|
||||
class IPAdapterApplySEGS:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"segs": ("SEGS",),
|
||||
"ipadapter_pipe": ("IPADAPTER_PIPE",),
|
||||
"weight": ("FLOAT", {"default": 0.7, "min": -1, "max": 3, "step": 0.05}),
|
||||
"noise": ("FLOAT", {"default": 0.4, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"weight_type": (["original", "linear", "channel penalty"], {"default": 'channel penalty'}),
|
||||
"start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
|
||||
"end_at": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 1.0, "step": 0.001}),
|
||||
"unfold_batch": ("BOOLEAN", {"default": False}),
|
||||
"faceid_v2": ("BOOLEAN", {"default": False}),
|
||||
"weight_v2": ("FLOAT", {"default": 1.0, "min": -1, "max": 3, "step": 0.05}),
|
||||
"context_crop_factor": ("FLOAT", {"default": 1.2, "min": 1.0, "max": 100, "step": 0.1}),
|
||||
"reference_image": ("IMAGE",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SEGS",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
def doit(self, segs, ipadapter_pipe, weight, noise, weight_type, start_at, end_at, unfold_batch, faceid_v2, weight_v2, context_crop_factor, reference_image):
|
||||
|
||||
if len(ipadapter_pipe) == 4:
|
||||
print(f"[Impact Pack] IPAdapterApplySEGS: Installed Inspire Pack is outdated.")
|
||||
raise Exception("Inspire Pack is outdated.")
|
||||
|
||||
new_segs = []
|
||||
|
||||
h, w = segs[0]
|
||||
|
||||
if reference_image.shape[2] != w or reference_image.shape[1] != h:
|
||||
reference_image = tensor_resize(reference_image, w, h)
|
||||
|
||||
for seg in segs[1]:
|
||||
# The context_crop_region sets how much wider the IPAdapter context will reflect compared to the crop_region, not the bbox
|
||||
context_crop_region = make_crop_region(w, h, seg.crop_region, context_crop_factor)
|
||||
cropped_image = crop_image(reference_image, context_crop_region)
|
||||
|
||||
control_net_wrapper = core.IPAdapterWrapper(ipadapter_pipe, weight, noise, weight_type, start_at, end_at, unfold_batch, faceid_v2, weight_v2, cropped_image, prev_control_net=seg.control_net_wrapper)
|
||||
new_seg = SEG(seg.cropped_image, seg.cropped_mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, control_net_wrapper)
|
||||
new_segs.append(new_seg)
|
||||
|
||||
return ((segs[0], new_segs), )
|
||||
|
||||
|
||||
class ControlNetApplySEGS:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -1354,7 +1480,7 @@ class MakeTileSEGS:
|
||||
|
||||
def doit(self, images, bbox_size, crop_factor, min_overlap, filter_segs_dilation, mask_irregularity=0, irregular_mask_mode="Reuse fast", filter_in_segs_opt=None, filter_out_segs_opt=None):
|
||||
if bbox_size <= 2*min_overlap:
|
||||
new_min_overlap = 2 / bbox_size
|
||||
new_min_overlap = bbox_size / 2
|
||||
print(f"[MakeTileSEGS] min_overlap should be greater than bbox_size. (value changed: {min_overlap} => {new_min_overlap})")
|
||||
min_overlap = new_min_overlap
|
||||
|
||||
@@ -1374,6 +1500,12 @@ class MakeTileSEGS:
|
||||
elif irregular_mask_mode == "All random fast":
|
||||
mask_quality = 512
|
||||
|
||||
# compensate overlap/bbox_size for irregular mask
|
||||
if mask_irregularity > 0:
|
||||
compensate = max(6, int(mask_quality * mask_irregularity / 4))
|
||||
min_overlap += compensate
|
||||
bbox_size += compensate*2
|
||||
|
||||
# create exclusion mask
|
||||
if filter_out_segs_opt is not None:
|
||||
exclusion_mask = core.segs_to_combined_mask(filter_out_segs_opt)
|
||||
@@ -1408,8 +1540,8 @@ class MakeTileSEGS:
|
||||
print(f"[MaskTileSEGS] bbox_size is greater than resolution (value changed: {bbox_size} => {new_bbox_size}")
|
||||
bbox_size = new_bbox_size
|
||||
|
||||
n_horizontal = int(w / (bbox_size - min_overlap))
|
||||
n_vertical = int(h / (bbox_size - min_overlap))
|
||||
n_horizontal = math.ceil(w / (bbox_size - min_overlap))
|
||||
n_vertical = math.ceil(h / (bbox_size - min_overlap))
|
||||
|
||||
w_overlap_sum = (bbox_size * n_horizontal) - w
|
||||
if w_overlap_sum < 0:
|
||||
@@ -1503,3 +1635,137 @@ class MakeTileSEGS:
|
||||
|
||||
res = (ih, iw), new_segs # segs
|
||||
return (res,)
|
||||
|
||||
|
||||
class SEGSUpscaler:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
resampling_methods = ["lanczos", "nearest", "bilinear", "bicubic"]
|
||||
|
||||
return {"required": {
|
||||
"image": ("IMAGE",),
|
||||
"segs": ("SEGS",),
|
||||
"model": ("MODEL",),
|
||||
"clip": ("CLIP",),
|
||||
"vae": ("VAE",),
|
||||
"rescale_factor": ("FLOAT", {"default": 2, "min": 0.01, "max": 100.0, "step": 0.01}),
|
||||
"resampling_method": (resampling_methods,),
|
||||
"supersample": (["true", "false"],),
|
||||
"rounding_modulus": ("INT", {"default": 8, "min": 8, "max": 1024, "step": 8}),
|
||||
"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": (comfy.samplers.KSampler.SCHEDULERS,),
|
||||
"positive": ("CONDITIONING",),
|
||||
"negative": ("CONDITIONING",),
|
||||
"denoise": ("FLOAT", {"default": 0.5, "min": 0.0001, "max": 1.0, "step": 0.01}),
|
||||
"feather": ("INT", {"default": 5, "min": 0, "max": 100, "step": 1}),
|
||||
"inpaint_model": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"noise_mask": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"noise_mask_feather": ("INT", {"default": 0, "min": 0, "max": 100, "step": 1}),
|
||||
},
|
||||
"optional": {
|
||||
"upscale_model_opt": ("UPSCALE_MODEL",),
|
||||
"upscaler_hook_opt": ("UPSCALER_HOOK",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Upscale"
|
||||
|
||||
@staticmethod
|
||||
def doit(image, segs, model, clip, vae, rescale_factor, resampling_method, supersample, rounding_modulus,
|
||||
seed, steps, cfg, sampler_name, scheduler, positive, negative, denoise, feather, inpaint_model, noise_mask, noise_mask_feather,
|
||||
upscale_model_opt=None, upscaler_hook_opt=None):
|
||||
|
||||
new_image = segs_upscaler.upscaler(image, upscale_model_opt, rescale_factor, resampling_method, supersample, rounding_modulus)
|
||||
|
||||
segs = core.segs_scale_match(segs, new_image.shape)
|
||||
|
||||
ordered_segs = segs[1]
|
||||
|
||||
for i, seg in enumerate(ordered_segs):
|
||||
cropped_image = crop_ndarray4(new_image.numpy(), seg.crop_region)
|
||||
cropped_image = to_tensor(cropped_image)
|
||||
mask = to_tensor(seg.cropped_mask)
|
||||
mask = tensor_gaussian_blur_mask(mask, feather)
|
||||
|
||||
is_mask_all_zeros = (seg.cropped_mask == 0).all().item()
|
||||
if is_mask_all_zeros:
|
||||
print(f"SEGSUpscaler: segment skip [empty mask]")
|
||||
continue
|
||||
|
||||
if noise_mask:
|
||||
cropped_mask = seg.cropped_mask
|
||||
else:
|
||||
cropped_mask = None
|
||||
|
||||
seg_seed = seed + i
|
||||
|
||||
enhanced_image = segs_upscaler.img2img_segs(cropped_image, model, clip, vae, seg_seed, steps, cfg, sampler_name, scheduler,
|
||||
positive, negative, denoise,
|
||||
noise_mask=cropped_mask, control_net_wrapper=seg.control_net_wrapper,
|
||||
inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather)
|
||||
if not (enhanced_image is None):
|
||||
new_image = new_image.cpu()
|
||||
enhanced_image = enhanced_image.cpu()
|
||||
left = seg.crop_region[0]
|
||||
top = seg.crop_region[1]
|
||||
tensor_paste(new_image, enhanced_image, (left, top), mask)
|
||||
|
||||
if upscaler_hook_opt is not None:
|
||||
upscaler_hook_opt.post_paste(new_image)
|
||||
|
||||
enhanced_img = tensor_convert_rgb(new_image)
|
||||
|
||||
return (enhanced_img,)
|
||||
|
||||
|
||||
class SEGSUpscalerPipe:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
resampling_methods = ["lanczos", "nearest", "bilinear", "bicubic"]
|
||||
|
||||
return {"required": {
|
||||
"image": ("IMAGE",),
|
||||
"segs": ("SEGS",),
|
||||
"basic_pipe": ("BASIC_PIPE",),
|
||||
"rescale_factor": ("FLOAT", {"default": 2, "min": 0.01, "max": 100.0, "step": 0.01}),
|
||||
"resampling_method": (resampling_methods,),
|
||||
"supersample": (["true", "false"],),
|
||||
"rounding_modulus": ("INT", {"default": 8, "min": 8, "max": 1024, "step": 8}),
|
||||
"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": (comfy.samplers.KSampler.SCHEDULERS,),
|
||||
"denoise": ("FLOAT", {"default": 0.5, "min": 0.0001, "max": 1.0, "step": 0.01}),
|
||||
"feather": ("INT", {"default": 5, "min": 0, "max": 100, "step": 1}),
|
||||
"inpaint_model": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"noise_mask": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"noise_mask_feather": ("INT", {"default": 0, "min": 0, "max": 100, "step": 1}),
|
||||
},
|
||||
"optional": {
|
||||
"upscale_model_opt": ("UPSCALE_MODEL",),
|
||||
"upscaler_hook_opt": ("UPSCALER_HOOK",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Upscale"
|
||||
|
||||
@staticmethod
|
||||
def doit(image, segs, basic_pipe, rescale_factor, resampling_method, supersample, rounding_modulus,
|
||||
seed, steps, cfg, sampler_name, scheduler, denoise, feather, inpaint_model, noise_mask, noise_mask_feather,
|
||||
upscale_model_opt=None, upscaler_hook_opt=None):
|
||||
|
||||
model, clip, vae, positive, negative = basic_pipe
|
||||
|
||||
return SEGSUpscaler.doit(image, segs, model, clip, vae, rescale_factor, resampling_method, supersample, rounding_modulus,
|
||||
seed, steps, cfg, sampler_name, scheduler, positive, negative, denoise, feather, inpaint_model, noise_mask, noise_mask_feather,
|
||||
upscale_model_opt=upscale_model_opt, upscaler_hook_opt=upscaler_hook_opt)
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
from impact.utils import *
|
||||
from impact import impact_sampling
|
||||
from comfy_extras.chainner_models import model_loading
|
||||
from comfy import model_management
|
||||
import nodes
|
||||
|
||||
|
||||
# Implementation based on `https://github.com/lingondricka2/Upscaler-Detailer`
|
||||
|
||||
# code from comfyroll --->
|
||||
# https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/blob/main/nodes/functions_upscale.py
|
||||
|
||||
def upscale_with_model(upscale_model, image):
|
||||
device = model_management.get_torch_device()
|
||||
upscale_model.to(device)
|
||||
in_img = image.movedim(-1,-3).to(device)
|
||||
free_memory = model_management.get_free_memory(device)
|
||||
|
||||
tile = 512
|
||||
overlap = 32
|
||||
|
||||
oom = True
|
||||
while oom:
|
||||
try:
|
||||
steps = in_img.shape[0] * comfy.utils.get_tiled_scale_steps(in_img.shape[3], in_img.shape[2], tile_x=tile, tile_y=tile, overlap=overlap)
|
||||
pbar = comfy.utils.ProgressBar(steps)
|
||||
s = comfy.utils.tiled_scale(in_img, lambda a: upscale_model(a), tile_x=tile, tile_y=tile, overlap=overlap, upscale_amount=upscale_model.scale, pbar=pbar)
|
||||
oom = False
|
||||
except model_management.OOM_EXCEPTION as e:
|
||||
tile //= 2
|
||||
if tile < 128:
|
||||
raise e
|
||||
|
||||
upscale_model.cpu()
|
||||
s = torch.clamp(s.movedim(-3, -1), min=0, max=1.0)
|
||||
return s
|
||||
|
||||
|
||||
def apply_resize_image(image: Image.Image, original_width, original_height, rounding_modulus, mode='scale', supersample='true', factor: int = 2, width: int = 1024, height: int = 1024,
|
||||
resample='bicubic'):
|
||||
# Calculate the new width and height based on the given mode and parameters
|
||||
if mode == 'rescale':
|
||||
new_width, new_height = int(original_width * factor), int(original_height * factor)
|
||||
else:
|
||||
m = rounding_modulus
|
||||
original_ratio = original_height / original_width
|
||||
height = int(width * original_ratio)
|
||||
|
||||
new_width = width if width % m == 0 else width + (m - width % m)
|
||||
new_height = height if height % m == 0 else height + (m - height % m)
|
||||
|
||||
# Define a dictionary of resampling filters
|
||||
resample_filters = {'nearest': 0, 'bilinear': 2, 'bicubic': 3, 'lanczos': 1}
|
||||
|
||||
# Apply supersample
|
||||
if supersample == 'true':
|
||||
image = image.resize((new_width * 8, new_height * 8), resample=Image.Resampling(resample_filters[resample]))
|
||||
|
||||
# Resize the image using the given resampling filter
|
||||
resized_image = image.resize((new_width, new_height), resample=Image.Resampling(resample_filters[resample]))
|
||||
|
||||
return resized_image
|
||||
|
||||
|
||||
def upscaler(image, upscale_model, rescale_factor, resampling_method, supersample, rounding_modulus):
|
||||
if upscale_model is not None:
|
||||
up_image = upscale_with_model(upscale_model, image)
|
||||
else:
|
||||
up_image = image
|
||||
|
||||
pil_img = tensor2pil(image)
|
||||
original_width, original_height = pil_img.size
|
||||
scaled_image = pil2tensor(apply_resize_image(tensor2pil(up_image), original_width, original_height, rounding_modulus, 'rescale',
|
||||
supersample, rescale_factor, 1024, resampling_method))
|
||||
return scaled_image
|
||||
|
||||
# <---
|
||||
|
||||
|
||||
def img2img_segs(image, model, clip, vae, seed, steps, cfg, sampler_name, scheduler,
|
||||
positive, negative, denoise, noise_mask, control_net_wrapper=None,
|
||||
inpaint_model=False, noise_mask_feather=0):
|
||||
if noise_mask is not None:
|
||||
noise_mask = tensor_gaussian_blur_mask(noise_mask, noise_mask_feather)
|
||||
noise_mask = noise_mask.squeeze(3)
|
||||
|
||||
if control_net_wrapper is not None:
|
||||
positive, negative, _ = control_net_wrapper.apply(positive, negative, image, noise_mask)
|
||||
|
||||
# prepare mask
|
||||
if noise_mask is not None and inpaint_model:
|
||||
positive, negative, latent_image = nodes.InpaintModelConditioning().encode(positive, negative, image, vae, noise_mask)
|
||||
else:
|
||||
latent_image = to_latent_image(image, vae)
|
||||
if noise_mask is not None:
|
||||
latent_image['noise_mask'] = noise_mask
|
||||
|
||||
refined_latent = latent_image
|
||||
|
||||
# ksampler
|
||||
refined_latent = impact_sampling.ksampler_wrapper(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, refined_latent, denoise)
|
||||
|
||||
# non-latent downscale - latent downscale cause bad quality
|
||||
refined_image = vae.decode(refined_latent['samples'])
|
||||
|
||||
# prevent mixing of device
|
||||
refined_image = refined_image.cpu()
|
||||
|
||||
# don't convert to latent - latent break image
|
||||
# preserving pil is much better
|
||||
return refined_image
|
||||
@@ -67,6 +67,7 @@ class KSamplerAdvancedProvider:
|
||||
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
|
||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
|
||||
"scheduler": (comfy.samplers.KSampler.SCHEDULERS, ),
|
||||
"sigma_factor": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||
"basic_pipe": ("BASIC_PIPE", )
|
||||
},
|
||||
"optional": {
|
||||
@@ -79,9 +80,9 @@ class KSamplerAdvancedProvider:
|
||||
|
||||
CATEGORY = "ImpactPack/Sampler"
|
||||
|
||||
def doit(self, cfg, sampler_name, scheduler, basic_pipe, sampler_opt=None):
|
||||
def doit(self, cfg, sampler_name, scheduler, basic_pipe, sigma_factor=1.0, sampler_opt=None):
|
||||
model, _, _, positive, negative = basic_pipe
|
||||
sampler = KSamplerAdvancedWrapper(model, cfg, sampler_name, scheduler, positive, negative, sampler_opt=sampler_opt)
|
||||
sampler = KSamplerAdvancedWrapper(model, cfg, sampler_name, scheduler, positive, negative, sampler_opt=sampler_opt, sigma_factor=sigma_factor)
|
||||
return (sampler, )
|
||||
|
||||
|
||||
@@ -251,7 +252,7 @@ class ConcatConditionings:
|
||||
RETURN_TYPES = ("CONDITIONING", )
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/__for_testing"
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
def doit(self, **kwargs):
|
||||
conditioning_to = list(kwargs.values())[0]
|
||||
|
||||
+10
-3
@@ -495,9 +495,16 @@ def to_latent_image(pixels, vae):
|
||||
y = pixels.shape[2]
|
||||
if pixels.shape[1] != x or pixels.shape[2] != y:
|
||||
pixels = pixels[:, :x, :y, :]
|
||||
pixels = nodes.VAEEncode.vae_encode_crop_pixels(pixels)
|
||||
t = vae.encode(pixels[:, :, :, :3])
|
||||
return {"samples": t}
|
||||
|
||||
vae_encode = nodes.VAEEncode()
|
||||
if hasattr(nodes.VAEEncode, "vae_encode_crop_pixels"):
|
||||
# backward compatibility
|
||||
print(f"[Impact Pack] ComfyUI is outdated.")
|
||||
pixels = nodes.VAEEncode.vae_encode_crop_pixels(pixels)
|
||||
t = vae.encode(pixels[:, :, :, :3])
|
||||
return {"samples": t}
|
||||
|
||||
return vae_encode.encode(vae, pixels)[0]
|
||||
|
||||
|
||||
def empty_pil_tensor(w=64, h=64):
|
||||
|
||||
Reference in New Issue
Block a user