Version: 2.0 Component: Standalone PBR Map Generator Purpose: Generate Physically Based Rendering maps from existing depth data
PBRProcessor is a standalone API for generating PBR maps (normal, roughness, ambient occlusion) from depth data without running the full depth estimation pipeline.
- 2.3x faster than full pipeline when regenerating PBR from existing depth
- Memory-efficient mode for custom post-processing workflows
- No orchestrator dependency - simple, focused API
- Material presets for wood, metal, glass, stone, fabric
- Batch processing support with progress tracking
Ensure Transformation Portal is installed:
cd Transformation_Portal
pip install -e .PBRProcessor requires existing depth maps (from previous orchestrator run or external source):
# Check for depth files
ls output/*_depth.npy
# Expected: scene1_depth.npy, scene2_depth.npy, etc.Python script (generate_pbr.py):
from pathlib import Path
from transformation_portal.lux_depth_v3 import PBRProcessor, get_preset
# Configure preset
config = get_preset("premium").to_pbr_config()
# Generate PBR from cached depth
paths = PBRProcessor.from_cached_depth(
depth_path=Path("output/scene1_depth.npy"),
config=config,
output_dir=Path("output/pbr/"),
base_name="scene1"
)
print(f"✓ Normal map: {paths['normal']}")
print(f"✓ Roughness: {paths['roughness']}")
print(f"✓ AO map: {paths['ao']}")Run:
python generate_pbr.pyOutput:
output/pbr/
├── scene1_normal.png # RGB normal map (tangent space)
├── scene1_roughness.png # Grayscale roughness
└── scene1_ao.png # Ambient occlusion
Check generated maps:
from PIL import Image
# Load and inspect
normal = Image.open("output/pbr/scene1_normal.png")
print(f"Normal map size: {normal.size}")
print(f"Normal map mode: {normal.mode}") # Should be RGB
roughness = Image.open("output/pbr/scene1_roughness.png")
print(f"Roughness mode: {roughness.mode}") # Should be L (grayscale)Scenario: You've run depth estimation once and want to test different PBR presets.
from pathlib import Path
from transformation_portal.lux_depth_v3 import PBRProcessor, get_preset
depth_path = Path("output/luxury_kitchen_depth.npy")
# Try different presets
for preset_name in ["standard", "premium", "wood", "metal"]:
config = get_preset(preset_name).to_pbr_config()
output_dir = Path(f"output/pbr_{preset_name}/")
paths = PBRProcessor.from_cached_depth(
depth_path=depth_path,
config=config,
output_dir=output_dir,
base_name="luxury_kitchen"
)
print(f"✓ Generated {preset_name} preset")
# Review outputs and choose best for final deliverablePerformance: ~1.2s per preset vs ~2.8s if re-running full pipeline.
Scenario: You have 100 depth files and need PBR maps for all.
from pathlib import Path
from transformation_portal.lux_depth_v3 import PBRProcessor, get_preset
# Input directory with depth files
depth_dir = Path("output/estate_depths/")
depth_files = sorted(depth_dir.glob("*_depth.npy"))
# Configure preset
config = get_preset("premium").to_pbr_config()
pbr_dir = Path("output/estate_pbr/")
# Process all files
for i, depth_file in enumerate(depth_files, 1):
base_name = depth_file.stem.replace("_depth", "")
paths = PBRProcessor.from_cached_depth(
depth_path=depth_file,
config=config,
output_dir=pbr_dir,
base_name=base_name
)
print(f"[{i}/{len(depth_files)}] ✓ {base_name}")
print(f"\nProcessed {len(depth_files)} images")
# Throughput: ~3,000 images/hour vs ~1,277 with full pipelineScenario: Custom post-processing without intermediate file I/O.
import numpy as np
from transformation_portal.lux_depth_v3 import PBRProcessor, get_preset
from PIL import Image
# Load depth array
depth = np.load("output/scene1_depth.npy")
# Generate PBR in memory (no file writes)
config = get_preset("standard").to_pbr_config()
processor = PBRProcessor(config=config, output_dir=None)
maps = processor.from_depth(depth, save=False)
# Access maps as NumPy arrays
normal_map = maps["normal"] # Shape: (H, W, 3), dtype: uint8
roughness_map = maps["roughness"] # Shape: (H, W), dtype: uint8
ao_map = maps["ao"] # Shape: (H, W), dtype: uint8
# Custom processing
ao_enhanced = (ao_map * 1.5).clip(0, 255).astype(np.uint8)
# Save final result
Image.fromarray(ao_enhanced).save("output/custom_ao.png")Performance: Fastest option (~1.16s for 24MP), no disk I/O overhead.
Scenario: Process architectural renders with material-optimized presets.
from pathlib import Path
from transformation_portal.lux_depth_v3 import PBRProcessor, get_preset
# Material classification (manual or automated)
scenes = {
"hardwood_floor": "wood",
"marble_countertop": "stone",
"glass_facade": "glass",
"metal_fixtures": "metal",
"linen_curtains": "fabric",
}
for scene_name, material in scenes.items():
depth_path = Path(f"output/{scene_name}_depth.npy")
config = get_preset(material).to_pbr_config()
paths = PBRProcessor.from_cached_depth(
depth_path=depth_path,
config=config,
output_dir=Path(f"output/{material}_pbr/"),
base_name=scene_name
)
print(f"✓ {scene_name} → {material} preset")| Preset | Use Case | Normal Detail | AO Bias | Speed |
|---|---|---|---|---|
standard |
Batch processing | Balanced | 0.45 | Fast |
premium |
Hero shots | Maximum | 0.35 | Medium |
draft |
Quick preview | Low | 0.55 | Fastest |
| Preset | Optimized For | Key Settings |
|---|---|---|
wood |
Hardwood, grain texture | High normal strength, no blur |
metal |
Polished metal surfaces | Low roughness strength |
glass |
Windows, mirrors | Heavy smoothing, bright AO |
stone |
Marble, granite, tile | High detail, dark AO for grout |
fabric |
Textiles, upholstery | Moderate detail, balanced AO |
Access presets:
from transformation_portal.lux_depth_v3 import get_preset, list_presets
# List all available
print(list_presets())
# ['standard', 'premium', 'draft', 'wood', 'metal', 'glass', 'stone', 'fabric']
# Get specific preset
config = get_preset("wood").to_pbr_config()Error:
FileNotFoundError: Depth file not found: output/scene1_depth.npy
Solution:
- Verify depth file exists:
ls output/scene1_depth.npy - Run full orchestrator first to generate depth
- Check file path is correct (relative or absolute)
Error:
ValueError: Expected 2D depth array, got shape (1080, 1920, 3)
Solution:
- Depth should be grayscale (H, W), not RGB (H, W, 3)
- If you have RGB depth visualization, extract single channel:
from PIL import Image import numpy as np depth_rgb = np.array(Image.open("depth.png")) depth = depth_rgb[:, :, 0] # Use R channel np.save("depth.npy", depth / 255.0) # Normalize to [0, 1]
Error:
ValueError: Depth contains NaN or Inf values
Solution:
- Check depth array for invalid values:
import numpy as np depth = np.load("depth.npy") print(f"NaN count: {np.isnan(depth).sum()}") print(f"Inf count: {np.isinf(depth).sum()}")
- Clean depth array:
depth = np.nan_to_num(depth, nan=0.0, posinf=1.0, neginf=0.0) np.save("depth_cleaned.npy", depth)
Symptom: Normal map is solid blue, AO is all gray
Cause: Depth map is flat (all same value) or has very low variation
Solution:
- Verify depth has meaningful variation:
import numpy as np depth = np.load("depth.npy") print(f"Min: {depth.min()}, Max: {depth.max()}, Std: {depth.std()}")
- If depth is flat, re-run depth estimation with higher quality model
- Check source image has actual depth variation
Expected performance (256x256 image):
- Memory-only: ~0.1s
- With file I/O: ~0.3s
If slower:
- Check disk I/O speed (SSD recommended)
- Verify depth array dtype is
float32(notfloat64) - Close other memory-intensive applications
- For large batches, process in smaller chunks
Test configuration:
- Image size: 24MP (6000×4000)
- Hardware: Apple M4 Max, 48GB RAM
- Depth model: METRIC_LARGE
| Workflow | Time (ms) | Throughput (img/hr) |
|---|---|---|
| Full Orchestrator | 2,800 | 1,277 |
| PBRProcessor (file) | 1,200 | 3,000 |
| PBRProcessor (memory) | 1,160 | 3,100 |
Speedup comparison:
- Single image: 2.3-2.4x faster than orchestrator
- 10 preset iterations: 2x faster (13.7s vs 28s)
- Batch processing: 2.3x higher throughput
Override preset parameters for fine-grained control:
from transformation_portal.lux_depth_v3 import PBRProcessor
from transformation_portal.lux_depth_v3.pbr import PBRConfig
# Create custom config (not using preset)
custom_config = PBRConfig(
normal_strength=1.8, # Very strong normals
normal_blur_radius=0, # No smoothing (sharp edges)
roughness_strength=0.8, # Lower roughness (smoother)
roughness_blur_radius=5, # Heavy roughness smoothing
ao_strength=1.3, # Stronger AO
ao_blur_radius=8, # Wide AO spread
ao_bias=0.3, # Darker shadows
)
processor = PBRProcessor(config=custom_config, output_dir="output/custom/")
maps = processor.from_depth(depth, save=True, base_name="custom_scene")Parameter guidelines:
normal_strength: 0.5-2.0 (higher = more pronounced surface detail)normal_blur_radius: 0-5 (0 = sharp, 5 = very smooth)roughness_strength: 0.5-2.0 (higher = rougher surfaces)roughness_blur_radius: 0-10 (higher = smoother transitions)ao_strength: 0.5-2.0 (higher = darker occlusion)ao_blur_radius: 0-15 (higher = wider shadow spread)ao_bias: 0.0-1.0 (0 = dark, 1 = bright, 0.5 = neutral)
PBRProcessor complements the full orchestrator pipeline:
from pathlib import Path
from transformation_portal.lux_depth_v3 import EnhanceOrchestrator, PBRProcessor, get_preset
# Step 1: Run orchestrator for depth only
config = get_preset("premium")
config.generate_pbr = False # Skip PBR in orchestrator
orchestrator = EnhanceOrchestrator(config, output_root=Path("output/"))
manifest = orchestrator.enhance_image(Path("input/scene1.jpg"))
# Step 2: Generate PBR separately (allows parameter iteration)
pbr_config = config.to_pbr_config()
paths = PBRProcessor.from_cached_depth(
depth_path=manifest.depth_path_float,
config=pbr_config,
output_dir=Path("output/pbr/"),
base_name="scene1"
)from pathlib import Path
from transformation_portal.lux_depth_v3 import PBRProcessor, get_preset
# Use depth from external source (MiDaS, ZoeDepth, etc.)
external_depth_path = Path("external/zoe_depth.npy")
config = get_preset("standard").to_pbr_config()
paths = PBRProcessor.from_cached_depth(
depth_path=external_depth_path,
config=config,
output_dir=Path("output/pbr/"),
base_name="external_scene"
)- Full documentation:
docs/guides/PBR_ENHANCE_CONFIG_GUIDE.md - Production example:
examples/process_750_picacho_pbr.py - API reference:
src/transformation_portal/lux_depth_v3/pbr_processor.py - Preset catalog:
src/transformation_portal/lux_depth_v3/pbr_presets.py - Integration tests:
tests/test_pbr_processor.py
Last Updated: 2026-02-01 Version: 2.0