-
Notifications
You must be signed in to change notification settings - Fork 0
API Reference
Thierry Soreze edited this page Oct 28, 2025
·
1 revision
Complete reference for DTUTMO classes, functions, and enums.
Main pipeline orchestrator.
class CompleteDTUTMO:
"""Complete DTUTMO pipeline
Orchestrates all 13 processing stages from HDR input to display output.
"""
def __init__(self, config: DTUTMOConfig = None):
"""Initialize pipeline
Parameters
----------
config : DTUTMOConfig, optional
Configuration object. If None, uses default configuration.
Examples
--------
>>> from dtutmo import CompleteDTUTMO, DTUTMOConfig
>>>
>>> # Default configuration
>>> tmo = CompleteDTUTMO()
>>>
>>> # Custom configuration
>>> config = DTUTMOConfig(observer_age=35, use_cam=CAMType.DTUCAM)
>>> tmo = CompleteDTUTMO(config)
"""def process(
self,
img_hdr: np.ndarray,
return_intermediate: bool = False
) -> Union[np.ndarray, dict]:
"""Process HDR image to display output
Parameters
----------
img_hdr : ndarray, shape (H, W, 3)
Input HDR image in linear RGB, units of cd/m².
Must be float32 or float64.
return_intermediate : bool, default False
If True, return dict with all intermediate results.
If False, return only the output image.
Returns
-------
img_display : ndarray, shape (H, W, 3)
Display-encoded output in range [0, 1].
Ready for 8-bit conversion and display.
OR
result : dict
Dictionary containing:
- 'output': Final display image
- 'local_adaptation': Adaptation map (cd/m²)
- 'photoreceptor_responses': Dict of cone/rod responses
- 'csf_output': After CSF filtering
- 'appearance': Appearance attributes (if CAM enabled)
- 'timings': Stage timing information
Raises
------
ValueError
If input is not 3D array with 3 channels
RuntimeError
If processing encounters numerical issues
Examples
--------
>>> import numpy as np
>>> from dtutmo import CompleteDTUTMO
>>>
>>> # Load HDR image (example with random data)
>>> hdr = np.random.rand(1080, 1920, 3).astype(np.float32) * 1000
>>>
>>> # Process
>>> tmo = CompleteDTUTMO()
>>> ldr = tmo.process(hdr)
>>>
>>> # Access intermediate results
>>> result = tmo.process(hdr, return_intermediate=True)
>>> adaptation = result['local_adaptation']
>>> cone_l = result['photoreceptor_responses']['cones']['L']
"""GPU-accelerated PyTorch implementation.
class TorchDTUTMO(nn.Module):
"""GPU-accelerated DTUTMO pipeline
PyTorch implementation for GPU processing. Supports batching
and mixed precision training.
"""
def __init__(self, config: DTUTMOConfig = None):
"""Initialize GPU pipeline
Parameters
----------
config : DTUTMOConfig, optional
Configuration object
Examples
--------
>>> import torch
>>> from dtutmo import TorchDTUTMO
>>>
>>> # Create and move to GPU
>>> tmo = TorchDTUTMO().cuda()
>>>
>>> # Prepare input (BCHW format)
>>> hdr = torch.randn(1, 3, 1080, 1920).cuda()
>>>
>>> # Process
>>> with torch.no_grad():
... ldr = tmo(hdr)
"""
def forward(
self,
img_hdr: torch.Tensor
) -> torch.Tensor:
"""Process HDR tensor
Parameters
----------
img_hdr : torch.Tensor, shape (B, C, H, W)
Batch of HDR images in linear RGB, cd/m².
B = batch size, C = 3 (RGB), H = height, W = width
Returns
-------
img_display : torch.Tensor, shape (B, C, H, W)
Display-encoded output in range [0, 1]
"""Configuration dataclass with validation.
@dataclass
class DTUTMOConfig:
"""Complete DTUTMO configuration
All parameters have validated defaults based on psychophysical data.
Attributes
----------
observer_age : float, default 24.0
Observer age in years (18-80)
field_diameter : float, default 60.0
Visual field diameter in degrees (10-180)
pixels_per_degree : float, default 45.0
Display resolution in pixels/degree (30-60)
use_otf : bool, default True
Enable optical transfer function
use_glare : bool, default True
Enable CIE disability glare
use_bilateral : bool, default True
Enable bilateral filtering
use_local_adapt : bool, default True
Enable local adaptation
use_cam : CAMType, default CAMType.DTUCAM
Color appearance model
viewing_condition : ViewingCondition, default ViewingCondition.AVERAGE
Viewing surround type
target_display : DisplayStandard, default DisplayStandard.REC_709
Target display standard
target_luminance : float, default 100.0
Target display peak luminance (cd/m²)
display_mapping : DisplayMapping, default DisplayMapping.PRODUCTION_HYBRID
Display mapping strategy
gradient_threshold : float, default 0.15
Gradient threshold for hybrid mapping (0.05-0.30)
gradient_smoothing : float, default 2.0
Gradient smoothing sigma (0.5-5.0)
Examples
--------
>>> from dtutmo import DTUTMOConfig, CAMType, DisplayStandard
>>>
>>> # Default configuration
>>> config = DTUTMOConfig()
>>>
>>> # Custom configuration
>>> config = DTUTMOConfig(
... observer_age=35,
... use_cam=CAMType.DTUCAM,
... target_display=DisplayStandard.REC_2100_PQ,
... target_luminance=1000.0
... )
>>>
>>> # Save/load
>>> config_dict = dataclasses.asdict(config)
>>> config_new = DTUTMOConfig(**config_dict)
"""Hood-adapted photoreceptor model.
class CorrectedPhotoreceptorResponse:
"""Hood & Finkelstein photoreceptor adaptation model
Implements complete photoreceptor response with:
- Hood adaptation: σ = k₁((O₁+Ia)/O₁)^m
- Adaptive gain: Rmax = k₂[(O₂+p·Ia)/O₂]^(-1/2)
- Bleaching: p = B/(B + ln(Ia))
- Adaptation-dependent offset
"""
def compute_response(
self,
I_signal: np.ndarray,
I_adapt: np.ndarray,
pupil_diameter: np.ndarray,
photoreceptor_type: str,
return_params: bool = False
) -> Union[np.ndarray, Tuple[np.ndarray, dict]]:
"""Compute photoreceptor response
Parameters
----------
I_signal : ndarray
Signal luminance (cd/m²)
I_adapt : ndarray
Adaptation luminance (cd/m²), same shape as I_signal
pupil_diameter : ndarray
Pupil diameter (mm), same shape or broadcastable
photoreceptor_type : str
One of: 'L_cone', 'M_cone', 'S_cone', 'rod'
return_params : bool, default False
If True, also return adaptation parameters
Returns
-------
response : ndarray
Neural response (arbitrary units), same shape as I_signal
params : dict, optional
Adaptation parameters if return_params=True:
- 'sigma_hood': Hood semi-saturation
- 'R_max': Adaptive maximum response
- 'p': Bleaching factor
- 's': Adaptation-dependent offset
Examples
--------
>>> from dtutmo.photoreceptors import CorrectedPhotoreceptorResponse
>>> import numpy as np
>>>
>>> photo = CorrectedPhotoreceptorResponse()
>>>
>>> # Compute L-cone response
>>> L_signal = np.array([1.0, 10.0, 100.0])
>>> L_adapt = np.array([0.1, 1.0, 10.0])
>>> pupil = np.array([6.0, 5.0, 4.0])
>>>
>>> R_L = photo.compute_response(L_signal, L_adapt, pupil, 'L_cone')
>>> print(R_L) # [0.45, 0.62, 0.78] (example values)
"""Analytical inverse of photoreceptor model.
class InversePhotoreceptorComplete:
"""Explicit inverse of Hood photoreceptor model
Provides O(1) closed-form inverse formulas without iteration.
"""
def inverse_response(
self,
R: np.ndarray,
I_adapt: np.ndarray,
pupil_diameter: np.ndarray,
n: float,
photoreceptor_type: str
) -> np.ndarray:
"""Compute luminance from desired response
Parameters
----------
R : ndarray
Desired response (0 to R_max)
I_adapt : ndarray
Display adaptation luminance (cd/m²)
pupil_diameter : ndarray
Viewer pupil diameter (mm)
n : float
Hill coefficient (typically 0.74)
photoreceptor_type : str
One of: 'L_cone', 'M_cone', 'S_cone', 'rod'
Returns
-------
L_required : ndarray
Required luminance (cd/m²) to achieve desired response
Examples
--------
>>> from dtutmo.photoreceptors import InversePhotoreceptorComplete
>>>
>>> inverse = InversePhotoreceptorComplete()
>>>
>>> # Compute required luminance for 50% response
>>> R_desired = 0.5 * R_max
>>> L_adapt_display = 100.0
>>> pupil_viewer = 4.5
>>>
>>> L_req = inverse.inverse_response(
... R_desired, L_adapt_display, pupil_viewer, 0.74, 'L_cone'
... )
"""Production gradient-adaptive hybrid mapper.
class HybridDisplayMapper:
"""Gradient-aware hybrid display mapping
Blends whiteboard (fast) and full inverse (accurate) based on
local gradient magnitude.
"""
def __init__(
self,
gradient_threshold: float = 0.15,
gradient_smoothing: float = 2.0
):
"""Initialize hybrid mapper
Parameters
----------
gradient_threshold : float, default 0.15
Gradient threshold for blending (0.05-0.30)
gradient_smoothing : float, default 2.0
Smoothing sigma for gradient map (0.5-5.0)
"""
def map_to_display(
self,
responses: dict,
adaptation: np.ndarray,
config: DTUTMOConfig
) -> np.ndarray:
"""Map photoreceptor responses to display
Parameters
----------
responses : dict
Photoreceptor responses:
{'L': ndarray, 'M': ndarray, 'S': ndarray}
adaptation : ndarray
Display adaptation map (cd/m²)
config : DTUTMOConfig
Configuration object
Returns
-------
img_display : ndarray, shape (H, W, 3)
Display-encoded RGB [0, 1]
Examples
--------
>>> from dtutmo.display import HybridDisplayMapper
>>>
>>> mapper = HybridDisplayMapper(
... gradient_threshold=0.15,
... gradient_smoothing=2.0
... )
>>>
>>> img_display = mapper.map_to_display(
... responses={'L': R_L, 'M': R_M, 'S': R_S},
... adaptation=L_adapt_display,
... config=config
... )
"""Fast analytical approximation.
class WhiteboardMapper:
"""Fast whiteboard formula display mapping
Provides 3-5× speedup over full inverse with good quality.
"""
def map_to_display(
self,
responses: dict,
L_mean_display: float,
n: float = 0.74
) -> np.ndarray:
"""Map responses using whiteboard formula
Parameters
----------
responses : dict
Normalized photoreceptor responses [0, 1)
L_mean_display : float
Target mean display luminance (cd/m²)
n : float, default 0.74
Hill coefficient
Returns
-------
img_display : ndarray
Display RGB [0, 1]
Examples
--------
>>> from dtutmo.display import WhiteboardMapper
>>>
>>> mapper = WhiteboardMapper()
>>> img_display = mapper.map_to_display(
... responses=R_normalized,
... L_mean_display=100.0
... )
"""DTU Color Appearance Model with dual adaptation.
class DTUCAM:
"""DTU Color Appearance Model
Novel CAM with physiologically-consistent dual adaptation:
- Semi-saturation (σ) adapts
- Response ceiling (R_max) adapts
Range: 0.001 to 100,000 cd/m²
Inverse: Explicit formulas (O(1))
"""
def forward(
self,
img_xyz: np.ndarray,
white_xyz: np.ndarray,
viewing_condition: str
) -> dict:
"""XYZ → Appearance attributes
Parameters
----------
img_xyz : ndarray, shape (H, W, 3)
Image in CIE XYZ
white_xyz : ndarray, shape (3,)
White point [X_w, Y_w, Z_w]
viewing_condition : str
One of: 'dark', 'dim', 'average'
Returns
-------
appearance : dict
Appearance attributes:
- 'lightness' (J): 0-100
- 'colorfulness' (M): Absolute
- 'hue' (h): 0-360 degrees
- 'chroma' (C): Relative
- 'brightness' (Q): Absolute
- 'saturation' (s): 0-100
Examples
--------
>>> from dtutmo.appearance import DTUCAM
>>>
>>> cam = DTUCAM()
>>>
>>> # Forward transform
>>> appearance = cam.forward(
... img_xyz,
... white_xyz=np.array([95.05, 100.0, 108.88]),
... viewing_condition='average'
... )
>>>
>>> J = appearance['lightness']
>>> M = appearance['colorfulness']
>>> h = appearance['hue']
"""
def inverse(
self,
J: np.ndarray,
M: np.ndarray,
h: np.ndarray,
display_white_xyz: np.ndarray,
display_max_lum: float,
viewing_condition: str
) -> np.ndarray:
"""Appearance → Display XYZ
Parameters
----------
J : ndarray
Lightness (0-100)
M : ndarray
Colorfulness (absolute)
h : ndarray
Hue angle (0-360 degrees)
display_white_xyz : ndarray, shape (3,)
Display white point
display_max_lum : float
Display peak luminance (cd/m²)
viewing_condition : str
Display viewing condition
Returns
-------
img_xyz : ndarray, shape (H, W, 3)
Display XYZ tristimulus values
Examples
--------
>>> # Inverse transform
>>> display_xyz = cam.inverse(
... J=lightness,
... M=colorfulness,
... h=hue,
... display_white_xyz=np.array([95.05, 100.0, 108.88]),
... display_max_lum=100.0,
... viewing_condition='dim'
... )
"""Extended Luminance Range CAM.
class XLRCAM:
"""Extended Luminance Range Color Appearance Model
HDR-specific CAM with psychophysical validation.
Range: 0.01 to 16,860 cd/m²
"""
def forward(self, img_xyz, white_xyz, viewing_condition) -> dict:
"""XYZ → Appearance (see DTUCAM for details)"""
def inverse(self, J, M, h, display_white_xyz,
display_max_lum, viewing_condition) -> np.ndarray:
"""Appearance → Display XYZ (see DTUCAM for details)"""CIE 2016 Color Appearance Model.
class CIECAM16:
"""CIE 2016 Color Appearance Model
CIE standard (CIE 248:2022).
Range: 0.1 to 1,000 cd/m²
Inverse: Iterative (5-7 iterations)
"""
def forward(self, img_xyz, white_xyz, viewing_condition,
L_A=64.0, Y_b=20.0) -> dict:
"""XYZ → Appearance
Additional Parameters
---------------------
L_A : float, default 64.0
Adapting field luminance (cd/m²)
Y_b : float, default 20.0
Background luminance factor (% of white)
"""
def inverse(self, J, M, h, display_white_xyz,
display_max_lum, viewing_condition,
L_A=64.0, Y_b=20.0) -> np.ndarray:
"""Appearance → Display XYZ (iterative)"""def rgb_to_xyz(rgb: np.ndarray, color_space: str = 'sRGB') -> np.ndarray:
"""Convert RGB to CIE XYZ
Parameters
----------
rgb : ndarray, shape (..., 3)
Linear RGB values
color_space : str, default 'sRGB'
RGB color space: 'sRGB', 'Adobe', 'ProPhoto'
Returns
-------
xyz : ndarray, shape (..., 3)
CIE XYZ tristimulus values
"""
def xyz_to_rgb(xyz: np.ndarray, color_space: str = 'sRGB') -> np.ndarray:
"""Convert CIE XYZ to RGB"""
def xyz_to_lms(xyz: np.ndarray, matrix: str = 'HPE') -> np.ndarray:
"""Convert XYZ to LMS cone fundamentals
Parameters
----------
xyz : ndarray
CIE XYZ
matrix : str, default 'HPE'
Transform matrix: 'HPE' (Hunt-Pointer-Estevez) or 'CAT02'
"""
def lms_to_xyz(lms: np.ndarray, matrix: str = 'HPE') -> np.ndarray:
"""Convert LMS to XYZ"""
def linearize_srgb(img_gamma: np.ndarray) -> np.ndarray:
"""Convert sRGB gamma to linear
Parameters
----------
img_gamma : ndarray
sRGB gamma-encoded image [0, 1]
Returns
-------
img_linear : ndarray
Linear RGB
"""
def encode_srgb(img_linear: np.ndarray) -> np.ndarray:
"""Convert linear RGB to sRGB gamma"""def apply_pq_eotf(signal: np.ndarray, L_max: float = 10000.0) -> np.ndarray:
"""PQ EOTF (SMPTE ST 2084)
Parameters
----------
signal : ndarray
Display code values [0, 1]
L_max : float, default 10000.0
Display peak luminance (cd/m²)
Returns
-------
luminance : ndarray
Linear luminance (cd/m²)
"""
def apply_pq_oetf(luminance: np.ndarray, L_max: float = 10000.0) -> np.ndarray:
"""PQ inverse EOTF"""
def apply_hlg_eotf(signal: np.ndarray, L_max: float = 1000.0) -> np.ndarray:
"""HLG EOTF (ITU-R BT.2100)"""
def apply_hlg_oetf(luminance: np.ndarray, L_max: float = 1000.0) -> np.ndarray:
"""HLG inverse EOTF"""class CAMType(Enum):
"""Color appearance model selection"""
NONE = "none" # No CAM (direct mapping)
DTUCAM = "dtucam" # DTU CAM (recommended)
XLRCAM = "xlrcam" # Extended luminance range
CIECAM16 = "ciecam16" # CIE standardclass DisplayStandard(Enum):
"""Display standards and color spaces"""
REC_709 = "rec709" # sRGB, HDTV
REC_2020 = "rec2020" # UHDTV
DCI_P3 = "dcip3" # Digital cinema
REC_2100_PQ = "rec2100pq" # HDR10
REC_2100_HLG = "rec2100hlg" # HLG HDRclass ViewingCondition(Enum):
"""Viewing environment surround"""
DARK = "dark" # <0.2 cd/m² (cinema)
DIM = "dim" # 0.2-20 cd/m² (home)
AVERAGE = "average" # >20 cd/m² (office)class DisplayMapping(Enum):
"""Display mapping strategies"""
LEGACY = "legacy" # v1.x compatibility
WHITEBOARD = "whiteboard" # Fast approximation
FULL_INVERSE = "full_inverse" # Accurate inverse
HYBRID = "hybrid" # Basic hybrid
PRODUCTION_HYBRID = "production_hybrid" # Gradient-aware (best)from typing import Union, Tuple, Dict, Optional
import numpy as np
import torch
# Common types
ImageArray = np.ndarray # (H, W, 3)
TensorImage = torch.Tensor # (B, C, H, W)
ResponseDict = Dict[str, np.ndarray] # {'L': ..., 'M': ..., 'S': ...}
AppearanceDict = Dict[str, np.ndarray] # {'lightness': ..., 'colorfulness': ..., ...}# Configuration errors
try:
config = DTUTMOConfig(observer_age=150) # Out of range
except ValueError as e:
print(f"Configuration error: {e}")
# Processing errors
try:
ldr = tmo.process(hdr)
except RuntimeError as e:
print(f"Processing error: {e}")
# Input validation
try:
ldr = tmo.process(wrong_shape)
except ValueError as e:
print(f"Input error: {e}")import dtutmo
print(dtutmo.__version__) # '2.1'
print(dtutmo.__author__) # 'Soreze, Thierry Silvio Claude'- Getting Started - Usage examples
- Configuration Guide - Parameter details
- Pipeline Stages - Algorithm details
- FAQ - Common questions