From 96f4e9dbe3f145f76ae8fed23a8516f0d5c4bdbd Mon Sep 17 00:00:00 2001 From: Alan Burns Date: Wed, 15 Oct 2025 18:12:25 -0500 Subject: [PATCH] Added ComparisonGate and added screenshots to the main README. --- README.md | 61 ++- __init__.py | 6 + __pycache__/__init__.cpython-312.pyc | Bin 0 -> 3321 bytes __pycache__/auto_adjust.cpython-312.pyc | Bin 0 -> 14501 bytes __pycache__/auto_color_match.cpython-312.pyc | Bin 0 -> 10737 bytes __pycache__/comparison_gate.cpython-312.pyc | Bin 0 -> 3536 bytes __pycache__/crop_utils.cpython-312.pyc | Bin 0 -> 16566 bytes .../filename_append_suffix.cpython-312.pyc | Bin 0 -> 3065 bytes .../flux_resolution_prepare.cpython-312.pyc | Bin 0 -> 12716 bytes __pycache__/gpu_cache_clear.cpython-312.pyc | Bin 0 -> 4363 bytes .../image_megapixel_selector.cpython-312.pyc | Bin 0 -> 9249 bytes .../load_image_combined.cpython-312.pyc | Bin 0 -> 16292 bytes __pycache__/multiprompt.cpython-312.pyc | Bin 0 -> 1428 bytes __pycache__/outpaint_config.cpython-312.pyc | Bin 0 -> 6261 bytes .../paired_image_loader.cpython-312.pyc | Bin 0 -> 21354 bytes __pycache__/smart_crop.cpython-312.pyc | Bin 0 -> 19342 bytes .../stitch_image_by_mask.cpython-312.pyc | Bin 0 -> 9900 bytes __pycache__/workflow_config.cpython-312.pyc | Bin 0 -> 12682 bytes comparison_gate.py | 104 +++++ docs/AutoAdjustNode.md | 1 - docs/AutoColorConfigNode.md | 1 - docs/AutoCropBorders.md | 1 - docs/AutoWBColorMatch.md | 1 - docs/ComparisonGate.md | 27 ++ docs/CropImageByMargins.md | 1 - docs/CropMaskByMargins.md | 1 - docs/FilenameAppendSuffix.md | 3 +- docs/FitAspectHeadSafe.md | 1 - docs/FluxResolutionPrepare.md | 1 - docs/GpuSyncCacheClear.md | 1 - docs/ImageMegapixelSelector.md | 1 - docs/LoadImageCombined.md | 3 +- docs/MQBBoxMin.md | 1 - docs/MultiPromptNode.md | 1 - docs/OutpaintConfigNode.md | 1 - docs/OutpaintPaddingComputeNode.md | 1 - ...redImageLoader.md => PairedImageLoader.md} | 5 +- docs/SeedVR2Prep.md | 1 - docs/StitchByMask.md | 1 - docs/WorkflowConfig.md | 1 - docs/smart_crop_before_refactor.py | 433 ++++++++++++++++++ paired_image_loader.py | 26 +- 42 files changed, 628 insertions(+), 57 deletions(-) create mode 100644 __pycache__/__init__.cpython-312.pyc create mode 100644 __pycache__/auto_adjust.cpython-312.pyc create mode 100644 __pycache__/auto_color_match.cpython-312.pyc create mode 100644 __pycache__/comparison_gate.cpython-312.pyc create mode 100644 __pycache__/crop_utils.cpython-312.pyc create mode 100644 __pycache__/filename_append_suffix.cpython-312.pyc create mode 100644 __pycache__/flux_resolution_prepare.cpython-312.pyc create mode 100644 __pycache__/gpu_cache_clear.cpython-312.pyc create mode 100644 __pycache__/image_megapixel_selector.cpython-312.pyc create mode 100644 __pycache__/load_image_combined.cpython-312.pyc create mode 100644 __pycache__/multiprompt.cpython-312.pyc create mode 100644 __pycache__/outpaint_config.cpython-312.pyc create mode 100644 __pycache__/paired_image_loader.cpython-312.pyc create mode 100644 __pycache__/smart_crop.cpython-312.pyc create mode 100644 __pycache__/stitch_image_by_mask.cpython-312.pyc create mode 100644 __pycache__/workflow_config.cpython-312.pyc create mode 100644 comparison_gate.py create mode 100644 docs/ComparisonGate.md rename docs/{PortraitUtils_PairedImageLoader.md => PairedImageLoader.md} (80%) create mode 100644 docs/smart_crop_before_refactor.py diff --git a/README.md b/README.md index 9606c58..3d755df 100644 --- a/README.md +++ b/README.md @@ -7,32 +7,53 @@ PortraitUtils is a focused collection of ComfyUI nodes designed for pipelines us Each entry links to a short, task-oriented reference page (coming soon) for deeper usage notes and parameter breakdowns. ### Image Adjustments & Preparation -- `AutoAdjustNode` – One-click levels, tone, and color balancing. [Docs](docs/AutoAdjustNode.md) -- `AutoWBColorMatch` – White-balances and colour matches against a reference image using several algorithms. [Docs](docs/AutoWBColorMatch.md) -- `AutoColorConfigNode` – Lightweight toggle bundle for sharing Auto Adjust presets. [Docs](docs/AutoColorConfigNode.md) -- `FluxResolutionPrepare` – Crops and resizes to the nearest Flux-friendly resolution, with optional pre-upscale. [Docs](docs/FluxResolutionPrepare.md) -- `ImageMegapixelSelector` – Chooses image sizes by target megapixels to match downstream model limits. [Docs](docs/ImageMegapixelSelector.md) +- `AutoAdjustNode` - One-click levels, tone, and color balancing. [Docs](docs/AutoAdjustNode.md) +
AutoAdjustNode screenshot
+- `AutoWBColorMatch` - White-balances and colour matches against a reference image using several algorithms. [Docs](docs/AutoWBColorMatch.md) +
AutoWBColorMatch screenshot
+- `AutoColorConfigNode` - Lightweight toggle bundle for sharing Auto Adjust presets. [Docs](docs/AutoColorConfigNode.md) +
AutoColorConfigNode screenshot
+- `FluxResolutionPrepare` - Crops and resizes to the nearest Flux-friendly resolution, with optional pre-upscale. [Docs](docs/FluxResolutionPrepare.md) +
FluxResolutionPrepare screenshot
+- `ImageMegapixelSelector` - Chooses image sizes by target megapixels to match downstream model limits. [Docs](docs/ImageMegapixelSelector.md) +
ImageMegapixelSelector screenshot
### Cropping, Framing, and Stitching -- `AutoCropBorders` – Removes uniform borders using adaptive fuzzy detection. GPU acceleration is currently broken, so leave that off for now. [Docs](docs/AutoCropBorders.md) -- `CropImageByMargins` – Manual margin-based cropper for RGB images. [Docs](docs/CropImageByMargins.md) -- `CropMaskByMargins` – Margin cropper designed for MASK tensors. [Docs](docs/CropMaskByMargins.md) -- `FitAspectHeadSafe` – Rescales and pads while preserving face-safe regions for portrait framing. [Docs](docs/FitAspectHeadSafe.md) -- `MQBBoxMin` – Utility for merging bounding boxes while enforcing minimum sizes. [Docs](docs/MQBBoxMin.md) -- `StitchByMask` – Composites foreground and background images using mask-driven blending. [Docs](docs/StitchByMask.md) +- `AutoCropBorders` - Removes uniform borders using adaptive fuzzy detection. GPU acceleration is currently broken, so leave that off for now. [Docs](docs/AutoCropBorders.md) +
AutoCropBorders screenshot
+- `CropImageByMargins` - Manual margin-based cropper for RGB images. [Docs](docs/CropImageByMargins.md) +
CropImageByMargins screenshot
+- `CropMaskByMargins` - Margin cropper designed for MASK tensors. [Docs](docs/CropMaskByMargins.md) +
CropMaskByMargins screenshot
+- `FitAspectHeadSafe` - Rescales and pads while preserving face-safe regions for portrait framing. [Docs](docs/FitAspectHeadSafe.md) +
FitAspectHeadSafe screenshot
+- `MQBBoxMin` - Utility for merging bounding boxes while enforcing minimum sizes. [Docs](docs/MQBBoxMin.md) +
MQBBoxMin screenshot
+- `StitchByMask` - Composites foreground and background images using mask-driven blending. [Docs](docs/StitchByMask.md) +
StitchByMask screenshot
### Prompting & Workflow Configuration -- `MultiPromptNode` – Cycles through prompt variants with per-slot weights. [Docs](docs/MultiPromptNode.md) -- `WorkflowConfig` – Centralized key/value store for workflow-wide settings. [Docs](docs/WorkflowConfig.md) -- `OutpaintConfigNode` – Packs outpainting preferences (steps, padding, sampler choices). [Docs](docs/OutpaintConfigNode.md) -- `OutpaintPaddingComputeNode` – Calculates padding offsets to feed outpaint chains as either percentage or absolute pixels, with gravity. [Docs](docs/OutpaintPaddingComputeNode.md) +- `MultiPromptNode` - Cycles through prompt variants with per-slot weights. [Docs](docs/MultiPromptNode.md) +
MultiPromptNode screenshot
+- `WorkflowConfig` - Centralized key/value store for workflow-wide settings. [Docs](docs/WorkflowConfig.md) +
WorkflowConfig screenshot
+- `OutpaintConfigNode` - Packs outpainting preferences (steps, padding, sampler choices). [Docs](docs/OutpaintConfigNode.md) +
OutpaintConfigNode screenshot
+- `OutpaintPaddingComputeNode` - Calculates padding offsets to feed outpaint chains as either percentage or absolute pixels, with gravity. [Docs](docs/OutpaintPaddingComputeNode.md) +
OutpaintPaddingComputeNode screenshot
### I/O & Utility Helpers -- `LoadImageCombined` – Hybrid single/batch image loader with auto-advance support. [Docs](docs/LoadImageCombined.md) -- `PortraitUtils_PairedImageLoader` – Loads synchronized source/output pairs for A/B comparison workflows. [Docs](docs/PortraitUtils_PairedImageLoader.md) -- `FilenameAppendSuffix` – Adds suffixes to filenames while respecting multi-extension cases. [Docs](docs/FilenameAppendSuffix.md) -- `GpuSyncCacheClear` – Forces a CUDA sync and empties selected caches to avoid VRAM buildup. [Docs](docs/GpuSyncCacheClear.md) -- `SeedVR2Prep` – Convenience helper for SeedVR2/VRAM-heavy pipelines (cache priming & cleanup). [Docs](docs/SeedVR2Prep.md) +- `LoadImageCombined` - Hybrid single/batch image loader with auto-advance support. [Docs](docs/LoadImageCombined.md) +
LoadImageCombined screenshot
+- `PairedImageLoader` - Loads synchronized source/output pairs for A/B comparison workflows. [Docs](docs/PairedImageLoader.md) +
PairedImageLoader screenshot
+- `ComparisonGate` - Gates comparison outputs until two populated images are available. [Docs](docs/ComparisonGate.md) +- `FilenameAppendSuffix` - Adds suffixes to filenames while respecting multi-extension cases. [Docs](docs/FilenameAppendSuffix.md) +
FilenameAppendSuffix screenshot
+- `GpuSyncCacheClear` - Forces a CUDA sync and empties selected caches to avoid VRAM buildup. [Docs](docs/GpuSyncCacheClear.md) +
GpuSyncCacheClear screenshot
+- `SeedVR2Prep` - Convenience helper for SeedVR2/VRAM-heavy pipelines (cache priming & cleanup). [Docs](docs/SeedVR2Prep.md) +
SeedVR2Prep screenshot
## License diff --git a/__init__.py b/__init__.py index 04f61fb..fe7dcd4 100644 --- a/__init__.py +++ b/__init__.py @@ -58,6 +58,10 @@ from .paired_image_loader import ( NODE_DISPLAY_NAME_MAPPINGS as PAIRED_LOADER_DISPLAY_MAP, ) from .load_image_combined import LoadImageCombined +from .comparison_gate import ( + NODE_CLASS_MAPPINGS as COMPARISON_GATE_CLASS_MAP, + NODE_DISPLAY_NAME_MAPPINGS as COMPARISON_GATE_DISPLAY_MAP, +) NODE_CLASS_MAPPINGS = {} @@ -77,6 +81,7 @@ NODE_CLASS_MAPPINGS.update(FLUX_PREP_CLASS_MAP) NODE_CLASS_MAPPINGS.update(WORKFLOW_CONFIG_CLASS_MAP) NODE_CLASS_MAPPINGS.update(SMART_CROP_CLASS_MAP) NODE_CLASS_MAPPINGS.update(PAIRED_LOADER_CLASS_MAP) +NODE_CLASS_MAPPINGS.update(COMPARISON_GATE_CLASS_MAP) NODE_DISPLAY_NAME_MAPPINGS.update(AUTO_ADJUST_DISPLAY_MAP) NODE_DISPLAY_NAME_MAPPINGS.update(OUTPAINT_DISPLAY_MAP) @@ -91,6 +96,7 @@ NODE_DISPLAY_NAME_MAPPINGS.update(FLUX_PREP_DISPLAY_MAP) NODE_DISPLAY_NAME_MAPPINGS.update(WORKFLOW_CONFIG_DISPLAY_MAP) NODE_DISPLAY_NAME_MAPPINGS.update(SMART_CROP_DISPLAY_MAP) NODE_DISPLAY_NAME_MAPPINGS.update(PAIRED_LOADER_DISPLAY_MAP) +NODE_DISPLAY_NAME_MAPPINGS.update(COMPARISON_GATE_DISPLAY_MAP) # Register the load-image node which does not expose pre-built mappings. NODE_CLASS_MAPPINGS["LoadImageCombined"] = LoadImageCombined diff --git a/__pycache__/__init__.cpython-312.pyc b/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..815e91f3e056cfc47880a1f44bf64694f0ce5df4 GIT binary patch literal 3321 zcmbW2TW=Fb6vtDme9Gt3z-NG2pU(m>1E2HxE8z3M z7ks`5yaJr*b6ELWy|j7Bk6nh?6^LaSB%{p`ydxy^1`ks*`X=FDm;o{93yR4=MJ!M; z6R4OARKx=n3xSGMpkgUdaVAi)9H=-Ss8|V9T%i<^&(<~?sF(^=Ob05C1uBjQDiVQ; zWS}A)s5ljhm6`4RqmQq|IAt|y7msE<9i2rLsnrw$QuXU#~N8Y`Uid?ORH@HfLFRjn1JjYwt`%-+p=<7_{`t_(vN)B9I2;ST|oGS}Tn zj3r&f*qs(SwvGkq4P>-0ap$>4y^Oj1ca2(ow4we?H%Z5a`-1JdvwFui1X;7OsB0~` z?Vcz%>J^SJQ5J6EV2qwRt#%Y!HcVYLY zwv7%J1#ydDQ6Ylqrq(MBoa1s&y^a<$&j?1p` z#X`USa5M^~Jgzm?*7z5c?eZ5CwEJAY)}pTHCgcgy=BE4g%$1AfDrCw_*}(~Pm;Mu@ zy-p4?uENzYsu84bmpFZ*_CKrRZ;mr!n=sTz$4yEI-G&a%qiUrz{r7 zL$p!MQUw#vG#m^!QQP3Ea45ZlF?yZ_YLuQChuZ+R+&`EK+(=@&Nw^W!LLL{(T)seO zC3QG($jF=P+lcf%cV_`1HL?X+@;aWI%E7has(h_n!s}d}YA5dEc!*wNqGL$jd}s>L z{4o9*eS|iF{>iUDXf`ZjT1`$7v}T@I+qP~rp{2CC4ZbP%n=tm8u+^+Uk23|??zhJ# z#rJmA5u#;nE`8szGb7^-yn5!9@gxVVc} g_HOV{x!z)S7hQ!&G`$yL_oLzI(D#$(Z8$ss0{&(wlK=n! literal 0 HcmV?d00001 diff --git a/__pycache__/auto_adjust.cpython-312.pyc b/__pycache__/auto_adjust.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..776a4fd59ec7d0adb8960f155da00a534422701a GIT binary patch literal 14501 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 Mdo7cuRuRScU!EtG{r~^~ literal 0 HcmV?d00001 diff --git a/__pycache__/comparison_gate.cpython-312.pyc b/__pycache__/comparison_gate.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ea0fcd03995ac742a43f139248259a077a02c62 GIT binary patch literal 3536 zcmai1OKcR$8LsZ>dG+{(?Zw8+qZSNeSTji+va2OtvJRdYXJPPSTN@@uO?#?lrsJOO zp{j@3L1W2@d~mcx;SdE58_9<~fKVci9COcQfQ>ZU;Sec@giAEY8|87zU)4RvMtPL& z>Z{odw^D2@&EcxaIpz5J^_=U_>(_-Lw^k}+TB&*sSgo*1J)XgfWs76SC9tx? ztF!>7=}pQS!8#KJONy2a@Pwme+17;~BY3QkxyZ(86Lu?{R>bEmE zl$k3w^|gx@_x$K$tqPsUw9_a+cy>w*S`mbl?R(I;i%MusX`&f)?YjuAsZDiVxgGO% z<~LO@j66bKh$e)JY8MY|e^cek?IT{iYo90^sHx7NhvC4(&=yl}Pd1ewCA;%pJ)3HG zj)HE_XsD?yptNd^3rt}sSEN%!05GRFYSaOAfhTX8b`5ahl}!)ZuDgnDYgI1cVSGx)-4oF4!$*Qz(AduRW8kiVE|ZRUV(Q2 zX~#1yhcW=+#{gu@hB%05L982IIY2b*5#U*{smUaw0_aME;#?&GMMvPZO9YOqwppNM z*Ct>YRs#wVx9)Hc@K%vP2Pla1?FlXf7JX9$cN`iYA_W56MAs4!TBWHFR71a1?keDB zT|*7@>quQmE4c^YZC}lN-PbISR=9{a#{c?o_U!T!hqafdZPQtvp?uYItIMG`mkTxS zxfMeYjxW!<%wwkIEqRvBmtS$*5a-HN8-8-CL>>A`m7TkF%qY0FprR3mBQs^Y1=Hr| zGCPwF!4~)%Q2hn{V;!{yCm&?DX6~H1cjgyU53>K{0*6288&J|wU$;2W-7^6bzGh3< zdjaerG=2=2P92ht_^|*jemq>%j|Kz6U*JR53r~OG^{ufH8192Vz5vxa>I|T zNHE6z-aR|c6{0YyTSQy>{t+}lX_cnhj5M{Pwiex$LZc7E6u1`iLV6?AjCDtevI>&R zl!m(No4`(_J7+E4jI(GnP9g_jB?ly7EzwlglFj7$&@P+t?tG%Hf_}&^H1P$@O#E1d z*|178(M%Rq8TS`L>(gp3$3)|W*e3W#h@Ky@sgu@y4X#Z^=@^D#n7t*cqhdmj2X_zK z2yStHa);0UKmoF+MSz@!bS`Jd#r)n7Mq&OGU=a)oiqv0r0>*B=y~p3LiB)EAaHin{ z{xhhgG#@y=Rk~k)9B++$u-Wr`czkR5+3@6M@_DLnv+?Pl0(@;dfI zxQ$Iw4rlgz zu4EUNuH*t{?eucfm$D0f;(JTE^NVwrf!gP%7qhdMuUwVxl_ZrL3|u}2hM1_1!5_~- zwSiu$iD;rTjAHvwEGXQUjR?+A8QW`2fv-v z;piz~tbX9DUT6hNi3M&Qd6jpt{8wSG7Nm1&jSY*5z1@qfaMm^U4KbD!S6bOm_pool zh;Ui42DfE!856AXV_u^Q63sN|Fu_bFZn{k9qlC0PrazRsJd-ufUz}c8FfL8c&(Gy% z7yL0fI5W2}e{uS%Aw0PA?lp(`iK!q^=h%0^uwV?&Le+^VitWJDxLmgfAv(P`RK0#yBS^oy~x&2E3 literal 0 HcmV?d00001 diff --git a/__pycache__/crop_utils.cpython-312.pyc b/__pycache__/crop_utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..79464fb343e5cbc9ac8294c920e13f59d0c0173a GIT binary patch literal 16566 zcmc(GYj7K9lGqI1?>ArI3*Z|PMTw;3hipp%DT&-`C&!`?~w1V<(!Zq`GE#=(`IM11Q6_lI@L1rn!n0jnCdvkN znT3AYpFd53{vt%eY2Lv&x$d}1Sq*j9^1$gRwc0R(Aw{mrcNtT?{<}>n~0d zSI=SSO$AF*Dhg`-j%-wh_5U4fRSRQmfR;;&^iECTkGz#zt*2`1iKFyr`XAsXVQ@D* z_`eYTtJHKZvVO4b0WPse&+8_F6MGiktzT5EU#kdS=t}+$av+%w114| z$xsk`<8uA)Bfa}Co(s}U@S=ChAGmm!4qgh)%wFuCnI2s@ckJTGd@wXK?F-CMbnxQo z879Q|Cqn12R~JW^nOUFE!M522zD(*YbZ(xZeZ%9IM|{lKFjEHY;I0nhV3&xj#<=9Y zr!R^6uUBU(c5xNEKG(P1GgM>@F3#YJThoU62$|JruPT-m@4l8X)^WzVw5EQ^n^kN6 zc>c}#e{?zO{x+L67_as%_r=I}`Xl6h$hrLH3W$>awiaa`p1^JIvkyYr)+sKW3J`QwQ+fTFm3jvG@b(N7K7^qXn<#}65(G-|U5Km_Aw&?uB&Kc& z6v-B)7@x|dvjcPn=Tr!j8Y#B%X5nm6@=q1Fan>rC7iu6`&@j{2`jDgG4;-!2^iXZEL zpigO>Y0ZnUkqjjnLltMJik*$OC;Xoq_9WXm!`_Vk1y27$TK`f?@e+fqXVY5Xn1r=> z9U@4zDH7X)w?JXwEl{$sJS+>7l!7G}Vf82_E5r99U{WepF2yJF^d2&Dfl^Df5@uD? z?~%2M69sRaUif~1r*G`%-;J6VpZVp%v47FpUG?Ap(|Y+$V_NiRj`IGs5 zztIc@Tm`s4%(pis;!A5xX zC7PZEP9bbZ;cSaXy#X>kI>w4Pa{q9Umj~yk;gr+N^gNu#T1*F40tcK|GXB7rctFKt zucYVZ{Zk$@FNaOaYlAdR`Gg~`8=he(nn5N2JZqfs0T0tm`>*&Wph}+X;>n{td7hVb z&+-atA{Y{prrLQ8JvA{lF+4@{@{xJK^zq6uX6AArh-1J#Ul7FYZ{oX$?@sF~m%8s6ZPDlcsV_p_v(&_bX-j>4oU^n= zl=t-I8GQ|>uZhjaM?Tl@$QrEo^`@oc>vF>ESeCDA2~+jDjxd;)j*CYzx9l)9k-Z88 ztsLNEkD?5Snj)+mBf_c>mJkA)7wrxcQS!Ah*m!CLY;&6t7Pg${#*fE8&QOXwX!Ur`w#z7=W~ptddoXDg;8M4;#Y9 zuqlu#IIdc;&&5RITe&%rYTZ;yAqcDaqb<$fhPW-23R}YFf|9yFs68F^^zGTg))1a_ zA;9=S;77775t6WeQ?7YSt|62ccwy#jlnfan-~i0mU@yQ?u$H1Yj)GQXtQ`nmVIPc} zaviK;Q?7|s6%*fCJ!@c%tZ6h)VoBJ~@50@SRRJSb_KRc>S@2C#Ac!KH)eD4=*#qf0 zl6HBVW`O$m0}H%-iVgs23xs$D(5-1+4ipT)M1WWMXJ_dEr`3wh-iHSAsk_#o$GRCol8lI4=j$n^z6bgh1xz6;t$R z2 zrS{F^IchkNHJFxqkUF(reFX$?0|@9bYm{0(1*FR8%otsq5v23)m)s~xw8u;Sc~#oj zmYCy=?Gf3Nvh}72^UkwT|JyHK?~F0udp7R>@WpI}Gv0r{9?D|3Jk1Kyr`NQh;u7lCiNcX*`Kx>Q~zGtyVz0pu? zWMwgJsf&2U?_g|jWg%^GOW*Ucxs`=@=gPOzmS#w^;jjf!C_chfc+$2V(4J7yT&#U% z{(YlVE*KkFx%|F4;)VOnRl~9YXA4IZz4&WQ-FhuyDqVLG2J6xZky^vV=YvGKA0C0E zRaC2x6fq>Dv{ZSfFx`66sLG97wg zT>{q)ZjyLW$0IO^w}mEAqN3!KVoU~dlVSqMJW5YcotDy3`s)he+W_Cj{5NP@C<|qU zN6ZJk3j8*{?NF0LsL4c?Kzb=WBIZWUMy%|*Zi0j(S>DfU7?2+(7@E3C^2%c;y}dnx z+%ZDKiPQKg|7>XD4VqU|^r(M+3Xa2(6Q{gq@hSBE+W^5who4vxTfH2_OI3J{a zQ}fe)-%Ma?fj6T-gyKqQT$GD!3>}(h0=}H^BuG+fEHgGce-r2y$V5}YAo9VS=&wK4 zfBM{6-`NYNdj=OPx9ozpg3O~(CWt(1iC9+=# zyCpetyZ#Q9y6EF}yq%Th*A*CikV9pHEfp|FT41VOp0PExL8WsMQIl2QHYeL${fUM**)m=^g9? z1Y8*AJc4roc;)2@Dg-K%aT=*7uP>ZUIWr$vGz)r=-Js401V?9>>5;qy0mTjQ2*)J^ zF}SSfqz>eBd9JNzbxdc0;w>*BLD^g=CM!uY15>EcdvnTCBV%jf8C* zT%1)(<-+!YSwM+3) z#fwmKD+X)V7vLAiDXi;p7*bs#4r?teC&jUEqoU-7YO*Z>WX2&wgK8<2 zYLQA8&WRwiKDj4lo9o!zpQj#AdBI9z!&?Gd!j`gSZ21^m?mx|&6T%g2#bo6cRQMEd zawlf8>e2kLldWK#lX#Qe5!?+*`8YQ#!NZO1AP3X`9lbbR4cK7;pWft+38e zqm;_#Z#tB25hzn$vaSv_=W+74HcFpU+*cRWke7kkDoIrpu4bzzQ8lzBPzF$)L=Tvt%1Dqp8Grh2&VHiS_66#`o?VSoyzlOQevUbs=9c}?Nwa7)<3HcUeL z)&Td5Mz)bs;%a_J#x}#xlZ!X8O>B!87tfV=3Z!;O;@y1;BD7DckutqSunmXvJ4?a~ z?9%nb(T5QZA;3Z<1#JkLv z!0rMO5q|7$c-lr)(%MMlfpHJQxO3|OO0;6sO=G=gnf5mt4Z zJfR05yxTstOI%}{mbT8-W9H*=ZG}6-9c(9MSp+d0Obl?jWtQPvFg7r4>>i27h&&i` za6fMFVB8MjJ#70^;k8TU!<~Vbx74lJ;PT?h3-5ixuG_=zh3k)HW9L26k4Nj=_k=pz z*?n8;Y^N;YPPSvq40Sv@Lx8hsZVIVLVb30m*DmFTI|^o~U>uujedYDeFaUqt+^#tI8dxZ=)!P5*6()~RJe56UDVg+fbwg z0Av5;fBdWX*Z)3wAa}{VaEKYkJiI9jSKCdR0lelQt9!T~Kp#O0>f_fyEEjwYAZ=@) z1iRdK$yE}Fdx?TrApfQOG6@C-RIBxaUd;;cn=)S3wudK2HW~S}FOJax`pPVGV6kcI zJr(X`Z7;$Y{ZqjMZF#)wP}f7;4*!k#pG!o_-gZ+NZ;yIcdslj6-hX!drt*J1#CzAb zD=Nu@U1JK>Hawh_-n}Z~`p% z3REn3T!P(PP)9P~24vna5%l@r@Pm023?stIGiXy1RhK9#F};YUT%?(qAcG1YUJdmM zUI6kDKUi0$X==g`YRfnLQ}dvSM9UJ>ig=*Un40oUOWqu8%e;JMKEx~wrBDY1>G-f&>RP8peYj5AmW=2;v8&ffPcT3pf6^YpoT?zk>mIc z$Ozs5s~H)eBtJ3!hiL^p%_dC9&wjQTn-| z<-XOi>RfTIs|l@LFk4;Szq~)@ysNKAGhEY3Q_Puch&H8d&qliMmz1wwSh=vQi;$7_ zh(BumO=HKq;n?8kX7`#rqR1Mpg2_#`{Edk03$WRl(uP`4N}H=P<~q(?mo=KAWVAi% zkDQ3xzwf-^{LxF9&ZAuCQ81lZ>K_s^i!P!7g;%vJqIi4YJu=z>YT=Ie<|40hnu=^b z71CBZKO`d;!OI|9RV(7Y*Dkt=Y{rFDa2;705peIDY!cRo?hgl|FL0*XjH#J3H7CeK zM}kS4IwJC{wd6g2%pJ2`vw!IS*czSUto0e|PR_bB;Y~2fj;JA zCUqbBqkWv&l`*$)=9Yvt(Up{euL;bC#qr+2ha{+)JFazpOhzwqmb#2(2WQ!lXip3$ z>(Z8eU`({tW^DDGtv)`OXi7G$DN_vxZhKR<`n2sxM19{>ay76Vh+89pw5dL0+QFH2 zKo@NlvF?vfUOSm^r>j~yTWiMJ&RN^j)=ub|$#Hdhc{)x;rqianjLE~9Jc+}cX-`D{ z1ujDq2iqg~X-~2>-FonLBiGuSHXn^B?t$ii;hh7qp}YF#ti9}dV_XM1{lp08Z0GFl zk;5=6xJhE&aeLa@7*XLycxB}kbh3$i<4n3_M?{x(I9K~t`r>4~J?>A~(vH@M_6x9@ z9!@kQb!*3d5xjFS-TP|VaxtRJmXyc#t-P9XG;np=_a_5MJxrPV2cD$X4?Fj={wJ)l@SFFZ2PBshiS3h%a>2~7P#C&q@=0f7tpSrj2cKpnZK^)@Kbvw~%MRpw{ZXG7x*cWTvx^>+HMJ zm+pLJBTFz6^n0|ToJ#}WV#X0nB0$~45oQ|US&xp__ByTv=bN1Yse$mL1|LXUi>>1=WN6z(kpFMU8Oj+IDvpv11 z&Rh_fDsu&!FGo;{;GzUjP79(G@fML)TE3K-l)8sv8qS1(k?9lvD75qCm0Xy9j0R-moP| zSE26_f09>nej9uuMI$~+{Gufv7kIJ0=gKTS5~3;Bt6i-}ThF(4zvh}@T(64JR@c}} z$mLOr=6;lUdELt>0QWEq_|hsv^B@ZfLNs{W{TXBzn$|Snc`LZxF~5MEzroz&0G0^! zI4$mry2(rRo~b-ldEn>sw+H^}{GXry*&n42^rtFLrA?<(n$uamVd<#2Jy5*euw4mK zsvxw2w-lH>C!m8PxO7s$2@{r)fW7@#Zy&+!4!ms;TL=`nu?#H9YQQr?x1n8@w0GcK zTD*jO+Y<6^O31*AsCXN)ZAvJ%Edh?0VI3<2|CD_1SY6JyL<0_)1tS56yA2$D4oA%@ zHsKgq_&Wo-#WE+rZ$yWAta1_N6d-KYunBxg1h14$spgHEDH&_Z&zWgcx;dY2E|_r* zt7A>5CC=v>3Ubw;Up8;bHG(UuV2^?vBWRp;tYNe63=V|6z^lb07q5fDwG4Nv$0FRF zifu0DFCl-+MT+?d(!ecc54fkmbEO^L?YuHTUxw?7@EQ-|5!!ms5Is+HKEWd#9lx3X z09bzuPp}b41A3b{Rx4I2HoQ%~Qh_U5WbT9dC>8SpOYTYA>c9hU>9D{GKIn|jgB0k# zMyKr!X+tA8rs#|j{|ENyAPCX^YmM*Mrgd&~rn5w?A9Tl5ARc;eDASInw6S^V2(T8N zX{jr#H7s@A(;6aYqVDCFqXV&$l?!QY?dG)7m7%oOwbTV=G#2zMzi)FynU&_)IA?82 zv~ZRV5G)@}5`99@A4FLg#r^6X}9Q=|x*T-T;AZmFU-;(>4Ln-G1x0sVB{+%b`x(58GCl+o2ykYY0DznFEq)%N z7Y7nXW)$*sjsd(H$0B%Pz8iN7Zm(cQxHO3HoVOQRZ{B(x|RX>(2z!t{XB5Y~3lEUftM zvw7zRHFQP_dG>*mx{Y&#dV`p;YF5U|Sp_)zscz-i5cqOyw)3x+NZTm-_W*IiB7$-f zh|@1Hj-)}*E`h)%XvNTq&!iE2j^I}aevKf50C_S`E(Epk&eI)HqRK+jVp);3z)8S= zvjtJJ3I852IHKp?K}R-2X>=~u9DgS9T=K$gW7+_J9?4p)tLhbXOdcBqx3HACKBcJ_ z1xnCm;#}R-F+YYEvxeX&0C=^}N6n1*e9TWV2?a#vX9)fcf`5wud$v{n`;VB0K!rWq z1(COi-zbjWM-fn$7Xcx*UQRe_Q1~m!34dQH@5(1?va5OJEj%{u= zk6!`t|BCjf=r0*?|J{32;E28*-8V}k-gu+u=q9?a>lZB?0;9%@-^PDU7E8~e4M@DP z3G_s;QMkn0`UbbqY~0vHp%#q`%wHh*cL*L~BQ8wc_B@}$Tpqx-jHL9@bB%mtFCJ(T z0NnZv7T_0p!_|Gu`$UIkWFGnzea!p5DPsX)u>~}gmeyr2=%lZnTR!*B`55{3Md|N| zbIXgdfw#Yvb(E|gTRA3bIYDI2IQDRkJ%avhh0I!Qt7V`cv#nId$-7oJA`9xz_xo4K zFG^jh`saV%c$@qy^`EPM)|PtySjv4oUD}tj^lfS}=KpBy+E{$}+NH$6-I}(n!SaQz zDpkAhNA#Na&w78-`@=wLUr(y~NZQt$GW34&dvQhPw>Xpc5C}A~9Akolz=cyL zF}U9146ZuQ;A)qSex@s@VizuuE9c(qYC+H21&_%8QF_gZ zx2_gJ`0*?X>$xcdNuvj@`P0~j1MdF9-)il`t1S>{8=Vh~gy8Q({wZD-2=Ka%Z_&mD z7eqod%hh4bgTRYm2!YULJ;nqv2emxnCe06nxA&hq+~ey$;T;_Go%EhQeXPHCkar4+ zhmQ@OKH_En)a;qB%u0-?x-38GolT zlkVR&$jAe~)5*v~7(25q`VNpIwqNB~m{Y^|uZsOAAZ1>soQ6CTVF(ved|wAW}ihs#wt6rL`(W zGH;i*r7jB{43t6+v5*4|Bn6aHUDyW!$w5JT(4~NZUZ`3?*sTsW+8){y1sN#NQ|B#z z%sPVe$elOuXWq`6nQvzQ+|uGe(5_bgz4A{Mp}&(sy>T_>{G zk;SlK$;P!6gHtuEWW{(YW!SK+<#0Mt6E=^q0%hPi8Kj6xI$DMP_4c?nkw;oXcGdFw zE`(O?-<{LZlNt)3q+@m?`i>t0W_o|mDiPo9N!bXwxGIZx6>TMx(?t26CZ|;; zlNJKz{@Enoa0RcZd=%&8Q69@{nL9Es-;wb;ui?0oQqs5ihAgkjx7Xyf#*fH>+W|hH z#c|;FM(xCnvm-y5SPuL=I2MbKZCo0A=NG^F!RUBixG-oSOdKV!hShwhL1$D<%wpI< z-E@3SHdvyp$~bQfYMC`Losnd9c_D+z<+LSDNvX?8C1t)^jAyfQS`yV< zGO64PWY>*0v1+4+0aV-u?;}yF{u;<8syL9V_0x&1iBI0$oIGmv6z$vgqI=uDV|ms( zv^jg^Zu|7^*4=O20|)K_m~#(p&ivKhdgSdWzP_kQ%uJ6T{WHstDfuu^jN zmn{7+8Pwicyt;k$Td)7X>))Aq<{f(!Kk!~Euw`d^eb)(SfD(k%TT6|pAzDU&VicnR z!PI?JhfT~P(J`yU09q}8RBMdoQ2i&Jk!&&tDCQ&<9!_>h4tSihBk7V{fO_Y8i{Lie zr)wq)nih+g21_(Um4L?tau^uMEdT`x@`z;p0(Gm9z}OB`4!BGN>`berE9X&)a9|E$#e zLAl4jJHEeoFf>#0&6fMm@5)b^gORyX-;J_xdH3eNrF3PgG%{TtzO?(!o>aOVDxIG! z5033}dsj*qe_HYfk6Y~BXDSTW>Zl-&b6^j=NT6_6wd;k>u-?ikDQSZ>(Q$Eh9+O$z zM@T;*1BCE|oCA_*&awoJrrY=7`bDHQLPDorW!rkWz_d=K9U9qhHl38qVk+>!&h|sD#7w=tqzpDU1#68&g$Ho*6&d&O+o1AIjvzM z-L6}n(Di#PLt4x`@=l#q8Qqz8r3ZB9>S=X{tKSdXbm!+tXFo^3rPFqk=W1gn@6xUK zE!}~6%`E!lTBp^e)zd1BLwD7+f^%F+0yHL!eQ*YZ2k|SUZ-=5PX;q7-6C_7#7{ez8 zGE<_#LgrLSvB5K}LB7GAnJ0Z zJ+>$9eYhXmAAjn3YAOBrMj`aV+4rSzF2 zuwaEEn#(HKbWJoG6K!Q6*p86^5dyzab9($2J!=@Gw+C z&^?+oyS=G>|Gv6E^3D7Snf*`yMXK`{0jVS@WbG;SUaBy37hOVLFa;Tp!?>S5KtA*G F{{xIe+2{ZO literal 0 HcmV?d00001 diff --git a/__pycache__/flux_resolution_prepare.cpython-312.pyc b/__pycache__/flux_resolution_prepare.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..214c00c96c3779daf0f6ede300df8e31ad6bd51e GIT binary patch literal 12716 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 JSyCRp{|B>F0CWHV literal 0 HcmV?d00001 diff --git a/__pycache__/multiprompt.cpython-312.pyc b/__pycache__/multiprompt.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a468663617ac673c385e318caf08ac06dd5f03ba GIT binary patch literal 1428 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 literal 0 HcmV?d00001 diff --git a/__pycache__/smart_crop.cpython-312.pyc b/__pycache__/smart_crop.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f75202974a8dd41c8478232e0577e6e6fff4a0af GIT binary patch literal 19342 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 CjFY$k literal 0 HcmV?d00001 diff --git a/__pycache__/stitch_image_by_mask.cpython-312.pyc b/__pycache__/stitch_image_by_mask.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..03a50b61f4fa4acfc96359c23c4d392c1fb0f47c GIT binary patch literal 9900 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 List[Any]: + """ + Return the sequence of values that look like real images. + + ComfyUI represents disconnected sockets as ``None`` and occasionally wraps + outputs in small containers. We filter out anything that is clearly empty so + the gate never forwards placeholders into downstream nodes. + """ + present: List[Any] = [] + for value in values: + if value is None: + continue + if isinstance(value, (list, tuple)): + if not value: + continue + if all(item is None for item in value): + continue + present.append(value) + return present + + +class ComparisonGate: + """ + Emit two images only when at least two inputs are populated. + + Designed for comparison viewers: the first populated comparison becomes the + "final image", while the designated source socket feeds the secondary output + (with a fallback to the next comparison if the source is missing). If fewer + than two images are available the node returns ``None`` for both sockets, so + downstream widgets remain blank. + """ + + @classmethod + def INPUT_TYPES(cls): + # Mark every socket as optional so that workflows can omit connections + # without triggering validation errors. ComfyUI fills unspecified inputs + # with ``None`` which this node explicitly understands. + return { + "optional": { + "source_image": ("IMAGE", {"default": None}), + "image_a": ("IMAGE", {"default": None}), + "image_b": ("IMAGE", {"default": None}), + "image_c": ("IMAGE", {"default": None}), + } + } + + RETURN_TYPES = ("IMAGE", "IMAGE") + RETURN_NAMES = ("final_image", "source_image") + FUNCTION = "forward_images" + CATEGORY = "PortraitUtils" + + def forward_images( + self, + source_image=None, + image_a=None, + image_b=None, + image_c=None, + ) -> Tuple[Any, Any]: + # Gather the inputs in priority order to ensure predictable selection. + total_inputs = (source_image, image_a, image_b, image_c) + populated = _collect_present(total_inputs) + if len(populated) < 2: + return None, None + + # The first comparison with data becomes the final output. The source + # socket remains reserved for the secondary output to keep viewer labels + # aligned with the user's expectations. + candidate_finals = _collect_present((image_a, image_b, image_c)) + final_image = candidate_finals[0] if candidate_finals else None + source_output = source_image + + # If the source socket is empty but multiple comparisons are available, + # fall back to the next comparison so we still deliver two images. + if source_output is None and len(candidate_finals) > 1: + source_output = candidate_finals[1] + + # Avoid emitting half-populated pairs; downstream nodes expect either a + # full tuple of tensors or pure ``None`` values. + if final_image is None or source_output is None: + return None, None + + return final_image, source_output + + +NODE_CLASS_MAPPINGS = { + "ComparisonGate": ComparisonGate, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "ComparisonGate": "Comparison Gate", +} diff --git a/docs/AutoAdjustNode.md b/docs/AutoAdjustNode.md index a6cea6e..dd04723 100644 --- a/docs/AutoAdjustNode.md +++ b/docs/AutoAdjustNode.md @@ -1,5 +1,4 @@ # AutoAdjustNode -

Screenshot
diff --git a/docs/AutoColorConfigNode.md b/docs/AutoColorConfigNode.md index e088a45..e4e371c 100644 --- a/docs/AutoColorConfigNode.md +++ b/docs/AutoColorConfigNode.md @@ -1,5 +1,4 @@ # AutoColorConfigNode -
Screenshot
## Overview diff --git a/docs/AutoCropBorders.md b/docs/AutoCropBorders.md index 0b98b83..7cf417e 100644 --- a/docs/AutoCropBorders.md +++ b/docs/AutoCropBorders.md @@ -1,5 +1,4 @@ # AutoCropBorders -
Screenshot
## Overview diff --git a/docs/AutoWBColorMatch.md b/docs/AutoWBColorMatch.md index 4eb8fa2..60158ef 100644 --- a/docs/AutoWBColorMatch.md +++ b/docs/AutoWBColorMatch.md @@ -1,5 +1,4 @@ # AutoWBColorMatch -
Screenshot
## Overview diff --git a/docs/ComparisonGate.md b/docs/ComparisonGate.md new file mode 100644 index 0000000..c14c840 --- /dev/null +++ b/docs/ComparisonGate.md @@ -0,0 +1,27 @@ +# ComparisonGate + +## Overview +`ComparisonGate` keeps comparison viewers tidy by only emitting images when a complete pair is available. It listens to a designated `source_image` input together with up to three comparison sockets and mirrors ComfyUI's unplugged behaviour (`None`, `None`) until two populated tensors are ready to forward. + +## Inputs +- `source_image` (`IMAGE`, optional): Preferred reference image for the secondary output. Leave disconnected when you want the first populated comparison to feed both sockets. +- `image_a` (`IMAGE`, optional): Primary comparison feed. Becomes the `final_image` output whenever populated. +- `image_b` (`IMAGE`, optional): Secondary comparison feed used either as a fallback `final_image` or to backfill the `source_image` output when the dedicated source socket is empty. +- `image_c` (`IMAGE`, optional): Tertiary comparison feed that participates in the fallback cascade. + +All inputs accept `None`; the node filters out empty lists/tuples as well as bare `None` values so placeholder widgets will not trigger accidental forwards. + +## Outputs +- `final_image` (`IMAGE`): The first populated comparison input (`image_a` › `image_b` › `image_c`). Returns `None` until at least one comparison produces data. +- `source_image` (`IMAGE`): Mirrors the dedicated `source_image` socket, or falls back to the next populated comparison input so the viewer always receives a pair. Returns `None` alongside `final_image` when fewer than two valid images are present. + +## Forwarding Rules +- The node inspects every input on each execution and counts how many look like real image tensors. If fewer than two are found it emits `(None, None)` to keep downstream nodes blank. +- When only comparison sockets are populated, the first comparison feeds `final_image` and the second feeds `source_image`, ensuring A/B layouts remain synchronised. +- When `source_image` is connected, it always feeds the `source_image` output unless it is empty—in that case the node automatically falls back to the second non-empty comparison input. +- Containers such as one-item tuples or lists are unwrapped only when they contain real data; sequences of `None` values are ignored. + +## Tips +- Place the gate directly before two-input comparison viewers to prevent half-populated renders during workflow warm-up. +- Combine with `PairedImageLoader` for workflow QA: let the loader drive comparisons and feed its outputs through the gate so the viewer only refreshes when both sockets advance. +- Because the node simply relays its inputs, it adds no meaningful processing cost; you can safely sprinkle it anywhere you need to guarantee paired images.*** End Patch diff --git a/docs/CropImageByMargins.md b/docs/CropImageByMargins.md index bcabd08..df6e86c 100644 --- a/docs/CropImageByMargins.md +++ b/docs/CropImageByMargins.md @@ -1,5 +1,4 @@ # CropImageByMargins -
Screenshot
## Overview diff --git a/docs/CropMaskByMargins.md b/docs/CropMaskByMargins.md index 7244322..098fd87 100644 --- a/docs/CropMaskByMargins.md +++ b/docs/CropMaskByMargins.md @@ -1,5 +1,4 @@ # CropMaskByMargins -
Screenshot
## Overview diff --git a/docs/FilenameAppendSuffix.md b/docs/FilenameAppendSuffix.md index 56e67d7..4c19786 100644 --- a/docs/FilenameAppendSuffix.md +++ b/docs/FilenameAppendSuffix.md @@ -1,5 +1,4 @@ # FilenameAppendSuffix -
Screenshot
## Overview @@ -22,5 +21,5 @@ - Directory handling uses `os.path.join`, so platform-specific separators are respected. ## Tips -- Chain this node after `LoadImageCombined` or `PortraitUtils_PairedImageLoader` to build save paths that mirror the input file names. +- Chain this node after `LoadImageCombined` or `PairedImageLoader` to build save paths that mirror the input file names. - Disable `preserve_directory` when you want to relocate files into a new output folder without carrying over their original path. diff --git a/docs/FitAspectHeadSafe.md b/docs/FitAspectHeadSafe.md index f5d10e6..4e6c85b 100644 --- a/docs/FitAspectHeadSafe.md +++ b/docs/FitAspectHeadSafe.md @@ -1,5 +1,4 @@ # FitAspectHeadSafe -
Screenshot
## Overview diff --git a/docs/FluxResolutionPrepare.md b/docs/FluxResolutionPrepare.md index bd34dbf..344b15a 100644 --- a/docs/FluxResolutionPrepare.md +++ b/docs/FluxResolutionPrepare.md @@ -1,5 +1,4 @@ # FluxResolutionPrepare -
Screenshot
## Overview diff --git a/docs/GpuSyncCacheClear.md b/docs/GpuSyncCacheClear.md index 44df6f9..c272bdf 100644 --- a/docs/GpuSyncCacheClear.md +++ b/docs/GpuSyncCacheClear.md @@ -1,5 +1,4 @@ # GpuSyncCacheClear -
Screenshot
## Overview diff --git a/docs/ImageMegapixelSelector.md b/docs/ImageMegapixelSelector.md index f554cd0..d72031a 100644 --- a/docs/ImageMegapixelSelector.md +++ b/docs/ImageMegapixelSelector.md @@ -1,5 +1,4 @@ # ImageMegapixelSelector -
Screenshot
## Overview diff --git a/docs/LoadImageCombined.md b/docs/LoadImageCombined.md index 9d64c38..9aae271 100644 --- a/docs/LoadImageCombined.md +++ b/docs/LoadImageCombined.md @@ -1,5 +1,4 @@ # LoadImageCombined -
Screenshot
## Overview @@ -26,6 +25,6 @@ - All images are converted to RGB using Pillow, with Exif orientation applied. ## Tips -- Chain this node with `PortraitUtils_PairedImageLoader` or comparison nodes to quickly inspect before/after renders. +- Chain this node with `PairedImageLoader` or comparison nodes to quickly inspect before/after renders. - Use `repeat_last` when you need to re-run the same batch frame after adjusting downstream parameters. - Combine with `FilenameAppendSuffix` to build descriptive output paths based on `filename_no_ext`.*** End Patch diff --git a/docs/MQBBoxMin.md b/docs/MQBBoxMin.md index 2e8912a..4779fa8 100644 --- a/docs/MQBBoxMin.md +++ b/docs/MQBBoxMin.md @@ -1,5 +1,4 @@ # MQBBoxMin -
Screenshot
## Overview diff --git a/docs/MultiPromptNode.md b/docs/MultiPromptNode.md index 0273ff0..44dc535 100644 --- a/docs/MultiPromptNode.md +++ b/docs/MultiPromptNode.md @@ -1,5 +1,4 @@ # MultiPromptNode -
Screenshot
## Overview diff --git a/docs/OutpaintConfigNode.md b/docs/OutpaintConfigNode.md index a6168ab..9439c30 100644 --- a/docs/OutpaintConfigNode.md +++ b/docs/OutpaintConfigNode.md @@ -1,5 +1,4 @@ # OutpaintConfigNode -
Screenshot
## Overview diff --git a/docs/OutpaintPaddingComputeNode.md b/docs/OutpaintPaddingComputeNode.md index 37d82bd..1cc6ddc 100644 --- a/docs/OutpaintPaddingComputeNode.md +++ b/docs/OutpaintPaddingComputeNode.md @@ -1,5 +1,4 @@ # OutpaintPaddingComputeNode -
Screenshot
## Overview diff --git a/docs/PortraitUtils_PairedImageLoader.md b/docs/PairedImageLoader.md similarity index 80% rename from docs/PortraitUtils_PairedImageLoader.md rename to docs/PairedImageLoader.md index 530b2c0..1a6221e 100644 --- a/docs/PortraitUtils_PairedImageLoader.md +++ b/docs/PairedImageLoader.md @@ -1,9 +1,8 @@ -# PortraitUtils_PairedImageLoader -
Screenshot
+# PairedImageLoader ## Overview -`PortraitUtils_PairedImageLoader` pairs images from two directories (typically “source” and “output”) and emits the next matching duo on every run. It keeps its position per node instance, supports forward/backward stepping, and reports the active filename for UI display or logging. +`PairedImageLoader` pairs images from two directories (typically "source" and "output") and emits the next matching duo on every run. It keeps its position per node instance, supports forward/backward stepping, and reports the active filename for UI display or logging. ## Inputs - `source_dir` (`STRING`): Directory containing the reference/source images. Relative paths resolve against the current working directory. diff --git a/docs/SeedVR2Prep.md b/docs/SeedVR2Prep.md index da7ab1f..605efa3 100644 --- a/docs/SeedVR2Prep.md +++ b/docs/SeedVR2Prep.md @@ -1,5 +1,4 @@ # SeedVR2Prep -
Screenshot
## Overview diff --git a/docs/StitchByMask.md b/docs/StitchByMask.md index 6175089..96182d0 100644 --- a/docs/StitchByMask.md +++ b/docs/StitchByMask.md @@ -1,5 +1,4 @@ # StitchByMask -
Screenshot
## Overview diff --git a/docs/WorkflowConfig.md b/docs/WorkflowConfig.md index 801367d..fb366f5 100644 --- a/docs/WorkflowConfig.md +++ b/docs/WorkflowConfig.md @@ -1,5 +1,4 @@ # WorkflowConfig -
Screenshot
## Overview diff --git a/docs/smart_crop_before_refactor.py b/docs/smart_crop_before_refactor.py new file mode 100644 index 0000000..419d553 --- /dev/null +++ b/docs/smart_crop_before_refactor.py @@ -0,0 +1,433 @@ +import math +import numpy as np +import torch + + +def _to_numpy_mask(m): + if torch is not None and isinstance(m, torch.Tensor): + arr = m.detach().cpu().numpy() + elif isinstance(m, dict) and "mask" in m: + v = m["mask"] + arr = v.detach().cpu().numpy() if torch is not None and isinstance(v, torch.Tensor) else np.asarray(v) + else: + arr = np.asarray(m) + arr = np.squeeze(arr) + if arr.ndim == 3: + arr = arr.max(axis=0) if arr.shape[0] in (1, 3, 4) else arr.max(axis=-1) + if arr.ndim != 2: + h, w = arr.shape[-2], arr.shape[-1] + arr = arr.reshape(h, w) + return arr.astype(np.float32, copy=False) + + +def _normalize01(m): + mmax = float(m.max()) if m.size else 0.0 + if mmax <= 0: + return m + if mmax > 1.5: + return (m / 255.0).astype(np.float32, copy=False) + return m + + +def _border_mass_ratio(m, border_px=8): + H, W = m.shape + border_px = int(max(1, min(border_px, min(H, W) // 4))) + total = float(m.sum()) + 1e-12 + top = m[:border_px, :].sum() + bottom = m[-border_px:, :].sum() + left = m[:, :border_px].sum() + right = m[:, -border_px:].sum() + corners = ( + m[:border_px, :border_px].sum() + + m[:border_px, -border_px:].sum() + + m[-border_px:, :border_px].sum() + + m[-border_px:, -border_px:].sum() + ) + border = top + bottom + left + right - corners + return float(border) / total + + +def _auto_pick_foreground(m, mode="auto"): + m = _normalize01(m) + if mode == "false": + fg, pick = m, "fg" + elif mode == "true": + fg, pick = 1.0 - m, "bg" + else: + r_fg = _border_mass_ratio(m) + r_bg = _border_mass_ratio(1.0 - m) + if r_bg < r_fg: + fg, pick = 1.0 - m, "auto->bg" + else: + fg, pick = m, "auto->fg" + return fg, pick + + +def _quantile_bounds(mass, qL, qR, qT, qB, min_span): + H, W = mass.shape + eps = 1e-8 + c = mass.sum(axis=0) + eps + r = mass.sum(axis=1) + eps + cc = np.cumsum(c) + rc = np.cumsum(r) + ct = float(cc[-1]) + rt = float(rc[-1]) + + def qidx(cum, total, q): + return int(np.clip(np.searchsorted(cum, total * float(q), "left"), 0, len(cum) - 1)) + + x0 = qidx(cc, ct, qL) + x1 = qidx(cc, ct, qR) + y0 = qidx(rc, rt, qT) + y1 = qidx(rc, rt, qB) + if x1 < x0: + x0, x1 = x1, x0 + if y1 < y0: + y0, y1 = y1, y0 + if (x1 - x0 + 1) < min_span: + cx = (x0 + x1) // 2 + half = max(1, min_span // 2) + x0 = max(0, cx - half) + x1 = min(W - 1, cx + half) + if (y1 - y0 + 1) < min_span: + cy = (y0 + y1) // 2 + half = max(1, min_span // 2) + y0 = max(0, cy - half) + y1 = min(H - 1, cy + half) + return int(x0), int(y0), int(x1 - x0 + 1), int(y1 - y0 + 1) + + +def _pad_rect(x, y, w, h, pad, W, H): + x2 = max(0, x - pad) + y2 = max(0, y - pad) + w2 = min(W - x2, w + 2 * pad) + h2 = min(H - y2, h + 2 * pad) + return int(x2), int(y2), int(w2), int(h2) + + +def _container_for_ar(x, y, w, h, R, W, H): + """ + Smallest rectangle of aspect R (=w/h) that contains (x,y,w,h), centered, + then clamped to image. If it can't fit (too large), return None. + """ + wA = max(w, int(np.ceil(h * R))) + hA = int(np.ceil(wA / R)) + hB = max(h, int(np.ceil(w / R))) + wB = int(np.ceil(hB * R)) + cand = [] + if wA <= W and hA <= H: + cand.append((wA, hA)) + if wB <= W and hB <= H: + cand.append((wB, hB)) + if not cand: + return None + cw, ch = min(cand, key=lambda t: t[0] * t[1]) + + cx = x + w / 2.0 + cy = y + h / 2.0 + X = int(np.round(cx - cw / 2.0)) + Y = int(np.round(cy - ch / 2.0)) + X = max(0, min(W - cw, X)) + Y = max(0, min(H - ch, Y)) + return X, Y, int(cw), int(ch) + + +class MQBBoxMin: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "mask": ("MASK",), + "invert_mask": (["auto", "false", "true"], {"default": "auto"}), + "q_left": ("FLOAT", {"default": 0.005, "min": 0.0, "max": 0.2, "step": 0.001}), + "q_right": ("FLOAT", {"default": 0.995, "min": 0.8, "max": 1.0, "step": 0.001}), + "q_top": ("FLOAT", {"default": 0.005, "min": 0.0, "max": 0.2, "step": 0.001}), + "q_bottom": ("FLOAT", {"default": 0.995, "min": 0.8, "max": 1.0, "step": 0.001}), + "min_span_px": ("INT", {"default": 8, "min": 1, "max": 2048}), + "tight_crop": ( + "BOOLEAN", + { + "default": False, + "tooltip": "ON: tight subject bbox with smart AR; OFF: full frame (minimal AR crop downstream).", + }, + ), + } + } + + RETURN_TYPES = ("INT", "INT", "INT", "INT", "STRING") + RETURN_NAMES = ("x", "y", "w", "h", "debug") + FUNCTION = "run" + CATEGORY = "utils/debug" + + def run(self, mask, invert_mask, q_left, q_right, q_top, q_bottom, min_span_px, tight_crop=True): + m = _to_numpy_mask(mask) + H, W = m.shape + + if not tight_crop: + dbg = f"tight=False full=({0},{0},{W},{H})" + return (0, 0, int(W), int(H), dbg) + + # --- original tight bbox --- + fg, picked = _auto_pick_foreground(m, invert_mask) + x, y, w, h = _quantile_bounds(fg, q_left, q_right, q_top, q_bottom, int(min_span_px)) + + # --- smart AR (minimal-area container), internal only --- + pad_px = max(8, int(round(0.02 * min(H, W)))) + sx, sy, sw, sh = _pad_rect(x, y, w, h, pad_px, W, H) + + # Baseline: container using current image AR + R0 = float(W) / max(1.0, float(H)) + base = _container_for_ar(sx, sy, sw, sh, R0, W, H) + if base is None: + dbg = f"tight=True pick={picked} mq=({x},{y},{w},{h})" + return (x, y, w, h, dbg) + bx, by, bw, bh = base + base_area = bw * bh + + # Try common ARs in BOTH orientations (includes 9:16) + ratios = [ + (1, 1), + (2, 3), + (3, 2), + (3, 4), + (4, 3), + (9, 16), + (16, 9), + ] + + best_rect = base + best_area = base_area + best_info = f"AR={W}:{H}" + + for aw, ah in ratios: + R = float(aw) / float(ah) + cand = _container_for_ar(sx, sy, sw, sh, R, W, H) + if cand is None: + continue + cx, cy, cw, ch = cand + area = cw * ch + if area < best_area: + best_area = area + best_rect = cand + best_info = f"AR={aw}:{ah}" + + rx, ry, rw, rh = best_rect + dbg = f"tight=True pick={picked} mq=({x},{y},{w},{h}) -> smart({rx},{ry},{rw},{rh}) {best_info}, pad={pad_px}" + return (int(rx), int(ry), int(rw), int(rh), dbg) + + +def _get_hw(image): + t = image["image"] if isinstance(image, dict) and "image" in image else image + s = tuple(getattr(t, "shape", np.asarray(t).shape)) + if len(s) == 4: + H, W = (s[1], s[2]) if s[-1] in (1, 3, 4) else (s[2], s[3]) + elif len(s) == 3: + H, W = (s[0], s[1]) if s[0] not in (1, 3, 4) else (s[1], s[2]) + else: + H, W = s[-2], s[-1] + return int(H), int(W) + + +def _parse_aspects(s): + out = [] + for p in s.split(","): + p = p.strip() + if not p: + continue + if ":" in p: + a, b = p.split(":", 1) + else: + a, b = p.replace("X", "x").split("x", 1) + try: + a = float(a) + b = float(b) + if a > 0 and b > 0: + out.append((a / b, f"{int(a)}:{int(b)}")) + except Exception: + pass + return out or [(2 / 3.0, "2:3")] + + +def _closest_ratio(target_ratio, candidates): + tr = float(target_ratio) + best = None + for r, tag in candidates: + d = abs(math.log(r) - math.log(tr)) + if best is None or d < best[0]: + best = (d, r, tag) + return best[1], best[2] + + +def _cover_min_rect(x0, y0, w0, h0, ratio): + """ + Smallest rectangle of aspect 'ratio' that fully covers the rect (x0,y0,w0,h0). + Returns floats (xa,ya,wa,ha) without clamping to image. + """ + if w0 <= 0 or h0 <= 0: + return x0, y0, w0, h0 + r0 = w0 / max(1.0, h0) + if r0 >= ratio: + wa = w0 + ha = w0 / ratio + else: + ha = h0 + wa = h0 * ratio + cx = x0 + w0 / 2.0 + cy = y0 + h0 / 2.0 + return cx - wa / 2.0, cy - ha / 2.0, wa, ha + + +class FitAspectHeadSafe: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "image": ("IMAGE",), + "x": ("INT",), + "y": ("INT",), + "w": ("INT",), + "h": ("INT",), + "aspects_csv": ( + "STRING", + {"default": "2:3,3:4,1:1,9:16,16:9,5:8,8:5"}, + ), + "match_to": (["image", "mq_box"], {"default": "mq_box"}), + "headroom_ratio": ("FLOAT", {"default": 0.12, "min": 0.0, "max": 0.5, "step": 0.01}), + "footroom_ratio": ("FLOAT", {"default": 0.06, "min": 0.0, "max": 0.5, "step": 0.01}), + "side_margin_ratio": ("FLOAT", {"default": 0.08, "min": 0.0, "max": 0.5, "step": 0.01}), + "bottom_priority": ("FLOAT", {"default": 0.75, "min": 0.0, "max": 1.0, "step": 0.05}), + "horiz_gravity": (["center", "left", "right"], {"default": "center"}), + } + } + + RETURN_TYPES = ("INT", "INT", "INT", "INT", "STRING", "STRING") + RETURN_NAMES = ("w", "h", "x", "y", "aspect_used", "debug") + FUNCTION = "run" + CATEGORY = "utils/geometry" + + def run( + self, + image, + x, + y, + w, + h, + aspects_csv, + match_to, + headroom_ratio, + footroom_ratio, + side_margin_ratio, + bottom_priority, + horiz_gravity, + ): + + H, W = _get_hw(image) + img_ratio = W / max(1.0, H) + mq_ratio = w / max(1.0, h) + target_ratio = img_ratio if match_to == "image" else mq_ratio + + aspects = _parse_aspects(aspects_csv) + ratio, tag = _closest_ratio(target_ratio, aspects) + + req_x0 = x + req_y0 = y + req_x1 = x + w + req_y1 = y + h + + guess_h = H + head_px = headroom_ratio * guess_h + foot_px = footroom_ratio * guess_h + side_px = side_margin_ratio * (guess_h * ratio) + + req_x0p = req_x0 - side_px + req_x1p = req_x1 + side_px + req_y0p = req_y0 - head_px + req_y1p = req_y1 + foot_px + + xa, ya, wa, ha = _cover_min_rect( + req_x0p, + req_y0p, + max(1.0, req_x1p - req_x0p), + max(1.0, req_y1p - req_y0p), + ratio, + ) + + head_px = headroom_ratio * ha + foot_px = footroom_ratio * ha + side_px = side_margin_ratio * wa + req_x0p = req_x0 - side_px + req_x1p = req_x1 + side_px + req_y0p = req_y0 - head_px + req_y1p = req_y1 + foot_px + xa, ya, wa, ha = _cover_min_rect( + req_x0p, + req_y0p, + max(1.0, req_x1p - req_x0p), + max(1.0, req_y1p - req_y0p), + ratio, + ) + + if horiz_gravity == "left": + xa = 0.0 + elif horiz_gravity == "right": + xa = W - wa + else: + cx = x + w / 2.0 + xa = cx - wa / 2.0 + xa = max(0.0, min(xa, W - wa)) + + y_head = y + y_feet = y + h + y_top_des = y_head - head_px + y_bot_des = y_feet + foot_px + ya = y_top_des + ya = max(0.0, min(ya, H - ha)) + + def violations(Y): + top_gap = (Y) - (y_head - head_px) + bot_gap = (Y + ha) - (y_feet + foot_px) + loss_top = max(0.0, -top_gap) + loss_bot = max(0.0, -bot_gap) + return loss_top, loss_bot, (1.0 - bottom_priority) * loss_top + bottom_priority * loss_bot + + candidates = [ + max(0.0, min(y_top_des, H - ha)), + max(0.0, min(y_bot_des - ha, H - ha)), + 0.0, + float(H - ha), + ] + best = None + for Yc in candidates: + lt, lb, cost = violations(Yc) + if best is None or cost < best[0]: + best = (cost, Yc, lt, lb) + _, ya, lt, lb = best + ya = max(0.0, min(ya, H - ha)) + + X = int(round(xa)) + Y = int(round(ya)) + Wc = int(round(wa)) + Hc = int(round(ha)) + X = max(0, min(X, W - 1)) + Y = max(0, min(Y, H - 1)) + Wc = max(1, min(Wc, W - X)) + Hc = max(1, min(Hc, H - Y)) + + dbg = ( + f"match_to={match_to}, chosen={tag}({ratio:.4f}), " + f"img={W}x{H}, mq=({x},{y},{w},{h}), " + f"margins(px): head={head_px:.1f}, foot={foot_px:.1f}, side≈{side_px:.1f}, " + f"viol: top={lt:.1f}, bot={lb:.1f}; final=({X},{Y},{Wc},{Hc})" + ) + return (Wc, Hc, X, Y, tag, dbg) + + +NODE_CLASS_MAPPINGS = { + "MQBBoxMin": MQBBoxMin, + "FitAspectHeadSafe": FitAspectHeadSafe, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "MQBBoxMin": "MQ BBox (MIN)", + "FitAspectHeadSafe": "Fit Aspect (Head-Safe) - Closest AR + Tight Cover", +} diff --git a/paired_image_loader.py b/paired_image_loader.py index d35f697..78b4139 100644 --- a/paired_image_loader.py +++ b/paired_image_loader.py @@ -93,7 +93,7 @@ def _coerce_str(value) -> str: return str(value) -class PortraitUtils_PairedImageLoader: +class PairedImageLoader: CATEGORY = "PortraitUtils" RETURN_TYPES = ("IMAGE", "IMAGE", "STRING") FUNCTION = "load_next_pair" @@ -161,20 +161,20 @@ class PortraitUtils_PairedImageLoader: self._state_key = state_key if not source_dir: - raise ValueError("PortraitUtils_PairedImageLoader: source_dir is required.") + raise ValueError("PairedImageLoader: source_dir is required.") if not output_dir: - raise ValueError("PortraitUtils_PairedImageLoader: output_dir is required.") + raise ValueError("PairedImageLoader: output_dir is required.") src_path = _resolve_directory(source_dir) out_path = _resolve_directory(output_dir) if not src_path.exists() or not src_path.is_dir(): raise ValueError( - f"PortraitUtils_PairedImageLoader: source_dir not found: {src_path}" + f"PairedImageLoader: source_dir not found: {src_path}" ) if not out_path.exists() or not out_path.is_dir(): raise ValueError( - f"PortraitUtils_PairedImageLoader: output_dir not found: {out_path}" + f"PairedImageLoader: output_dir not found: {out_path}" ) state = _STATE.setdefault(state_key, migrated_state or _NodeState()) @@ -196,7 +196,7 @@ class PortraitUtils_PairedImageLoader: if not pairs: raise RuntimeError( - "PortraitUtils_PairedImageLoader: no matching images found " + "PairedImageLoader: no matching images found " f"between {src_path} and {out_path}." ) @@ -216,7 +216,7 @@ class PortraitUtils_PairedImageLoader: _SIGNATURE_INDEX[signature] = state.index print( - "[PortraitUtils_PairedImageLoader] " + "[PairedImageLoader] " f"{pair.display_name} ({next_index + 1}/{len(pairs)})" ) @@ -383,23 +383,23 @@ class PortraitUtils_PairedImageLoader: if source_only: print( - "[PortraitUtils_PairedImageLoader] Unmatched source files " + "[PairedImageLoader] Unmatched source files " f"({len(source_only)}) in {source_dir}: {_summarize(source_only)}" ) if output_only: print( - "[PortraitUtils_PairedImageLoader] Unmatched output files " + "[PairedImageLoader] Unmatched output files " f"({len(output_only)}) in {output_dir}: {_summarize(output_only)}" ) if collisions: for key, files in collisions.items(): print( - "[PortraitUtils_PairedImageLoader] Normalised name collision " + "[PairedImageLoader] Normalised name collision " f"for '{key}': {_summarize(sorted(set(files), key=_natural_sort_key))}" ) if ext_mismatch: print( - "[PortraitUtils_PairedImageLoader] Chosen fallback matches with differing " + "[PairedImageLoader] Chosen fallback matches with differing " f"extensions ({len(ext_mismatch)}): {_summarize(ext_mismatch)}" ) @@ -473,9 +473,9 @@ class PortraitUtils_PairedImageLoader: NODE_CLASS_MAPPINGS = { - "PortraitUtils_PairedImageLoader": PortraitUtils_PairedImageLoader, + "PairedImageLoader": PairedImageLoader, } NODE_DISPLAY_NAME_MAPPINGS = { - "PortraitUtils_PairedImageLoader": "Paired Image Loader", + "PairedImageLoader": "Paired Image Loader", }