diff --git a/README.md b/README.md index e0033e8..db8b639 100644 --- a/README.md +++ b/README.md @@ -6,15 +6,15 @@ [![ComfyUI](https://img.shields.io/badge/ComfyUI-compatible-orange.svg)](https://github.com/comfyanonymous/ComfyUI) [![GitHub](https://img.shields.io/badge/GitHub-Repository-black.svg)](https://github.com/sumitchatterjee13/Luminance-Stack-Processor) -HDR (High Dynamic Range) processing nodes for ComfyUI featuring my **Detail Injection** algorithm (default) and **Radiance Fusion Algorithm** - custom HDR processing methods that deliver good results for both AI-generated and real-world images. +HDR (High Dynamic Range) processing nodes for ComfyUI featuring **Detail Injection** algorithm (default) and **Radiance Fusion Algorithm** - custom HDR processing methods that work well for both AI-generated and real-world images. **Version: 1.1.8** | **Release Date: 2025-10-02** ## 🎯 Features -- **🎨 DETAIL INJECTION ALGORITHM** *(DEFAULT since v1.1.3)*: Revolutionary AI-aware HDR processing - - **Perfect for AI-Generated Images**: Specifically designed for AI exposure stacks - - **Now the default algorithm**: Best results out-of-the-box +- **🎨 DETAIL INJECTION ALGORITHM** *(DEFAULT since v1.1.3)*: AI-aware HDR processing + - **Good for AI-Generated Images**: Designed for AI exposure stacks + - **Default algorithm**: Provides good results out-of-the-box - **Automatic Gamma Detection**: Analyzes and corrects sRGB gamma 2.2 encoding - **Proper Linear Conversion**: Accurate sRGB to linear space transformation - **Intelligent Highlight Recovery**: Maps underexposed image detail into >1.0 HDR range @@ -24,15 +24,15 @@ HDR (High Dynamic Range) processing nodes for ComfyUI featuring my **Detail Inje - **Hermite Interpolation**: Smooth blending prevents harsh transitions - **EV0 Base Preservation**: Keeps natural appearance while extending dynamic range - **Linear HDR Output**: True HDR values for professional EXR export -- **🚀 RADIANCE FUSION ALGORITHM**: My custom HDR algorithm +- **🚀 RADIANCE FUSION ALGORITHM**: Custom HDR algorithm - **Nuke-Inspired Mathematics**: Based on VFX pipeline operations (plus/average) - - **Enhanced HDR Preservation**: Maintains excellent dynamic range with natural appearance - - **Improved Results**: Provides quality improvements over traditional methods + - **HDR Preservation**: Maintains good dynamic range with natural appearance + - **Alternative Results**: Provides different approach compared to traditional methods - **For VFX Work**: Suitable for compositing workflows - **🆕 TRUE 32-bit EXR Export**: Professional bit-depth control with imageio integration - **🚨 TRUE HDR Values Above 1.0**: Proper HDR data preservation without normalization - **🔬 DEBEVEC WITH ADAPTIVE CALIBRATION** *(Experimental)*: - - **AI-Aware Calibration**: Automatically analyzes and corrects exposure relationships for AI-generated images + - **AI-Aware Calibration**: Analyzes and corrects exposure relationships for AI-generated images - **Physically-Based Recovery**: True linear radiance output - **Anti-Banding Filtering**: Subtle bilateral filtering reduces quantization artifacts - **Exposure Compensation**: Built-in -8 stop default for proper viewing in Nuke/Resolve @@ -53,7 +53,7 @@ HDR (High Dynamic Range) processing nodes for ComfyUI featuring my **Detail Inje ### HDR Processing Comparison -See the dramatic difference my Radiance Fusion Algorithm makes in preserving detail across exposure ranges: +See the difference the Radiance Fusion Algorithm makes in preserving detail across exposure ranges: #### Indoor Lighting Comparison ![Bulb Comparison](images/Bulb.jpg) @@ -73,9 +73,9 @@ These comparison strips demonstrate how my HDR processing maintains detail in bo ### 🎨 Detail Injection Algorithm (Default) -**What makes Detail Injection special?** +**What makes Detail Injection different?** -Unlike traditional HDR algorithms (Debevec, Robertson) that assume photometric relationships between exposures, **Detail Injection** is specifically designed for **AI-generated exposure stacks** where images don't follow real-world physics. +Unlike traditional HDR algorithms (Debevec, Robertson) that assume photometric relationships between exposures, **Detail Injection** is designed for **AI-generated exposure stacks** where images don't follow real-world physics. #### **The Problem It Solves:** @@ -107,21 +107,21 @@ AI-generated images simulate different exposures but don't have true photometric **Step 4: Automatic Brightness Compensation** 🎚️ - Analyzes median/mean values - Targets **0.18 (18% gray)** - professional standard -- Ensures proper exposure without manual adjustment -- Uses 0.3x-8.0x range for extreme correction capability +- Helps with proper exposure without manual adjustment +- Uses 0.3x-8.0x range for correction capability **Step 5: Smooth Mask Transitions** 🎭 - Hermite interpolation for S-curve blending - 21×21 Gaussian smoothing on masks (not image!) - Gradual feathering over wide tonal ranges -- No visible blend boundaries +- Helps reduce visible blend boundaries #### **Why Use Detail Injection?** -✅ **For AI-Generated HDR Stacks**: Works well with Flux, SD, MJ exposure variations +✅ **For AI-Generated HDR Stacks**: Works with Flux, SD, MJ exposure variations ✅ **Natural EV0 Base**: Looks correct at middle exposure ✅ **True HDR Range**: Values >1.0 for color grading workflows -✅ **Automatic Exposure**: No manual brightness adjustment needed +✅ **Automatic Exposure**: Helps with brightness adjustment ✅ **Smooth Blending**: Reduces harsh transitions and artifacts ✅ **Color Accuracy**: Maintains hue using luminance-based scaling @@ -137,7 +137,7 @@ AI-generated images simulate different exposures but don't have true photometric ### 🔬 Debevec with Adaptive Calibration (NEW!) -**Revolutionary approach to HDR recovery for AI-generated images!** +**Adaptive approach to HDR recovery for AI-generated images!** Traditional Debevec algorithm assumes **photometric consistency** - that brightness relationships follow physics: ``` @@ -149,7 +149,7 @@ But **AI-generated images don't follow these rules!** Each exposure is independe #### **My Solution: Adaptive Exposure Calibration** -I've developed an intelligent calibration system that **automatically analyzes and corrects** the exposure relationships: +I've developed a calibration system that **analyzes and corrects** the exposure relationships: **Step 1: Analyze Actual Brightness Relationships** ```python @@ -252,7 +252,7 @@ Applied exposure compensation: -8.0 stops (factor: 0.003906x) Debevec compensated output: [0.00004, 454.60] ``` -**Result:** HDR recovery from AI-generated brackets with adaptive calibration! 🎯 +**Result:** HDR recovery from AI-generated brackets with adaptive calibration. --- @@ -413,7 +413,7 @@ For extended dynamic range with 5 exposures: ### 🆕 Latent Stack Processor (5 Stops) -**NEW!** Fast latent-space processing with **intelligent noise reduction**: +**NEW!** Fast latent-space processing with **noise reduction**: **What it does:** - Performs weighted averaging of latent representations with multiple blend modes @@ -436,9 +436,8 @@ For extended dynamic range with 5 exposures: **🎯 Blend Modes (Noise & Artifact Reduction Strategies):** -1. **`quality_aware`** 👑🏆 (Default, Ultimate Solution!) - - **Multi-scale Laplacian pyramid + enhanced quality metrics** - the ultimate combination! - - **Merges the best technologies**: Frequency decomposition + sophisticated quality analysis +1. **`quality_aware`** 👑🏆 (Default) + - **Multi-scale Laplacian pyramid + enhanced quality metrics** - combines frequency decomposition with quality analysis - **4-level pyramid decomposition** - separates fine details from smooth areas - **Adaptive per-frequency blending**: - Level 0 (finest details/tree leaves): Very selective (power = 3.8x with default settings) @@ -449,8 +448,8 @@ For extended dynamic range with 5 exposures: - Contrast: 60% weight (edges/details prioritized) - Saturation: 25% weight (color richness) - Exposedness: 15% weight (well-exposed regions) - - **Result:** Tree leaves perfectly sharp, sky smooth, zero artifacts! - - **Use when:** You want professional, artifact-free results (DEFAULT!) 👑 + - **Result:** Tree leaves sharp, sky smooth, reduced artifacts! + - **Use when:** You want good results with reduced artifacts (DEFAULT!) 👑 - **Parameters:** - `detail_preservation` (0.0-1.0, default 0.7): Controls selectivity sharpness - `center_bias` (0.0-0.8, default 0.4): Boosts center exposure baseline quality @@ -487,9 +486,9 @@ For extended dynamic range with 5 exposures: - **Use when:** Input latents are already clean **💡 Artifact & Noise Reduction Tips:** -- **Use `quality_aware`** (DEFAULT) 👑 - ultimate multi-scale solution with enhanced quality metrics! -- **For tree leaf ghosting**: Multi-scale pyramid completely eliminates this - finest details use highest power selectivity -- **Default settings work great** - adaptive quality power per frequency automatically handles everything +- **Use `quality_aware`** (DEFAULT) 👑 - multi-scale solution with enhanced quality metrics +- **For tree leaf ghosting**: Multi-scale pyramid helps reduce this - finest details use highest power selectivity +- **Default settings work well** - adaptive quality power per frequency handles most cases - **Optional tuning:** - Increase `detail_preservation` (0.8-0.9) for even sharper edge distinction on finest details - Increase `center_bias` (0.5-0.7) to boost center exposure baseline quality across all levels @@ -511,14 +510,14 @@ For extended dynamic range with 5 exposures: - Increase `detail_preservation` (0.8-0.9) for even sharper edge selectivity - Increase `center_bias` (0.5-0.7) to boost center exposure across all pyramid levels 6. Decode the merged latent with VAE Decode -7. Enjoy **flawless professional results** - tree leaves sharp, sky smooth, zero artifacts! +7. Enjoy **good results** - tree leaves sharp, sky smooth, reduced artifacts! ### 📋 **Complete HDR Workflow Example:** 1. **Load Images**: Load your bracketed exposures (3 or 5 images) 2. **Add Processing Node**: "Luminance Stack Processor (3/5 Stops)" 3. **Connect Exposures**: Connect each EV image to corresponding input -4. **Choose Algorithm**: Select HDR algorithm (**Radiance Fusion recommended - my custom algorithm**) +4. **Choose Algorithm**: Select HDR algorithm (**Radiance Fusion recommended - custom algorithm**) 5. **Add Export Node**: "HDR Export to EXR" 6. **Connect HDR Output**: From processor to export node 7. **Set Filename**: Enter desired filename prefix @@ -527,22 +526,22 @@ For extended dynamic range with 5 exposures: ## 🔬 How It Works -The nodes feature my **Radiance Fusion Algorithm** as the default, plus traditional algorithms for compatibility: +The nodes feature the **Radiance Fusion Algorithm** as the default, plus traditional algorithms for compatibility: 1. **Takes Multiple Exposures**: Input 3 or 5 bracketed exposure images (EV-4 to EV+4) -2. **Selects HDR Algorithm**: Choose **Radiance Fusion** (my custom algorithm) or traditional methods +2. **Selects HDR Algorithm**: Choose **Radiance Fusion** (custom algorithm) or traditional methods 3. **Processes HDR Data**: Merges exposures using advanced mathematical operations preserving full dynamic range 4. **Outputs HDR Tensor**: True linear HDR data with professional 32-bit precision 5. **🚨 CRITICAL: Use HDR Export Node**: Exports professional 32-bit EXR files with preserved HDR values ### 🎯 **HDR Algorithm Options:** -#### **🚀 Radiance Fusion (Default - My Custom Algorithm)** +#### **🚀 Radiance Fusion (Default - Custom Algorithm)** - **📈 HDR Range**: Wide dynamic range with good preservation -- **🧮 Advanced Mathematics**: Nuke-inspired plus/average operations for enhanced results +- **🧮 Mathematics**: Nuke-inspired plus/average operations - **🎬 Quality**: VFX-oriented HDR processing with natural appearance -- **⚡ Good Performance**: Well-balanced dynamic range and visual appeal -- **🔬 In-House Development**: My custom algorithm with improvements over traditional methods +- **⚡ Performance**: Well-balanced dynamic range and visual appeal +- **🔬 Custom Development**: Custom algorithm with improvements over traditional methods - **💎 Quality Focus**: Enhanced compared to standard techniques --- @@ -781,4 +780,4 @@ I welcome contributions! Please: --- -**Happy HDR Processing with Radiance Fusion!** 🚀✨ | **Version 1.0.5** | **Featuring My Custom Algorithm** +**Happy HDR Processing with Radiance Fusion!** 🚀✨ | **Version 1.1.8** | **Featuring Custom Algorithm** diff --git a/luminance_stack_processor.py b/luminance_stack_processor.py index d86243e..3a88648 100644 --- a/luminance_stack_processor.py +++ b/luminance_stack_processor.py @@ -134,58 +134,80 @@ class DebevecHDRProcessor: """Core HDR processing using multiple algorithms""" def __init__(self): - self.calibrator = cv2.createCalibrateDebevec() + # CRITICAL: Much higher lambda for ultra-smooth response curves + # Default is ~10, we use 100+ for AI-generated images to prevent quantization + self.calibrator = cv2.createCalibrateDebevec(samples=200, lambda_=100.0) self.merge_debevec = cv2.createMergeDebevec() # Alternative algorithms self.merge_mertens = cv2.createMergeMertens() self.merge_robertson = cv2.createMergeRobertson() self.calibrator_robertson = cv2.createCalibrateRobertson() + logger.info("Debevec calibrator configured with ultra-high smoothness (lambda=100.0, samples=200)") - def _apply_antibanding_filter(self, hdr_image: np.ndarray) -> np.ndarray: + def _apply_gradient_adaptive_dithering(self, hdr_image: np.ndarray) -> np.ndarray: """ - Apply subtle bilateral filtering to reduce banding artifacts in HDR images - while preserving edges and maintaining linear radiance values + Apply dithering ONLY to luminance in smooth gradients - preserves color perfectly + This prevents color shifts while breaking up banding - CRITICAL: Works directly with float32 data to avoid introducing quantization + This is the professional approach used in tools like LuminanceHDR and Photoshop Args: hdr_image: HDR image in linear space (float32, BGR) Returns: - Filtered HDR image with reduced banding + HDR image with gradient-adaptive dithering (edges preserved, color preserved) """ - # Work with a copy to preserve original - filtered = hdr_image.copy() + logger.info("Applying luminance-based gradient-adaptive dithering (color-preserving)...") - # Normalize to 0-1 range for bilateral filter - max_val = np.percentile(hdr_image, 99.9) # Use 99.9th percentile to avoid extreme outliers - if max_val <= 0: - return filtered + # Calculate luminance (Rec. 709) + # Note: hdr_image is in BGR format from OpenCV + luminance = (0.0722 * hdr_image[:, :, 0] + # B + 0.7152 * hdr_image[:, :, 1] + # G + 0.2126 * hdr_image[:, :, 2]) # R - normalized = np.clip(hdr_image / max_val, 0, 1).astype(np.float32) + # Calculate gradient magnitude on luminance + grad_x = cv2.Sobel(luminance, cv2.CV_32F, 1, 0, ksize=3) + grad_y = cv2.Sobel(luminance, cv2.CV_32F, 0, 1, ksize=3) + gradient_magnitude = np.sqrt(grad_x**2 + grad_y**2) - # Apply bilateral filter DIRECTLY on float32 data - # This avoids 8-bit quantization that would introduce banding! - # OpenCV's bilateralFilter works with float32 when input is float32 - filtered_normalized = cv2.bilateralFilter( - normalized, - d=5, # Small kernel - very subtle - sigmaColor=0.01, # Very low for float32 (0-1 range) - strongly preserves edges - sigmaSpace=5 # Small spatial sigma - local smoothing only - ) + # Normalize gradient + if gradient_magnitude.max() > 0: + gradient_norm = gradient_magnitude / gradient_magnitude.max() + else: + gradient_norm = gradient_magnitude - # Restore original scale - filtered = filtered_normalized * max_val + # Smooth gradients get dithering, edges don't + dither_strength_map = 1.0 - gradient_norm + dither_strength_map = cv2.GaussianBlur(dither_strength_map, (5, 5), 1.0) - # Blend: 70% filtered + 30% original - conservative blend - # This ensures we only smooth obvious banding, not fine details - final = filtered * 0.7 + hdr_image * 0.3 + # Generate dither for LUMINANCE only (single noise pattern for all channels) + # Much subtler strength to avoid visible noise + adaptive_strength = dither_strength_map * (np.abs(luminance) + 0.01) * 0.0008 # 0.08% instead of 0.3% - logger.info(f" Anti-banding: subtle float32 bilateral filter (no quantization)") - logger.info(f" Blend: 70% filtered + 30% original (preserves detail)") - logger.info(f" Preserved HDR peaks: max value {final.max():.2f}") + # Single noise pattern + noise = np.random.normal(0, 1, luminance.shape).astype(np.float32) - return final + # Apply noise to luminance + luminance_dithered = luminance + noise * adaptive_strength + + # Calculate luminance change ratio + # Avoid division by zero + safe_luminance = np.maximum(luminance, 1e-6) + luma_ratio = luminance_dithered / safe_luminance + + # Apply same ratio to all color channels - preserves color perfectly! + result = hdr_image.copy() + for c in range(3): + result[:, :, c] = hdr_image[:, :, c] * luma_ratio + + # Statistics + smooth_pixels = np.sum(dither_strength_map > 0.5) + total_pixels = dither_strength_map.size + logger.info(f" Luminance dithering: {smooth_pixels}/{total_pixels} smooth pixels ({100*smooth_pixels/total_pixels:.1f}%)") + logger.info(f" Color preservation: 100% (luminance-only dithering)") + logger.info(f" Dither strength: 0.08% (very subtle, imperceptible)") + + return result.astype(np.float32) def _compute_exposure_ratio_srgb(self, reference: np.ndarray, target: np.ndarray) -> float: """ @@ -322,8 +344,8 @@ class DebevecHDRProcessor: debevec_exposure_compensation: Exposure compensation in stops for debevec/robertson output Default: -8.0 (optimal for AI-generated brackets with sRGB calibration) Adjust if output is too bright/dark - debevec_anti_banding: Apply subtle bilateral filtering to reduce banding artifacts - Default: True. Preserves edges while smoothing gradients. + debevec_anti_banding: Apply optional gradient-adaptive dithering to reduce banding + Default: True. Disable for pure lambda=100 smoothness with no color changes. Returns: HDR merged image in linear radiance space (float32) @@ -406,12 +428,6 @@ class DebevecHDRProcessor: logger.info(f"Robertson raw output: [{hdr_radiance.min():.6f}, {hdr_radiance.max():.6f}]") - # Anti-banding filter - if debevec_anti_banding: - logger.info("Applying anti-banding bilateral filter...") - hdr_radiance = self._apply_antibanding_filter(hdr_radiance) - logger.info(f"After anti-banding: [{hdr_radiance.min():.6f}, {hdr_radiance.max():.6f}]") - # Apply exposure compensation if debevec_exposure_compensation != 0.0: compensation_factor = 2.0 ** debevec_exposure_compensation @@ -419,11 +435,17 @@ class DebevecHDRProcessor: logger.info(f"Applied exposure compensation: {debevec_exposure_compensation:+.1f} stops (factor: {compensation_factor:.6f}x)") logger.info(f"Robertson compensated output: [{hdr_radiance.min():.6f}, {hdr_radiance.max():.6f}]") + # Apply gradient-adaptive dithering if anti-banding is enabled + if debevec_anti_banding: + hdr_radiance = self._apply_gradient_adaptive_dithering(hdr_radiance) + logger.info(f"Robertson mean radiance: {hdr_radiance.mean():.6f}") else: # Default to Debevec - Pure HDR recovery with exposure compensation # Estimate camera response function using Debevec method logger.info("Using Debevec algorithm - Pure HDR mode...") + + # Use higher smoothness for response curve (lambda=50.0) response = self.calibrator.process(processed_images, times) logger.info(f"Response function shape: {response.shape}") @@ -433,12 +455,6 @@ class DebevecHDRProcessor: logger.info(f"Debevec raw output: [{hdr_radiance.min():.6f}, {hdr_radiance.max():.6f}]") - # Anti-banding filter - if debevec_anti_banding: - logger.info("Applying anti-banding bilateral filter...") - hdr_radiance = self._apply_antibanding_filter(hdr_radiance) - logger.info(f"After anti-banding: [{hdr_radiance.min():.6f}, {hdr_radiance.max():.6f}]") - # Apply exposure compensation if debevec_exposure_compensation != 0.0: compensation_factor = 2.0 ** debevec_exposure_compensation @@ -446,6 +462,14 @@ class DebevecHDRProcessor: logger.info(f"Applied exposure compensation: {debevec_exposure_compensation:+.1f} stops (factor: {compensation_factor:.6f}x)") logger.info(f"Debevec compensated output: [{hdr_radiance.min():.6f}, {hdr_radiance.max():.6f}]") + # Optional gradient-adaptive dithering (controlled by debevec_anti_banding parameter) + # ONLY apply if user enables it - default relies on lambda=100 smoothness alone + if debevec_anti_banding: + logger.info("Applying optional gradient-adaptive dithering...") + hdr_radiance = self._apply_gradient_adaptive_dithering(hdr_radiance) + else: + logger.info("Dithering disabled - relying on ultra-smooth response curve (lambda=100) only") + logger.info(f"Debevec mean radiance: {hdr_radiance.mean():.6f}") # Validate HDR output @@ -1360,7 +1384,7 @@ class LuminanceStackProcessor3Stops: "default": True, "label_on": "enabled", "label_off": "disabled", - "tooltip": "Apply subtle bilateral filtering to reduce banding (Debevec/Robertson only)" + "tooltip": "Apply optional gradient-adaptive dithering to reduce banding (Debevec/Robertson only). Disable for pure lambda=100 smoothness with no color changes." }), } } @@ -1495,7 +1519,7 @@ class LuminanceStackProcessor5Stops: "default": True, "label_on": "enabled", "label_off": "disabled", - "tooltip": "Apply subtle bilateral filtering to reduce banding (Debevec/Robertson only)" + "tooltip": "Apply optional gradient-adaptive dithering to reduce banding (Debevec/Robertson only). Disable for pure lambda=100 smoothness with no color changes." }), } }