refactor of some nodes

This commit is contained in:
Fred Bliss
2025-09-02 11:15:36 -05:00
parent 71bea5707c
commit cfbb4073cb
6 changed files with 1063 additions and 544 deletions
+26 -23
View File
@@ -1,6 +1,31 @@
# Changelog
## v1.2 (December 28, 2024)
## v1.3
### Added
- Resize modes for `QwenMultiReferenceHandler` to prevent image distortion
- `match_first`: resize all to image1 dimensions (original behavior)
- `common_height`: same height, preserve aspect ratios (recommended for concat)
- `common_width`: same width, preserve aspect ratios
- `largest_dims`: use largest dimensions found across images
### Changed
- Simplified `QwenVLTextEncoder` by removing unnecessary resolution interface
- Removed confusing resolution controls that didn't affect generation
- Node now focuses on text encoding and vision processing
- Returns clean `(conditioning,)` output like standard ComfyUI text encoders
- Resolution controlled by proper nodes (Empty Latent, Multi-Reference Handler)
### Fixed
- Multi-reference concat mode squishing images with different aspect ratios
- Added smart resize logic before concatenation
- Aspect ratio preservation prevents distortion
- Maintains backward compatibility with existing workflows
- Grid mode tensor size mismatch when using aspect-preserving resize modes
- Grid now uses uniform dimensions to prevent concatenation errors
- Calculates average aspect ratio for balanced proportions
## v1.2
### Added
- Documentation for multi-reference spatial ordering (MULTI_REFERENCE_SPATIAL_ORDERING.md)
@@ -60,25 +85,3 @@
- Improved parameter tooltips with plain English descriptions
- Reference method tooltips now explain each option clearly
- Removed redundant Scale Image node from workflows (optimizer handles this internally)
### Fixed
- RoPE (Rotary Position Embeddings) batch processing bug
- Applied monkey patch from DiffSynth-Studio
- Fixes issues with different image sizes in batch
- Resolution optimizer selecting wrong aspect ratios
- Now considers both pixel count and aspect ratio
- Parameter order breaking backward compatibility
- Moved new parameters to end of function signature
- Double image scaling causing quality degradation
- Removed redundant scaling in workflows
### Technical Details
- Integrated fixes from DiffSynth-Studio and DiffSynth-Engine
- Maintained compatibility with ComfyUI's CLIP infrastructure
- Reference latents passed through conditioning metadata
- Support for 36 official Qwen resolutions
- 16-channel VAE latent support throughout
### Documentation
- Created REFERENCE_METHODS_EXPLAINED.md for multi-reference usage
- Added decision guides for common use cases
+18 -9
View File
@@ -65,16 +65,18 @@ ComfyUI nodes for the Qwen Image Edit model and Qwen2.5-VL text encoder. Does *N
- `QwenVLCLIPLoader` - Loads Qwen2.5-VL model with RoPE fix applied
- `QwenVLTextEncoder` - Text encoder with:
- Default Qwen formatting or custom Template Builder input
- Resolution optimization with aspect ratio preservation (36 Qwen resolutions)
- Optional multi-reference input accepts `QwenMultiReferenceHandler` output
- Context image support for ControlNet-style conditioning
- Resolution control: dropdown (36 Qwen resolutions), custom (exact dimensions)
- Optional edit_image input for vision-based editing
- Optional context_image input for ControlNet-style conditioning (separate from vision processing)
- Width/height outputs for downstream workflow integration
- Debug mode for troubleshooting
- `QwenLowresFixNode` - Two-stage generation for quality improvement
- `QwenMultiReferenceHandler` - Combines up to 4 images with:
- Six resize modes: keep_proportion, stretch, resize, pad, pad_edge, crop
- `QwenMultiReferenceHandler` - Combines up to 4 images with aspect ratio preservation:
- Four resize modes: match_first, common_height, common_width, largest_dims
- Five upscale methods: nearest-exact, bilinear, area, bicubic, lanczos
- Four combination methods: index, offset, concat, grid
- Weighted blending for offset method
- Smart resizing prevents distortion in concat mode
**Helper Nodes:**
- `QwenVLEmptyLatent` - Creates empty 16-channel latents
@@ -119,17 +121,24 @@ ComfyUI nodes for the Qwen Image Edit model and Qwen2.5-VL text encoder. Does *N
- Vision processing duplication bug (2x speedup via patch)
- Template token dropping standardized (consistency with DiffSynth)
- Template Builder for custom system prompts (removed duplicate from encoder)
- Resolution optimization with aspect ratio preservation
- Resolution optimization with improved aspect ratio matching
- Two-stage refinement (Lowres Fix)
- Debug mode for troubleshooting
- Multi-reference image support (up to 4 images) with spatial positioning clarity
- Context image support for ControlNet-style conditioning
- Multi-reference image support with aspect ratio preservation
- Context image support for ControlNet-style conditioning (separate from vision processing)
- Width/height outputs from QwenVLTextEncoder for workflow integration
- Simplified resolution interface (removed buggy auto mode, dropdown + custom only)
- Fixed grid mode tensor mismatch in multi-reference handler
### Phase 1 Complete (December 2024)
### Phase 1 Complete (January 2025)
- Fixed vision processing duplication (2x performance improvement)
- Standardized template token dropping for consistency with DiffSynth
- Added context_image support for ControlNet-style workflows
- Clarified multi-reference spatial behavior vs temporal frames
- Added width/height outputs to QwenVLTextEncoder
- Simplified resolution interface (removed confusing auto mode, dropdown/custom only)
- Fixed multi-reference concat distortion with aspect ratio preservation
- Fixed grid mode tensor mismatch when using aspect-preserving resize modes
### Remaining Implementation Plan
+152 -21
View File
@@ -1,28 +1,159 @@
*Despite the name, this is becoming more of a Qwen Image Edit repo.*
# Overview
ComfyUI nodes for Qwen2.5-VL and Qwen Image Edit models with enhanced template control.
# ComfyUI Qwen Image Edit Nodes
ComfyUI nodes for Qwen-Image-Edit for system prompt modifications, with multi-reference support and other fun features.
## Multi-Reference Methods
The `QwenMultiReferenceHandler` offers three composition methods for combining images:
- **concat**: Side-by-side layout for character/object comparison. Best with `common_height` resize for aspect preservation.
- **grid**: 2x2 layout for balanced influence of all references. Uses uniform dimensions to prevent tensor mismatches.
- **offset**: Weighted blending creates single composite. Perfect for style transfer with adjustable influence via weights (e.g., `1.0,1.0,0.5,1.2`).
## Spatial Reference Tokens FYI
Qwen-Image-Edit includes precision control tokens for surgical editing:
- **`<|object_ref_start|>...<|object_ref_end|>`** - Reference specific objects by description
- **`<|box_start|>x1,y1,x2,y2<|box_end|>`** - Target rectangular regions (normalized 0-1 coordinates)
- **`<|quad_start|>x1,y1,x2,y2,x3,y3,x4,y4<|quad_end|>`** - Define complex quadrilateral areas
*[Examples and usage patterns →](#spatial-token-examples)*
## Core Nodes
### Text Encoding
- **QwenVLCLIPLoader**: Loads Qwen2.5-VL models from `models/text_encoders/` folder
- **QwenVLTextEncoder**: Enhanced encoder with flexible template control
- `apply_template`: Enable/disable system templates
- `token_removal`: Choose between auto (ComfyUI), diffsynth (fixed), or none
- `mode`: Text-to-image or image_edit (defaults to image_edit)
### QwenVLCLIPLoader
Loads Qwen2.5-VL models with RoPE position embedding fixes.
- **Use for:** Loading any Qwen2.5-VL model from `models/text_encoders/`
- **Example:** Text-to-image, image editing workflows
### Template Building
- **QwenTemplateBuilder**: Interactive template builder with presets
- 9 presets including DiffSynth defaults, artistic, photorealistic, minimal edit
- Custom template mode with system prompt control
- Automatic vision token handling for image editing
- **QwenTemplateBuilderV2**: Simplified Python-only version
- All options visible, no JavaScript required
- custom_t2i and custom_edit modes
- **QwenTemplateConnector**: Bridges template builder to encoder
### QwenVLTextEncoder
Main text encoder with vision support and dual image inputs.
- **edit_image:** Vision-processed image for semantic understanding
- **context_image:** ControlNet-style spatial conditioning (no vision processing)
- **Outputs:** conditioning
- **Use for:** Text+image encoding with vision token processing
- **Example:** "Change dress to red" (edit_image) + pose skeleton (context_image)
### Helper Nodes
- **QwenOptimalResolution**: Auto-resizes images to nearest Qwen-preferred resolution
- **QwenResolutionSelector**: Dropdown selector for Qwen resolutions
- **QwenTokenInfo**: Reference display for special tokens
### QwenMultiReferenceHandler
Combines up to 4 images with aspect ratio preservation to prevent distortion.
- **Methods:** concat (side-by-side), grid (2x2), offset (weighted blend)
- **Resize modes:** match_first, common_height (best for concat), common_width, largest_dims
- **Use for:** Character fusion, style transfer, pose references
- **Example:** Combine Mona Lisa + Shrek + fighting pose → Mona Lisa fighting Shrek in the specified pose
### QwenLowresFixNode
Two-stage refinement: generate → upscale → polish details.
- **Use for:** Quality enhancement of generated images
- **Example:** 1024px generation → 1.5x upscale = detail refinement
## Helper Nodes
### QwenVLEmptyLatent / QwenVLImageToLatent
Creates/converts 16-channel latents for Qwen model.
- **Use for:** Latent space operations
## Template System
### QwenTemplateBuilder
Interactive template builder with style presets.
- **Presets:** DiffSynth, artistic, photorealistic, minimal edit, action, etc.
- **Use for:** Custom system prompts and specialized generation styles
## Usage Examples
### Text-to-Image
```
QwenVLCLIPLoader → QwenVLTextEncoder (mode: text_to_image) → KSampler
```
### Image Editing
```
LoadImage → QwenVLTextEncoder (edit_image, mode: image_edit) → KSampler
```
### Multi-Reference Composition
```
LoadImage(3x) → QwenMultiReferenceHandler → QwenVLTextEncoder → KSampler
```
### ControlNet-Style Conditioning
```
LoadImage(edit) + LoadImage(control) → QwenVLTextEncoder (edit_image + context_image) → KSampler
```
### Quality Enhancement
```
KSampler → QwenLowresFixNode → Final Image
```
## Key Features
- Clean text encoder focused on vision and language processing
- Multi-reference aspect ratio preservation prevents concat distortion
- Context image support for ControlNet workflows
- Template Builder with 20+ presets
- Debug mode with detailed logging
- Patterns and patches from DiffSynth code
- Two-stage refinement system
## Model Requirements
Place Qwen2.5-VL models in `ComfyUI/models/text_encoders/`:
- `qwen_2.5_vl_7b.safetensors` (recommended)
- Any Qwen2.5-VL checkpoint
## Advanced Usage
**Spatial Reference Tokens** (Phase 3 roadmap):
```
"Change <|object_ref_start|>the red car<|object_ref_end|> to blue"
"Replace <|box_start|>0.2,0.3,0.8,0.7<|box_end|> with flowers"
```
**Denoise Guidelines:**
- High denoise (0.9-1.0): Full creative reimagining
- Low denoise (0.3-0.7): Structure preservation
- Use Empty Latent for high denoise, VAE Encode for low denoise
## Spatial Token Examples
### Object Reference - Independent Usage
Target specific objects by description:
```
"Change <|object_ref_start|>the red car<|object_ref_end|> to blue while keeping everything else the same"
"Make <|object_ref_start|>the woman's dress<|object_ref_end|> flowing in the wind"
"Replace <|object_ref_start|>the old wooden door<|object_ref_end|> with a modern glass entrance"
```
### Box Coordinates - Precise Region Control
Target rectangular areas with normalized coordinates (0-1):
```
"Fill <|box_start|>0.2,0.3,0.8,0.7<|box_end|> with blooming cherry blossoms"
"Replace the content in <|box_start|>0.0,0.0,0.5,1.0<|box_end|> with a mountain landscape"
"Add storm clouds to <|box_start|>0.1,0.0,0.9,0.4<|box_end|>"
```
### Quadrilateral - Complex Shape Targeting
Define irregular areas with four corner points:
```
"Paint graffiti art within <|quad_start|>0.1,0.2,0.6,0.1,0.8,0.7,0.2,0.8<|quad_end|>"
"Transform <|quad_start|>0.3,0.1,0.9,0.2,0.7,0.9,0.1,0.6<|quad_end|> into stained glass"
```
### Combined Usage - Multiple Token Types
Mix different tokens for complex edits:
```
"Change <|object_ref_start|>the building<|object_ref_end|> in <|box_start|>0.4,0.2,1.0,0.8<|box_end|> to Gothic architecture"
"Make <|object_ref_start|>the tree<|object_ref_end|> seasonal: spring leaves in <|quad_start|>0.2,0.1,0.8,0.0,0.9,0.6,0.1,0.7<|quad_end|>"
"Replace <|object_ref_start|>the sky<|object_ref_end|> above <|box_start|>0.0,0.0,1.0,0.4<|box_end|> with aurora borealis"
```
### Multi-Object Coordination
Target multiple objects with spatial relationships:
```
"Change <|object_ref_start|>the left car<|object_ref_end|> to red and <|object_ref_start|>the right car<|object_ref_end|> to blue"
"Make <|object_ref_start|>the foreground flowers<|object_ref_end|> roses and <|object_ref_start|>the background trees<|object_ref_end|> cherry blossoms"
```
File diff suppressed because it is too large Load Diff
+85 -14
View File
@@ -37,9 +37,13 @@ class QwenMultiReferenceHandler:
"default": "1.0,1.0,1.0,1.0",
"tooltip": "Weights for each image (comma-separated, used in 'offset' blend mode)"
}),
"resize_mode": (["match_first", "common_height", "common_width", "largest_dims"], {
"default": "common_height",
"tooltip": "match_first: resize all to image1 size | common_height: same height, keep aspect (uniform in grid) | common_width: same width, keep aspect (uniform in grid) | largest_dims: resize all to largest dimensions"
}),
"upscale_method": (["nearest-exact", "bilinear", "area", "bicubic", "lanczos"], {
"default": "bicubic",
"tooltip": "Interpolation method for resizing images to match the first image's resolution."
"tooltip": "Interpolation method for resizing images."
}),
}
}
@@ -52,10 +56,16 @@ class QwenMultiReferenceHandler:
TITLE = "Multi-Reference Canvas Composer"
DESCRIPTION = """Combines multiple images into a single high-resolution canvas for spatial reference.
• All images are resized to match the dimensions of image1.
• 'concat' creates a side-by-side image.
• 'grid' creates a 2x2 image layout.
• 'offset' creates a weighted blend.
Resize modes:
• match_first: resize all to image1 dimensions (may distort)
• common_height: same height, preserve aspect ratios (uniform dimensions in grid mode)
• common_width: same width, preserve aspect ratios (uniform dimensions in grid mode)
• largest_dims: resize all to largest width/height found
Composition methods:
• 'concat' creates a side-by-side image
• 'grid' creates a 2x2 image layout (requires uniform dimensions)
• 'offset' creates a weighted blend
The output is a single image tensor, ready for the QwenVLTextEncoder."""
@@ -65,6 +75,7 @@ The output is a single image tensor, ready for the QwenVLTextEncoder."""
image3: Optional[torch.Tensor] = None,
image4: Optional[torch.Tensor] = None,
weights: str = "1.0,1.0,1.0,1.0",
resize_mode: str = "common_height",
upscale_method: str = "bicubic") -> Tuple[torch.Tensor]:
# Collect all provided, non-None images
@@ -73,21 +84,81 @@ The output is a single image tensor, ready for the QwenVLTextEncoder."""
if not images:
raise ValueError("At least one image must be provided to the Multi-Reference Handler.")
# Step 1: Standardize all images to the resolution of the first image
# This ensures all parts of the canvas have consistent dimensions.
target_h, target_w = images[0].shape[1:3]
logger.info(f"[Multi-Reference] Standardizing all images to {target_w}x{target_h}.")
# Step 1: Calculate target dimensions based on resize mode
if resize_mode == "match_first":
# Original behavior: resize all to match first image
target_h, target_w = images[0].shape[1:3]
logger.info(f"[Multi-Reference] Resizing all images to match first image: {target_w}x{target_h}")
elif resize_mode == "common_height":
# Find common height (use minimum to avoid upscaling)
target_h = min(img.shape[1] for img in images)
# For grid mode, we need uniform dimensions to avoid tensor mismatch
if reference_method == "grid":
# Calculate average width to maintain reasonable proportions
avg_aspect = sum(img.shape[2] / img.shape[1] for img in images) / len(images)
target_w = int(target_h * avg_aspect)
logger.info(f"[Multi-Reference] Grid mode: using common dimensions {target_w}x{target_h}")
else:
logger.info(f"[Multi-Reference] Using common height: {target_h}, aspect ratios preserved")
elif resize_mode == "common_width":
# Find common width (use minimum to avoid upscaling)
target_w = min(img.shape[2] for img in images)
# For grid mode, we need uniform dimensions to avoid tensor mismatch
if reference_method == "grid":
# Calculate average height to maintain reasonable proportions
avg_aspect = sum(img.shape[1] / img.shape[2] for img in images) / len(images)
target_h = int(target_w * avg_aspect)
logger.info(f"[Multi-Reference] Grid mode: using common dimensions {target_w}x{target_h}")
else:
logger.info(f"[Multi-Reference] Using common width: {target_w}, aspect ratios preserved")
elif resize_mode == "largest_dims":
# Use largest width and height found across all images
target_h = max(img.shape[1] for img in images)
target_w = max(img.shape[2] for img in images)
logger.info(f"[Multi-Reference] Using largest dimensions: {target_w}x{target_h}")
standardized_images = [images[0]]
for img in images[1:]:
# Use 'stretch' logic to force all images to the same dimensions
# Step 2: Resize all images according to the chosen mode
standardized_images = []
for i, img in enumerate(images):
if resize_mode == "match_first":
# Stretch to exact target dimensions
new_w, new_h = target_w, target_h
elif resize_mode == "common_height":
if reference_method == "grid":
# Grid mode requires uniform dimensions
new_w, new_h = target_w, target_h
else:
# Keep aspect ratio, adjust width based on common height
aspect_ratio = img.shape[2] / img.shape[1] # w/h
new_h = target_h
new_w = int(target_h * aspect_ratio)
elif resize_mode == "common_width":
if reference_method == "grid":
# Grid mode requires uniform dimensions
new_w, new_h = target_w, target_h
else:
# Keep aspect ratio, adjust height based on common width
aspect_ratio = img.shape[1] / img.shape[2] # h/w
new_w = target_w
new_h = int(target_w * aspect_ratio)
elif resize_mode == "largest_dims":
# Stretch to largest dimensions (may distort)
new_w, new_h = target_w, target_h
# Resize the image
resized_img_chw = comfy.utils.common_upscale(
img.movedim(-1, 1), # HWC to CHW for upscale function
target_w, target_h, upscale_method, "disabled"
new_w, new_h, upscale_method, "disabled"
)
standardized_images.append(resized_img_chw.movedim(1, -1)) # CHW to HWC
# Step 2: Create the composite canvas based on the chosen method
# Step 3: Create the composite canvas based on the chosen method
if reference_method == "offset":
# Weighted average blend. The result is a single image of target_w x target_h.
logger.info(f"[Multi-Reference] Creating composite using 'offset' (blend) method.")
+28 -61
View File
@@ -119,19 +119,31 @@ class QwenVLCLIPLoader:
class QwenVLTextEncoder:
"""
Enhanced text encoder for Qwen2.5-VL with all DiffSynth fixes
Text encoder for Qwen2.5-VL with all DiffSynth fixes
Uses ComfyUI's internal CLIP infrastructure for compatibility
"""
# DiffSynth-Studio's exact resolution list for Qwen
# Resolution list combining DiffSynth-Studio resolutions with modern aspect ratios
QWEN_RESOLUTIONS = [
(256, 256), (256, 512), (256, 768), (256, 1024), (256, 1280), (256, 1536), (256, 1792),
(512, 256), (512, 512), (512, 768), (512, 1024), (512, 1280), (512, 1536), (512, 1792),
(768, 256), (768, 512), (768, 768), (768, 1024), (768, 1280), (768, 1536),
(1024, 256), (1024, 512), (1024, 768), (1024, 1024), (1024, 1280), (1024, 1536),
(1280, 256), (1280, 512), (1280, 768), (1280, 1024), (1280, 1280),
(1536, 256), (1536, 512), (1536, 768), (1536, 1024),
(1792, 256), (1792, 512)
# Square resolutions
(1024, 1024), (1328, 1328),
# Common landscape ratios (optimized for quality)
(1328, 800), (1456, 720), (1584, 1056), (1920, 1080), # 16:9
(2048, 1024), (1344, 768), (1536, 640),
# Common portrait ratios
(800, 1328), (720, 1456), (1056, 1584), (1080, 1920), # 9:16
(1024, 2048), (768, 1344), (640, 1536),
# Original DiffSynth-Studio resolutions for compatibility
(672, 1568), (688, 1504), (752, 1392), (832, 1248),
(880, 1184), (944, 1104), (1104, 944), (1184, 880),
(1248, 832), (1392, 752), (1504, 688), (1568, 672),
# Smaller resolutions for low VRAM
(512, 512), (768, 768), (512, 768), (768, 512),
(1024, 768), (768, 1024), (1024, 512), (512, 1024)
]
@classmethod
@@ -171,10 +183,6 @@ class QwenVLTextEncoder:
"default": False,
"tooltip": "Shows detailed processing info in console. Turn on if things aren't working."
}),
"optimize_resolution": ("BOOLEAN", {
"default": True,
"tooltip": "ON: Snap to best Qwen resolution (RECOMMENDED) | OFF: Scale to 1M pixels"
}),
}
}
@@ -193,57 +201,27 @@ KEY DECISION: What goes into KSampler.latent_image?
ALWAYS connect VAE to this node for reference latents!
"""
def get_optimal_resolution(self, width: int, height: int) -> Tuple[int, int]:
"""Find the nearest Qwen-supported resolution preserving aspect ratio"""
target_pixels = width * height
aspect_ratio = width / height
best_res = min(
self.QWEN_RESOLUTIONS,
key=lambda r: abs(r[0] * r[1] - target_pixels) * 0.5 +
abs((r[0] / r[1]) - aspect_ratio) * target_pixels * 0.5
)
return best_res
def encode(self, clip, text: str, mode: str = "text_to_image",
edit_image: Optional[torch.Tensor] = None, context_image: Optional[torch.Tensor] = None,
vae=None, use_custom_system_prompt: bool = False,
token_removal: str = "auto", debug_mode: bool = False,
optimize_resolution: bool = True) -> Tuple[Any]:
token_removal: str = "auto", debug_mode: bool = False) -> Tuple[Any]:
images_for_tokenizer = []
ref_latent = None
context_latent = None
original_text = text
# No resolution controls needed - images will be processed optimally
# Prepare edit_image if in edit mode
if mode == "image_edit" and edit_image is not None:
import math
import comfy.utils
if debug_mode:
logger.info(f"[Encoder] Input image/canvas shape: {edit_image.shape}")
samples = edit_image.movedim(-1, 1)
# Determine target resolution
if optimize_resolution:
opt_w, opt_h = self.get_optimal_resolution(samples.shape[3], samples.shape[2])
width, height = opt_w, opt_h
if debug_mode:
logger.info(f"[Encoder] Using optimal Qwen resolution: {width}x{height}")
else:
total = int(1024 * 1024)
scale_by = math.sqrt(total / (samples.shape[3] * samples.shape[2]))
width = round(samples.shape[3] * scale_by)
height = round(samples.shape[2] * scale_by)
if debug_mode:
logger.info(f"[Encoder] Using legacy 1MP area scaling: {width}x{height}")
s = comfy.utils.common_upscale(samples, width, height, "area", "disabled")
image = s.movedim(1, -1)
# Use input image as-is for vision processing (no resizing needed)
image = edit_image
images_for_tokenizer = [image[:, :, :, :3]]
if vae is not None:
@@ -306,22 +284,11 @@ ALWAYS connect VAE to this node for reference latents!
# Process context_image separately (ControlNet-style)
if context_image is not None and vae is not None:
import comfy.utils
if debug_mode:
logger.info(f"[Encoder] Processing context image: {context_image.shape}")
samples = context_image.movedim(-1, 1)
# Use same resolution logic as edit_image for consistency
if 'width' in locals() and 'height' in locals():
context_w, context_h = width, height
else: # Fallback if not in edit mode
context_w, context_h = self.get_optimal_resolution(samples.shape[3], samples.shape[2])
s = comfy.utils.common_upscale(samples, context_w, context_h, "area", "disabled")
context_img_resized = s.movedim(1, -1)
context_latent = vae.encode(context_img_resized[:, :, :, :3])
# Use context image as-is (no resizing needed)
context_latent = vae.encode(context_image[:, :, :, :3])
if debug_mode:
logger.info(f"[Encoder] Encoded context_image latent shape: {context_latent.shape}")