Skip to content

Getting Started

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

Getting Started

This guide will help you install DTUTMO and process your first HDR image.

Installation

Basic Installation

DTUTMO requires Python 3.10 or higher. Install the core dependencies:

pip install numpy scipy

Clone and install DTUTMO:

git clone https://github.com/TSOR666/DTUCAM25.git
cd dtutmo
pip install -e .

Optional: GPU Acceleration

For significantly faster processing (10-50× speedup on large images), install PyTorch:

pip install "torch>=2.5.1" "torchvision>=0.20.1"

Or install with GPU support directly:

pip install -e .[torch]

Verify Installation

import dtutmo
print(dtutmo.__version__)  # Should print: 2.1

Quick Start

Basic HDR Tone Mapping

Process an HDR image with default settings:

import numpy as np
from dtutmo import CompleteDTUTMO

# Load your HDR image (linear RGB, cd/m²)
hdr_image = load_hdr_image("scene.exr")  # Replace with your loader

# Create tone mapper with defaults
tmo = CompleteDTUTMO()

# Process the image
ldr_image = tmo.process(hdr_image)

# Save result (values in [0, 1])
save_image("output.png", ldr_image)

That's it! DTUTMO will automatically:

  • Apply optical transfer function and glare
  • Extract cone and rod signals
  • Compute local adaptation
  • Apply photoreceptor responses
  • Map to display values
  • Encode for sRGB display

Customized Processing

Configure specific behaviors:

from dtutmo import CompleteDTUTMO, DTUTMOConfig, CAMType, DisplayStandard

config = DTUTMOConfig(
    # Observer properties
    observer_age=35,
    field_diameter=60,  # degrees
    
    # Enable/disable stages
    use_otf=True,          # Optical transfer function
    use_glare=True,        # CIE disability glare
    use_bilateral=True,    # Base-detail separation
    use_local_adapt=True,  # Local adaptation
    
    # Color appearance model
    use_cam=CAMType.DTUCAM,  # Recommended
    
    # Display target
    target_display=DisplayStandard.REC_709,
    target_luminance=100.0,  # cd/m²
)

tmo = CompleteDTUTMO(config)
ldr_image = tmo.process(hdr_image)

HDR Display Output

Target HDR10 displays:

config = DTUTMOConfig(
    target_display=DisplayStandard.REC_2100_PQ,
    target_luminance=1000.0,  # cd/m² (HDR display)
)

tmo = CompleteDTUTMO(config)
hdr10_output = tmo.process(hdr_image)

GPU Acceleration

Use PyTorch for GPU processing:

import torch
from dtutmo import TorchDTUTMO

# Move image to GPU (BCHW format)
hdr_tensor = torch.from_numpy(hdr_image).permute(2, 0, 1).unsqueeze(0).cuda()

# Create GPU-accelerated tone mapper
tmo = TorchDTUTMO()

# Process on GPU
ldr_tensor = tmo.process(hdr_tensor)

# Convert back to numpy
ldr_image = ldr_tensor.squeeze(0).permute(1, 2, 0).cpu().numpy()

Examples

Example 1: Photography Workflow

from dtutmo import CompleteDTUTMO, DTUTMOConfig, DisplayMapping

config = DTUTMOConfig(
    observer_age=30,
    use_cam=CAMType.DTUCAM,
    display_mapping=DisplayMapping.PRODUCTION_HYBRID,  # Best quality/speed
    target_display=DisplayStandard.REC_709,
)

tmo = CompleteDTUTMO(config)

# Process multiple images
for hdr_path in hdr_image_paths:
    hdr = load_hdr(hdr_path)
    ldr = tmo.process(hdr)
    save_image(hdr_path.replace('.exr', '.png'), ldr)

Example 2: Inspect Intermediate Results

tmo = CompleteDTUTMO()

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

# Access intermediate products
ldr_output = result['output']
adaptation_map = result['local_adaptation']
cone_responses = result['photoreceptor_responses']['cones']
rod_response = result['photoreceptor_responses']['rod']
csf_output = result['csf_output']

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

Example 3: Medical Imaging

from dtutmo import CompleteDTUTMO, DTUTMOConfig, ViewingCondition

config = DTUTMOConfig(
    viewing_condition=ViewingCondition.DIM,  # Typical reading room
    observer_age=45,  # Radiologist age
    use_bilateral=True,  # Preserve fine details
    use_cam=CAMType.DTUCAM,  # Physiological accuracy
    target_luminance=150.0,  # Medical display
)

tmo = CompleteDTUTMO(config)
diagnostic_image = tmo.process(hdr_scan)

Example 4: Batch Processing

from pathlib import Path
from dtutmo import CompleteDTUTMO
import OpenEXR
import numpy as np

def load_exr(path):
    """Load OpenEXR HDR image"""
    exr = OpenEXR.InputFile(str(path))
    # ... implementation ...
    return rgb_array

def process_directory(input_dir, output_dir):
    """Process all HDR images in a directory"""
    tmo = CompleteDTUTMO()
    
    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_exr(hdr_file)
        ldr = tmo.process(hdr)
        
        output_file = output_path / f"{hdr_file.stem}.png"
        save_image(output_file, ldr)
        
    print("Done!")

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

Common Workflows

Workflow 1: Fast Preview (Whiteboard Method)

For quick previews during HDR capture or editing:

from dtutmo import DisplayMapping

config = DTUTMOConfig(
    display_mapping=DisplayMapping.WHITEBOARD,  # Fastest
    use_bilateral=False,  # Skip expensive stages
    use_cam=CAMType.NONE,  # Direct mapping
)

tmo = CompleteDTUTMO(config)
preview = tmo.process(hdr_image)  # ~0.4s for 1920×1080

Workflow 2: High-Quality Export (Hybrid Method)

For final output with best quality/performance balance:

from dtutmo import DisplayMapping

config = DTUTMOConfig(
    display_mapping=DisplayMapping.PRODUCTION_HYBRID,  # Recommended
    use_cam=CAMType.DTUCAM,  # Best color appearance
)

tmo = CompleteDTUTMO(config)
final_output = tmo.process(hdr_image)  # ~0.7s for 1920×1080

Workflow 3: Research/Validation (Full Inverse)

For research requiring maximum accuracy:

from dtutmo import DisplayMapping

config = DTUTMOConfig(
    display_mapping=DisplayMapping.FULL_INVERSE,  # Most accurate
    use_cam=CAMType.DTUCAM,
)

tmo = CompleteDTUTMO(config)
reference = tmo.process(hdr_image)  # ~2.1s for 1920×1080

Input Requirements

Image Format

DTUTMO expects:

  • Format: NumPy array, shape (H, W, 3)
  • Color space: Linear RGB (sRGB primaries, Rec. 709)
  • Units: cd/m² (candelas per square meter)
  • Range: Arbitrary (handles 0.001 to 100,000+ cd/m²)

Loading HDR Images

From OpenEXR

import OpenEXR
import Imath
import numpy as np

def load_exr(filename):
    exr = OpenEXR.InputFile(filename)
    header = exr.header()
    
    dw = header['dataWindow']
    size = (dw.max.x - dw.min.x + 1, dw.max.y - dw.min.y + 1)
    
    channels = ['R', 'G', 'B']
    channel_data = [exr.channel(c, Imath.PixelType(Imath.PixelType.FLOAT)) 
                    for c in channels]
    
    rgb = [np.frombuffer(data, dtype=np.float32).reshape(size[::-1]) 
           for data in channel_data]
    
    return np.stack(rgb, axis=-1)

From Radiance HDR (.hdr)

import cv2

def load_hdr(filename):
    # OpenCV loads as float32 in BGR
    bgr = cv2.imread(filename, cv2.IMREAD_ANYDEPTH | cv2.IMREAD_COLOR)
    rgb = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)
    return rgb

From TIFF (16-bit or 32-bit)

from PIL import Image
import numpy as np

def load_tiff(filename, max_luminance=1000.0):
    img = Image.open(filename)
    arr = np.array(img, dtype=np.float32)
    
    # Normalize to cd/m²
    if arr.max() > 1.0:
        arr = arr / 65535.0  # 16-bit
    
    arr = arr * max_luminance  # Scale to physical units
    return arr

Output Format

DTUTMO outputs display-encoded images:

  • Format: NumPy array, shape (H, W, 3)
  • Range: [0, 1] (display code values)
  • Encoding: Depends on target_display:
    • REC_709: sRGB gamma (2.4)
    • REC_2100_PQ: PQ (SMPTE ST 2084)
    • REC_2100_HLG: HLG (Hybrid Log-Gamma)

Saving Output

from PIL import Image
import numpy as np

def save_image(filename, img_array):
    """Save tone-mapped image as PNG"""
    # Convert [0,1] to [0,255]
    img_8bit = (np.clip(img_array, 0, 1) * 255).astype(np.uint8)
    Image.fromarray(img_8bit).save(filename)

Troubleshooting

Issue: Image too dark/bright

Solution: Check input units. DTUTMO expects physical luminance (cd/m²). If your HDR uses relative values, scale appropriately:

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

# Process
ldr = tmo.process(hdr_scaled)

Issue: Colors look wrong

Solution: Verify input is linear RGB. If gamma-encoded:

def linearize_srgb(img_gamma):
    """Convert sRGB gamma to linear"""
    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)

Issue: Processing too slow

Solutions:

  1. Use GPU acceleration (10-50× faster)
  2. Reduce resolution for preview
  3. Use DisplayMapping.WHITEBOARD (3× faster)
  4. Disable optional stages:
config = DTUTMOConfig(
    use_otf=False,      # Skip optical blur
    use_glare=False,    # Skip glare
    use_bilateral=False # Skip detail preservation
)

Issue: Out of memory

Solutions:

  1. Process tiles:
def process_tiled(tmo, hdr, tile_size=512):
    h, w = hdr.shape[:2]
    output = np.zeros((h, w, 3))
    
    for y in range(0, h, tile_size):
        for x in range(0, w, tile_size):
            tile = hdr[y:y+tile_size, x:x+tile_size]
            output[y:y+tile_size, x:x+tile_size] = tmo.process(tile)
    
    return output
  1. Reduce image resolution
  2. Use GPU (handles larger images)

Next Steps

Now that you can process HDR images, explore:

Clone this wiki locally