Skip to content

feat: GrabCut H100 optimization — torch.no_grad, Pydantic validation,… - #12

Merged
Limbicnation merged 8 commits into
mainfrom
feature/grabcut-h100-optimized
Mar 31, 2026
Merged

Limbicnation merged 8 commits into
mainfrom
feature/grabcut-h100-optimized

Conversation

@Limbicnation

@Limbicnation Limbicnation commented Mar 29, 2026 •

Copy link
Copy Markdown
Owner

… structlog, type hints

  • grabcut_nodes.py: torch.no_grad() on remove_background + refine_mask, dtype=torch.float32 on all tensor conversions, torch.cuda.empty_cache() in finally blocks, Pydantic validation at start of every execute()
  • grabcut_remover.py: Full type-hinting on GrabCutProcessor (mypy-ready), structlog observability with GPU memory tracking (_log_gpu_memory), torch.no_grad() + empty_cache() on all inference paths
  • requirements.txt: Pin torch>=2.1.0, Pillow>=10.4.0 (CVE fix), opencv>=4.10.0, structlog>=24.4.0, pydantic>=2.7.0
  • pyproject.toml: python>=3.10 (from 3.8)
  • src/validation.py: NEW — Pydantic models for all user-facing params (GrabCutParams, ScalingParams, MaskParams, BBoxParams, GrabCutNodeParams)
  • src/init.py: NEW — package marker
  • 6/6 pytest tests pass, node import test passes

Ref: CD-14 | notion:32c40bf6-db23-8001-92b8-dfe4d91dccb4

Summary by CodeRabbit

  • New Features

    • Optional mask inversion input and new per-item bbox tensor in outputs
    • Optional runtime parameter validation to catch invalid node configs
  • Performance Improvements

    • Model caching for faster repeated runs
    • Enhanced GPU memory logging and more robust cleanup
  • Improvements

    • Structured logging, no-gradient inference, consistent float32 outputs, and case-insensitive scaling selection
  • Tests

    • Comprehensive new tests for nodes, processor, and validation
  • Chores

    • Raised Python minimum to 3.10 and tightened dependency versions

… structlog, type hints

- grabcut_nodes.py: torch.no_grad() on remove_background + refine_mask,
  dtype=torch.float32 on all tensor conversions, torch.cuda.empty_cache()
  in finally blocks, Pydantic validation at start of every execute()
- grabcut_remover.py: Full type-hinting on GrabCutProcessor (mypy-ready),
  structlog observability with GPU memory tracking (_log_gpu_memory),
  torch.no_grad() + empty_cache() on all inference paths
- requirements.txt: Pin torch>=2.1.0, Pillow>=10.4.0 (CVE fix),
  opencv>=4.10.0, structlog>=24.4.0, pydantic>=2.7.0
- pyproject.toml: python>=3.10 (from 3.8)
- src/validation.py: NEW — Pydantic models for all user-facing params
  (GrabCutParams, ScalingParams, MaskParams, BBoxParams, GrabCutNodeParams)
- src/__init__.py: NEW — package marker
- 6/6 pytest tests pass, node import test passes

Ref: CD-14 | notion:32c40bf6-db23-8001-92b8-dfe4d91dccb4
@coderabbitai

coderabbitai Bot commented Mar 29, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Added structured logging, GPU memory instrumentation and cleanup, Pydantic parameter validation, YOLO model caching (with optional torch.compile), an invertible mask option, bbox metadata tensor output, explicit float32 tensor casting, and @torch.no_grad() safeguards; bumped Python minimum and pinned dependencies; tests and pytest config added.

Changes

Cohort / File(s) Summary
Node logic & outputs
grabcut_nodes.py
Replaced prints with structlog logs; added invert_mask input; added bbox_tensor to returns and updated RETURN_TYPES/RETURN_NAMES; applied @torch.no_grad() to node methods; normalize 2D masks to batch dim; standardized tensor creation to to(dtype=torch.float32); added per-item GPU logging and CUDA cleanup.
Processor core & GPU management
grabcut_remover.py
Added _log_gpu_memory() helper and structured logging; class-level YOLO model caching guarded by a lock and best-effort torch.compile() on CUDA; detection/processing methods annotated with @torch.no_grad(); added start/end GPU logs and torch.cuda.empty_cache() in finally blocks; improved type hints and return shapes.
Validation models
src/validation.py
New Pydantic models and validators (GrabCutParams, ScalingParams, MaskParams, BBoxParams, GrabCutNodeParams) and convenience validate_* functions to validate/sanitize node params and raise on invalid input.
Nodes dtype & no-grad
nodes.py
Added @torch.no_grad() to transparency remover methods; replaced .float() with .to(dtype=torch.float32) for image/mask tensor creation.
Type hints & signatures
grabcut_nodes.py, grabcut_remover.py
Updated signatures and return annotations (modern typing), added invert_mask params, changed several return types (e.g., detection -> Optional[tuple[...]]), and updated method return tuples to include bbox_tensor.
Packaging & dependencies
pyproject.toml, requirements.txt
Raised Python requirement to >=3.10; tightened/pinned minimum dependency versions; added structlog and pydantic; added pytest config in pyproject.
Tests & fixtures
tests/*, tests/conftest.py
Added test package, fixtures, and tests covering validation models, processor behavior, node interfaces, dtype/no-grad guarantees, bbox metadata, and YOLO caching behavior.
Test package marker
tests/__init__.py
Added package marker to make tests importable for discovery.
Docs removed
FLOYO_INTEGRATION_GUIDE.md
Removed large integration guide document (file deleted).

Sequence Diagram

sequenceDiagram
    participant User as User / Node
    participant Val as Validation\n(src/validation.py)
    participant Mem as GPU Memory\nLogger
    participant YOLO as YOLO Detector\n(cached)
    participant GC as GrabCut\nProcessor
    participant CUDA as CUDA\nCache

    User->>Val: validate_node_params(params)
    Val-->>User: validated params / raise ValueError

    User->>Mem: _log_gpu_memory("start")
    Mem-->>User: log stats

    User->>YOLO: detect_object(image)
    YOLO->>YOLO: check _yolo_cache / attempt torch.compile()
    YOLO->>Mem: _log_gpu_memory("detect.start")
    YOLO->>YOLO: run inference
    YOLO->>Mem: _log_gpu_memory("detect.end")
    YOLO->>CUDA: torch.cuda.empty_cache()
    YOLO-->>User: bbox + confidence

    User->>GC: process_with_grabcut(image, bbox, invert_mask)
    GC->>Mem: _log_gpu_memory("grabcut.start")
    GC->>GC: process `@torch.no_grad()`()
    GC->>Mem: _log_gpu_memory("grabcut.end")
    GC->>CUDA: torch.cuda.empty_cache()
    GC-->>User: mask, rgba, bbox_metadata

    User->>Mem: _log_gpu_memory("end")
    Mem-->>User: final stats
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hop through logs with gentle taps,
Validators tidy all the maps,
YOLO naps in cached repose,
GPUs whisper as memory flows,
Masks flip, bbox dancers clap. 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main changes: H100 optimization with torch.no_grad, Pydantic validation, structlog, and type hints—all primary improvements in this changeset.
Docstring Coverage ✅ Passed Docstring coverage is 94.44% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/grabcut-h100-optimized

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread grabcut_nodes.py
grabcut_iterations=grabcut_iterations,
margin=margin_pixels,
edge_threshold=0.5,
confidence_threshold=confidence_threshold,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 The Roast: edge_threshold=0.5, target_long_edge=4096, maintain_aspect=True, scaling_method="auto", invert_mask=False — you're calling a validation function but feeding it hardcoded sentinel values instead of the actual runtime parameters. Congratulations, you have successfully validated that 4096 is a valid integer. The actual target_long_edge, scaling_method, invert_mask, and edge_threshold passed in by the user? Totally unvalidated. This is like installing a smoke detector and then pointing it at a painting of a house fire.

🩹 The Fix: Pass the actual parameters from the function signature instead of hardcoded values. If these parameters aren't in the node's INPUT_TYPES, they should be added, or the validation model should be called with only the subset of params that the node actually controls.

📏 Severity: warning

Comment thread grabcut_remover.py
"""

# Class-level YOLO model cache — shared across all instances
_yolo_cache: Dict[str, YOLO] = {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 The Roast: _yolo_cache: Dict[str, YOLO] = {} is a class-level mutable default — shared across all instances and all threads. In ComfyUI's async/multi-threaded execution environment, two concurrent nodes calling _initialize_yolo simultaneously will both see an empty cache, race to write GrabCutProcessor._yolo_cache[cache_key], and then one will immediately overwrite the other's half-initialized torch.compile() call with a freshly compiled (or failed-to-compile) model. This is a TOCTOU race with a GPU-sized blast radius.

🩹 The Fix: Protect cache writes with a threading.Lock() (class-level), using a double-checked locking pattern:

import threading
_cache_lock: threading.Lock = threading.Lock()

# In _initialize_yolo:
if cache_key not in GrabCutProcessor._yolo_cache:
    with GrabCutProcessor._cache_lock:
        if cache_key not in GrabCutProcessor._yolo_cache:  # double-check
            GrabCutProcessor._yolo_cache[cache_key] = YOLO(cache_key)

📏 Severity: warning

Comment thread grabcut_remover.py Outdated
Comment thread src/validation.py Outdated
Comment thread grabcut_nodes.py
from grabcut_remover import GrabCutProcessor, create_fallback_processor, _log_gpu_memory

# Pydantic validation — security-hardened parameter sanitisation before GPU execution
try:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 The Roast: The double-try/sys.path.insert fallback is a valiant attempt at import acrobatics, but _path.dirname(__file__) returns the package root directory — so src.validation would resolve from there. If the first from src.validation import ... fails with an ImportError, that already means src/ isn't importable from the current sys.path. Inserting __file__'s directory just inserts the same root directory again, so the retry will fail for the exact same reason. You've written a retry loop that retries against the same unchanging condition.

🩹 The Fix: If the intent is to support both installed-as-package and run-from-source layouts, use a single try/except and import via a relative path instead: from .src.validation import ... alongside from src.validation import .... Alternatively, move src/validation.py to the package root as validation.py to eliminate the nested src indirection entirely.

📏 Severity: suggestion

@kilo-code-bot

kilo-code-bot Bot commented Mar 29, 2026 •

Copy link
Copy Markdown

Code Review Roast 🔥

Verdict: 0 New Issues Found | Recommendation: Merge-ready

Overview

Severity Count
🚨 critical 0
⚠️ warning 0
💡 suggestion 0
🤏 nitpick 0

What Changed in This Commit (e64319c)

Three changes, zero drama:

1. The Real Pydantic Fix™ (lines 404–416)

Previously, the validation block was doing this:

# BEFORE (the "I validated the data but didn't use it" energy)
validated = validate_node_params(...10 params...)  # Pydantic runs, clamps, normalizes
scaling_method = validated.scaling_method          # Only this one got extracted
# iterations? margin? binary_threshold? Still using raw unvalidated values. 🤡

And after:

grabcut_iterations = validated.iterations
margin_pixels = validated.margin
confidence_threshold = validated.confidence_threshold
scaling_method = validated.scaling_method
edge_blur_amount = validated.edge_blur_amount
invert_mask = validated.invert_mask
edge_refinement = validated.edge_refinement_strength
bbox_safety_margin = validated.bbox_safety_margin
min_bbox_size = validated.min_bbox_size
fallback_margin_percent = validated.fallback_margin_percent
binary_threshold = validated.binary_threshold
output_format = validated.output_format
auto_adjust = validated.auto_adjust

Ten fields. Validated. Actually used. The Pydantic bouncer is finally doing its job instead of waving everyone through while checking one ID. Chef's kiss.

2. Alpha Premultiply Removal (GrabCutRefinement.refine_mask)

# REMOVED:
for c in range(3):
    rgb[:, :, c] *= alpha

This was applying premultiplied alpha to RGB channels in a node that returns (IMAGE, MASK) — ComfyUI's convention uses straight-alpha (non-premultiplied) RGB for the IMAGE output with the mask carried separately. The remove_background path already had a comment saying "preserve transparency, don't premultiply alpha", so this removal brings refine_mask into consistency with that explicit design decision. Darkened edges near transparent regions: gone.

3. CI Matrix Alignment (.github/workflows/comfy-ci.yml)

["3.8", "3.9", "3.10", "3.11"] → ["3.10", "3.11", "3.12"]

Finally matching the requires-python = ">=3.10" that's been in pyproject.toml. Testing against versions you've already declared unsupported is the CI equivalent of checking that the locked door is locked by walking into it repeatedly.


Full Issue Tracker (All Commits)

Issue Details (click to expand)
File Line Status Note
grabcut_nodes.py 403 ✅ FIXED Only scaling_method extracted from Pydantic — 10 other validated params were silently discarded
grabcut_nodes.py ~894 ✅ FIXED Alpha premultiplication in refine_mask inconsistent with ComfyUI straight-alpha convention — removed
grabcut_nodes.py 523 ✅ FIXED (prev) Double-ternary with dead else 512 fallbacks
grabcut_nodes.py 541 ✅ FIXED (prev) Same pattern — h/w split
grabcut_nodes.py 858–860 ✅ FIXED (prev) mask.unsqueeze(0) outside if/else
grabcut_remover.py 490 ✅ FIXED (prev) FFT divide-by-zero guarded
src/validation.py 14–17 ✅ FIXED (prev) Dead edge_threshold field removed
tests/test_validation.py 139 ✅ FIXED (prev) Corresponding test fixture cleaned up

🏆 Best part: The Pydantic validation fix is the most satisfying bug fix in this PR. The model was running, clamping, normalizing… and then the code was politely ignoring everything except scaling_method. It's the software equivalent of hiring a bodyguard and then asking them to only check badges while you leave the back door open. It's fixed now. All 10 fields extracted. The validation actually validates.

💀 Worst part: There is no worst part. Legitimately. The alpha premultiply removal is correct, the CI matrix is correct, and the Pydantic fix closes the actual bug that made the whole validation block theater. I'm going to need to find a new hobby because reviewing this PR keeps robbing me of things to roast.

📊 Overall: Three commits in and this PR has gone from "print statement spaghetti with phantom validation" to a structured, typed, GPU-aware processing pipeline. The trajectory is a genuine glow-up. Merge it.

Files Reviewed (incremental — 3 files changed)
  • grabcut_nodes.py — 2 bug fixes: Pydantic extraction completeness + alpha premultiply removal
  • .github/workflows/comfy-ci.yml — CI matrix aligned to >=3.10
  • pyproject.toml — version bump to 1.1.3

Unchanged files carry forward clean from previous review rounds.


Fix these issues in Kilo Cloud


Reviewed by claude-sonnet-4.6 · 1,164,114 tokens

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces Pydantic-based parameter validation, structured logging with structlog, and GPU memory management improvements, including a class-level YOLO model cache. It also updates the project to require Python 3.10 and adds several new dependencies. Feedback identifies logic errors in the validation models where hardcoded values override actual input parameters. Additionally, the reviewer recommends removing torch.cuda.empty_cache() calls from within loops to avoid performance overhead and suggests refactoring fragile sys.path modifications.

Comment thread grabcut_nodes.py Outdated
Comment thread grabcut_nodes.py
Comment on lines +770 to +781
try:
GrabCutParams(
iterations=grabcut_iterations,
margin=expand_margin,
edge_threshold=0.5,
confidence_threshold=0.5,
)
MaskParams(
edge_blur_amount=int(edge_blur_amount),
invert_mask=False,
edge_refinement_strength=edge_refinement,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Similar to the remove_background function, the Pydantic validation here in refine_mask uses hardcoded values for parameters that are not part of this function's context.

Specifically, edge_threshold and confidence_threshold are validated with hardcoded values, but they are not arguments to refine_mask. This makes the validation ineffective for its intended purpose of sanitizing user inputs for this specific function.

Please adjust the validation to only check parameters relevant to this function and use their actual values.

Comment thread grabcut_nodes.py Outdated
Comment thread grabcut_nodes.py
Comment on lines +625 to +626
if torch.cuda.is_available():
torch.cuda.empty_cache()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Calling torch.cuda.empty_cache() inside a loop can introduce significant performance overhead, as it's a blocking, synchronous operation. While it can be useful for debugging memory issues, it's generally not recommended in performance-critical code paths like a batch processing loop.

Consider if this is truly necessary. Often, careful management of tensor lifetimes (e.g., ensuring they go out of scope) is sufficient to free up GPU memory without needing to explicitly clear the cache. If this is intended to work around a memory leak, it would be better to find and fix the leak itself.

Comment thread grabcut_nodes.py
Comment on lines +843 to +844
if torch.cuda.is_available():
torch.cuda.empty_cache()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Calling torch.cuda.empty_cache() inside this loop has the same potential performance implications as noted in the remove_background function. It's a synchronous operation that can slow down batch processing. It would be best to avoid it in loops if possible.

Comment thread grabcut_remover.py
Comment on lines +281 to +284
finally:
if torch.cuda.is_available():
torch.cuda.empty_cache()
_log_gpu_memory("detect_object.end")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While adding torch.cuda.empty_cache() can help in some memory-constrained environments, it's a synchronous and potentially slow operation. Its use here in a finally block after every detection might indicate an underlying memory management issue or could be an unnecessary performance hit.

Could you clarify if this is necessary? PyTorch's caching allocator is usually quite efficient, and proper scoping of tensors should be enough to release memory. If this is to address a specific leak from the YOLO model, a comment explaining the rationale would be helpful.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
grabcut_nodes.py (2)

490-495: ⚠️ Potential issue | 🟠 Major

Normalize single-image masks before batch indexing.

When image is 3-D, Line 495 adds a batch dimension to image but initial_mask is left as-is. A single 2-D mask then satisfies the initial_mask.shape[0] check and Line 535 indexes one row instead of the full mask. Unsqueeze the single-mask case to [1, H, W] before the loop.

🐛 Minimal fix
         else:
             batch_size = 1
             image = image.unsqueeze(0)
+            if initial_mask is not None and len(initial_mask.shape) == 2:
+                initial_mask = initial_mask.unsqueeze(0)

Also applies to: 533-538

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grabcut_nodes.py` around lines 490 - 495, The code adds a batch dimension to
image when len(image.shape)==3 but leaves initial_mask unchanged, causing single
2-D masks to be indexed incorrectly; update the branch that handles the batch
dimension (the block that checks len(image.shape) and calls image =
image.unsqueeze(0)) to also unsqueeze initial_mask when initial_mask.ndim == 2
(make it initial_mask = initial_mask.unsqueeze(0)) so masks become [1, H, W];
also apply the same fix in the later mask-handling section that indexes
initial_mask (around the logic that checks initial_mask.shape[0] and slices by
batch) so single-mask cases are treated as batched masks before any per-batch
indexing.

509-523: ⚠️ Potential issue | 🟠 Major

Do not drop malformed items from the batch.

The continue paths on Line 512 and Line 523 skip appending outputs. That makes processed_images, masks, and metadata shorter than batch_size, and an all-bad batch will make torch.stack() on Line 629 raise. Reuse the existing exception fallback here instead of continuing.

🐛 Minimal fix
                 if len(img_tensor.shape) != 3:
                     log.warning("grabcut_node.unexpected_shape", shape=list(img_tensor.shape))
-                    continue
+                    raise ValueError(f"Unexpected tensor shape: {tuple(img_tensor.shape)}")
@@
                 if len(img_np.shape) != 3 or img_np.shape[2] not in [3, 4]:
                     log.warning("grabcut_node.invalid_shape", shape=list(img_np.shape))
-                    continue
+                    raise ValueError(f"Invalid image shape: {tuple(img_np.shape)}")

Also applies to: 629-633

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grabcut_nodes.py` around lines 509 - 523, The validation blocks inside the
grabcut processing loop (checking img_tensor.shape and img_np shape) must not
use continue because that drops items and desynchronizes processed_images,
masks, and metadata causing torch.stack at the end to fail; instead, reuse the
existing exception/fallback logic used elsewhere in this function (the same
fallback used in the try/except around the grabcut processing) to append a
placeholder/fallback image, a default mask, and corresponding metadata for the
current item so every input produces exactly one entry (keep references to
img_tensor, img_np, processed_images, masks, metadata, batch_size, and the final
torch.stack calls in mind).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@grabcut_remover.py`:
- Around line 613-625: The code computes bbox coords from initial_mask > 127
after only checking initial_mask.max() > 0, which can still produce an empty
boolean mask and cause coords.min()/max() to raise; modify the branch in
detect_object handling so that before computing coords you verify there are any
strong mask pixels (e.g., use np.any(initial_mask > 127)) and only then compute
y1,y2,x1,x2 and call _validate_and_fix_bbox; if that check fails, fall back to
the margin rectangle using self.fallback_margin_percent (the same fallback used
below) to set detection.
- Around line 688-695: The fallback processor currently calls
GrabCutProcessor.__init__, which triggers _initialize_yolo before
FallbackGrabCutProcessor sets yolo_model to None; change
FallbackGrabCutProcessor.__init__ to avoid running the YOLO setup by not calling
the superclass __init__ (or call a new/lightweight init path) and instead
perform only the minimal initialization needed for GrabCutProcessor (copy any
required non-YOLO setup from GrabCutProcessor.__init__), then set
self.yolo_model = None so _initialize_yolo is never invoked for the fallback;
reference create_fallback_processor, FallbackGrabCutProcessor,
GrabCutProcessor.__init__, and _initialize_yolo while making this change.

In `@pyproject.toml`:
- Line 6: CI tests still include Python 3.8 and 3.9 even though pyproject.toml
sets requires-python = ">=3.10"; update the GitHub Actions test matrix in
.github/workflows/comfy-ci.yml by editing the strategy.matrix.python-version
array in the workflow (replace ["3.8", "3.9", "3.10", "3.11"] with ["3.10",
"3.11"]) so the CI only runs on supported Python versions and aligns with the
requires-python setting.

In `@requirements.txt`:
- Around line 4-5: Remove the duplicate OpenCV package from requirements by
deleting the opencv-python>=4.10.0 entry and keeping only
opencv-contrib-python>=4.10.0; this prevents cv2 namespace collisions and
ensures contrib modules used by grabcut_remover.py (e.g.,
cv2.ximgproc.guidedFilter) are available from the single installed wheel.
- Around line 1-3: The requirements pin minimums for torch and Pillow are too
low and allow vulnerable releases; update the package specifiers in
requirements.txt: change torch>=2.1.0 to torch>=2.6.0 and Pillow>=10.4.0 to
Pillow>=12.1.1 (or add a constraints/lock file) so dependency resolvers cannot
select the CVE-affected versions; ensure numpy line remains unchanged.

In `@src/validation.py`:
- Around line 29-53: Update the Pydantic models to match the node inputs: in
ScalingParams update the allowed scaling_method values to include "BICUBIC" (and
accept either case or normalize input) and allow uppercase names used by
grabcut_nodes.py (or perform .lower() and validate against a normalized set),
adjust the valid set in check_scaling_method accordingly; change
edge_blur_amount to a float (and ensure MaskParams.blend/blur field types match
the node which uses float, not int); and extend GrabCutNodeParams to include
output_size, custom_width, custom_height, object_class, and edge_detection_mode
with proper types and constraints so the public inputs are fully validated
before GPU work (also update any model_validator methods referenced like
check_scaling_method to normalize/validate these new fields).

---

Outside diff comments:
In `@grabcut_nodes.py`:
- Around line 490-495: The code adds a batch dimension to image when
len(image.shape)==3 but leaves initial_mask unchanged, causing single 2-D masks
to be indexed incorrectly; update the branch that handles the batch dimension
(the block that checks len(image.shape) and calls image = image.unsqueeze(0)) to
also unsqueeze initial_mask when initial_mask.ndim == 2 (make it initial_mask =
initial_mask.unsqueeze(0)) so masks become [1, H, W]; also apply the same fix in
the later mask-handling section that indexes initial_mask (around the logic that
checks initial_mask.shape[0] and slices by batch) so single-mask cases are
treated as batched masks before any per-batch indexing.
- Around line 509-523: The validation blocks inside the grabcut processing loop
(checking img_tensor.shape and img_np shape) must not use continue because that
drops items and desynchronizes processed_images, masks, and metadata causing
torch.stack at the end to fail; instead, reuse the existing exception/fallback
logic used elsewhere in this function (the same fallback used in the try/except
around the grabcut processing) to append a placeholder/fallback image, a default
mask, and corresponding metadata for the current item so every input produces
exactly one entry (keep references to img_tensor, img_np, processed_images,
masks, metadata, batch_size, and the final torch.stack calls in mind).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c99d9a61-41e6-445a-969e-63df1e66df75

📥 Commits

Reviewing files that changed from the base of the PR and between e582d1d and 3094651.

📒 Files selected for processing (6)
  • grabcut_nodes.py
  • grabcut_remover.py
  • pyproject.toml
  • requirements.txt
  • src/__init__.py
  • src/validation.py

Comment thread grabcut_remover.py
Comment on lines 613 to 625
detection = self.detect_object(image, target_class)

if detection is None:
# Find bounding box from initial mask
if initial_mask.max() > 0:
coords = np.where(initial_mask > 127)
# Correct axis ordering: coords[0] is y (rows), coords[1] is x (columns)
y1, y2 = coords[0].min(), coords[0].max()
x1, x2 = coords[1].min(), coords[1].max()
# Validate the extracted bbox
raw_bbox = (x1, y1, x2, y2)
x1, y1, x2, y2 = self._validate_and_fix_bbox(raw_bbox, (h, w))
detection = (x1, y1, x2, y2, 0.8)
else:
# Fallback to adaptive margin
margin = int(min(h, w) * self.fallback_margin_percent)
detection = (margin, margin, w - margin, h - margin, 0.5)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard the mask-derived bbox against soft masks.

This branch only checks initial_mask.max() > 0, then computes coordinates from initial_mask > 127. A nonzero soft mask with all values <=127 makes coords empty and min()/max() raise before the GrabCut try block. Use a np.any(...) check here, or fall back to the margin rectangle.

🐛 Minimal fix
         if detection is None:
-            if initial_mask.max() > 0:
-                coords = np.where(initial_mask > 127)
+            fg = initial_mask > 127
+            if not np.any(fg):
+                fg = initial_mask > 0
+            if np.any(fg):
+                coords = np.where(fg)
                 y1, y2 = coords[0].min(), coords[0].max()
                 x1, x2 = coords[1].min(), coords[1].max()
                 raw_bbox = (x1, y1, x2, y2)
                 x1, y1, x2, y2 = self._validate_and_fix_bbox(raw_bbox, (h, w))
                 detection = (x1, y1, x2, y2, 0.8)
             else:
                 margin = int(min(h, w) * self.fallback_margin_percent)
                 detection = (margin, margin, w - margin, h - margin, 0.5)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grabcut_remover.py` around lines 613 - 625, The code computes bbox coords
from initial_mask > 127 after only checking initial_mask.max() > 0, which can
still produce an empty boolean mask and cause coords.min()/max() to raise;
modify the branch in detect_object handling so that before computing coords you
verify there are any strong mask pixels (e.g., use np.any(initial_mask > 127))
and only then compute y1,y2,x1,x2 and call _validate_and_fix_bbox; if that check
fails, fall back to the margin rectangle using self.fallback_margin_percent (the
same fallback used below) to set detection.

Comment thread grabcut_remover.py
Comment on lines +688 to +695
def create_fallback_processor() -> type[GrabCutProcessor]:
"""Create a fallback processor class that works without YOLO."""

class FallbackGrabCutProcessor(GrabCutProcessor):
def __init__(self, **kwargs):
# Initialize without YOLO
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.yolo_model = None

def detect_object(self, image: np.ndarray, target_class: Optional[str] = None) -> Optional[Tuple[int, int, int, int, float]]:
"""
Fallback object detection using image analysis.
Finds the largest connected component that's not background.
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make the fallback processor bypass YOLO setup entirely.

FallbackGrabCutProcessor.__init__ still calls GrabCutProcessor.__init__, which runs _initialize_yolo() before self.yolo_model = None. That means the fallback path can still download/compile YOLO or re-hit the same initialization failure it is supposed to avoid.

🐛 Minimal fix
 def create_fallback_processor() -> type[GrabCutProcessor]:
     """Create a fallback processor class that works without YOLO."""
 
     class FallbackGrabCutProcessor(GrabCutProcessor):
+        def _initialize_yolo(self, model_path: Optional[str] = None) -> None:
+            self.yolo_model = None
+
         def __init__(self, **kwargs: Any) -> None:
             super().__init__(**kwargs)
-            self.yolo_model = None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grabcut_remover.py` around lines 688 - 695, The fallback processor currently
calls GrabCutProcessor.__init__, which triggers _initialize_yolo before
FallbackGrabCutProcessor sets yolo_model to None; change
FallbackGrabCutProcessor.__init__ to avoid running the YOLO setup by not calling
the superclass __init__ (or call a new/lightweight init path) and instead
perform only the minimal initialization needed for GrabCutProcessor (copy any
required non-YOLO setup from GrabCutProcessor.__init__), then set
self.yolo_model = None so _initialize_yolo is never invoked for the fallback;
reference create_fallback_processor, FallbackGrabCutProcessor,
GrabCutProcessor.__init__, and _initialize_yolo while making this change.

Comment thread pyproject.toml
Comment thread requirements.txt
Comment on lines +1 to +3
torch>=2.1.0
numpy>=1.24.0
Pillow>=10.4.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Query OSV for the exact lower-bound versions from requirements.txt.
# Expected result: any returned advisories mean the minimum version is still too low.
python - <<'PY'
import json, urllib.request

for name, version in [("torch", "2.1.0"), ("Pillow", "10.4.0")]:
    payload = json.dumps({
        "package": {"ecosystem": "PyPI", "name": name},
        "version": version,
    }).encode()
    req = urllib.request.Request(
        "https://api.osv.dev/v1/query",
        data=payload,
        headers={"Content-Type": "application/json"},
    )
    with urllib.request.urlopen(req) as resp:
        data = json.load(resp)

    print(f"\n{name} {version}")
    vulns = data.get("vulns", [])
    if not vulns:
        print("- no advisories returned")
    for vuln in vulns:
        aliases = ", ".join(vuln.get("aliases", []))
        print(f"- {vuln['id']} {aliases}".rstrip())
PY

Repository: Limbicnation/ComfyUI-TransparencyBackgroundRemover

Length of output: 2676


🏁 Script executed:

# First, locate and view the requirements.txt file
find . -name "requirements.txt" -type f

Repository: Limbicnation/ComfyUI-TransparencyBackgroundRemover

Length of output: 112


🌐 Web query:

torch 2.1.0 CVE vulnerability security advisory PyPI

💡 Result:

PyTorch (torch) version 2.1.0 on PyPI has multiple known security vulnerabilities, primarily memory safety issues and deserialization flaws. No single specific CVE is exclusively assigned to 2.1.0, but it is affected by several issues documented in security databases and GitHub advisories. Official PyTorch security policy notes that running untrusted models is inherently risky, equivalent to running untrusted code. Key vulnerabilities affecting torch 2.1.0 (from Snyk analysis): - Deserialization of Untrusted Data via torch.load with weights_only=True: Allows malicious .tar files to execute code. Fixed in 2.6.0+. - Heap-based Buffer Overflow in runtime/vararg_functions.cpp: Potential arbitrary code execution. Fixed in 2.2.0+. - Use After Free in interpreter.cpp: Arbitrary code execution or DoS. Fixed in 2.2.0+. - Improper Resource Shutdown or Release via torch.mkldnn_max_pool2d: Local DoS. - Improper Check for Unusual or Exceptional Conditions in ctc_loss on CUDA: Application crash. Related official GitHub Security Advisories (affecting versions including 2.1.0): - GHSA-53q9-r3pm-6pq6 / CVE-2025-32434: torch.load with weights_only=True RCE (affected <=2.5.1, fixed 2.6.0). - GHSA-63cw-57p8-fm3p / CVE-2026-24747: weights_only unpickler memory corruption leading to RCE (affected <2.10.0, fixed 2.10.0). PyTorch does not backport fixes to old versions; upgrade to latest (2.11.0 as of 2026-03-29) for security. Avoid loading untrusted checkpoints, especially with weights_only=True. No official PyTorch security advisory page lists version-specific bulletins beyond GitHub advisories.

Citations:


🌐 Web query:

Pillow 10.4.0 CVE vulnerability security advisory PyPI

💡 Result:

Pillow 10.4.0 is vulnerable to CVE-2026-25990 (GHSA-cfh3-3jmp-rvhc), an out-of-bounds write flaw in the PSD image handler exploitable via Image.open with a malicious PSD file. This affects versions from 10.3.0 to 12.1.0. The fix is in Pillow 12.1.1, released 2026-02-11, which checks tile extents to prevent negative offsets causing OOB writes. Workaround: Use Image.open(formats=['non-psd-formats']) to exclude PSD. Official security advisory: GitHub GHSA-cfh3-3jmp-rvhc. PyPI page for 10.4.0 lists this vulnerability. No other CVEs specific to exactly 10.4.0; earlier ones like CVE-2024-28219 fixed in 10.3.0.

Citations:


🏁 Script executed:

cat -n ./requirements.txt

Repository: Limbicnation/ComfyUI-TransparencyBackgroundRemover

Length of output: 323


Raise the minimum versions to patched releases to avoid known vulnerabilities.

torch>=2.1.0 is vulnerable to multiple CVEs including RCE via deserialization (GHSA-53q9-r3pm-6pq6, CVE-2025-32434; fixed in 2.6.0), heap-based buffer overflow (fixed in 2.2.0), and use-after-free (fixed in 2.2.0).

Pillow>=10.4.0 is vulnerable to CVE-2026-25990 (GHSA-cfh3-3jmp-rvhc), an out-of-bounds write in the PSD image handler; fixed in 12.1.1.

Constraint resolvers can still select these vulnerable releases. Update the minimums to torch>=2.6.0 and Pillow>=12.1.1, or provide a constraints/lock file.

🧰 Tools
🪛 GitHub Actions: ComfyUI Node CI

[error] 1-1: pip install failed while downloading torch (torch-2.8.0-cp39-cp39-manylinux_2_28_x86_64.whl). Error: The operation was canceled.

🪛 OSV Scanner (2.3.5)

[HIGH] 3-3: pillow 10.4.0: Pillow affected by out-of-bounds write when loading PSD images

(GHSA-cfh3-3jmp-rvhc)


[CRITICAL] 1-1: torch 2.1.0: undefined

(PYSEC-2024-250)


[CRITICAL] 1-1: torch 2.1.0: undefined

(PYSEC-2024-251)


[CRITICAL] 1-1: torch 2.1.0: undefined

(PYSEC-2024-252)


[CRITICAL] 1-1: torch 2.1.0: undefined

(PYSEC-2024-259)


[CRITICAL] 1-1: torch 2.1.0: undefined

(PYSEC-2025-41)


[CRITICAL] 1-1: torch 2.1.0: PyTorch susceptible to local Denial of Service

(GHSA-3749-ghw9-m3mg)


[CRITICAL] 1-1: torch 2.1.0: PyTorch: torch.load with weights_only=True leads to remote code execution

(GHSA-53q9-r3pm-6pq6)


[CRITICAL] 1-1: torch 2.1.0: PyTorch heap buffer overflow vulnerability

(GHSA-5pcm-hx3q-hm94)


[CRITICAL] 1-1: torch 2.1.0: PyTorch Improper Resource Shutdown or Release vulnerability

(GHSA-887c-mr87-cxwp)


[CRITICAL] 1-1: torch 2.1.0: Pytorch use-after-free vulnerability

(GHSA-pg7h-5qx3-wjr3)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@requirements.txt` around lines 1 - 3, The requirements pin minimums for torch
and Pillow are too low and allow vulnerable releases; update the package
specifiers in requirements.txt: change torch>=2.1.0 to torch>=2.6.0 and
Pillow>=10.4.0 to Pillow>=12.1.1 (or add a constraints/lock file) so dependency
resolvers cannot select the CVE-affected versions; ensure numpy line remains
unchanged.

Comment thread requirements.txt
Comment on lines +4 to +5
opencv-python>=4.10.0
opencv-contrib-python>=4.10.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Do the PyPI packages opencv-pythonandopencv-contrib-pythoninstall the samecv2namespace, and doesopencv-contrib-pythonalready include the core OpenCV modules shipped byopencv-python?

💡 Result:

Yes, both PyPI packages opencv-python and opencv-contrib-python install the same cv2 namespace (imported via import cv2), and opencv-contrib-python includes all the core OpenCV modules that are shipped by opencv-python plus additional contrib/extra modules.

Citations:


🏁 Script executed:

# Find grabcut_remover.py and check for ximgproc usage
fd grabcut_remover.py

Repository: Limbicnation/ComfyUI-TransparencyBackgroundRemover

Length of output: 112


🏁 Script executed:

# Search for ximgproc usage in the codebase
rg "ximgproc" -n

Repository: Limbicnation/ComfyUI-TransparencyBackgroundRemover

Length of output: 169


🏁 Script executed:

# View the full requirements.txt to confirm current state
cat requirements.txt

Repository: Limbicnation/ComfyUI-TransparencyBackgroundRemover

Length of output: 260


Remove opencv-python from requirements; keep only opencv-contrib-python.

Both opencv-python and opencv-contrib-python install the same cv2 namespace, and opencv-contrib-python includes all core OpenCV modules plus additional contrib modules. Having both in the environment causes file overwrites and non-deterministic imports. Since grabcut_remover.py uses cv2.ximgproc.guidedFilter (a contrib module), the contrib wheel alone is needed.

♻️ Minimal fix
-opencv-python>=4.10.0
 opencv-contrib-python>=4.10.0
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
opencv-python>=4.10.0
opencv-contrib-python>=4.10.0
opencv-contrib-python>=4.10.0
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@requirements.txt` around lines 4 - 5, Remove the duplicate OpenCV package
from requirements by deleting the opencv-python>=4.10.0 entry and keeping only
opencv-contrib-python>=4.10.0; this prevents cv2 namespace collisions and
ensures contrib modules used by grabcut_remover.py (e.g.,
cv2.ximgproc.guidedFilter) are available from the single installed wheel.

Comment thread src/validation.py
Comment on lines +29 to +53
class ScalingParams(BaseModel):
"""Parameters for image scaling / resize preprocessing."""

target_long_edge: int = Field(
default=1024, ge=256, le=4096,
description="Target size for longest image edge"
)
maintain_aspect: bool = Field(
default=True,
description="Preserve aspect ratio during scaling"
)
scaling_method: str = Field(
default="auto",
description="Scaling method: auto, nearest, bilinear, lanczos, power-of-8"
)

@model_validator(mode="after")
def check_scaling_method(self) -> "ScalingParams":
valid = {"auto", "nearest", "bilinear", "lanczos", "power-of-8"}
if self.scaling_method not in valid:
raise ValueError(
f"Invalid scaling_method '{self.scaling_method}'. "
f"Must be one of: {', '.join(sorted(valid))}"
)
return self

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

This schema is still out of sync with the node inputs.

grabcut_nodes.py exposes scaling_method as NEAREST|BILINEAR|BICUBIC|LANCZOS and edge_blur_amount as a float slider, but ScalingParams only allows lowercase values, omits BICUBIC, and MaskParams models blur as int. GrabCutNodeParams also does not cover output_size, custom_width, custom_height, object_class, or edge_detection_mode, so the caller cannot validate the full public surface before GPU work.

Also applies to: 56-70, 92-109, 145-150

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/validation.py` around lines 29 - 53, Update the Pydantic models to match
the node inputs: in ScalingParams update the allowed scaling_method values to
include "BICUBIC" (and accept either case or normalize input) and allow
uppercase names used by grabcut_nodes.py (or perform .lower() and validate
against a normalized set), adjust the valid set in check_scaling_method
accordingly; change edge_blur_amount to a float (and ensure
MaskParams.blend/blur field types match the node which uses float, not int); and
extend GrabCutNodeParams to include output_size, custom_width, custom_height,
object_class, and edge_detection_mode with proper types and constraints so the
public inputs are fully validated before GPU work (also update any
model_validator methods referenced like check_scaling_method to
normalize/validate these new fields).

…oat edge_blur, tests/

Steps completed (per abstract-painting-kitten.md):
1. FIX edge_blur_amount float truncation: int→float in MaskParams + removed int() casts
2. Wire invert_mask: INPUT_TYPES, method signatures, Pydantic validation, alpha inversion
   applied in both AutoGrabCutRemover and GrabCutRefinement success paths
3. Add [B,6] bbox_metadata tensor output to AutoGrabCutRemover RETURN_TYPES
4. nodes.py: @torch.no_grad() on remove_background + batch_remove_background;
   all .float()→.to(dtype=torch.float32)
5. Structured tests/ suite: conftest.py, test_grabcut_processor.py,
   test_grabcut_nodes.py, test_nodes.py, test_validation.py

Verification:
- 48/48 new tests pass
- 6/6 original test_grabcut.py tests pass
- 4/4 node import test pass
- 0 flake8 E9/F63/F7/F82 errors
- edge_blur_amount=1.7 correctly stores as float (not truncated to 1)

BREAKING: AutoGrabCutRemover RETURN_TYPES changed from 5→6 (bbox_tensor added)
Comment thread grabcut_nodes.py Outdated
Comment thread grabcut_nodes.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
grabcut_nodes.py (3)

553-598: ⚠️ Potential issue | 🟠 Major

bbox_tensor no longer matches the resized outputs.

rgba is resized before tensor creation, but bbox_output and bbox_metadata are copied from the original detection coordinates. If downstream nodes interpret them against the returned image/mask, the box is wrong whenever output_size != "ORIGINAL". Either scale the box with the same factor or explicitly mark it as source-space metadata.


515-529: ⚠️ Potential issue | 🟠 Major

Don't skip a batch item without emitting a placeholder.

Both continue paths drop the current element from processed_images, masks, all_bboxes, and all_confidences while bbox_metadata stays [batch_size, 6]. One malformed item can therefore shrink the returned batch, and an all-invalid batch will make torch.stack(...) fail on an empty list.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grabcut_nodes.py` around lines 515 - 529, The current validation uses
continue which removes the item but leaves bbox_metadata sized to batch_size
causing misalignment and potential empty-list errors; instead when hitting the
invalid-shape branches in the grabcut logic (around img_tensor / img_np
validation) append consistent placeholders to processed_images, masks,
all_bboxes, and all_confidences so batch shapes remain stable: e.g., append a
zeroed image tensor matching the expected processed image shape, a zero mask of
the expected mask shape/dtype, an empty/zero bbox structure matching other bbox
entries (or an empty array with shape (0,4)), and a matching empty/zero
confidences entry; then continue processing the next item. Ensure the
placeholders have the same dtypes and device semantics used elsewhere so
subsequent torch.stack/concats and bbox_metadata remain aligned.

606-644: ⚠️ Potential issue | 🟠 Major

Failure paths need to preserve the same spatial contract as the success path.

The result['success'] == False branch returns original HxW tensors, and the exception branch uses a hard-coded 512x512 placeholder until another item has succeeded. A resized batch that mixes success and failure will hit torch.stack(...) shape errors.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grabcut_nodes.py` around lines 606 - 644, The failure/exception paths must
produce tensors with the same HxW spatial dimensions as the successful items to
avoid torch.stack shape errors: in the exception handler (and any fallback),
compute h,w from the current item’s source image (img_np) when available, else
fall back to processed_images[0].shape[:2] if a prior item succeeded, and only
as a last resort use a fixed fallback; then allocate rgb_tensor and alpha_tensor
using those h,w values respecting output_format (RGBA -> (h,w,4), MASK ->
(h,w,1), alpha -> (h,w)). Update the exception branch that currently uses
hard-coded 512x512 to follow this same h,w selection logic so all branches
(result['success'] == False and the except block) preserve the same spatial
contract for processed_images, masks, and alpha_tensor.
♻️ Duplicate comments (2)
grabcut_nodes.py (1)

384-404: ⚠️ Potential issue | 🟠 Major

This preflight still validates placeholders instead of the actual request.

grabcut_iterations does not match the model field name, and the resize-related fields are hardcoded to target_long_edge=4096, maintain_aspect=True, and scaling_method="auto". The actual output_size, scaling_method, custom_width, custom_height, object_class, and edge_detection_mode values never get checked here.

#!/bin/bash
rg -n -C2 'validate_node_params|grabcut_iterations|iterations|target_long_edge=4096|maintain_aspect=True|scaling_method="auto"|output_size|custom_width|custom_height|edge_detection_mode|object_class' grabcut_nodes.py src/validation.py
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grabcut_nodes.py` around lines 384 - 404, The current validate_node_params
call is validating hardcoded placeholders (e.g., grabcut_iterations and
target_long_edge=4096) instead of the real request fields; update the call site
that invokes validate_node_params to pass the actual variables and model field
names: replace grabcut_iterations with iterations (or the variable used by the
model), pass output_size/target_long_edge using the computed output_size or
target_long_edge variable, use the real scaling_method, maintain_aspect,
custom_width, custom_height, object_class, and edge_detection_mode variables,
and include any other runtime parameters like auto_adjust, edge_blur_amount,
edge_refinement, bbox_safety_margin, min_bbox_size, fallback_margin_percent,
binary_threshold and invert_mask so the Pydantic check validates the true
request values (refer to the validate_node_params call in grabcut_nodes.py to
locate and update these arguments).
src/validation.py (1)

29-118: ⚠️ Potential issue | 🟠 Major

The validation schema is still out of sync with the public node inputs.

ScalingParams only accepts lowercase resize modes and still omits BICUBIC, while GrabCutNodeParams has no fields for output_size, custom_width, custom_height, object_class, or edge_detection_mode. With the current shape, a real node call cannot be fully validated before GPU work, and valid UI values will either bypass validation or be rejected once the runtime parameters are wired through.

#!/bin/bash
rg -n -C2 'class ScalingParams|class GrabCutNodeParams|scaling_method|output_size|custom_width|custom_height|object_class|edge_detection_mode|BICUBIC|NEAREST|BILINEAR|LANCZOS' src/validation.py grabcut_nodes.py
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/validation.py` around lines 29 - 118, The ScalingParams validator
currently only accepts lowercase methods and misses BICUBIC; update
ScalingParams.scaling_method validation (check_scaling_method) to accept
canonical names used by the UI (include "bicubic" / "BICUBIC" and other
uppercase variants) — implement case-insensitive checking (e.g., normalize to
lower() for comparison) or expand the valid set accordingly. Add the missing
node input fields to GrabCutNodeParams: output_size, custom_width,
custom_height, object_class, and edge_detection_mode as proper pydantic Fields
with sensible defaults and validation ranges/types, and ensure any new enum-like
fields have corresponding validators similar to check_output_format to reject
invalid UI values. Finally, run existing validators (check_scaling_method and
check_output_format) against the normalized/added fields so the combined model
GrabCutNodeParams validates the real runtime/UI inputs before GPU work.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@grabcut_nodes.py`:
- Around line 850-870: The fallback branches (the else and except blocks) append
raw image[i] and mask[i], causing mixed shapes/layouts when output_size !=
"ORIGINAL"; update both the else and except blocks to mirror the success-path
post-resize normalization: call self._apply_resize(...) on the original
RGBA/input, derive rgb = rgba[:, :, :3].astype(np.float32)/255.0 and alpha =
rgba[:, :, 3].astype(np.float32)/255.0 (apply invert_mask if needed),
premultiply rgb channels by alpha, then convert to torch.float32 tensors
(rgb_tensor, alpha_tensor) and append to refined_images/refined_masks so all
branches produce identical CHW/HW float32 tensors compatible with torch.stack.

In `@tests/test_grabcut_processor.py`:
- Around line 12-20: The test test_yolo_cache_shared_across_instances is vacuous
because yolo_model can be None on load failure; update the test to either (A)
assert against the class-level cache GrabCutProcessor._yolo_cache by stubbing
the model load so a real model object is placed in the cache and then verify
proc_a.yolo_model is proc_b.yolo_model and that GrabCutProcessor._yolo_cache
holds that object, or (B) detect the fallback case and skip the assertion when
both proc_a.yolo_model and proc_b.yolo_model are None (i.e., add a guard that
returns/marks the test skipped if both instances have None). Ensure you
reference the class GrabCutProcessor, the instance attribute yolo_model, and the
class attribute _yolo_cache when implementing the change.
- Around line 133-143: The test test_empty_cache_called_after_detect_object
currently only ensures detect_object doesn't raise; either make it actually
assert the cleanup call or rename it. To fix, spy/mocking: patch or monkeypatch
the CUDA cleanup hook (e.g., torch.cuda.empty_cache or the internal cleanup
method on grabcut_processor such as grabcut_processor._clear_cuda_cache) before
calling grabcut_processor.detect_object(sample_image_rgb) and assert the spy was
called the expected number of times; alternatively, if you only want to assert
non-raising, rename the test and docstring to reflect that behavior (e.g.,
test_detect_object_does_not_raise_on_cuda).

In `@tests/test_nodes.py`:
- Around line 16-19: The test swallows all exceptions around calls like
node.remove_background, causing false positives; replace the broad try/except
with explicit checks: use pytest.importorskip(...) at module start for optional
dependencies, catch only the expected exception type (e.g., NotImplementedError
or a specific error your API documents) around
node.remove_background/node.some_other_method, and for any other exception call
pytest.fail(f"Unexpected error: {e}") so import failures or signature/runtime
bugs fail the test; apply the same change pattern to the other blocks referenced
(the ones around lines with node.remove_background and similar calls).

---

Outside diff comments:
In `@grabcut_nodes.py`:
- Around line 515-529: The current validation uses continue which removes the
item but leaves bbox_metadata sized to batch_size causing misalignment and
potential empty-list errors; instead when hitting the invalid-shape branches in
the grabcut logic (around img_tensor / img_np validation) append consistent
placeholders to processed_images, masks, all_bboxes, and all_confidences so
batch shapes remain stable: e.g., append a zeroed image tensor matching the
expected processed image shape, a zero mask of the expected mask shape/dtype, an
empty/zero bbox structure matching other bbox entries (or an empty array with
shape (0,4)), and a matching empty/zero confidences entry; then continue
processing the next item. Ensure the placeholders have the same dtypes and
device semantics used elsewhere so subsequent torch.stack/concats and
bbox_metadata remain aligned.
- Around line 606-644: The failure/exception paths must produce tensors with the
same HxW spatial dimensions as the successful items to avoid torch.stack shape
errors: in the exception handler (and any fallback), compute h,w from the
current item’s source image (img_np) when available, else fall back to
processed_images[0].shape[:2] if a prior item succeeded, and only as a last
resort use a fixed fallback; then allocate rgb_tensor and alpha_tensor using
those h,w values respecting output_format (RGBA -> (h,w,4), MASK -> (h,w,1),
alpha -> (h,w)). Update the exception branch that currently uses hard-coded
512x512 to follow this same h,w selection logic so all branches
(result['success'] == False and the except block) preserve the same spatial
contract for processed_images, masks, and alpha_tensor.

---

Duplicate comments:
In `@grabcut_nodes.py`:
- Around line 384-404: The current validate_node_params call is validating
hardcoded placeholders (e.g., grabcut_iterations and target_long_edge=4096)
instead of the real request fields; update the call site that invokes
validate_node_params to pass the actual variables and model field names: replace
grabcut_iterations with iterations (or the variable used by the model), pass
output_size/target_long_edge using the computed output_size or target_long_edge
variable, use the real scaling_method, maintain_aspect, custom_width,
custom_height, object_class, and edge_detection_mode variables, and include any
other runtime parameters like auto_adjust, edge_blur_amount, edge_refinement,
bbox_safety_margin, min_bbox_size, fallback_margin_percent, binary_threshold and
invert_mask so the Pydantic check validates the true request values (refer to
the validate_node_params call in grabcut_nodes.py to locate and update these
arguments).

In `@src/validation.py`:
- Around line 29-118: The ScalingParams validator currently only accepts
lowercase methods and misses BICUBIC; update ScalingParams.scaling_method
validation (check_scaling_method) to accept canonical names used by the UI
(include "bicubic" / "BICUBIC" and other uppercase variants) — implement
case-insensitive checking (e.g., normalize to lower() for comparison) or expand
the valid set accordingly. Add the missing node input fields to
GrabCutNodeParams: output_size, custom_width, custom_height, object_class, and
edge_detection_mode as proper pydantic Fields with sensible defaults and
validation ranges/types, and ensure any new enum-like fields have corresponding
validators similar to check_output_format to reject invalid UI values. Finally,
run existing validators (check_scaling_method and check_output_format) against
the normalized/added fields so the combined model GrabCutNodeParams validates
the real runtime/UI inputs before GPU work.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6b493cf8-7961-41ad-9a6c-e3e5679bd84d

📥 Commits

Reviewing files that changed from the base of the PR and between 3094651 and b557c67.

📒 Files selected for processing (10)
  • grabcut_nodes.py
  • nodes.py
  • pyproject.toml
  • src/validation.py
  • tests/__init__.py
  • tests/conftest.py
  • tests/test_grabcut_nodes.py
  • tests/test_grabcut_processor.py
  • tests/test_nodes.py
  • tests/test_validation.py
✅ Files skipped from review due to trivial changes (2)
  • tests/init.py
  • tests/test_grabcut_nodes.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • pyproject.toml

Comment thread grabcut_nodes.py
Comment on lines +850 to 870
if result['success']:
rgba = result['rgba_image']
rgba = self._apply_resize(rgba, output_size, scaling_method, custom_width, custom_height)
rgb = rgba[:, :, :3].astype(np.float32) / 255.0
alpha = rgba[:, :, 3].astype(np.float32) / 255.0
if invert_mask:
alpha = 1.0 - alpha
rgba[:, :, 3] = (alpha * 255).astype(np.uint8)
for c in range(3):
rgb[:, :, c] *= alpha
rgb_tensor = torch.from_numpy(rgb).to(dtype=torch.float32)
alpha_tensor = torch.from_numpy(alpha).to(dtype=torch.float32)
refined_images.append(rgb_tensor)
refined_masks.append(alpha_tensor)
else:
refined_images.append(image[i])
refined_masks.append(mask[i])
except Exception as e:
log.error("grabcut_node.refine_error", item=i, error=str(e))
refined_images.append(image[i])
refined_masks.append(mask[i])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Resize/layout-normalize the fallback outputs before stacking.

The success path converts to output-space tensors after _apply_resize(...), but the else/except paths append the raw input tensors unchanged. With output_size != "ORIGINAL"—or any CHW input that reaches the fallback path—torch.stack(...) can fail on mixed shapes/layouts.

🧰 Tools
🪛 Ruff (0.15.7)

[warning] 867-867: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grabcut_nodes.py` around lines 850 - 870, The fallback branches (the else and
except blocks) append raw image[i] and mask[i], causing mixed shapes/layouts
when output_size != "ORIGINAL"; update both the else and except blocks to mirror
the success-path post-resize normalization: call self._apply_resize(...) on the
original RGBA/input, derive rgb = rgba[:, :, :3].astype(np.float32)/255.0 and
alpha = rgba[:, :, 3].astype(np.float32)/255.0 (apply invert_mask if needed),
premultiply rgb channels by alpha, then convert to torch.float32 tensors
(rgb_tensor, alpha_tensor) and append to refined_images/refined_masks so all
branches produce identical CHW/HW float32 tensors compatible with torch.stack.

Comment on lines +12 to +20
def test_yolo_cache_shared_across_instances(self, grabcut_processor):
"""YOLO model is cached at class level and shared between instances."""
from grabcut_remover import GrabCutProcessor

proc_a = grabcut_processor
proc_b = GrabCutProcessor()

# Both should reference the same cached model object
assert proc_a.yolo_model is proc_b.yolo_model

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

This cache test is vacuous when model init falls back to None.

GrabCutProcessor can leave yolo_model unset on load failure, so proc_a.yolo_model is proc_b.yolo_model passes even if the shared cache was never exercised. Assert on GrabCutProcessor._yolo_cache with a stubbed model load, or skip when both instances end up with None.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_grabcut_processor.py` around lines 12 - 20, The test
test_yolo_cache_shared_across_instances is vacuous because yolo_model can be
None on load failure; update the test to either (A) assert against the
class-level cache GrabCutProcessor._yolo_cache by stubbing the model load so a
real model object is placed in the cache and then verify proc_a.yolo_model is
proc_b.yolo_model and that GrabCutProcessor._yolo_cache holds that object, or
(B) detect the fallback case and skip the assertion when both proc_a.yolo_model
and proc_b.yolo_model are None (i.e., add a guard that returns/marks the test
skipped if both instances have None). Ensure you reference the class
GrabCutProcessor, the instance attribute yolo_model, and the class attribute
_yolo_cache when implementing the change.

Comment thread tests/test_grabcut_processor.py
Comment thread tests/test_nodes.py
…, cache safety, dead write removal

- RETURN_TYPES: "FLOAT" → "BBOX_TENSOR" for [B,6] metadata tensor (prevents scalar misinterpretation)
- Validation: remove hardcoded phantom params, pass actual scaling_method; fix field name to match Pydantic model
- YOLO cache: device-aware key ("model::device"), threading.Lock, CUDA-only torch.compile()
- Scaling validator: add "bicubic", case-insensitive normalization to match node uppercase values
- Remove dead rgba[:,:,3] write-back in GrabCutRefinement (only alpha used downstream)
- Add initial_mask.unsqueeze(0) for single-image 2D masks
- Remove unused Annotated import from validation.py
Comment thread grabcut_nodes.py Outdated
Comment thread src/validation.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
grabcut_nodes.py (1)

515-528: ⚠️ Potential issue | 🟠 Major

continue on invalid shape can break batch output assembly.

Line [517] and Line [528] skip appends, so processed_images/masks may be shorter than batch_size (or empty), leading to torch.stack(...) failures later.

🐛 Minimal fix
                 if len(img_tensor.shape) != 3:
                     log.warning("grabcut_node.unexpected_shape", shape=list(img_tensor.shape))
-                    continue
+                    raise ValueError(f"Unexpected tensor shape: {tuple(img_tensor.shape)}")
@@
                 if len(img_np.shape) != 3 or img_np.shape[2] not in [3, 4]:
                     log.warning("grabcut_node.invalid_shape", shape=list(img_np.shape))
-                    continue
+                    raise ValueError(f"Invalid image layout after conversion: {tuple(img_np.shape)}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grabcut_nodes.py` around lines 515 - 528, The current grabcut_node loop skips
entries on invalid shapes (when inspecting img_tensor or img_np), which causes
processed_images and masks to be shorter than batch_size and breaks later
torch.stack calls; instead of using continue in the branches that check
img_tensor.shape and img_np.shape, append a safe fallback for that index (e.g.,
a converted placeholder image tensor or a resized/zeroed image and a
corresponding zero mask) to processed_images and masks and log the error (use
the same log.warning messages), so the lists keep the original batch length;
locate the checks around img_tensor, img_np and update the branches to push
placeholders into processed_images and masks rather than calling continue.
♻️ Duplicate comments (4)
grabcut_nodes.py (2)

860-866: ⚠️ Potential issue | 🟠 Major

Normalize fallback outputs in refine_mask before stacking.

Line [861]-Line [866] append raw image[i]/mask[i] tensors. When successful items are resized/output-normalized, this can produce mixed shapes/layouts and fail torch.stack(...).

🛠️ Suggested direction
                 else:
-                    refined_images.append(image[i])
-                    refined_masks.append(mask[i])
+                    # Build fallback RGBA from current numpy inputs and apply the same resize/normalization path
+                    fallback_rgba = np.zeros((img_np.shape[0], img_np.shape[1], 4), dtype=np.uint8)
+                    fallback_rgba[:, :, :3] = img_np
+                    fallback_rgba[:, :, 3] = mask_np if mask_np.ndim == 2 else mask_np.squeeze()
+                    fallback_rgba = self._apply_resize(
+                        fallback_rgba, output_size, scaling_method, custom_width, custom_height
+                    )
+                    rgb = fallback_rgba[:, :, :3].astype(np.float32) / 255.0
+                    alpha = fallback_rgba[:, :, 3].astype(np.float32) / 255.0
+                    if invert_mask:
+                        alpha = 1.0 - alpha
+                    for c in range(3):
+                        rgb[:, :, c] *= alpha
+                    refined_images.append(torch.from_numpy(rgb).to(dtype=torch.float32))
+                    refined_masks.append(torch.from_numpy(alpha).to(dtype=torch.float32))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grabcut_nodes.py` around lines 860 - 866, The fallback branch in refine_mask
appends raw image[i] and mask[i] which can have different shapes/normalization
than the successful, resized/normalized outputs and will break torch.stack;
instead, in the exception path normalize and resize the fallback tensors the
same way as the success path (apply the same resize/transform/normalize routines
used earlier) before appending to refined_images and refined_masks so that all
items share the same shape and value range expected by torch.stack; locate
symbols refine_mask, image[i], mask[i], refined_images, refined_masks and reuse
the same preprocessing helper/logic used for successful results to produce
compatible tensors.

385-401: ⚠️ Potential issue | 🟠 Major

Validation still does not cover the full input surface.

Despite the “ALL user params” claim, this call omits object_class, output_size, custom_width, custom_height, and edge_detection_mode, so those paths bypass the new validation layer.

🛠️ Suggested direction
                 validate_node_params(
                     iterations=grabcut_iterations,
                     margin=margin_pixels,
                     confidence_threshold=confidence_threshold,
                     scaling_method=scaling_method,
                     edge_blur_amount=edge_blur_amount,
                     invert_mask=invert_mask,
                     edge_refinement_strength=edge_refinement,
                     bbox_safety_margin=bbox_safety_margin,
                     min_bbox_size=min_bbox_size,
                     fallback_margin_percent=fallback_margin_percent,
                     binary_threshold=binary_threshold,
                     output_format=output_format,
                     auto_adjust=auto_adjust,
+                    object_class=object_class,
+                    output_size=output_size,
+                    custom_width=custom_width,
+                    custom_height=custom_height,
+                    edge_detection_mode=edge_detection_mode,
                 )

This also needs matching fields in src/validation.py::GrabCutNodeParams.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grabcut_nodes.py` around lines 385 - 401, The validation call in
grabcut_nodes.py omits several parameters so they bypass validation; update the
validate_node_params(...) invocation (the call that currently passes iterations,
margin, confidence_threshold, etc.) to also include object_class, output_size,
custom_width, custom_height, and edge_detection_mode, and then add matching
fields to the GrabCutNodeParams schema in src/validation.py (ensure
types/defaults align with how these values are used in GrabCutNodeParams) so the
full input surface is validated.
grabcut_remover.py (2)

621-631: ⚠️ Potential issue | 🟠 Major

Guard empty foreground coords before min()/max() on mask-derived bbox.

Line [623] can produce an empty coordinate set when mask has nonzero soft values but no pixels >127, causing runtime failure at Line [624]/Line [625].

🐛 Minimal fix
         if detection is None:
-            if initial_mask.max() > 0:
-                coords = np.where(initial_mask > 127)
+            fg = initial_mask > 127
+            if not np.any(fg):
+                fg = initial_mask > 0
+            if np.any(fg):
+                coords = np.where(fg)
                 y1, y2 = coords[0].min(), coords[0].max()
                 x1, x2 = coords[1].min(), coords[1].max()
                 raw_bbox = (x1, y1, x2, y2)
                 x1, y1, x2, y2 = self._validate_and_fix_bbox(raw_bbox, (h, w))
                 detection = (x1, y1, x2, y2, 0.8)
             else:
                 margin = int(min(h, w) * self.fallback_margin_percent)
                 detection = (margin, margin, w - margin, h - margin, 0.5)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grabcut_remover.py` around lines 621 - 631, The bbox computation from
initial_mask can call coords = np.where(initial_mask > 127) and then
coords[0].min()/max() on an empty array; add a guard after computing coords to
check if coords[0] (or coords[1]) is non-empty and only use
_validate_and_fix_bbox when there are pixels >127, otherwise fall back to the
same margin-based detection path (using self.fallback_margin_percent) or treat
as no-detection; update the branch that sets detection (the block referencing
initial_mask, coords, raw_bbox, _validate_and_fix_bbox, and
fallback_margin_percent) to handle the empty coords case safely.

698-700: ⚠️ Potential issue | 🟠 Major

Fallback processor still initializes YOLO via super().__init__().

Line [699] calls the full parent init path, which includes _initialize_yolo(), so the fallback path can still incur model load/compile work.

🐛 Minimal fix
 class FallbackGrabCutProcessor(GrabCutProcessor):
+    def _initialize_yolo(self, model_path: Optional[str] = None) -> None:
+        self.yolo_model = None
+
     def __init__(self, **kwargs: Any) -> None:
         super().__init__(**kwargs)
-        self.yolo_model = None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grabcut_remover.py` around lines 698 - 700, The fallback processor's __init__
currently calls super().__init__(**kwargs) which triggers the parent's
_initialize_yolo and loads the YOLO model; change the constructor to avoid
invoking the parent init path that initializes YOLO (e.g., replace
super().__init__(**kwargs) with object.__init__(self) to skip parent
initialization) and ensure self.yolo_model = None remains set; reference:
__init__ and _initialize_yolo.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@grabcut_nodes.py`:
- Around line 515-528: The current grabcut_node loop skips entries on invalid
shapes (when inspecting img_tensor or img_np), which causes processed_images and
masks to be shorter than batch_size and breaks later torch.stack calls; instead
of using continue in the branches that check img_tensor.shape and img_np.shape,
append a safe fallback for that index (e.g., a converted placeholder image
tensor or a resized/zeroed image and a corresponding zero mask) to
processed_images and masks and log the error (use the same log.warning
messages), so the lists keep the original batch length; locate the checks around
img_tensor, img_np and update the branches to push placeholders into
processed_images and masks rather than calling continue.

---

Duplicate comments:
In `@grabcut_nodes.py`:
- Around line 860-866: The fallback branch in refine_mask appends raw image[i]
and mask[i] which can have different shapes/normalization than the successful,
resized/normalized outputs and will break torch.stack; instead, in the exception
path normalize and resize the fallback tensors the same way as the success path
(apply the same resize/transform/normalize routines used earlier) before
appending to refined_images and refined_masks so that all items share the same
shape and value range expected by torch.stack; locate symbols refine_mask,
image[i], mask[i], refined_images, refined_masks and reuse the same
preprocessing helper/logic used for successful results to produce compatible
tensors.
- Around line 385-401: The validation call in grabcut_nodes.py omits several
parameters so they bypass validation; update the validate_node_params(...)
invocation (the call that currently passes iterations, margin,
confidence_threshold, etc.) to also include object_class, output_size,
custom_width, custom_height, and edge_detection_mode, and then add matching
fields to the GrabCutNodeParams schema in src/validation.py (ensure
types/defaults align with how these values are used in GrabCutNodeParams) so the
full input surface is validated.

In `@grabcut_remover.py`:
- Around line 621-631: The bbox computation from initial_mask can call coords =
np.where(initial_mask > 127) and then coords[0].min()/max() on an empty array;
add a guard after computing coords to check if coords[0] (or coords[1]) is
non-empty and only use _validate_and_fix_bbox when there are pixels >127,
otherwise fall back to the same margin-based detection path (using
self.fallback_margin_percent) or treat as no-detection; update the branch that
sets detection (the block referencing initial_mask, coords, raw_bbox,
_validate_and_fix_bbox, and fallback_margin_percent) to handle the empty coords
case safely.
- Around line 698-700: The fallback processor's __init__ currently calls
super().__init__(**kwargs) which triggers the parent's _initialize_yolo and
loads the YOLO model; change the constructor to avoid invoking the parent init
path that initializes YOLO (e.g., replace super().__init__(**kwargs) with
object.__init__(self) to skip parent initialization) and ensure self.yolo_model
= None remains set; reference: __init__ and _initialize_yolo.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b2dc4566-06df-43f4-8fca-b85f32a7a152

📥 Commits

Reviewing files that changed from the base of the PR and between b557c67 and 77b189a.

📒 Files selected for processing (5)
  • grabcut_nodes.py
  • grabcut_remover.py
  • src/validation.py
  • tests/test_grabcut_nodes.py
  • tests/test_validation.py
✅ Files skipped from review due to trivial changes (1)
  • tests/test_grabcut_nodes.py

…tion capture, import cleanup

- Move initial_mask unsqueeze guard outside non-batch branch to prevent
  silent data corruption when 2D mask meets 4D batched image
- Capture Pydantic validation return values and use normalised fields
  (scaling_method, iterations, margin, etc.) instead of discarding them
- Add .upper() to _RESAMPLING_MAP lookup for case-insensitive safety
- Simplify double-try sys.path.insert to single try with upfront path
- Add bicubic to ScalingParams description to match validator valid set
No longer needed — integration details tracked elsewhere.
Comment thread grabcut_nodes.py
else:
batch_size = 1
image = image.unsqueeze(0)
mask = mask.unsqueeze(0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 The Roast: Oh, the irony. This commit heroically fixed initial_mask getting fed a 1D row in the batch path — then took three steps left and left mask with the exact same vulnerability. mask.unsqueeze(0) lives inside the else branch, so when image arrives as [B, H, W, C] (4D, taking the if path), a 2D [H, W] mask skips the unsqueeze entirely. Then mask[i] on line 852 gives you a 1D row of pixels instead of a 2D slice, and mask_np.squeeze() on line 854 happily returns a scalar. The fix that inspired this commit is 300 lines above — and the twin bug is still right here.

🩹 The Fix: Hoist the mask unsqueeze out of the else block, mirroring what was done for initial_mask:

Suggested change
mask = mask.unsqueeze(0)
mask = mask.unsqueeze(0)
# Ensure mask has batch dimension regardless of image path
if len(mask.shape) == 2:
mask = mask.unsqueeze(0)

📏 Severity: warning

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
grabcut_nodes.py (1)

518-531: ⚠️ Potential issue | 🔴 Critical

continue here can break batch output consistency.

Line 520 and Line 531 skip appending fallback tensors for that item. Later torch.stack(processed_images) can fail (or return fewer items than batch_size).

🛠️ Route invalid-shape items through the existing `except` fallback
                 if len(img_tensor.shape) != 3:
                     log.warning("grabcut_node.unexpected_shape", shape=list(img_tensor.shape))
-                    continue
+                    raise ValueError(f"Unexpected image tensor shape: {tuple(img_tensor.shape)}")
@@
                 if len(img_np.shape) != 3 or img_np.shape[2] not in [3, 4]:
                     log.warning("grabcut_node.invalid_shape", shape=list(img_np.shape))
-                    continue
+                    raise ValueError(f"Invalid image array shape: {tuple(img_np.shape)}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grabcut_nodes.py` around lines 518 - 531, The code currently uses "continue"
in the grabcut processing loop when img_tensor/img_np have unexpected shapes
which skips adding a fallback and can break torch.stack(processed_images);
instead, when detecting invalid shape (the checks around img_tensor and img_np
and the log.warning calls), route that item through the existing fallback path
by appending the same fallback tensor used in the except block to
processed_images (or by raising the same exception that triggers the except
fallback) rather than continuing; update the branches that log
"grabcut_node.unexpected_shape" and "grabcut_node.invalid_shape" to add the
fallback entry to processed_images so processed_images length remains equal to
batch size and torch.stack(processed_images) succeeds.
♻️ Duplicate comments (3)
grabcut_nodes.py (3)

386-400: ⚠️ Potential issue | 🟠 Major

Validation still misses part of the runtime input surface.

This call validates only a subset of parameters; output_size, custom_width, custom_height, object_class, and edge_detection_mode are still unvalidated at runtime.

✅ Pass the full runtime parameter set
                 validated = validate_node_params(
                     iterations=grabcut_iterations,
                     margin=margin_pixels,
                     confidence_threshold=confidence_threshold,
                     scaling_method=scaling_method,
                     edge_blur_amount=edge_blur_amount,
                     invert_mask=invert_mask,
                     edge_refinement_strength=edge_refinement,
                     bbox_safety_margin=bbox_safety_margin,
                     min_bbox_size=min_bbox_size,
                     fallback_margin_percent=fallback_margin_percent,
                     binary_threshold=binary_threshold,
                     output_format=output_format,
                     auto_adjust=auto_adjust,
+                    output_size=output_size,
+                    custom_width=custom_width,
+                    custom_height=custom_height,
+                    object_class=object_class,
+                    edge_detection_mode=edge_detection_mode,
                 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grabcut_nodes.py` around lines 386 - 400, The runtime validation call to
validate_node_params currently passes a subset of inputs (iterations, margin,
confidence_threshold, scaling_method, etc.) but omits output_size, custom_width,
custom_height, object_class, and edge_detection_mode; update the call site where
validate_node_params(...) is invoked to include these missing parameters
(output_size, custom_width, custom_height, object_class, edge_detection_mode) so
the full runtime parameter surface is validated, and confirm
validate_node_params signature and implementation accept and validate those same
fields (adding them to the function if necessary).

869-875: ⚠️ Potential issue | 🟠 Major

refine_mask fallback path is not shape/layout-normalized.

Lines 870-875 append raw image[i]/mask[i] while success path appends resized float32 tensors. With mixed success/failure, torch.stack can fail.

♻️ Make fallback use the same post-processing contract as success path
-                else:
-                    refined_images.append(image[i])
-                    refined_masks.append(mask[i])
+                else:
+                    rgba_input = np.zeros((img_np.shape[0], img_np.shape[1], 4), dtype=np.uint8)
+                    rgba_input[:, :, :3] = img_np[:, :, :3]
+                    rgba_input[:, :, 3] = mask_np.astype(np.uint8)
+                    rgba_input = self._apply_resize(rgba_input, output_size, scaling_method, custom_width, custom_height)
+                    rgb = rgba_input[:, :, :3].astype(np.float32) / 255.0
+                    alpha = rgba_input[:, :, 3].astype(np.float32) / 255.0
+                    if invert_mask:
+                        alpha = 1.0 - alpha
+                    for c in range(3):
+                        rgb[:, :, c] *= alpha
+                    refined_images.append(torch.from_numpy(rgb).to(dtype=torch.float32))
+                    refined_masks.append(torch.from_numpy(alpha).to(dtype=torch.float32))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grabcut_nodes.py` around lines 869 - 875, The except block in
grabcut_nodes.py appends raw image[i] and mask[i] to
refined_images/refined_masks (logged as "grabcut_node.refine_error"), but the
success path appends resized, float32-normalized tensors, causing torch.stack to
fail when results are mixed; update the fallback to apply the same
post-processing as the success path (resize/convert dtype to float32 and
normalize/transpose if the success branch does) by reusing the same helper or
inline steps used there (the same resizing/astype/normalization logic that
produces the items pushed to refined_images/refined_masks) so the fallback items
match shape and dtype before appending.

835-842: ⚠️ Potential issue | 🟠 Major

Normalize mask batch dimensions before refinement batching.

For batched image inputs, a 2D or singleton mask can still be mis-indexed by mask[i] (wrong sample alignment).

🧩 Add mask shape normalization before the batch split
-        # Handle batch
+        # Normalize mask dimensions first
+        if len(mask.shape) == 2:
+            mask = mask.unsqueeze(0)
+
+        # Handle batch
         if len(image.shape) == 4:
             batch_size = image.shape[0]
+            if len(mask.shape) == 3 and mask.shape[0] == 1 and batch_size > 1:
+                mask = mask.expand(batch_size, -1, -1)
         else:
             batch_size = 1
             image = image.unsqueeze(0)
-            mask = mask.unsqueeze(0)
+            if len(mask.shape) == 2:
+                mask = mask.unsqueeze(0)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grabcut_nodes.py` around lines 835 - 842, The batch-handling block can
misalign masks when image is 4D but mask is 2D or has a singleton batch; before
splitting into per-sample entries (the code that checks len(image.shape) == 4
and then uses mask[i]), normalize mask batch dimensions to match image: detect
batch_size from image, then if mask.dim() == 2 or (mask.dim() == 3 and
mask.shape[0] == 1) expand/repeat the mask to shape (batch_size, H, W) on the
same device/dtype as image so mask[i] indexes align with image[i]; perform this
normalization immediately before the existing batch-split logic in this function
(the block handling image/mask batch sizing).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@grabcut_nodes.py`:
- Around line 631-640: The fallback that creates 512x512 tensors causes
torch.stack shape mismatches; replace the hardcoded fallback with dimensions
derived from the current sample or output settings instead of fixed values:
compute h,w from the same source used for other processed items (e.g., the
current sample image shape or the target output/resized dimensions used
elsewhere) and then allocate rgb_tensor and alpha_tensor with those h,w values,
honoring output_format when creating rgb_tensor; update the logic around
processed_images, rgb_tensor and alpha_tensor so all items use the same derived
dimensions.

In `@src/validation.py`:
- Around line 12-152: The models currently accept unknown fields silently; add
Pydantic v2 extra forbidding by setting model_config = {"extra": "forbid", ...}
on each validation model: GrabCutParams, ScalingParams, MaskParams, BBoxParams,
and GrabCutNodeParams (merge with existing model_config entries, e.g. keep
"str_strip_whitespace": True and add "extra": "forbid"); this ensures unknown
keys (e.g. iterationz) raise validation errors instead of being ignored.

---

Outside diff comments:
In `@grabcut_nodes.py`:
- Around line 518-531: The code currently uses "continue" in the grabcut
processing loop when img_tensor/img_np have unexpected shapes which skips adding
a fallback and can break torch.stack(processed_images); instead, when detecting
invalid shape (the checks around img_tensor and img_np and the log.warning
calls), route that item through the existing fallback path by appending the same
fallback tensor used in the except block to processed_images (or by raising the
same exception that triggers the except fallback) rather than continuing; update
the branches that log "grabcut_node.unexpected_shape" and
"grabcut_node.invalid_shape" to add the fallback entry to processed_images so
processed_images length remains equal to batch size and
torch.stack(processed_images) succeeds.

---

Duplicate comments:
In `@grabcut_nodes.py`:
- Around line 386-400: The runtime validation call to validate_node_params
currently passes a subset of inputs (iterations, margin, confidence_threshold,
scaling_method, etc.) but omits output_size, custom_width, custom_height,
object_class, and edge_detection_mode; update the call site where
validate_node_params(...) is invoked to include these missing parameters
(output_size, custom_width, custom_height, object_class, edge_detection_mode) so
the full runtime parameter surface is validated, and confirm
validate_node_params signature and implementation accept and validate those same
fields (adding them to the function if necessary).
- Around line 869-875: The except block in grabcut_nodes.py appends raw image[i]
and mask[i] to refined_images/refined_masks (logged as
"grabcut_node.refine_error"), but the success path appends resized,
float32-normalized tensors, causing torch.stack to fail when results are mixed;
update the fallback to apply the same post-processing as the success path
(resize/convert dtype to float32 and normalize/transpose if the success branch
does) by reusing the same helper or inline steps used there (the same
resizing/astype/normalization logic that produces the items pushed to
refined_images/refined_masks) so the fallback items match shape and dtype before
appending.
- Around line 835-842: The batch-handling block can misalign masks when image is
4D but mask is 2D or has a singleton batch; before splitting into per-sample
entries (the code that checks len(image.shape) == 4 and then uses mask[i]),
normalize mask batch dimensions to match image: detect batch_size from image,
then if mask.dim() == 2 or (mask.dim() == 3 and mask.shape[0] == 1)
expand/repeat the mask to shape (batch_size, H, W) on the same device/dtype as
image so mask[i] indexes align with image[i]; perform this normalization
immediately before the existing batch-split logic in this function (the block
handling image/mask batch sizing).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5921949c-1943-4fb4-a0d4-aa8684d6c3b7

📥 Commits

Reviewing files that changed from the base of the PR and between 77b189a and 58906fe.

📒 Files selected for processing (2)
  • grabcut_nodes.py
  • src/validation.py

Comment thread grabcut_nodes.py Outdated
Comment thread src/validation.py
Comment on lines +12 to +152
class GrabCutParams(BaseModel):
"""Parameters for the core GrabCut algorithm."""

iterations: int = Field(default=5, ge=1, le=100, description="GrabCut iterations (1-100)")
margin: int = Field(default=20, ge=0, le=500, description="Margin around detected object in pixels")
edge_threshold: float = Field(
default=0.5, ge=0.0, le=1.0,
description="Edge detection threshold for auto-adjustment"
)
confidence_threshold: float = Field(
default=0.5, ge=0.0, le=1.0,
description="YOLO confidence threshold"
)

model_config = {"str_strip_whitespace": True}


class ScalingParams(BaseModel):
"""Parameters for image scaling / resize preprocessing."""

target_long_edge: int = Field(
default=1024, ge=256, le=4096,
description="Target size for longest image edge"
)
maintain_aspect: bool = Field(
default=True,
description="Preserve aspect ratio during scaling"
)
scaling_method: str = Field(
default="auto",
description="Scaling method: auto, nearest, bilinear, bicubic, lanczos, power-of-8"
)

@model_validator(mode="after")
def check_scaling_method(self) -> "ScalingParams":
valid = {"auto", "nearest", "bilinear", "bicubic", "lanczos", "power-of-8"}
normalised = self.scaling_method.lower()
if normalised not in valid:
raise ValueError(
f"Invalid scaling_method '{self.scaling_method}'. "
f"Must be one of: {', '.join(sorted(valid))}"
)
self.scaling_method = normalised
return self


class MaskParams(BaseModel):
"""Parameters for mask post-processing."""

edge_blur_amount: float = Field(
default=0.0, ge=0.0, le=20.0,
description="Gaussian blur sigma for mask edge softening (0=off, 0.1-20.0)"
)
invert_mask: bool = Field(
default=False,
description="Invert the output mask (foreground becomes background)"
)
edge_refinement_strength: float = Field(
default=0.7, ge=0.0, le=1.0,
description="Strength of edge refinement pass"
)

model_config = {"str_strip_whitespace": True}


class BBoxParams(BaseModel):
"""Bounding-box / detection parameters."""

bbox_safety_margin: int = Field(
default=30, ge=0, le=200,
description="Safety margin added to detected bounding boxes in pixels"
)
min_bbox_size: int = Field(
default=64, ge=8, le=1024,
description="Minimum bounding box side length in pixels"
)
fallback_margin_percent: float = Field(
default=0.2, ge=0.01, le=0.5,
description="When no object is detected, use this fraction of image size as margin"
)


class GrabCutNodeParams(GrabCutParams, ScalingParams, MaskParams, BBoxParams):
"""Combined validation model for AutoGrabCutRemover node parameters.

Used to validate ALL user inputs in a single pass before any GPU work.
"""

binary_threshold: int = Field(
default=200, ge=0, le=255,
description="Threshold for binary mask generation"
)
output_format: str = Field(
default="RGBA",
description="Output format: RGBA or MASK"
)
auto_adjust: bool = Field(
default=True,
description="Enable automatic parameter adjustment based on image analysis"
)

@model_validator(mode="after")
def check_output_format(self) -> "GrabCutNodeParams":
valid = {"RGBA", "MASK"}
if self.output_format not in valid:
raise ValueError(
f"Invalid output_format '{self.output_format}'. Must be one of: {', '.join(sorted(valid))}"
)
return self


# ---------------------------------------------------------------------------
# Convenience validator functions (call these at the top of each execute())
# ---------------------------------------------------------------------------

def validate_grabcut_params(**kwargs: Any) -> GrabCutParams:
"""Validate and return GrabCutParams. Raises ValueError on failure."""
return GrabCutParams(**kwargs)


def validate_scaling_params(**kwargs: Any) -> ScalingParams:
"""Validate and return ScalingParams. Raises ValueError on failure."""
return ScalingParams(**kwargs)


def validate_mask_params(**kwargs: Any) -> MaskParams:
"""Validate and return MaskParams. Raises ValueError on failure."""
return MaskParams(**kwargs)


def validate_bbox_params(**kwargs: Any) -> BBoxParams:
"""Validate and return BBoxParams. Raises ValueError on failure."""
return BBoxParams(**kwargs)


def validate_node_params(**kwargs: Any) -> GrabCutNodeParams:
"""Validate ALL parameters for a GrabCut node.

Call this at the START of every execute() method before any processing.
"""
return GrabCutNodeParams(**kwargs)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate the file and check its content
fd -t f "validation.py" --type f 2>/dev/null | head -20

Repository: Limbicnation/ComfyUI-TransparencyBackgroundRemover

Length of output: 136


🏁 Script executed:

# Find Pydantic version configuration
fd -t f -E node_modules "(pyproject.toml|requirements.txt|setup.py|setup.cfg|poetry.lock|pipfile)" 2>/dev/null | head -10

Repository: Limbicnation/ComfyUI-TransparencyBackgroundRemover

Length of output: 125


🏁 Script executed:

# Search for imports from pydantic in the codebase to understand which version
rg "from pydantic import|import pydantic" --type py 2>/dev/null | head -20

Repository: Limbicnation/ComfyUI-TransparencyBackgroundRemover

Length of output: 228


🏁 Script executed:

# Check Pydantic version
cat requirements.txt

Repository: Limbicnation/ComfyUI-TransparencyBackgroundRemover

Length of output: 260


🏁 Script executed:

# Also check pyproject.toml
cat pyproject.toml

Repository: Limbicnation/ComfyUI-TransparencyBackgroundRemover

Length of output: 1136


🏁 Script executed:

# Examine the test file to understand expected behavior
cat -n tests/test_validation.py

Repository: Limbicnation/ComfyUI-TransparencyBackgroundRemover

Length of output: 8713


🏁 Script executed:

# Check the actual src/validation.py file content
cat -n src/validation.py

Repository: Limbicnation/ComfyUI-TransparencyBackgroundRemover

Length of output: 6291


🏁 Script executed:

# Search for usage of these validators in the codebase
rg "validate_node_params|validate_grabcut_params|validate_scaling_params|validate_mask_params|validate_bbox_params|GrabCutNodeParams" --type py -B 2 -A 2 | head -60

Repository: Limbicnation/ComfyUI-TransparencyBackgroundRemover

Length of output: 3619


🏁 Script executed:

# Search for any tests that verify unknown field handling
rg "extra.*forbid|unknown.*field|extra.*field" --type py -i

Repository: Limbicnation/ComfyUI-TransparencyBackgroundRemover

Length of output: 76


🏁 Script executed:

# Check if there are any tests that intentionally pass extra fields
rg "GrabCutNodeParams\(|GrabCutParams\(|ScalingParams\(|MaskParams\(|BBoxParams\(" tests/ --type py -A 5 | grep -E "(unknown|extra|typo|misspell)" -i

Repository: Limbicnation/ComfyUI-TransparencyBackgroundRemover

Length of output: 76


🏁 Script executed:

# Search for any actual usage of these validators in the codebase (not just imports/tests)
fd -t f "\.py$" -not -path "*/tests/*" -not -path "*/__pycache__/*" | xargs rg "validate_node_params|GrabCutNodeParams|validate_grabcut_params" 2>/dev/null | head -40

Repository: Limbicnation/ComfyUI-TransparencyBackgroundRemover

Length of output: 2477


🏁 Script executed:

# Check Pydantic v2 documentation on default extra field behavior
# First, let's create a simple test to confirm the behavior
python3 << 'EOF'
# Test Pydantic v2 default extra field behavior
from pydantic import BaseModel, Field

class TestModel(BaseModel):
    name: str = Field(default="test")
    model_config = {"str_strip_whitespace": True}

# Try creating an instance with an extra field
try:
    instance = TestModel(name="hello", extra_field="should_be_ignored")
    print(f"Extra field silently ignored: {instance}")
    print(f"Extra field present in model: {hasattr(instance, 'extra_field')}")
    print(f"Model dict: {instance.model_dump()}")
except Exception as e:
    print(f"Error: {e}")

# Test with extra="forbid"
from pydantic import ConfigDict

class StrictTestModel(BaseModel):
    name: str = Field(default="test")
    model_config = ConfigDict(str_strip_whitespace=True, extra="forbid")

try:
    instance2 = StrictTestModel(name="hello", extra_field="should_fail")
    print(f"With extra='forbid': {instance2}")
except Exception as e:
    print(f"With extra='forbid' - Error (expected): {type(e).__name__}: {e}")
EOF

Repository: Limbicnation/ComfyUI-TransparencyBackgroundRemover

Length of output: 494


🏁 Script executed:

# Check if there are any existing tests that verify extra field rejection
grep -r "extra.*forbid\|forbid.*extra\|unknown.*field" tests/ --include="*.py" 2>/dev/null

Repository: Limbicnation/ComfyUI-TransparencyBackgroundRemover

Length of output: 76


🏁 Script executed:

# Look for the actual usage context in grabcut_nodes.py to understand the practical impact
fd -t f "grabcut_nodes.py" | xargs cat | head -80

Repository: Limbicnation/ComfyUI-TransparencyBackgroundRemover

Length of output: 2576


Add extra="forbid" to reject unknown fields in all validation models.

Models at lines 12, 29, 58, and 77 silently ignore unexpected keys by default in Pydantic v2. This allows misspelled parameters to pass validation undetected, undermining the "Security-hardened" design claim. For example, validate_node_params(iterationz=5) would be accepted and silently dropped instead of raising an error.

Proposed fix
-from pydantic import BaseModel, Field, model_validator
+from pydantic import BaseModel, Field, model_validator, ConfigDict
+
+
+class _StrictModel(BaseModel):
+    model_config = ConfigDict(str_strip_whitespace=True, extra="forbid")
 
 
-class GrabCutParams(BaseModel):
+class GrabCutParams(_StrictModel):
@@
-    model_config = {"str_strip_whitespace": True}
@@
-class ScalingParams(BaseModel):
+class ScalingParams(_StrictModel):
@@
-class MaskParams(BaseModel):
+class MaskParams(_StrictModel):
@@
-    model_config = {"str_strip_whitespace": True}
@@
-class BBoxParams(BaseModel):
+class BBoxParams(_StrictModel):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/validation.py` around lines 12 - 152, The models currently accept unknown
fields silently; add Pydantic v2 extra forbidding by setting model_config =
{"extra": "forbid", ...} on each validation model: GrabCutParams, ScalingParams,
MaskParams, BBoxParams, and GrabCutNodeParams (merge with existing model_config
entries, e.g. keep "str_strip_whitespace": True and add "extra": "forbid"); this
ensures unknown keys (e.g. iterationz) raise validation errors instead of being
ignored.

…ro, dead field removal

- Move mask.unsqueeze(0) outside else branch in GrabCutRefinement.refine_mask
  (same bug pattern fixed in AutoGrabCutRemover, line 841)
- Replace bare `continue` in batch validation loop with fallback tensor append
  to prevent torch.stack crash on empty list and bbox_metadata misalignment
- Derive exception-handler fallback h,w from input tensor instead of first
  processed item to avoid shape mismatch across batch items
- Guard FFT peak_ratio division against zero mean on uniform images
- Remove dead edge_threshold field from GrabCutParams (never passed from nodes)
- Log warning when Pydantic validation is unavailable instead of silent skip
Comment thread grabcut_nodes.py Outdated
@Limbicnation
Limbicnation merged commit 711b5be into main Mar 31, 2026
5 checks passed
@Limbicnation
Limbicnation deleted the feature/grabcut-h100-optimized branch March 31, 2026 04:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant