Skip to content
Thierry Soreze edited this page Oct 28, 2025 · 1 revision

FAQ & Troubleshooting

Common questions and solutions for DTUTMO users.

General Questions

What is DTUTMO?

DTUTMO (DTU Tone Mapping Operator) is a biologically-inspired HDR tone mapping framework that simulates the complete human visual processing pipeline. Unlike simple compression operators, it models optical phenomena, photoreceptor responses, neural processing, and perceptual appearance to produce physiologically-accurate tone mapping.

Who should use DTUTMO?

  • HDR photographers - Professional tone mapping with color accuracy
  • VR/AR developers - Accurate rendering of wide luminance ranges
  • Medical imaging - Diagnostic display of HDR scans
  • Researchers - Validated components for vision science
  • Display engineers - Cross-display appearance matching

What makes DTUTMO different from other tone mapping operators?

  1. Complete physiological model: Simulates the entire visual pipeline
  2. Hood adaptation: Power-law adaptation with adaptive gain control
  3. DTUCAM: First CAM with physiologically-consistent dual adaptation
  4. Explicit inverses: O(1) formulas eliminate iteration
  5. Hybrid mapping: Gradient-adaptive blending (2.5-4× speedup)
  6. Extreme range: Handles 0.001 to 100,000 cd/m² (9+ orders)
  7. Validated: Each stage independently validated against psychophysics

Is DTUTMO production-ready?

Yes! DTUTMO is designed for production use:

  • No parameter tuning required (validated defaults)
  • Real-time performance (GPU acceleration)
  • Robust across diverse content
  • Stable numerical behavior
  • Well-documented API

Installation & Setup

Installation fails with "No module named 'dtutmo'"

Problem: Package not installed correctly.

Solutions:

# Option 1: Install from source
git clone https://github.com/TSOR666/DTUCAM25.git
cd dtutmo
pip install -e .

# Option 2: Verify installation
python -c "import dtutmo; print(dtutmo.__version__)"

Can I use DTUTMO without GPU?

Yes! DTUTMO works perfectly on CPU. GPU acceleration is optional:

  • CPU: Works out of the box with NumPy/SciPy
  • GPU: Install PyTorch for 10-50× speedup
# CPU only
pip install numpy scipy

# With GPU support
pip install "torch>=2.5.1" "torchvision>=0.20.1"

Which Python version is required?

Python 3.10 or higher is required. Tested on:

  • Python 3.10
  • Python 3.11
  • Python 3.12

Input/Output Issues

My output is too dark/bright

Problem: Incorrect input units or scaling.

Solution: DTUTMO expects physical luminance in cd/m²:

# If your HDR is in relative units [0, 1]
hdr_physical = hdr_relative * peak_luminance  # e.g., * 1000.0

# Process
ldr = tmo.process(hdr_physical)

Check input range:

print(f"Min: {hdr.min()}, Max: {hdr.max()}, Mean: {hdr.mean()}")
# Expected: Min ~0.01-1, Max ~100-10000, Mean ~10-100

Colors look wrong/unrealistic

Problem: Input is gamma-encoded instead of linear.

Solution: Linearize sRGB gamma before processing:

def linearize_srgb(img_gamma):
    """Convert sRGB gamma to linear RGB"""
    linear = img_gamma.copy()
    low = img_gamma <= 0.04045
    linear[low] = img_gamma[low] / 12.92
    linear[~low] = ((img_gamma[~low] + 0.055) / 1.055) ** 2.4
    return linear

hdr_linear = linearize_srgb(hdr_gamma)
ldr = tmo.process(hdr_linear)

Output has artifacts/halos

Possible causes and solutions:

  1. Bilateral filtering artifacts:
# Adjust sigma or disable
config = DTUTMOConfig(use_bilateral=False)
  1. Glare PSF ringing:
# Use smaller field diameter
config = DTUTMOConfig(field_diameter=30)
  1. Gradient map artifacts in hybrid:
# Increase smoothing
config = DTUTMOConfig(
    display_mapping=DisplayMapping.PRODUCTION_HYBRID,
    gradient_smoothing=3.0
)

How do I save the output?

Output is in [0, 1] range, ready for 8-bit encoding:

from PIL import Image
import numpy as np

# Tone map
ldr = tmo.process(hdr)

# Convert to 8-bit
img_8bit = (np.clip(ldr, 0, 1) * 255).astype(np.uint8)

# Save
Image.fromarray(img_8bit).save('output.png')

Performance Issues

Processing is too slow

Solutions in order of impact:

  1. Use GPU acceleration (10-50× speedup):
import torch
from dtutmo import TorchDTUTMO

tmo = TorchDTUTMO().cuda()
  1. Use faster display mapping:
config = DTUTMOConfig(
    display_mapping=DisplayMapping.WHITEBOARD  # 3× faster
)
  1. Disable optional stages:
config = DTUTMOConfig(
    use_otf=False,      # Skip optical blur
    use_glare=False,    # Skip glare
    use_bilateral=False # Skip detail separation
)
  1. Reduce resolution:
# Downscale for processing
scale = 0.5
h, w = int(hdr.shape[0] * scale), int(hdr.shape[1] * scale)
hdr_small = cv2.resize(hdr, (w, h))

# Process
ldr_small = tmo.process(hdr_small)

# Upscale
ldr = cv2.resize(ldr_small, (hdr.shape[1], hdr.shape[0]))

Out of memory errors

Solutions:

  1. Process tiles:
def process_tiled(tmo, hdr, tile_size=512, overlap=64):
    """Process image in overlapping tiles"""
    h, w = hdr.shape[:2]
    output = np.zeros((h, w, 3))
    
    for y in range(0, h, tile_size - overlap):
        for x in range(0, w, tile_size - overlap):
            # Extract tile with overlap
            y_end = min(y + tile_size, h)
            x_end = min(x + tile_size, w)
            tile = hdr[y:y_end, x:x_end]
            
            # Process
            tile_out = tmo.process(tile)
            
            # Blend into output (handle overlap)
            output[y:y_end, x:x_end] = tile_out
    
    return output
  1. Reduce image size:
# Process at lower resolution
hdr_small = cv2.resize(hdr, None, fx=0.5, fy=0.5)
  1. Disable bilateral:
config = DTUTMOConfig(use_bilateral=False)
  1. Use GPU (larger memory pool):
tmo = TorchDTUTMO().cuda()

GPU runs out of memory

Solutions:

  1. Reduce batch size:
# Process one image at a time
for img in images:
    output = tmo.process(img.unsqueeze(0))
  1. Use mixed precision:
with torch.cuda.amp.autocast():
    output = tmo.process(input)
  1. Clear cache:
import torch
torch.cuda.empty_cache()

Configuration Issues

Which CAM should I use?

Decision tree:

Luminance range?
├─ >16,860 cd/m² → CAMType.DTUCAM
├─ 0.01-16,860 → CAMType.XLRCAM  
└─ 0.1-1,000 → CAMType.CIECAM16

Need CIE standard? → CAMType.CIECAM16
Need speed? → CAMType.DTUCAM (explicit inverse)
Need physiological accuracy? → CAMType.DTUCAM
Don't need cross-media? → CAMType.NONE (fastest)

Recommendation: Start with CAMType.DTUCAM

Which display mapping should I use?

Use Case Mapping Why
Production PRODUCTION_HYBRID Best quality/speed
Preview WHITEBOARD Fastest
Research FULL_INVERSE Most accurate
Legacy LEGACY v1.x compatibility

Recommendation: PRODUCTION_HYBRID for almost everything

How do I target HDR displays?

# HDR10 (PQ)
config = DTUTMOConfig(
    target_display=DisplayStandard.REC_2100_PQ,
    target_luminance=1000.0,  # Your display's peak
    viewing_condition=ViewingCondition.DIM,
)

# HLG
config = DTUTMOConfig(
    target_display=DisplayStandard.REC_2100_HLG,
    target_luminance=1000.0,
    viewing_condition=ViewingCondition.DIM,
)

What viewing condition should I use?

Based on ambient lighting:

# Dark room (cinema, <0.2 cd/m²)
config = DTUTMOConfig(viewing_condition=ViewingCondition.DARK)

# Dim room (home theater, 0.2-20 cd/m²)
config = DTUTMOConfig(viewing_condition=ViewingCondition.DIM)

# Bright room (office, >20 cd/m²)
config = DTUTMOConfig(viewing_condition=ViewingCondition.AVERAGE)

Algorithm Questions

How does Hood adaptation work?

Hood & Finkelstein (1986) showed that the semi-saturation parameter σ is NOT constant but increases with adaptation:

σ(I_a) = k₁ · ((O₁ + I_a) / O₁)^m

Additionally, DTUTMO implements adaptive gain control:

R_max(I_a) = k₂ · [(O₂ + p·I_a) / O₂]^(-1/2)

This dual adaptation (σ + R_max) enables accurate responses across scotopic to photopic vision (10⁻³ to 10⁴ Td).

See Pipeline Stages - Stage 8 for details.

What's the Purkinje shift?

Rods and cones have different spectral sensitivities:

  • Cones: Peak at 555nm (yellowish-green)
  • Rods: Peak at 507nm (bluish-green)

This 48nm difference explains why blue-green appears brighter in dim conditions. DTUTMO accounts for this by extracting separate rod signals:

L_scotopic = 0.05·R + 0.85·G + 0.10·B

Why does DTUTMO use two-stage RGB→XYZ→LMS?

Common error: Treating RGB channels as LMS cone responses.

Correct approach: Two-stage transformation through CIE XYZ ensures physiologically-accurate cone fundamentals:

RGB (device) → XYZ (standard) → LMS (physiological)

This accounts for:

  • Device-dependent primaries
  • Standard illuminant (D65)
  • Physiological cone spectral sensitivities

See Pipeline Stages - Stage 3 for details.

What's the hybrid display mapping advantage?

Observation: Full inverse accuracy needed primarily at edges.

Strategy: Compute both methods, blend based on gradient:

L_d = (1 - w) · L_whiteboard + w · L_full_inverse

Where w ∈ [0,1] depends on gradient magnitude.

Result:

  • 70-90% of pixels use fast whiteboard
  • 10-30% use accurate inverse
  • 2.5-4× speedup vs full inverse
  • Identical visual quality

See Display Mapping for details.


Comparison with Other TMOs

DTUTMO vs Reinhard (2002)

Feature DTUTMO Reinhard
Adaptation Hood (physiological) Global/local heuristic
Photoreceptor Full model None
Spectral RGB→XYZ→LMS RGB direct
Validation Per-stage Subjective
Speed Fast (hybrid) Very fast

When to use Reinhard: Need simplest possible method, don't care about physiological accuracy.

When to use DTUTMO: Need accurate color, cross-display matching, validated components.

DTUTMO vs Mantiuk (2008)

Feature DTUTMO Mantiuk
Basis Physiology Contrast perception
Metric Custom stages HDR-VDP-2
Adaptation Hood model Simplified
Speed Fast Slow (iterative)

When to use Mantiuk: Optimizing for HDR-VDP-2 metric.

When to use DTUTMO: Need physiological accuracy, faster processing, modular pipeline.

DTUTMO vs Photographic TMOs

Feature DTUTMO Photographic
Model Physiological Exposure adjustment
Parameters Validated defaults User tuning required
Color Accurate Subjective
Automation Fully automatic Manual adjustment

When to use Photographic: Want artistic control, film look.

When to use DTUTMO: Want consistent results, color accuracy, no manual tuning.


Advanced Usage

How do I batch process images?

from pathlib import Path
from dtutmo import CompleteDTUTMO

def process_directory(input_dir, output_dir, config=None):
    """Process all HDR images in directory"""
    tmo = CompleteDTUTMO(config)
    
    input_path = Path(input_dir)
    output_path = Path(output_dir)
    output_path.mkdir(exist_ok=True)
    
    for hdr_file in input_path.glob("*.exr"):
        print(f"Processing {hdr_file.name}...")
        
        hdr = load_hdr(hdr_file)
        ldr = tmo.process(hdr)
        
        output_file = output_path / f"{hdr_file.stem}.png"
        save_image(output_file, ldr)

# Usage
process_directory("hdr_images/", "output/")

Can I access intermediate results?

Yes! Use return_intermediate=True:

result = tmo.process(hdr_image, return_intermediate=True)

# Access stages
adaptation = result['local_adaptation']
cone_L = result['photoreceptor_responses']['cones']['L']
cone_M = result['photoreceptor_responses']['cones']['M']
cone_S = result['photoreceptor_responses']['cones']['S']
rod = result['photoreceptor_responses']['rod']
csf_output = result['csf_output']
display_output = result['output']

# Visualize
import matplotlib.pyplot as plt
plt.imshow(adaptation, cmap='viridis')
plt.colorbar(label='Adaptation (cd/m²)')
plt.show()

How do I profile performance?

import time

# Profile total time
start = time.time()
output = tmo.process(hdr_image)
total_time = time.time() - start
print(f"Total: {total_time:.3f}s")

# Profile with intermediate stages
result = tmo.process(hdr_image, return_intermediate=True)
# Each stage timing is in result['timings']

For detailed profiling:

import cProfile
import pstats

profiler = cProfile.Profile()
profiler.enable()

output = tmo.process(hdr_image)

profiler.disable()
stats = pstats.Stats(profiler)
stats.sort_stats('cumulative')
stats.print_stats(20)  # Top 20 functions

Can I customize individual stages?

Yes! DTUTMO is modular:

from dtutmo.optics import apply_otf
from dtutmo.photoreceptors import CorrectedPhotoreceptorResponse
from dtutmo.display import HybridDisplayMapper

# Custom pipeline
img = apply_otf(hdr_image, age=30, ppd=45)
# ... custom processing ...
photo = CorrectedPhotoreceptorResponse()
responses = photo.compute_response(img, adapt, pupil, 'L_cone')
# ... more stages ...
mapper = HybridDisplayMapper()
output = mapper.map_to_display(responses, adapt, config)

Debugging

Enable verbose logging

import logging

logging.basicConfig(level=logging.DEBUG)

from dtutmo import CompleteDTUTMO

tmo = CompleteDTUTMO()
output = tmo.process(hdr_image)  # Will print debug info

Check intermediate values

result = tmo.process(hdr_image, return_intermediate=True)

# Check for NaN/Inf
import numpy as np
for key, value in result.items():
    if isinstance(value, np.ndarray):
        print(f"{key}:")
        print(f"  Min: {value.min()}")
        print(f"  Max: {value.max()}")
        print(f"  Mean: {value.mean()}")
        print(f"  NaN: {np.isnan(value).sum()}")
        print(f"  Inf: {np.isinf(value).sum()}")

Validate input

def validate_hdr_input(img):
    """Validate HDR input"""
    assert img.ndim == 3, f"Expected 3D array, got {img.ndim}D"
    assert img.shape[2] == 3, f"Expected RGB, got {img.shape[2]} channels"
    assert img.dtype in [np.float32, np.float64], f"Expected float, got {img.dtype}"
    assert img.min() >= 0, f"Negative values found: {img.min()}"
    assert img.max() > 1, f"Suspiciously low max: {img.max()} (should be in cd/m²)"
    print("✓ Input validation passed")

validate_hdr_input(hdr_image)

Getting Help

Where can I get support?

How do I report a bug?

Please include:

  1. DTUTMO version: python -c "import dtutmo; print(dtutmo.__version__)"
  2. Python version: python --version
  3. Operating system: Windows/macOS/Linux
  4. Minimal reproducible example:
import numpy as np
from dtutmo import CompleteDTUTMO

hdr = np.random.rand(100, 100, 3) * 1000
tmo = CompleteDTUTMO()
output = tmo.process(hdr)  # Bug occurs here
  1. Error message: Full traceback
  2. Expected behavior: What should happen
  3. Actual behavior: What actually happens

How do I request a feature?

Open an issue on GitHub with:

  1. Use case: Why do you need this feature?
  2. Proposed API: How would you like to use it?
  3. Alternatives: What workarounds have you tried?
  4. Impact: Who else might benefit?

How can I contribute?

See Development Guide for:

  • Setting up development environment
  • Running tests
  • Code style guidelines
  • Pull request process

References

Papers

  • Hood, D. C., & Finkelstein, M. A. (1986). Sensitivity to Light. Handbook of Perception and Human Performance.
  • Kim, M., Weyrich, T., & Kautz, J. (2009). Modeling Human Color Perception Under Extended Luminance Levels. ACM TOG.
  • CIE 180:2010. Disability Glare.
  • CIE 248:2022. CIECAM16.
  • Ashraf, M., et al. (2024). CastleCSF. Journal of Vision.

Books

  • Reinhard, E., et al. (2010). High Dynamic Range Imaging, 2nd ed. Morgan Kaufmann.
  • Fairchild, M. D. (2013). Color Appearance Models, 3rd ed. Wiley.

Links


Can't find your question? Ask on GitHub Discussions!

Clone this wiki locally