first commit
This commit is contained in:
+139
@@ -0,0 +1,139 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# IDEs
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS generated files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# ComfyUI specific
|
||||
test_outputs/
|
||||
temp_images/
|
||||
*.tmp
|
||||
|
||||
# Development files
|
||||
dev_test.py
|
||||
scratch.py
|
||||
@@ -0,0 +1,61 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- Nothing yet
|
||||
|
||||
### Changed
|
||||
- Nothing yet
|
||||
|
||||
### Fixed
|
||||
- Nothing yet
|
||||
|
||||
## [1.0.0] - 2025-01-20
|
||||
|
||||
### Added
|
||||
- **Luminance Stack Processor (3 Stops)** - Professional HDR processing node for merging EV+2, EV+0, EV-2 exposures
|
||||
- **Luminance Stack Processor (5 Stops)** - Professional HDR processing node for merging EV+4, EV+2, EV+0, EV-2, EV-4 exposures
|
||||
- **Debevec Algorithm Implementation** - Industry-standard HDR reconstruction using Paul Debevec's method
|
||||
- **16-bit Output Support** - Maximum dynamic range preservation in final images
|
||||
- **Automatic Camera Response Function Estimation** - Intelligent sensor response curve calculation
|
||||
- **Configurable Exposure Steps** - Adjustable EV step sizes for different capture methods
|
||||
- **Error Handling & Fallbacks** - Graceful degradation with middle exposure fallback
|
||||
- **Professional Documentation** - Complete README, installation guide, and usage examples
|
||||
- **Modern Python Packaging** - Full pyproject.toml configuration with semantic versioning
|
||||
- **Comprehensive Testing** - Unit tests for core HDR processing functionality
|
||||
- **ComfyUI Integration** - Seamless tensor format handling and node registration
|
||||
|
||||
### Technical Features
|
||||
- OpenCV-based HDR processing pipeline
|
||||
- Automatic image alignment preparation
|
||||
- Memory-efficient processing with cleanup
|
||||
- Detailed logging for debugging
|
||||
- Cross-platform compatibility (Windows, Linux, macOS)
|
||||
|
||||
### Dependencies
|
||||
- OpenCV >= 4.8.0 (only external dependency)
|
||||
- NumPy and PyTorch provided by ComfyUI
|
||||
- Python 3.8+ support
|
||||
|
||||
---
|
||||
|
||||
## Version Numbering
|
||||
|
||||
This project follows [Semantic Versioning](https://semver.org/):
|
||||
|
||||
- **MAJOR** version when you make incompatible API changes
|
||||
- **MINOR** version when you add functionality in a backward compatible manner
|
||||
- **PATCH** version when you make backward compatible bug fixes
|
||||
|
||||
### Version History Guidelines
|
||||
|
||||
- **1.x.x** - Stable production releases with Debevec HDR algorithm
|
||||
- **0.x.x** - Pre-release/beta versions (not used in this project)
|
||||
- **x.x.0** - Major feature releases
|
||||
- **x.x.1+** - Bug fixes and minor improvements
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Luminance Stack Processor Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,224 @@
|
||||
# Luminance Stack Processor - ComfyUI Custom Nodes
|
||||
|
||||
[](https://github.com/sumitchatterjee13/Luminance-Stack-Processor)
|
||||
[](https://python.org)
|
||||
[](https://github.com/sumitchatterjee13/Luminance-Stack-Processor/blob/main/LICENSE)
|
||||
[](https://github.com/comfyanonymous/ComfyUI)
|
||||
[](https://github.com/sumitchatterjee13/Luminance-Stack-Processor)
|
||||
|
||||
Professional HDR (High Dynamic Range) processing nodes for ComfyUI using the **Debevec Algorithm** for merging multiple exposure images into a single high dynamic range image.
|
||||
|
||||
**Version: 1.0.0** | **Release Date: 2025-01-20**
|
||||
|
||||
## 🎯 Features
|
||||
|
||||
- **Professional HDR Processing**: Implements the industry-standard Debevec Algorithm for HDR image creation
|
||||
- **Two Processing Modes**:
|
||||
- **3-Stop Processor**: Merges EV+2, EV+0, EV-2 exposures
|
||||
- **5-Stop Processor**: Merges EV+4, EV+2, EV+0, EV-2, EV-4 exposures
|
||||
- **16-bit Output**: Preserves maximum dynamic range in the final image
|
||||
- **Automatic Camera Response Function**: Estimates and applies camera response curves
|
||||
- **Fallback Safety**: Gracefully handles errors with fallback to middle exposure
|
||||
- **ComfyUI Integration**: Seamless integration with ComfyUI workflows
|
||||
|
||||
## 📋 Requirements
|
||||
|
||||
- ComfyUI
|
||||
- Python 3.8+
|
||||
- OpenCV (cv2) >= 4.8.0
|
||||
- NumPy >= 1.21.0
|
||||
- PyTorch >= 1.9.0
|
||||
|
||||
## 🚀 Installation
|
||||
|
||||
1. **Clone or Download** this repository to your ComfyUI custom nodes directory:
|
||||
```bash
|
||||
# Method 1: Clone directly into ComfyUI custom nodes directory
|
||||
cd ComfyUI/custom_nodes/
|
||||
git clone https://github.com/sumitchatterjee13/Luminance-Stack-Processor.git luminance-stack-processor
|
||||
|
||||
# Method 2: Download ZIP and extract to:
|
||||
# ComfyUI/custom_nodes/luminance-stack-processor/
|
||||
```
|
||||
|
||||
2. **Install Dependencies**:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
Or install manually:
|
||||
```bash
|
||||
pip install opencv-python>=4.8.0 numpy>=1.21.0 torch>=1.9.0
|
||||
```
|
||||
|
||||
3. **Restart ComfyUI** to load the new nodes
|
||||
|
||||
## 🎨 Usage
|
||||
|
||||
### Luminance Stack Processor (3 Stops)
|
||||
|
||||
Perfect for standard HDR bracketing with 3 exposures:
|
||||
|
||||
**Inputs:**
|
||||
- `ev_plus_2`: Overexposed image (+2 EV)
|
||||
- `ev_0`: Normal exposure image (0 EV)
|
||||
- `ev_minus_2`: Underexposed image (-2 EV)
|
||||
- `exposure_step`: (Optional) EV step size (default: 2.0)
|
||||
|
||||
**Output:**
|
||||
- `hdr_image`: Merged 16-bit HDR image
|
||||
|
||||
### Luminance Stack Processor (5 Stops)
|
||||
|
||||
For extended dynamic range with 5 exposures:
|
||||
|
||||
**Inputs:**
|
||||
- `ev_plus_4`: Most overexposed image (+4 EV)
|
||||
- `ev_plus_2`: Overexposed image (+2 EV)
|
||||
- `ev_0`: Normal exposure image (0 EV)
|
||||
- `ev_minus_2`: Underexposed image (-2 EV)
|
||||
- `ev_minus_4`: Most underexposed image (-4 EV)
|
||||
- `exposure_step`: (Optional) EV step size (default: 2.0)
|
||||
|
||||
**Output:**
|
||||
- `hdr_image`: Merged 16-bit HDR image
|
||||
|
||||
## 🔬 How It Works
|
||||
|
||||
The nodes implement the **Debevec Algorithm** (Paul E. Debevec, Jitendra Malik, 1997) which:
|
||||
|
||||
1. **Analyzes Multiple Exposures**: Takes differently exposed images of the same scene
|
||||
2. **Estimates Camera Response Function**: Determines how the camera sensor responds to light
|
||||
3. **Recovers Scene Radiance**: Calculates the actual light values in the scene
|
||||
4. **Merges to HDR**: Combines all exposures into a single high dynamic range image
|
||||
5. **Tone Maps for Output**: Converts to 16-bit format for maximum compatibility
|
||||
|
||||
## 📸 Best Practices
|
||||
|
||||
### For Capturing Source Images:
|
||||
- Use a **tripod** for perfect alignment
|
||||
- Keep the **same white balance** across all exposures
|
||||
- Use **manual focus** to prevent focus shifts
|
||||
- Capture in **RAW format** when possible
|
||||
- Use **exposure compensation** or **manual mode**
|
||||
|
||||
### EV (Exposure Value) Guidelines:
|
||||
- **3-Stop**: +2, 0, -2 EV (4x range)
|
||||
- **5-Stop**: +4, +2, 0, -2, -4 EV (16x range)
|
||||
- Adjust `exposure_step` parameter if using different increments
|
||||
|
||||
## ⚙️ Technical Details
|
||||
|
||||
- **Algorithm**: Debevec & Malik HDR reconstruction
|
||||
- **Input Format**: ComfyUI IMAGE tensors (0-1 float range)
|
||||
- **Output Format**: 16-bit images for extended dynamic range
|
||||
- **Processing**: OpenCV's `createCalibrateDebevec()` and `createMergeDebevec()`
|
||||
- **Memory**: Efficient processing with automatic cleanup
|
||||
- **Error Handling**: Graceful fallbacks with detailed logging
|
||||
|
||||
## 🔧 Troubleshooting
|
||||
|
||||
### Common Issues:
|
||||
|
||||
1. **"Module not found" error**:
|
||||
- Ensure all dependencies are installed: `pip install -r requirements.txt`
|
||||
- Restart ComfyUI completely
|
||||
|
||||
2. **Poor HDR results**:
|
||||
- Ensure input images are properly exposed (not all over/under)
|
||||
- Check that images are aligned (use tripod)
|
||||
- Verify EV differences match your capture method
|
||||
|
||||
3. **Memory issues**:
|
||||
- Process smaller images first
|
||||
- Ensure adequate RAM available
|
||||
- Close unnecessary applications
|
||||
|
||||
### Debug Information:
|
||||
The nodes provide detailed logging. Check your ComfyUI console for processing information and error details.
|
||||
|
||||
## 🏗️ Development
|
||||
|
||||
### Project Structure:
|
||||
```
|
||||
luminance-stack-processor/
|
||||
├── __init__.py # ComfyUI registration
|
||||
├── luminance_stack_processor.py # Main node implementations
|
||||
├── requirements.txt # Dependencies
|
||||
└── README.md # Documentation
|
||||
```
|
||||
|
||||
### Contributing:
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Test with various HDR image sets
|
||||
4. Submit a pull request
|
||||
|
||||
## 📝 Changelog
|
||||
|
||||
See [CHANGELOG.md](CHANGELOG.md) for version history and detailed release notes.
|
||||
|
||||
## 🔧 Development
|
||||
|
||||
### Version Management
|
||||
|
||||
This project uses [Semantic Versioning](https://semver.org/). To bump version:
|
||||
|
||||
```bash
|
||||
# Install bumpversion
|
||||
pip install bump2version
|
||||
|
||||
# Bump patch version (1.0.0 -> 1.0.1)
|
||||
bump2version patch
|
||||
|
||||
# Bump minor version (1.0.0 -> 1.1.0)
|
||||
bump2version minor
|
||||
|
||||
# Bump major version (1.0.0 -> 2.0.0)
|
||||
bump2version major
|
||||
```
|
||||
|
||||
### Project Structure
|
||||
```
|
||||
luminance-stack-processor/
|
||||
├── __init__.py # ComfyUI node registration & version info
|
||||
├── luminance_stack_processor.py # Main HDR processing nodes
|
||||
├── version.py # Centralized version management
|
||||
├── pyproject.toml # Modern Python packaging configuration
|
||||
├── requirements.txt # Runtime dependencies
|
||||
├── README.md # Documentation
|
||||
└── CHANGELOG.md # Version history
|
||||
```
|
||||
|
||||
## 📚 References
|
||||
|
||||
- **Debevec, P. E., & Malik, J.** (1997). Recovering high dynamic range radiance maps from photographs. *ACM SIGGRAPH Computer Graphics*, 31(Annual Conference Series), 367-378.
|
||||
- **OpenCV HDR Documentation**: https://docs.opencv.org/4.x/d3/db7/tutorial_hdr_imaging.html
|
||||
- **ComfyUI Custom Node Guidelines**: https://docs.comfy.org/custom-nodes/
|
||||
- **Semantic Versioning**: https://semver.org/
|
||||
|
||||
## 📜 License
|
||||
|
||||
MIT License - Feel free to use and modify for your projects.
|
||||
|
||||
## 🤝 Support
|
||||
|
||||
For issues, questions, or contributions:
|
||||
- **GitHub Issues**: [Report bugs or request features](https://github.com/sumitchatterjee13/Luminance-Stack-Processor/issues)
|
||||
- **GitHub Discussions**: [Ask questions or share workflows](https://github.com/sumitchatterjee13/Luminance-Stack-Processor/discussions)
|
||||
- Check the troubleshooting section above
|
||||
- Review ComfyUI console logs for detailed error information
|
||||
- Check [CHANGELOG.md](CHANGELOG.md) for version-specific issues
|
||||
- Ensure all dependencies are properly installed
|
||||
|
||||
### Contributing
|
||||
We welcome contributions! Please:
|
||||
1. Fork the repository
|
||||
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
||||
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
||||
4. Push to the branch (`git push origin feature/amazing-feature`)
|
||||
5. Open a Pull Request
|
||||
|
||||
---
|
||||
|
||||
**Happy HDR Processing!** 📸✨ | **Version 1.0.0**
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
"""
|
||||
Luminance Stack Processor - ComfyUI Custom Nodes
|
||||
Professional HDR processing nodes using the Debevec Algorithm
|
||||
|
||||
This module provides two custom nodes for ComfyUI:
|
||||
1. Luminance Stack Processor (3 Stops) - For processing EV+2, EV+0, EV-2 exposures
|
||||
2. Luminance Stack Processor (5 Stops) - For processing EV+4, EV+2, EV+0, EV-2, EV-4 exposures
|
||||
|
||||
Requirements:
|
||||
- OpenCV (cv2)
|
||||
- NumPy
|
||||
- PyTorch
|
||||
|
||||
Installation:
|
||||
1. Place this folder in your ComfyUI/custom_nodes/ directory
|
||||
2. Install required dependencies: pip install opencv-python numpy torch
|
||||
3. Restart ComfyUI
|
||||
|
||||
Author: Sumit Chatterjee
|
||||
Version: 1.0.0
|
||||
License: MIT
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Import version information
|
||||
try:
|
||||
from .version import __version__, get_version_string, get_full_version_info
|
||||
except ImportError:
|
||||
__version__ = "1.0.0"
|
||||
get_version_string = lambda: f"v{__version__}"
|
||||
get_full_version_info = lambda: {"version": __version__}
|
||||
|
||||
# Add current directory to Python path for imports
|
||||
current_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
if current_dir not in sys.path:
|
||||
sys.path.insert(0, current_dir)
|
||||
|
||||
try:
|
||||
# Import the node classes
|
||||
from .luminance_stack_processor import (
|
||||
LuminanceStackProcessor3Stops,
|
||||
LuminanceStackProcessor5Stops,
|
||||
NODE_CLASS_MAPPINGS as NODES,
|
||||
NODE_DISPLAY_NAME_MAPPINGS as DISPLAY_NAMES
|
||||
)
|
||||
|
||||
# Export the mappings that ComfyUI expects
|
||||
NODE_CLASS_MAPPINGS = NODES
|
||||
NODE_DISPLAY_NAME_MAPPINGS = DISPLAY_NAMES
|
||||
|
||||
print("✅ Luminance Stack Processor nodes loaded successfully!")
|
||||
print(f" - Version: {get_version_string()}")
|
||||
print(f" - Available nodes: {list(NODE_CLASS_MAPPINGS.keys())}")
|
||||
|
||||
except ImportError as e:
|
||||
print(f"❌ Failed to import Luminance Stack Processor nodes: {e}")
|
||||
print(" Please ensure all required dependencies are installed:")
|
||||
print(" pip install opencv-python numpy torch")
|
||||
|
||||
# Provide empty mappings to prevent ComfyUI errors
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {}
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Unexpected error loading Luminance Stack Processor nodes: {e}")
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {}
|
||||
|
||||
# Metadata for ComfyUI
|
||||
__all__ = [
|
||||
'NODE_CLASS_MAPPINGS',
|
||||
'NODE_DISPLAY_NAME_MAPPINGS',
|
||||
'__version__'
|
||||
]
|
||||
|
||||
# Version info for external access
|
||||
__version__ = __version__
|
||||
@@ -0,0 +1,286 @@
|
||||
"""
|
||||
Luminance Stack Processor - Professional ComfyUI Custom Nodes
|
||||
Implements HDR processing using the Debevec Algorithm for multiple exposure fusion
|
||||
|
||||
Author: Sumit Chatterjee
|
||||
Version: 1.0.0
|
||||
Semantic Versioning: MAJOR.MINOR.PATCH
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
from typing import Tuple, List
|
||||
import logging
|
||||
|
||||
# Set up logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def tensor_to_cv2(tensor: torch.Tensor) -> np.ndarray:
|
||||
"""Convert ComfyUI tensor to OpenCV format"""
|
||||
# ComfyUI tensors are typically [B, H, W, C] in 0-1 range
|
||||
if len(tensor.shape) == 4:
|
||||
tensor = tensor.squeeze(0) # Remove batch dimension
|
||||
|
||||
# Convert to numpy and scale to 0-255
|
||||
image = tensor.cpu().numpy()
|
||||
if image.dtype == np.float32 or image.dtype == np.float64:
|
||||
image = np.clip(image * 255.0, 0, 255).astype(np.uint8)
|
||||
|
||||
return image
|
||||
|
||||
|
||||
def cv2_to_tensor(image: np.ndarray) -> torch.Tensor:
|
||||
"""Convert OpenCV image to ComfyUI tensor format"""
|
||||
# Ensure image is float32 in 0-1 range
|
||||
if image.dtype == np.uint8:
|
||||
image = image.astype(np.float32) / 255.0
|
||||
elif image.dtype == np.uint16:
|
||||
image = image.astype(np.float32) / 65535.0
|
||||
elif image.dtype == np.float32:
|
||||
image = np.clip(image, 0.0, 1.0)
|
||||
|
||||
# Add batch dimension and convert to tensor
|
||||
tensor = torch.from_numpy(image).unsqueeze(0)
|
||||
return tensor
|
||||
|
||||
|
||||
class DebevecHDRProcessor:
|
||||
"""Core HDR processing using Debevec Algorithm"""
|
||||
|
||||
def __init__(self):
|
||||
self.calibrator = cv2.createCalibrateDebevec()
|
||||
self.merge_debevec = cv2.createMergeDebevec()
|
||||
|
||||
def process_hdr(self, images: List[np.ndarray], exposure_times: List[float]) -> np.ndarray:
|
||||
"""
|
||||
Process multiple exposure images using Debevec algorithm
|
||||
|
||||
Args:
|
||||
images: List of images (OpenCV format)
|
||||
exposure_times: List of exposure times in seconds
|
||||
|
||||
Returns:
|
||||
HDR merged image as 16-bit
|
||||
"""
|
||||
try:
|
||||
# Ensure images are in uint8 format for calibration
|
||||
processed_images = []
|
||||
for img in images:
|
||||
if img.dtype != np.uint8:
|
||||
if img.dtype == np.float32:
|
||||
img = np.clip(img * 255.0, 0, 255).astype(np.uint8)
|
||||
else:
|
||||
img = img.astype(np.uint8)
|
||||
processed_images.append(img)
|
||||
|
||||
# Convert exposure times to numpy array
|
||||
times = np.array(exposure_times, dtype=np.float32)
|
||||
|
||||
logger.info(f"Processing {len(processed_images)} images with exposure times: {times}")
|
||||
|
||||
# Estimate camera response function
|
||||
response = self.calibrator.process(processed_images, times)
|
||||
|
||||
# Merge images into HDR
|
||||
hdr = self.merge_debevec.process(processed_images, times, response)
|
||||
|
||||
# Convert HDR to 16-bit for better dynamic range preservation
|
||||
# Apply tone mapping to fit into 16-bit range
|
||||
hdr_normalized = cv2.normalize(hdr, None, 0, 65535, cv2.NORM_MINMAX)
|
||||
hdr_16bit = hdr_normalized.astype(np.uint16)
|
||||
|
||||
logger.info(f"HDR processing completed. Output shape: {hdr_16bit.shape}")
|
||||
|
||||
return hdr_16bit
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"HDR processing error: {str(e)}")
|
||||
# Fallback: return the middle exposure image
|
||||
if images:
|
||||
middle_idx = len(images) // 2
|
||||
fallback = images[middle_idx]
|
||||
if fallback.dtype != np.uint16:
|
||||
fallback = (fallback.astype(np.float32) * 257).astype(np.uint16)
|
||||
return fallback
|
||||
raise e
|
||||
|
||||
|
||||
class LuminanceStackProcessor3Stops:
|
||||
"""
|
||||
Professional ComfyUI Custom Node for 3-stop HDR processing
|
||||
Processes EV+2, EV+0, EV-2 exposures using Debevec Algorithm
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"ev_plus_2": ("IMAGE",),
|
||||
"ev_0": ("IMAGE",),
|
||||
"ev_minus_2": ("IMAGE",),
|
||||
},
|
||||
"optional": {
|
||||
"exposure_step": ("FLOAT", {
|
||||
"default": 2.0,
|
||||
"min": 0.5,
|
||||
"max": 5.0,
|
||||
"step": 0.1,
|
||||
"display": "number"
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("hdr_image",)
|
||||
FUNCTION = "process_3_stop_hdr"
|
||||
CATEGORY = "image/luminance"
|
||||
|
||||
def __init__(self):
|
||||
self.processor = DebevecHDRProcessor()
|
||||
|
||||
def process_3_stop_hdr(self, ev_plus_2, ev_0, ev_minus_2, exposure_step=2.0):
|
||||
"""
|
||||
Process 3-stop HDR merge
|
||||
|
||||
Args:
|
||||
ev_plus_2: Overexposed image (+2 EV)
|
||||
ev_0: Normal exposure image (0 EV)
|
||||
ev_minus_2: Underexposed image (-2 EV)
|
||||
exposure_step: EV step size
|
||||
|
||||
Returns:
|
||||
Tuple containing merged HDR image
|
||||
"""
|
||||
try:
|
||||
# Convert tensors to OpenCV format
|
||||
img_plus_2 = tensor_to_cv2(ev_plus_2)
|
||||
img_0 = tensor_to_cv2(ev_0)
|
||||
img_minus_2 = tensor_to_cv2(ev_minus_2)
|
||||
|
||||
# Calculate exposure times based on EV values
|
||||
# EV difference formula: time = base_time * 2^(-EV_difference)
|
||||
base_time = 1.0 / 60.0 # 1/60s as base exposure
|
||||
|
||||
time_plus_2 = base_time * (2.0 ** (-exposure_step)) # Shorter time (overexposed)
|
||||
time_0 = base_time # Normal exposure
|
||||
time_minus_2 = base_time * (2.0 ** exposure_step) # Longer time (underexposed)
|
||||
|
||||
images = [img_plus_2, img_0, img_minus_2]
|
||||
times = [time_plus_2, time_0, time_minus_2]
|
||||
|
||||
logger.info(f"3-Stop HDR: Processing with times {times}")
|
||||
|
||||
# Process HDR
|
||||
hdr_result = self.processor.process_hdr(images, times)
|
||||
|
||||
# Convert back to tensor
|
||||
output_tensor = cv2_to_tensor(hdr_result)
|
||||
|
||||
return (output_tensor,)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"3-Stop HDR processing failed: {str(e)}")
|
||||
# Return middle exposure as fallback
|
||||
return (ev_0,)
|
||||
|
||||
|
||||
class LuminanceStackProcessor5Stops:
|
||||
"""
|
||||
Professional ComfyUI Custom Node for 5-stop HDR processing
|
||||
Processes EV+4, EV+2, EV+0, EV-2, EV-4 exposures using Debevec Algorithm
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"ev_plus_4": ("IMAGE",),
|
||||
"ev_plus_2": ("IMAGE",),
|
||||
"ev_0": ("IMAGE",),
|
||||
"ev_minus_2": ("IMAGE",),
|
||||
"ev_minus_4": ("IMAGE",),
|
||||
},
|
||||
"optional": {
|
||||
"exposure_step": ("FLOAT", {
|
||||
"default": 2.0,
|
||||
"min": 0.5,
|
||||
"max": 5.0,
|
||||
"step": 0.1,
|
||||
"display": "number"
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("hdr_image",)
|
||||
FUNCTION = "process_5_stop_hdr"
|
||||
CATEGORY = "image/luminance"
|
||||
|
||||
def __init__(self):
|
||||
self.processor = DebevecHDRProcessor()
|
||||
|
||||
def process_5_stop_hdr(self, ev_plus_4, ev_plus_2, ev_0, ev_minus_2, ev_minus_4, exposure_step=2.0):
|
||||
"""
|
||||
Process 5-stop HDR merge
|
||||
|
||||
Args:
|
||||
ev_plus_4: Most overexposed image (+4 EV)
|
||||
ev_plus_2: Overexposed image (+2 EV)
|
||||
ev_0: Normal exposure image (0 EV)
|
||||
ev_minus_2: Underexposed image (-2 EV)
|
||||
ev_minus_4: Most underexposed image (-4 EV)
|
||||
exposure_step: EV step size
|
||||
|
||||
Returns:
|
||||
Tuple containing merged HDR image
|
||||
"""
|
||||
try:
|
||||
# Convert tensors to OpenCV format
|
||||
img_plus_4 = tensor_to_cv2(ev_plus_4)
|
||||
img_plus_2 = tensor_to_cv2(ev_plus_2)
|
||||
img_0 = tensor_to_cv2(ev_0)
|
||||
img_minus_2 = tensor_to_cv2(ev_minus_2)
|
||||
img_minus_4 = tensor_to_cv2(ev_minus_4)
|
||||
|
||||
# Calculate exposure times based on EV values
|
||||
base_time = 1.0 / 60.0 # 1/60s as base exposure
|
||||
|
||||
time_plus_4 = base_time * (2.0 ** (-2 * exposure_step))
|
||||
time_plus_2 = base_time * (2.0 ** (-exposure_step))
|
||||
time_0 = base_time
|
||||
time_minus_2 = base_time * (2.0 ** exposure_step)
|
||||
time_minus_4 = base_time * (2.0 ** (2 * exposure_step))
|
||||
|
||||
images = [img_plus_4, img_plus_2, img_0, img_minus_2, img_minus_4]
|
||||
times = [time_plus_4, time_plus_2, time_0, time_minus_2, time_minus_4]
|
||||
|
||||
logger.info(f"5-Stop HDR: Processing with times {times}")
|
||||
|
||||
# Process HDR
|
||||
hdr_result = self.processor.process_hdr(images, times)
|
||||
|
||||
# Convert back to tensor
|
||||
output_tensor = cv2_to_tensor(hdr_result)
|
||||
|
||||
return (output_tensor,)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"5-Stop HDR processing failed: {str(e)}")
|
||||
# Return middle exposure as fallback
|
||||
return (ev_0,)
|
||||
|
||||
|
||||
# Node class mappings for ComfyUI
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"LuminanceStackProcessor3Stops": LuminanceStackProcessor3Stops,
|
||||
"LuminanceStackProcessor5Stops": LuminanceStackProcessor5Stops,
|
||||
}
|
||||
|
||||
# Display names for nodes in ComfyUI interface
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"LuminanceStackProcessor3Stops": "Luminance Stack Processor (3 Stops)",
|
||||
"LuminanceStackProcessor5Stops": "Luminance Stack Processor (5 Stops)",
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=65.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "luminance-stack-processor"
|
||||
version = "1.0.0"
|
||||
description = "Professional HDR processing nodes for ComfyUI using the Debevec Algorithm"
|
||||
readme = "README.md"
|
||||
license = { text = "MIT" }
|
||||
authors = [
|
||||
{ name = "Sumit Chatterjee", email = "sumitchatterjee13@gmail.com" }
|
||||
]
|
||||
keywords = [
|
||||
"comfyui",
|
||||
"custom-node",
|
||||
"hdr",
|
||||
"image-processing",
|
||||
"debevec-algorithm",
|
||||
"exposure-fusion",
|
||||
"computer-vision"
|
||||
]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Developers",
|
||||
"Intended Audience :: End Users/Desktop",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Multimedia :: Graphics :: Graphics Conversion",
|
||||
"Topic :: Scientific/Engineering :: Image Processing",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules"
|
||||
]
|
||||
requires-python = ">=3.8"
|
||||
|
||||
dependencies = [
|
||||
"opencv-python>=4.8.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pytest>=6.0.0",
|
||||
"black>=21.0.0",
|
||||
"flake8>=3.9.0",
|
||||
"mypy>=0.910"
|
||||
]
|
||||
enhanced = [
|
||||
"opencv-contrib-python>=4.8.0"
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
"Homepage" = "https://github.com/sumitchatterjee13/Luminance-Stack-Processor"
|
||||
"Bug Reports" = "https://github.com/sumitchatterjee13/Luminance-Stack-Processor/issues"
|
||||
"Source" = "https://github.com/sumitchatterjee13/Luminance-Stack-Processor"
|
||||
"Documentation" = "https://github.com/sumitchatterjee13/Luminance-Stack-Processor/blob/main/README.md"
|
||||
"Discussions" = "https://github.com/sumitchatterjee13/Luminance-Stack-Processor/discussions"
|
||||
"Changelog" = "https://github.com/sumitchatterjee13/Luminance-Stack-Processor/blob/main/CHANGELOG.md"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["."]
|
||||
include = ["luminance_stack_processor*"]
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
"*" = ["*.md", "*.txt", "*.toml"]
|
||||
|
||||
# Version management
|
||||
[tool.bumpversion]
|
||||
current_version = "1.0.0"
|
||||
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
|
||||
serialize = ["{major}.{minor}.{patch}"]
|
||||
search = "{current_version}"
|
||||
replace = "{new_version}"
|
||||
|
||||
[[tool.bumpversion.files]]
|
||||
filename = "version.py"
|
||||
search = '__version__ = "{current_version}"'
|
||||
replace = '__version__ = "{new_version}"'
|
||||
|
||||
[[tool.bumpversion.files]]
|
||||
filename = "pyproject.toml"
|
||||
search = 'version = "{current_version}"'
|
||||
replace = 'version = "{new_version}"'
|
||||
|
||||
[[tool.bumpversion.files]]
|
||||
filename = "README.md"
|
||||
search = "Version: {current_version}"
|
||||
replace = "Version: {new_version}"
|
||||
|
||||
# Code formatting
|
||||
[tool.black]
|
||||
line-length = 100
|
||||
target-version = ['py38', 'py39', 'py310', 'py311', 'py312']
|
||||
include = '\.pyi?$'
|
||||
extend-exclude = '''
|
||||
/(
|
||||
# directories
|
||||
\.eggs
|
||||
| \.git
|
||||
| \.hg
|
||||
| \.mypy_cache
|
||||
| \.tox
|
||||
| \.venv
|
||||
| build
|
||||
| dist
|
||||
)/
|
||||
'''
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
line_length = 100
|
||||
@@ -0,0 +1,18 @@
|
||||
# Luminance Stack Processor - ComfyUI Custom Nodes
|
||||
# Required dependencies for HDR processing using Debevec Algorithm
|
||||
|
||||
# Core dependency for HDR processing
|
||||
opencv-python>=4.8.0
|
||||
|
||||
# Optional but recommended for better performance
|
||||
opencv-contrib-python>=4.8.0
|
||||
|
||||
# Development dependencies (optional)
|
||||
# pytest>=6.0.0
|
||||
# black>=21.0.0
|
||||
# flake8>=3.9.0
|
||||
|
||||
# Notes:
|
||||
# - NumPy and PyTorch are provided by ComfyUI, no need to install separately
|
||||
# - Only OpenCV is required for the Debevec HDR algorithm implementation
|
||||
# - If you encounter issues, try: pip install opencv-python --upgrade
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
"""
|
||||
Version information for Luminance Stack Processor
|
||||
"""
|
||||
|
||||
__version__ = "1.0.0"
|
||||
__version_info__ = (1, 0, 0)
|
||||
|
||||
# Semantic versioning components
|
||||
MAJOR = 1
|
||||
MINOR = 0
|
||||
PATCH = 0
|
||||
|
||||
# Build metadata
|
||||
BUILD_DATE = "2025-01-20"
|
||||
AUTHOR = "Sumit Chatterjee"
|
||||
DESCRIPTION = "Professional HDR processing nodes using the Debevec Algorithm"
|
||||
|
||||
# ComfyUI compatibility
|
||||
COMFYUI_MIN_VERSION = "0.1.0"
|
||||
PYTHON_MIN_VERSION = "3.8"
|
||||
|
||||
def get_version_string():
|
||||
"""Get formatted version string"""
|
||||
return f"v{__version__}"
|
||||
|
||||
def get_full_version_info():
|
||||
"""Get complete version information"""
|
||||
return {
|
||||
"version": __version__,
|
||||
"version_info": __version_info__,
|
||||
"build_date": BUILD_DATE,
|
||||
"author": AUTHOR,
|
||||
"description": DESCRIPTION,
|
||||
"comfyui_min_version": COMFYUI_MIN_VERSION,
|
||||
"python_min_version": PYTHON_MIN_VERSION
|
||||
}
|
||||
Reference in New Issue
Block a user