Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save djdarcy/f7aaf10d36f2c9f207e948e6f39e8ad7 to your computer and use it in GitHub Desktop.

Select an option

Save djdarcy/f7aaf10d36f2c9f207e948e6f39e8ad7 to your computer and use it in GitHub Desktop.
PreviewBridge Additional Enhancements (IS_CHANGED, content comparison, debug logging)

PreviewBridge Additional Enhancements

Context: These are additional features developed during the ComfyUI v1.34+ compatibility fix that weren't included in the minimal PR to keep the fix focused. They may be valuable for future improvements.

Original commit: 735c997 (with debug logging and content-based comparison) Minimal PR commit: 92e7173 (clean fix without these features)


1. Content-Based Cache Invalidation (IS_CHANGED)

Problem Addressed

ComfyUI's execution caching may pass the same tensor reference even when content has changed. The current object identity check (is not) can miss these cases.

Solution

Add IS_CHANGED classmethod that computes a fingerprint based on tensor content:

# For PreviewBridge
@classmethod
def IS_CHANGED(cls, images, image, unique_id, block=False, restore_mask="never", **kwargs):
    """Compute cache key based on image content fingerprint.

    This ensures ComfyUI properly invalidates the node cache when the input
    image tensor content changes, even if the object reference is the same.
    """
    import hashlib

    # Handle case where images is None (can happen during graph validation)
    if images is None:
        return "no_images"

    # Quick fingerprint: shape + sample pixels from corners
    shape_str = f"{images.shape}"

    try:
        if images.numel() > 0:
            flat = images.reshape(-1)
            # Sample first and last 4 values as fingerprint
            first = flat[:min(4, flat.numel())].cpu().numpy().tobytes()
            last = flat[-min(4, flat.numel()):].cpu().numpy().tobytes()
        else:
            first = last = b''
    except Exception:
        first = last = b''

    fingerprint = hashlib.md5(shape_str.encode() + first + last).hexdigest()

    # Include restore_mask in key so mode changes trigger refresh
    return f"{fingerprint}_{restore_mask}"
# For PreviewBridgeLatent
@classmethod
def IS_CHANGED(cls, latent, image, preview_method, vae_opt=None, block=False, unique_id=None, restore_mask='never', **kwargs):
    """Compute cache key based on latent content fingerprint."""
    import hashlib

    samples = latent.get('samples')
    if samples is None:
        return "no_samples"

    shape_str = f"{samples.shape}"

    try:
        if samples.numel() > 0:
            flat = samples.reshape(-1)
            first = flat[:min(4, flat.numel())].cpu().numpy().tobytes()
            last = flat[-min(4, flat.numel()):].cpu().numpy().tobytes()
        else:
            first = last = b''
    except Exception:
        first = last = b''

    fingerprint = hashlib.md5(shape_str.encode() + first + last).hexdigest()

    # Include restore_mask and preview_method in key so changes trigger refresh
    return f"{fingerprint}_{restore_mask}_{preview_method}"

Benefits

  • Detects content changes even when object reference is the same
  • Includes restore_mask mode in cache key so mode changes trigger refresh
  • Low overhead (only samples 8 values total)

2. Content-Based Image/Latent Comparison

Problem Addressed

The current cache comparison uses is not (object identity), which can fail when ComfyUI's execution caching passes the same tensor reference with different content.

Solution

Replace object identity with content-based comparison:

# For PreviewBridge
@staticmethod
def _images_match(cached, current):
    """Compare images using shape and sample pixels for content equality.

    This replaces object identity check (is not) with content-based comparison.
    """
    if cached is None or current is None:
        return cached is current

    if cached.shape != current.shape:
        return False

    # Quick sample comparison - check corners
    try:
        flat_cached = cached.reshape(-1)
        flat_current = current.reshape(-1)
        n = min(4, flat_cached.numel())

        # Compare first and last samples
        first_match = torch.equal(flat_cached[:n], flat_current[:n])
        last_match = torch.equal(flat_cached[-n:], flat_current[-n:])

        return first_match and last_match
    except Exception:
        return False
# For PreviewBridgeLatent
@staticmethod
def _latents_match(cached_latent, current_latent):
    """Compare latents using shape and sample values for content equality."""
    if cached_latent is None or current_latent is None:
        return cached_latent is current_latent

    cached_samples = cached_latent.get('samples')
    current_samples = current_latent.get('samples')

    if cached_samples is None or current_samples is None:
        return cached_samples is current_samples

    if cached_samples.shape != current_samples.shape:
        return False

    # Quick sample comparison - check corners
    try:
        flat_cached = cached_samples.reshape(-1)
        flat_current = current_samples.reshape(-1)
        n = min(4, flat_cached.numel())

        first_match = torch.equal(flat_cached[:n], flat_current[:n])
        last_match = torch.equal(flat_cached[-n:], flat_current[-n:])

        return first_match and last_match
    except Exception:
        return False

Usage in doit()

Replace the object identity check:

# Before (object identity)
elif core.preview_bridge_cache[unique_id][0] is not images:

# After (content-based)
elif not PreviewBridge._images_match(core.preview_bridge_cache[unique_id][0], images):

3. Optional Debug Logging

Use Case

Enable verbose logging for diagnosing issues without modifying code.

JavaScript (impact-image-util.js)

console.log('[PreviewBridge] impact-image-util.js LOADED - NEW APPROACH v3');

// In nodeCreated:
console.log('[PreviewBridge] nodeCreated called for:', node.comfyClass, 'node.id:', node.id);

// In onExecuted:
console.log('[PreviewBridge] onExecuted: resetting widget value to:', newValue);
console.log('[PreviewBridge] onExecuted: current widget value was:', w.value);
console.log('[PreviewBridge] onExecuted: widget value after reset:', w.value);

// In imgs setter:
console.log('[PreviewBridge] imgs SETTER called, v length:', v ? v.length : 'null');
console.log('[PreviewBridge] imgs SETTER current widget value:', w.value);

Python (bridge_nodes.py)

# In IS_CHANGED:
logging.warning(f"[PreviewBridge DEBUG] IS_CHANGED called for unique_id={unique_id}")
logging.warning(f"[PreviewBridge DEBUG] IS_CHANGED returning: {result[:20]}...")

# In doit():
logging.warning(f"[PreviewBridge DEBUG] doit called: unique_id={unique_id}, restore_mask={restore_mask}")
logging.warning(f"[PreviewBridge DEBUG] images shape: {images.shape}, image widget: {image[:50] if image else 'None'}...")
logging.warning(f"[PreviewBridge DEBUG] cache exists: {unique_id in core.preview_bridge_cache}")
logging.warning(f"[PreviewBridge DEBUG] Cache miss - need_refresh=True, images_changed=True")
logging.warning(f"[PreviewBridge DEBUG] Images DON'T match - need_refresh=True, images_changed=True")
logging.warning(f"[PreviewBridge DEBUG] Images MATCH - need_refresh=False, images_changed=False")
logging.warning(f"[PreviewBridge DEBUG] Saved NEW image: {image2[0]['filename']}")

Recommended: Environment Variable Toggle

import os
PB_DEBUG = os.environ.get('IMPACT_PREVIEW_BRIDGE_DEBUG', '').lower() == 'true'

def pb_log(msg):
    if PB_DEBUG:
        logging.warning(f"[PB DEBUG] {msg}")

Why These Weren't in the Minimal PR

The minimal PR focused solely on fixing the ComfyUI v1.34+ compatibility issue. These enhancements address potential edge cases that weren't observed during testing:

  1. IS_CHANGED - May help with ComfyUI's execution caching, but the current fix works without it
  2. Content comparison - Object identity currently works in tested scenarios
  3. Debug logging - Useful during development but adds noise to production logs

These could be valuable additions if users report edge cases where the minimal fix doesn't fully address their workflow.


Reference

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment