hdr: tuning on exposure bracketed datasets

This commit is contained in:
Harry Chen
2026-09-18 02:16:32 -04:00
parent 4b727b6c71
commit d579cf8c75
22 changed files with 3894 additions and 3507 deletions
+8 -2
View File
@@ -24,7 +24,7 @@ display = TONE( M · (is_linear ? c : EOTF_sRGB(c)) )
| value | display encode |
|---|---|
| `none` (unset) | image side: `srgb`. splat side: whatever the image side resolved to |
| `srgb` | sRGB OETF, unbounded — the default, and what matches a photograph |
| `srgb` | sRGB OETF, clipped at black and open above — the default, and what matches a photograph |
| `srgb-clamped` | clipped to `[0,1]`, then the OETF |
| `aces` | Narkowicz ACES fit, clipped, then the OETF |
| `filmic` | Hejl / Burgess-Dawson (its gamma is baked in) |
@@ -58,7 +58,7 @@ image side, which is what keeps the render matching the photographs.
## Why a tone curve is worth having
Under `srgb`, the OETF is monotone and unbounded, so a photograph whose pixel
Under `srgb`, the OETF is monotone and unbounded above, so a photograph whose pixel
reads 0.99 is explained by a splat at linear 0.977 and nothing more. Every
capture that nearly clipped is reconstructed as if it had not been bright.
@@ -108,6 +108,12 @@ the only place the curves are not exact inverses of each other.
`--convert-initial-point-cloud-color`).
- **The noise background.** `--background-mode noise` draws in display space
and inverts both halves per pixel, so mid-grey stays mid-grey on screen.
`--background-match-luminance` first raises the draw to a per-image power,
computed on the host each step from the photo's mean luma and its current
PPISP exposure (`_bg_luma_exponent`), that puts the draw's median on that
luma ahead of the exposure, so a night capture is not plugged with haze to
hide a mid-grey background. The draw's 0 and 1 are fixed points, so the
`pseudorandom` corners are unaffected once the warm-up is over.
## `none` is unset, everywhere
+73 -4
View File
@@ -2,6 +2,7 @@
import argparse
import os
import re
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor
from tqdm import tqdm
@@ -10,6 +11,7 @@ import cv2
import numpy as np
import OpenEXR
import Imath
import piexif
IMAGE_EXTENSIONS = {
@@ -25,6 +27,9 @@ IMAGE_EXTENSIONS = {
}
EXR_EXTENSIONS = {".exr"}
JPEG_EXTENSIONS = {".jpg", ".jpeg"}
# Formats Pillow re-saves losslessly with an EXIF block attached.
PIL_EXIF_EXTENSIONS = {".png", ".tif", ".tiff"}
_FLOAT_PT = Imath.PixelType(Imath.PixelType.FLOAT)
@@ -49,6 +54,62 @@ def find_images(root: Path):
]
def read_exif(path: Path):
"""The source's EXIF as a piexif dict, or None when it carries none."""
try:
exif = piexif.load(str(path))
except Exception:
try:
from PIL import Image
with Image.open(path) as im:
raw = im.info.get("exif")
exif = piexif.load(raw) if raw else None
except Exception:
exif = None
if exif and any(exif.get(k) for k in ("0th", "Exif", "GPS", "Interop")):
return exif
return None
def exif_bytes_for(exif: dict, w: int, h: int) -> bytes:
"""Serialize `exif` with the pixel-dimension tags rewritten for a w x h image.
Everything else is copied as is; a tag piexif cannot re-encode is dropped
rather than losing the whole block.
"""
exif = {k: (dict(v) if isinstance(v, dict) else v) for k, v in exif.items()}
zeroth, ex = exif.setdefault("0th", {}), exif.setdefault("Exif", {})
if piexif.ImageIFD.ImageWidth in zeroth:
zeroth[piexif.ImageIFD.ImageWidth] = w
if piexif.ImageIFD.ImageLength in zeroth:
zeroth[piexif.ImageIFD.ImageLength] = h
if piexif.ExifIFD.PixelXDimension in ex:
ex[piexif.ExifIFD.PixelXDimension] = w
if piexif.ExifIFD.PixelYDimension in ex:
ex[piexif.ExifIFD.PixelYDimension] = h
while True:
try:
return piexif.dump(exif)
except ValueError as e:
# piexif reports an unencodable tag as "<tag> in <ifd> IFD".
m = re.search(r"(\d+) in (\w+) IFD", str(e))
if not m or int(m.group(1)) not in exif.get(m.group(2), {}):
raise
del exif[m.group(2)][int(m.group(1))]
def attach_exif(dst_path: Path, exif: dict, w: int, h: int, dtype):
ext = dst_path.suffix.lower()
if ext in JPEG_EXTENSIONS:
piexif.insert(exif_bytes_for(exif, w, h), str(dst_path))
elif ext in PIL_EXIF_EXTENSIONS and dtype == np.uint8:
# Pillow truncates >8-bit color to 8 bits, hence the dtype guard.
from PIL import Image
with Image.open(dst_path) as im:
im.load()
im.save(dst_path, exif=exif_bytes_for(exif, w, h))
def read_exr(path: Path):
"""Return (HxWxC float32 array, channel_names, channel_types)."""
exr = OpenEXR.InputFile(str(path))
@@ -84,15 +145,19 @@ def write_exr(path: Path, img: np.ndarray, channel_names: list, channel_types: l
def save_image(dst_path: Path, image, jpeg_quality: int,
exr_channels=None, exr_types=None):
exr_channels=None, exr_types=None, exif=None):
dst_path.parent.mkdir(parents=True, exist_ok=True)
ext = dst_path.suffix.lower()
if ext in EXR_EXTENSIONS:
write_exr(dst_path, image, exr_channels, exr_types)
elif ext in (".jpg", ".jpeg"):
return
if ext in JPEG_EXTENSIONS:
cv2.imwrite(str(dst_path), image, [cv2.IMWRITE_JPEG_QUALITY, jpeg_quality])
else:
cv2.imwrite(str(dst_path), image)
if exif is not None:
h, w = image.shape[:2]
attach_exif(dst_path, exif, w, h, image.dtype)
def process_image(
@@ -105,14 +170,18 @@ def process_image(
try:
ext = src_path.suffix.lower()
exr_channels = exr_types = None
exif = None
if ext in EXR_EXTENSIONS:
img, exr_channels, exr_types = read_exr(src_path)
else:
# IMREAD_UNCHANGED leaves the pixels in stored orientation, so the
# EXIF Orientation tag stays valid for the copies.
img = cv2.imread(str(src_path), cv2.IMREAD_UNCHANGED)
if img is None:
print(f"Failed to read: {src_path}")
return
exif = read_exif(src_path)
orig_h, orig_w = img.shape[:2]
rel_path = src_path.relative_to(src_root)
@@ -126,7 +195,7 @@ def process_image(
new_h = max(1, orig_h // scale)
resized = cv2.resize(prev_img, (new_w, new_h), interpolation=cv2.INTER_AREA)
dst_path = dst_roots[scale] / rel_path
save_image(dst_path, resized, jpeg_quality, exr_channels, exr_types)
save_image(dst_path, resized, jpeg_quality, exr_channels, exr_types, exif)
prev_img = resized
except Exception as e:
@@ -135,7 +204,7 @@ def process_image(
def main():
parser = argparse.ArgumentParser(
description="Generate downscaled image pyramids."
description="Generate downscaled image pyramids, carrying EXIF over."
)
parser.add_argument("work_folder", type=Path)
+2 -1
View File
@@ -369,7 +369,7 @@ EngineStepConfig build_step_config(const TrainConfig& c, const RunState& st, int
cfg.loss.loss_map_power = c.densify_loss_map_power;
cfg.loss.loss_map_accum_mode = densify_accum_mode_int(c.densify_accum_mode);
cfg.loss.saturation_threshold = c.loss_saturation_threshold;
cfg.loss.normalize_by_luminance = c.normalize_loss_by_luminance;
cfg.loss.luminance_normalization = c.loss_luminance_normalization;
cfg.loss.overexposure_reg_weight = c.overexposure_reg;
if (st.bilagrid_rgb_init || st.ppisp_init) {
cfg.loss.color_shift_reg_weight = c.color_shift_reg_weight;
@@ -497,6 +497,7 @@ EngineStepConfig build_step_config(const TrainConfig& c, const RunState& st, int
float rw = std::min((float)step / std::max(c.background_noise_warmup, 1), 1.0f);
cfg.background.randomize_weight =
1.0f - (1.0f - c.background_noise_pre_warmup) * (1.0f - rw);
cfg.background.match_luminance = c.background_match_luminance;
} else if (c.background_mode == "sh") {
cfg.background.lr_dc = scheduled_lr(step, max_steps_lr, c.background_dc_lr);
cfg.background.lr_sh = scheduled_lr(step, max_steps_lr, c.background_sh_lr);
+26 -1
View File
@@ -107,13 +107,38 @@ int main(int argc, char** argv) {
float* d_vt = fresh1();
blend_background_noise_backward(xf, lin != 0, blocky != 0, block_px,
t3(d_rgb), t1(d_T), 0.7f,
1234u + xf, over, t3(d_vout),
1234u + xf, nullptr, nullptr,
over, t3(d_vout),
t3(d_vr), t1(d_vt));
backend::device_synchronize();
readback_f(acc, d_vr, PIX * 3);
readback_f(acc, d_vt, PIX);
}
// ---- the luma-matched draw: a per-slot power table and slot indices
{
const float* d_exp = upload<float>({1.0f, 3.9f, 0.6f, 6.5f});
const int32_t* d_cams = upload<int32_t>({3, 1});
for (int xf : {0, 4}) for (int lin = 0; lin < 2; lin++)
for (int blocky = 0; blocky < 2; blocky++)
for (unsigned block_px : {0u, 4u}) {
float* d_out = fresh3();
blend_background_noise_forward(xf, lin != 0, blocky != 0, block_px,
t3(d_rgb), t1(d_T), 0.7f, 77u + xf,
d_exp, d_cams, t3(d_out));
float* d_vr = fresh3();
float* d_vt = fresh1();
blend_background_noise_backward(xf, lin != 0, blocky != 0, block_px,
t3(d_rgb), t1(d_T), 0.7f, 77u + xf,
d_exp, d_cams, 0.0f, t3(d_vout),
t3(d_vr), t1(d_vt));
backend::device_synchronize();
readback_f(acc, d_out, PIX * 3);
readback_f(acc, d_vr, PIX * 3);
readback_f(acc, d_vt, PIX);
}
}
// ---- blend_background_color_backward (reg off, then the fused one) ----
for (float over : {0.0f, 3.0f}) {
float* d_vr = fresh3();
@@ -20,11 +20,11 @@ static_assert(sizeof(BlendBgParams) == 4 * 8 + 2 * 4, "layout");
// Mirrors BlendBgNoiseParams.
struct BlendBgNoiseParams {
uint64_t rgb, transmittance, out_rgb;
uint64_t rgb, transmittance, out_rgb, exponent_by_cam, cam_indices;
float randomize_weight;
uint32_t seed, HW, total, wgs_per_row, W, blocky, block_px;
uint32_t seed, HW, total, wgs_per_row, W, blocky, block_px, match_luma;
};
static_assert(sizeof(BlendBgNoiseParams) == 3 * 8 + 8 * 4, "layout");
static_assert(sizeof(BlendBgNoiseParams) == 5 * 8 + 9 * 4 + 4 /*pad*/, "layout");
// Mirrors BlendBgColorParams.
struct BlendBgColorParams {
@@ -82,6 +82,8 @@ void blend_background_noise_forward(
DeviceTensor3D<float> transmittance,
float randomize_weight,
uint32_t seed,
const float* exponent_by_cam,
const int32_t* cam_indices,
DeviceTensor3D<float3> out_rgb
) {
const int64_t hw = rgb.size<1>() * rgb.size<2>();
@@ -90,6 +92,9 @@ void blend_background_noise_forward(
p.rgb = (uint64_t)rgb.data_ptr();
p.transmittance = (uint64_t)transmittance.data_ptr();
p.out_rgb = (uint64_t)out_rgb.data_ptr();
p.exponent_by_cam = (uint64_t)exponent_by_cam;
p.cam_indices = (uint64_t)cam_indices;
p.match_luma = exponent_by_cam ? 1u : 0u;
p.randomize_weight = randomize_weight;
p.seed = seed;
p.HW = (uint32_t)hw;
@@ -24,12 +24,12 @@ static_assert(sizeof(BlendBgBwdParams) == 7 * 8 + 3 * 4 + 4 /*pad*/,
// Mirrors BlendBgNoiseBwdParams.
struct BlendBgNoiseBwdParams {
uint64_t rgb, transmittance, v_out_rgb, v_rgb, v_transmittance;
uint64_t rgb, transmittance, v_out_rgb, v_rgb, v_transmittance,
exponent_by_cam, cam_indices;
float overexposure_scale, randomize_weight;
uint32_t seed, HW, total, wgs_per_row, W, blocky, block_px;
uint32_t seed, HW, total, wgs_per_row, W, blocky, block_px, match_luma;
};
static_assert(sizeof(BlendBgNoiseBwdParams) == 5 * 8 + 9 * 4 + 4 /*pad*/,
"layout");
static_assert(sizeof(BlendBgNoiseBwdParams) == 7 * 8 + 10 * 4, "layout");
// Mirrors BlendBgColorBwdParams.
struct BlendBgColorBwdParams {
@@ -138,6 +138,8 @@ void blend_background_noise_backward(
DeviceTensor3D<float> transmittance,
float randomize_weight,
uint32_t seed,
const float* exponent_by_cam,
const int32_t* cam_indices,
float overexposure_weight,
DeviceTensor3D<float3> v_out_rgb,
DeviceTensor3D<float3> v_rgb,
@@ -146,6 +148,9 @@ void blend_background_noise_backward(
const int64_t hw = rgb.size<1>() * rgb.size<2>();
const int64_t total = rgb.size<0>() * hw;
BlendBgNoiseBwdParams p{};
p.exponent_by_cam = (uint64_t)exponent_by_cam;
p.cam_indices = (uint64_t)cam_indices;
p.match_luma = exponent_by_cam ? 1u : 0u;
p.overexposure_scale = overexposure_scale(
rgb.size<0>(), rgb.size<1>(), rgb.size<2>(), overexposure_weight);
p.rgb = (uint64_t)rgb.data_ptr();
@@ -114,10 +114,12 @@ float3 _bg_sample(uint blocky, uint block_px, uint seed, uint gid, uint bid,
float(hash_uint3(seed + 2, cid, bid)) * exp2(-31.0f) - 1.0f);
}
struct BlendBgNoiseParams { // 56 bytes: pushed directly
struct BlendBgNoiseParams { // 80 bytes: pushed directly
float* rgb; // [B,H,W,3]
float* transmittance; // [B,H,W,1]
float* out_rgb; // [B,H,W,3]
float* exponent_by_cam; // power per camera slot, read when match_luma != 0
int* cam_indices; // [B] slot per image
float randomize_weight;
uint32_t seed;
uint32_t HW; // H*W (hash uses per-image pixel id + batch id)
@@ -126,8 +128,15 @@ struct BlendBgNoiseParams { // 56 bytes: pushed directly
uint32_t W;
uint32_t blocky;
uint32_t block_px;
uint32_t match_luma;
};
// Mirrors _bg_exponent in kernels/pixelwise/ImageColorOps.cu.
float _bg_exponent(uint bid, uint match_luma, float* exponent_by_cam,
int* cam_indices) {
return match_luma != 0u ? exponent_by_cam[uint(cam_indices[bid])] : 1.0f;
}
[shader("compute")]
[numthreads(128, 1, 1)]
void blend_background_noise_fwd(uint3 wg: SV_GroupID,
@@ -144,6 +153,8 @@ void blend_background_noise_fwd(uint3 wg: SV_GroupID,
float3 background = _bg_sample(p.blocky, p.block_px, p.seed, gid, bid, p.W);
background = 0.5f + 0.5f * p.randomize_weight * background;
background = background_apply_exponent(background,
_bg_exponent(bid, p.match_luma, p.exponent_by_cam, p.cam_indices));
background = display_to_working3(background, kTransfer, kIsLinear != 0);
rgb = blend_background(rgb, T, background);
@@ -131,12 +131,14 @@ float3 _bg_sample(uint blocky, uint block_px, uint seed, uint gid, uint bid,
float(_pwt_hash_uint3(seed + 2, cid, bid)) * exp2(-31.0f) - 1.0f);
}
struct BlendBgNoiseBwdParams { // 80 bytes: pushed directly
struct BlendBgNoiseBwdParams { // 96 bytes: pushed directly
float* rgb; // PRE-blend
float* transmittance;
float* v_out_rgb;
float* v_rgb;
float* v_transmittance;
float* exponent_by_cam; // power per camera slot, read when match_luma != 0
int* cam_indices; // [B] slot per image
float overexposure_scale;
float randomize_weight;
uint32_t seed;
@@ -146,8 +148,15 @@ struct BlendBgNoiseBwdParams { // 80 bytes: pushed directly
uint32_t W;
uint32_t blocky;
uint32_t block_px;
uint32_t match_luma;
};
// Mirrors _bg_exponent in kernels/pixelwise/ImageColorOps.cu.
float _pwt_bg_exponent(uint bid, uint match_luma, float* exponent_by_cam,
int* cam_indices) {
return match_luma != 0u ? exponent_by_cam[uint(cam_indices[bid])] : 1.0f;
}
[shader("compute")]
[numthreads(128, 1, 1)]
void blend_bg_noise_bwd(uint3 wg: SV_GroupID, uint tid: SV_GroupThreadID,
@@ -163,6 +172,8 @@ void blend_bg_noise_bwd(uint3 wg: SV_GroupID, uint tid: SV_GroupThreadID,
float3 background = _bg_sample(p.blocky, p.block_px, p.seed, gid, bid, p.W);
background = 0.5f + 0.5f * p.randomize_weight * background;
background = background_apply_exponent(background,
_pwt_bg_exponent(bid, p.match_luma, p.exponent_by_cam, p.cam_indices));
background = display_to_working3(background, kTransfer, kIsLinear != 0);
float3 v_out = float3(p.v_out_rgb[3 * idx], p.v_out_rgb[3 * idx + 1],
+4 -2
View File
@@ -152,6 +152,7 @@ inline int train_tier_rank(const char* tier) {
X(int, background_sh_degree, 4, "splats", "basic", "") \
X(int, background_noise_warmup, 2000, "splats", "expert", "") \
X(float, background_noise_pre_warmup, 0.25f, "splats", "expert", "") \
X(bool, background_match_luminance, false, "splats", "advanced", "") \
X(std::optional<float>, scale_init, std::nullopt, "splats", "advanced", "") \
X(std::optional<float>, opacity_init, std::nullopt, "splats", "advanced", "") \
X(bool, suppress_initial_scales, false, "splats", "expert", "") \
@@ -205,7 +206,7 @@ inline int train_tier_rank(const char* tier) {
X(float, alpha_loss_weight, 0.1f, "loss", "basic", "") \
X(float, alpha_loss_weight_under, 0.0f, "loss", "basic", "") \
X(float, loss_saturation_threshold, -1.0f, "loss", "advanced", "") \
X(bool, normalize_loss_by_luminance, false, "loss", "advanced", "") \
X(float, loss_luminance_normalization, 0.0f, "loss", "advanced", "") \
\
/* ==== geometry -- how crisp the surfaces come out, and depth/normal guidance ==== */ \
X(std::string, floater_suppression, "off", "geometry", "basic", "off|mild|strong") \
@@ -433,9 +434,10 @@ inline bool train_apply_preset(TrainConfig& c, const std::string& name) {
// c.ppisp_adagrad_lr = 0.25f;
c.ppisp_exposure_from_exif = true;
c.background_mode = "random";
c.background_match_luminance = true;
// c.depth_distortion_reg = 0.01f;
c.loss_saturation_threshold = 0.98f;
c.normalize_loss_by_luminance = true;
// c.loss_luminance_normalization = 0.5f;
c.dc_reg = 0.0f;
c.max_screen_size = 0.15f;
// c.features_dc_lr = 0.0015f;
+1 -1
View File
@@ -131,7 +131,7 @@ inline float tone_encode(float x, Transfer t) {
return linear_to_srgb(std::min(tone_uncharted2(x), 1.0f));
case Transfer::SrgbClamped:
return linear_to_srgb(std::min(std::max(x, 0.0f), 1.0f));
default: return linear_to_srgb(x);
default: return linear_to_srgb(std::max(x, 0.0f));
}
}
+1
View File
@@ -257,6 +257,7 @@ enum class SaveClass : uint8_t {
X(EngBgSkyImage , "eng.bg_sky.image", Appearance, Never) \
X(EngBgSkyRgbPost , "eng.bg_sky.rgb_post", Appearance, Never) \
X(EngBgSkyVTsScratch , "eng.bg_sky.v_Ts_scratch", Appearance, Never) \
X(EngBgExponentByCam , "eng.bg.exponent_by_cam", Appearance, Never) \
X(EngBgSkyVBg , "eng.bg_sky.v_bg", Appearance, Never) \
X(EngBgSkyVSh , "eng.bg_sky.v_sh", Appearance, Never) \
/* ---- PPISP ---- */ \
+25
View File
@@ -122,6 +122,21 @@ static unsigned _bg_block_px(const EngineBackground& bg, int H, int W) {
return base << (std::mt19937(bg.cur_seed)() % levels);
}
// The per-image power table for the blend kernels, or null for the plain
// draw. Decided by the forward and reused by the backward.
struct BgLumaViews {
const float* exponent = nullptr;
const int32_t* cams = nullptr;
};
static BgLumaViews _bg_luma_views(const EngineBackground& bg) {
BgLumaViews v;
if (!bg.cur_match_luma) return v;
v.exponent = bg.exponent_by_cam.data_ptr();
v.cams = engine().bilagrid_cur_cam_indices.data_ptr();
return v;
}
static void _ensure_bg_sh_optim_state() {
auto& bg = engine().background;
if (bg.sh_optim_initialized) return;
@@ -199,6 +214,12 @@ void _engine_background_forward() {
int W = engine().camera.width;
if (C_batch <= 0) return;
// One-shot, so a viewer render between steps keeps the plain draw.
bg.cur_match_luma = bg.match_luma_pending &&
bg.exponent_by_cam.size() > 0 &&
engine().bilagrid_cur_cam_indices.size() >= C_batch;
bg.match_luma_pending = false;
auto& fwd_rgb_tensor = std::get<0>(engine().fwd.renders);
auto& fwd_Ts_tensor = engine().fwd.render_Ts;
if (fwd_rgb_tensor.data_ptr() == nullptr || fwd_Ts_tensor.data_ptr() == nullptr)
@@ -221,12 +242,14 @@ void _engine_background_forward() {
if (bg.mode != EngineBackground::Mode::Sh) {
bg.cur_block_px = _bg_block_px(bg, H, W);
const BgLumaViews lv = _bg_luma_views(bg);
blend_background_noise_forward(
bg.splat_transfer, bg.splat_is_linear,
bg.mode == EngineBackground::Mode::Pseudorandom,
bg.cur_block_px,
bg.fwd_pre_blend_rgb, Ts_in,
bg.cur_randomize_weight, bg.cur_seed,
lv.exponent, lv.cams,
post_rgb);
fwd_rgb_tensor = post_rgb;
return;
@@ -298,12 +321,14 @@ void _engine_background_backward_hook(
overexposure_reg_weight,
v_out, v_rgb, v_Ts_scratch_dt);
} else {
const BgLumaViews lv = _bg_luma_views(bg);
blend_background_noise_backward(
bg.splat_transfer, bg.splat_is_linear,
bg.mode == EngineBackground::Mode::Pseudorandom,
bg.cur_block_px,
bg.fwd_pre_blend_rgb, Ts_in,
bg.cur_randomize_weight, bg.cur_seed,
lv.exponent, lv.cams,
overexposure_reg_weight,
v_out, v_rgb, v_Ts_scratch_dt);
}
+2 -1
View File
@@ -60,7 +60,7 @@ struct LossConfig {
// Divide the photometric weights (RGB / YUV supervision and SSIM) by
// 0.5 / max(mean sRGB luma of this step's reference pixels, 1/255), so a
// dark capture pulls on the splats as hard as a bright one.
bool normalize_by_luminance = false;
float luminance_normalization = 0.0f; // power on 0.5 / mean luma; 0 = off
// Image-space overexposure regularization weight. When non-zero, a
// dedicated kernel adds dL/dx of L = w * mean(max(-x, x-1, 0)^2) directly
// into v_render_rgb (in the pre-bilagrid / pre-PPISP / pre-color-space
@@ -265,6 +265,7 @@ struct BackgroundStepConfig {
float lr_sh = 0.0f;
float randomize_weight = 0.0f;
uint32_t seed = 0;
bool match_luminance = false; // randomized draw ^ per-image power
};
+99 -22
View File
@@ -165,36 +165,54 @@ static float _sampled_mean_luma(uint64_t base, int64_t n_px, int64_t width,
}
// LossConfig::normalize_by_luminance: the photometric weights divided by
// 0.5 / max(mean sRGB luma, 1/255). Leaves them alone when the step carries
// no reference image to measure.
static void _normalize_weights_by_luminance(LossConfig& loss, const TrainStep& stp) {
static bool _sub_has_gt_rgb(const DecodedBatch& sub) {
const auto& shape = std::get<2>(sub.rgb_view);
return std::get<0>(sub.rgb_view) != 0 && shape.size() == 4 &&
shape[3] == 3 && shape[1] * shape[2] > 0;
}
// Mean display luma of row `j` of a sub-batch's reference images, through the
// per-camera cache when the rows are whole input images (indices per row).
static float _gt_row_luma(const DecodedBatch& sub, int64_t j) {
const TorchTensorView& rgb = sub.rgb_view;
const auto& shape = std::get<2>(rgb);
const int64_t px = shape[1] * shape[2];
const uint32_t elem = std::get<1>(rgb);
auto compute = [&] {
return _sampled_mean_luma(std::get<0>(rgb) + (uint64_t)(j * px) * 3 * elem,
px, shape[2], elem);
};
if ((int64_t)sub.indices.size() != shape[0]) return compute();
const int32_t id = sub.indices[(size_t)j];
if (id < 0) return compute();
std::vector<float>& cache = engine().gt_mean_luma;
if ((size_t)id >= cache.size())
cache.resize((size_t)id + 1, std::numeric_limits<float>::quiet_NaN());
if (std::isnan(cache[(size_t)id])) cache[(size_t)id] = compute();
return cache[(size_t)id];
}
// LossConfig::luminance_normalization: the photometric weights times
// 1 - factor (1 - (0.5 / max(mean sRGB luma, 1/255))). Leaves them alone when the step
// carries no reference image to measure.
static void _normalize_weights_by_luminance(LossConfig& loss, const TrainStep& stp,
float factor) {
double sum = 0.0, total_px = 0.0;
for (const auto& sub : stp.subs) {
const TorchTensorView& rgb = sub->rgb_view;
const auto& shape = std::get<2>(rgb);
const uint64_t base = std::get<0>(rgb);
if (base == 0 || shape.size() != 4 || shape[3] != 3) continue;
if (!_sub_has_gt_rgb(*sub)) continue;
const auto& shape = std::get<2>(sub->rgb_view);
const int64_t B = shape[0], px = shape[1] * shape[2];
if (px <= 0 || (int64_t)sub->indices.size() != B) continue;
const uint32_t elem = std::get<1>(rgb);
if ((int64_t)sub->indices.size() != B) continue;
for (int64_t j = 0; j < B; j++) {
const int32_t id = sub->indices[(size_t)j];
if (id < 0) continue;
if ((size_t)id >= cache.size())
cache.resize((size_t)id + 1, std::numeric_limits<float>::quiet_NaN());
if (std::isnan(cache[(size_t)id]))
cache[(size_t)id] = _sampled_mean_luma(
base + (uint64_t)(j * px) * 3 * elem, px, shape[2], elem);
sum += (double)cache[(size_t)id] * (double)px;
if (sub->indices[(size_t)j] < 0) continue;
sum += (double)_gt_row_luma(*sub, j) * (double)px;
total_px += (double)px;
}
}
if (total_px <= 0.0) return;
const float scale = (float)(0.5 / std::max(sum / total_px, 1.0 / 255.0));
const float scale =
1.0f - factor * (1.0f - 0.5f / std::max((float)(sum / total_px), 1.0f / 255.0f));
constexpr LossWeightIndex photometric[] = {
LossWeightIndex::RgbSupL1, LossWeightIndex::RgbSupL2,
LossWeightIndex::YSupL1, LossWeightIndex::YSupL2,
@@ -205,6 +223,62 @@ static void _normalize_weights_by_luminance(LossConfig& loss, const TrainStep& s
}
// 0.5^p = luma / gain: the draw's median lands on the image's mean luma ahead
// of PPISP, whose 2^e scales display values, or linear light (2^(e/2.2) in
// display) when it runs before the encode. Clamped to [1/255, 1]: p in [0, 8].
static float _bg_luma_exponent(float luma, float exposure_log2,
bool ppisp_before_color_space) {
const double e = ppisp_before_color_space ? exposure_log2 / 2.2 : exposure_log2;
const double target = std::min(std::max((double)luma * std::exp2(-e), 1.0 / 255.0), 1.0);
return (float)-std::log2(target);
}
// BackgroundStepConfig::match_luminance: the per-slot power table the blend
// kernels read, recomputed for this step's slots from the cached luma and the
// current PPISP exposure, and re-uploaded whole (it is tiny).
static void _update_background_exponents(const TrainStep& stp,
bool ppisp_before_color_space) {
auto& bg = engine().background;
std::vector<float>& luma = bg.luma_by_cam_host;
std::vector<float>& expo = bg.exponent_by_cam_host;
const auto& pp = engine().ppisp;
const int P = pp.num_params;
std::vector<float> ppisp_host;
if (pp.enabled && pp.params.data_ptr() && P > 0) {
ppisp_host.resize((size_t)pp.params.size<0>() * P);
backend::memcpy_sync(ppisp_host.data(), pp.params.data_ptr(),
ppisp_host.size() * sizeof(float),
backend::MemcpyKind::DeviceToHost);
}
bool any = false;
for (const auto& sub : stp.subs) {
if (!_sub_has_gt_rgb(*sub)) continue;
const int64_t B = std::get<2>(sub->rgb_view)[0];
const int K = std::max(sub->K, 1);
if ((int64_t)sub->post_offsets.size() * K < B) continue;
for (int64_t j = 0; j < B; j++) {
const size_t slot = (size_t)sub->post_offsets[(size_t)(j / K)] + (size_t)(j % K);
if (slot >= luma.size()) {
luma.resize(slot + 1, std::numeric_limits<float>::quiet_NaN());
expo.resize(slot + 1, 1.0f); // the plain draw for slots never seen
}
luma[slot] = _gt_row_luma(*sub, j);
const float e = (slot + 1) * (size_t)P <= ppisp_host.size()
? ppisp_host[slot * (size_t)P] : 0.0f;
expo[slot] = _bg_luma_exponent(luma[slot], e, ppisp_before_color_space);
any = true;
}
}
if (!any) return;
if (bg.exponent_by_cam.size() != (int64_t)expo.size())
bg.exponent_by_cam.resize(PoolSlot::EngBgExponentByCam, (int64_t)expo.size());
backend::memcpy_sync(bg.exponent_by_cam.data_ptr(), expo.data(),
expo.size() * sizeof(float), backend::MemcpyKind::HostToDevice);
}
std::map<std::string, float> engine_train_step_managed(
int step, int max_steps,
std::string primitive,
@@ -229,8 +303,11 @@ std::map<std::string, float> engine_train_step_managed(
if (stp.subs.empty())
throw std::runtime_error("engine_train_step_managed: empty training step");
if (cfg.loss.normalize_by_luminance)
_normalize_weights_by_luminance(cfg.loss, stp);
if (cfg.loss.luminance_normalization > 0.0f)
_normalize_weights_by_luminance(cfg.loss, stp, cfg.loss.luminance_normalization);
if (cfg.background.match_luminance && engine().background.enabled)
_update_background_exponents(
stp, cfg.ppisp.run_before_color_space && engine().color_space.splat_enabled);
// Build a POST-split bilagrid cam-index buffer for one sub-batch.
// bilagrid_cam_indices must be the POST-split camera id, not the input
+9
View File
@@ -417,6 +417,15 @@ struct EngineBackground {
// Resolved by the forward (it knows this batch's H/W) and read again by
// the backward, which must reconstruct the same background.
unsigned cur_block_px = 0;
// Per post-split camera slot: the reference image's mean display luma and
// the power the randomized draw is raised to. Armed per training forward
// (never for a viewer render), consumed into cur_match_luma for the bwd.
std::vector<float> luma_by_cam_host;
std::vector<float> exponent_by_cam_host;
DeviceVector<float> exponent_by_cam;
bool match_luma_pending = false;
bool cur_match_luma = false;
};
// Linear / wide-gamut color space conversion.
+1
View File
@@ -137,6 +137,7 @@ static std::map<std::string, float> _engine_step_fwd_bwd_only(
cfg.ppisp.run_before_color_space && engine().color_space.splat_enabled;
engine().ppisp.forward_pending =
engine().ppisp.enabled && engine().ppisp.cur_run_before_color_space;
engine().background.match_luma_pending = cfg.background.match_luminance;
forward_3dgs(primitive, sh_degree, packed, /*output_median=*/false, (int)dist_type);
+3295 -3271
View File
File diff suppressed because it is too large Load Diff
Generated Vendored
+126 -126
View File
@@ -2641,25 +2641,7 @@ inline __device__ void s_bwd_prop_apply_ppisp_0(DiffPair_vectorx3Cfloatx2C3x3E_0
_S645[int(33)] = 0.0f;
_S645[int(34)] = 0.0f;
_S645[int(35)] = 0.0f;
_S645[int(35)] = _S618;
_S645[int(34)] = _S619;
_S645[int(33)] = _S620;
_S645[int(32)] = _S621;
_S645[int(31)] = _S622;
_S645[int(30)] = _S623;
_S645[int(29)] = _S624;
_S645[int(28)] = _S625;
_S645[int(27)] = _S626;
_S645[int(26)] = _S627;
_S645[int(25)] = _S628;
_S645[int(24)] = _S629;
_S645[int(23)] = _S617.color_params_3.n_0.y;
_S645[int(22)] = _S617.color_params_3.n_0.x;
_S645[int(21)] = _S617.color_params_3.g_0.y;
_S645[int(20)] = _S617.color_params_3.g_0.x;
_S645[int(19)] = _S617.color_params_3.r_0.y;
_S645[int(18)] = _S617.color_params_3.r_0.x;
_S645[int(17)] = _S617.color_params_3.b_0.y;
_S645[int(8)] = _S637;
_S645[int(16)] = _S617.color_params_3.b_0.x;
_S645[int(15)] = _S630;
_S645[int(14)] = _S631;
@@ -2668,7 +2650,7 @@ inline __device__ void s_bwd_prop_apply_ppisp_0(DiffPair_vectorx3Cfloatx2C3x3E_0
_S645[int(11)] = _S634;
_S645[int(10)] = _S635;
_S645[int(9)] = _S636;
_S645[int(8)] = _S637;
_S645[int(17)] = _S617.color_params_3.b_0.y;
_S645[int(7)] = _S638;
_S645[int(6)] = _S639;
_S645[int(5)] = _S640;
@@ -2677,6 +2659,24 @@ inline __device__ void s_bwd_prop_apply_ppisp_0(DiffPair_vectorx3Cfloatx2C3x3E_0
_S645[int(2)] = _S643;
_S645[int(1)] = _S644;
_S645[int(0)] = _S201.exposure_3;
_S645[int(26)] = _S627;
_S645[int(34)] = _S619;
_S645[int(33)] = _S620;
_S645[int(32)] = _S621;
_S645[int(31)] = _S622;
_S645[int(30)] = _S623;
_S645[int(29)] = _S624;
_S645[int(28)] = _S625;
_S645[int(27)] = _S626;
_S645[int(35)] = _S618;
_S645[int(25)] = _S628;
_S645[int(24)] = _S629;
_S645[int(23)] = _S617.color_params_3.n_0.y;
_S645[int(22)] = _S617.color_params_3.n_0.x;
_S645[int(21)] = _S617.color_params_3.g_0.y;
_S645[int(20)] = _S617.color_params_3.g_0.x;
_S645[int(19)] = _S617.color_params_3.r_0.y;
_S645[int(18)] = _S617.color_params_3.r_0.x;
dpparams_0->primal_0 = dpparams_0->primal_0;
dpparams_0->differential_0 = _S645;
dprgb_in_0->primal_0 = (*dprgb_in_0).primal_0;
@@ -3908,26 +3908,7 @@ inline __device__ void s_bwd_prop_apply_ppisp_rqs_0(DiffPair_vectorx3Cfloatx2C3x
_S1185[int(36)] = 0.0f;
_S1185[int(37)] = 0.0f;
_S1185[int(38)] = 0.0f;
_S1185[int(38)] = _S1155;
_S1185[int(37)] = _S1156;
_S1185[int(36)] = _S1157;
_S1185[int(35)] = _S1158;
_S1185[int(34)] = _S1159;
_S1185[int(33)] = _S1160;
_S1185[int(32)] = _S1161;
_S1185[int(31)] = _S1162;
_S1185[int(30)] = _S1163;
_S1185[int(29)] = _S1164;
_S1185[int(28)] = _S1165;
_S1185[int(27)] = _S1166;
_S1185[int(26)] = _S1167;
_S1185[int(25)] = _S1168;
_S1185[int(24)] = _S1169;
_S1185[int(23)] = _S1154.color_params_2.n_0.y;
_S1185[int(22)] = _S1154.color_params_2.n_0.x;
_S1185[int(21)] = _S1154.color_params_2.g_0.y;
_S1185[int(20)] = _S1154.color_params_2.g_0.x;
_S1185[int(19)] = _S1154.color_params_2.r_0.y;
_S1185[int(9)] = _S1176;
_S1185[int(18)] = _S1154.color_params_2.r_0.x;
_S1185[int(17)] = _S1154.color_params_2.b_0.y;
_S1185[int(16)] = _S1154.color_params_2.b_0.x;
@@ -3937,7 +3918,7 @@ inline __device__ void s_bwd_prop_apply_ppisp_rqs_0(DiffPair_vectorx3Cfloatx2C3x
_S1185[int(12)] = _S1173;
_S1185[int(11)] = _S1174;
_S1185[int(10)] = _S1175;
_S1185[int(9)] = _S1176;
_S1185[int(19)] = _S1154.color_params_2.r_0.y;
_S1185[int(8)] = _S1177;
_S1185[int(7)] = _S1178;
_S1185[int(6)] = _S1179;
@@ -3947,6 +3928,25 @@ inline __device__ void s_bwd_prop_apply_ppisp_rqs_0(DiffPair_vectorx3Cfloatx2C3x
_S1185[int(2)] = _S1183;
_S1185[int(1)] = _S1184;
_S1185[int(0)] = _S663.exposure_2;
_S1185[int(28)] = _S1165;
_S1185[int(37)] = _S1156;
_S1185[int(36)] = _S1157;
_S1185[int(35)] = _S1158;
_S1185[int(34)] = _S1159;
_S1185[int(33)] = _S1160;
_S1185[int(32)] = _S1161;
_S1185[int(31)] = _S1162;
_S1185[int(30)] = _S1163;
_S1185[int(29)] = _S1164;
_S1185[int(38)] = _S1155;
_S1185[int(27)] = _S1166;
_S1185[int(26)] = _S1167;
_S1185[int(25)] = _S1168;
_S1185[int(24)] = _S1169;
_S1185[int(23)] = _S1154.color_params_2.n_0.y;
_S1185[int(22)] = _S1154.color_params_2.n_0.x;
_S1185[int(21)] = _S1154.color_params_2.g_0.y;
_S1185[int(20)] = _S1154.color_params_2.g_0.x;
dpparams_1->primal_0 = dpparams_1->primal_0;
dpparams_1->differential_0 = _S1185;
dprgb_in_1->primal_0 = (*dprgb_in_1).primal_0;
@@ -4517,30 +4517,30 @@ inline __device__ void s_bwd_prop_apply_ppisp_no_crf_0(DiffPair_vectorx3Cfloatx2
_S1391[int(21)] = 0.0f;
_S1391[int(22)] = 0.0f;
_S1391[int(23)] = 0.0f;
_S1391[int(23)] = _S1375.color_params_1.n_0.y;
_S1391[int(22)] = _S1375.color_params_1.n_0.x;
_S1391[int(21)] = _S1375.color_params_1.g_0.y;
_S1391[int(20)] = _S1375.color_params_1.g_0.x;
_S1391[int(19)] = _S1375.color_params_1.r_0.y;
_S1391[int(18)] = _S1375.color_params_1.r_0.x;
_S1391[int(17)] = _S1375.color_params_1.b_0.y;
_S1391[int(16)] = _S1375.color_params_1.b_0.x;
_S1391[int(15)] = _S1376;
_S1391[int(14)] = _S1377;
_S1391[int(13)] = _S1378;
_S1391[int(12)] = _S1379;
_S1391[int(11)] = _S1380;
_S1391[int(10)] = _S1381;
_S1391[int(9)] = _S1382;
_S1391[int(8)] = _S1383;
_S1391[int(7)] = _S1384;
_S1391[int(6)] = _S1385;
_S1391[int(5)] = _S1386;
_S1391[int(4)] = _S1387;
_S1391[int(3)] = _S1388;
_S1391[int(2)] = _S1389;
_S1391[int(1)] = _S1390;
_S1391[int(0)] = _S1201.exposure_1;
_S1391[int(1)] = _S1390;
_S1391[int(2)] = _S1389;
_S1391[int(3)] = _S1388;
_S1391[int(4)] = _S1387;
_S1391[int(5)] = _S1386;
_S1391[int(6)] = _S1385;
_S1391[int(7)] = _S1384;
_S1391[int(8)] = _S1383;
_S1391[int(9)] = _S1382;
_S1391[int(10)] = _S1381;
_S1391[int(23)] = _S1375.color_params_1.n_0.y;
_S1391[int(12)] = _S1379;
_S1391[int(13)] = _S1378;
_S1391[int(14)] = _S1377;
_S1391[int(15)] = _S1376;
_S1391[int(16)] = _S1375.color_params_1.b_0.x;
_S1391[int(17)] = _S1375.color_params_1.b_0.y;
_S1391[int(18)] = _S1375.color_params_1.r_0.x;
_S1391[int(19)] = _S1375.color_params_1.r_0.y;
_S1391[int(20)] = _S1375.color_params_1.g_0.x;
_S1391[int(21)] = _S1375.color_params_1.g_0.y;
_S1391[int(22)] = _S1375.color_params_1.n_0.x;
dpparams_2->primal_0 = dpparams_2->primal_0;
dpparams_2->differential_0 = _S1391;
dprgb_in_2->primal_0 = (*dprgb_in_2).primal_0;
@@ -5691,25 +5691,7 @@ inline __device__ void s_bwd_prop_compute_raw_ppisp_regularization_loss_0(DiffPa
_S1806[int(33)] = 0.0f;
_S1806[int(34)] = 0.0f;
_S1806[int(35)] = 0.0f;
_S1806[int(35)] = _S1779;
_S1806[int(34)] = _S1780;
_S1806[int(33)] = _S1781;
_S1806[int(32)] = _S1782;
_S1806[int(31)] = _S1783;
_S1806[int(30)] = _S1784;
_S1806[int(29)] = _S1785;
_S1806[int(28)] = _S1786;
_S1806[int(27)] = _S1787;
_S1806[int(26)] = _S1788;
_S1806[int(25)] = _S1789;
_S1806[int(24)] = _S1790;
_S1806[int(23)] = _S1778.color_params_3.n_0.y;
_S1806[int(22)] = _S1778.color_params_3.n_0.x;
_S1806[int(21)] = _S1778.color_params_3.g_0.y;
_S1806[int(20)] = _S1778.color_params_3.g_0.x;
_S1806[int(19)] = _S1778.color_params_3.r_0.y;
_S1806[int(18)] = _S1778.color_params_3.r_0.x;
_S1806[int(17)] = _S1778.color_params_3.b_0.y;
_S1806[int(8)] = _S1798;
_S1806[int(16)] = _S1778.color_params_3.b_0.x;
_S1806[int(15)] = _S1791;
_S1806[int(14)] = _S1792;
@@ -5718,7 +5700,7 @@ inline __device__ void s_bwd_prop_compute_raw_ppisp_regularization_loss_0(DiffPa
_S1806[int(11)] = _S1795;
_S1806[int(10)] = _S1796;
_S1806[int(9)] = _S1797;
_S1806[int(8)] = _S1798;
_S1806[int(17)] = _S1778.color_params_3.b_0.y;
_S1806[int(7)] = _S1799;
_S1806[int(6)] = _S1800;
_S1806[int(5)] = _S1801;
@@ -5727,6 +5709,24 @@ inline __device__ void s_bwd_prop_compute_raw_ppisp_regularization_loss_0(DiffPa
_S1806[int(2)] = _S1804;
_S1806[int(1)] = _S1805;
_S1806[int(0)] = _S1611.exposure_3;
_S1806[int(26)] = _S1788;
_S1806[int(34)] = _S1780;
_S1806[int(33)] = _S1781;
_S1806[int(32)] = _S1782;
_S1806[int(31)] = _S1783;
_S1806[int(30)] = _S1784;
_S1806[int(29)] = _S1785;
_S1806[int(28)] = _S1786;
_S1806[int(27)] = _S1787;
_S1806[int(35)] = _S1779;
_S1806[int(25)] = _S1789;
_S1806[int(24)] = _S1790;
_S1806[int(23)] = _S1778.color_params_3.n_0.y;
_S1806[int(22)] = _S1778.color_params_3.n_0.x;
_S1806[int(21)] = _S1778.color_params_3.g_0.y;
_S1806[int(20)] = _S1778.color_params_3.g_0.x;
_S1806[int(19)] = _S1778.color_params_3.r_0.y;
_S1806[int(18)] = _S1778.color_params_3.r_0.x;
dpparams_4->primal_0 = dpparams_4->primal_0;
dpparams_4->differential_0 = _S1806;
return;
@@ -6194,26 +6194,7 @@ inline __device__ void s_bwd_prop_compute_raw_ppisp_rqs_regularization_loss_0(Di
_S2028[int(36)] = 0.0f;
_S2028[int(37)] = 0.0f;
_S2028[int(38)] = 0.0f;
_S2028[int(38)] = _S1998;
_S2028[int(37)] = _S1999;
_S2028[int(36)] = _S2000;
_S2028[int(35)] = _S2001;
_S2028[int(34)] = _S2002;
_S2028[int(33)] = _S2003;
_S2028[int(32)] = _S2004;
_S2028[int(31)] = _S2005;
_S2028[int(30)] = _S2006;
_S2028[int(29)] = _S2007;
_S2028[int(28)] = _S2008;
_S2028[int(27)] = _S2009;
_S2028[int(26)] = _S2010;
_S2028[int(25)] = _S2011;
_S2028[int(24)] = _S2012;
_S2028[int(23)] = _S1997.color_params_2.n_0.y;
_S2028[int(22)] = _S1997.color_params_2.n_0.x;
_S2028[int(21)] = _S1997.color_params_2.g_0.y;
_S2028[int(20)] = _S1997.color_params_2.g_0.x;
_S2028[int(19)] = _S1997.color_params_2.r_0.y;
_S2028[int(9)] = _S2019;
_S2028[int(18)] = _S1997.color_params_2.r_0.x;
_S2028[int(17)] = _S1997.color_params_2.b_0.y;
_S2028[int(16)] = _S1997.color_params_2.b_0.x;
@@ -6223,7 +6204,7 @@ inline __device__ void s_bwd_prop_compute_raw_ppisp_rqs_regularization_loss_0(Di
_S2028[int(12)] = _S2016;
_S2028[int(11)] = _S2017;
_S2028[int(10)] = _S2018;
_S2028[int(9)] = _S2019;
_S2028[int(19)] = _S1997.color_params_2.r_0.y;
_S2028[int(8)] = _S2020;
_S2028[int(7)] = _S2021;
_S2028[int(6)] = _S2022;
@@ -6233,6 +6214,25 @@ inline __device__ void s_bwd_prop_compute_raw_ppisp_rqs_regularization_loss_0(Di
_S2028[int(2)] = _S2026;
_S2028[int(1)] = _S2027;
_S2028[int(0)] = _S1819.exposure_2;
_S2028[int(28)] = _S2008;
_S2028[int(37)] = _S1999;
_S2028[int(36)] = _S2000;
_S2028[int(35)] = _S2001;
_S2028[int(34)] = _S2002;
_S2028[int(33)] = _S2003;
_S2028[int(32)] = _S2004;
_S2028[int(31)] = _S2005;
_S2028[int(30)] = _S2006;
_S2028[int(29)] = _S2007;
_S2028[int(38)] = _S1998;
_S2028[int(27)] = _S2009;
_S2028[int(26)] = _S2010;
_S2028[int(25)] = _S2011;
_S2028[int(24)] = _S2012;
_S2028[int(23)] = _S1997.color_params_2.n_0.y;
_S2028[int(22)] = _S1997.color_params_2.n_0.x;
_S2028[int(21)] = _S1997.color_params_2.g_0.y;
_S2028[int(20)] = _S1997.color_params_2.g_0.x;
dpparams_5->primal_0 = dpparams_5->primal_0;
dpparams_5->differential_0 = _S2028;
return;
@@ -6685,30 +6685,30 @@ inline __device__ void s_bwd_prop_compute_raw_ppisp_no_crf_regularization_loss_0
_S2202[int(21)] = 0.0f;
_S2202[int(22)] = 0.0f;
_S2202[int(23)] = 0.0f;
_S2202[int(23)] = _S2186.color_params_1.n_0.y;
_S2202[int(22)] = _S2186.color_params_1.n_0.x;
_S2202[int(21)] = _S2186.color_params_1.g_0.y;
_S2202[int(20)] = _S2186.color_params_1.g_0.x;
_S2202[int(19)] = _S2186.color_params_1.r_0.y;
_S2202[int(18)] = _S2186.color_params_1.r_0.x;
_S2202[int(17)] = _S2186.color_params_1.b_0.y;
_S2202[int(16)] = _S2186.color_params_1.b_0.x;
_S2202[int(15)] = _S2187;
_S2202[int(14)] = _S2188;
_S2202[int(13)] = _S2189;
_S2202[int(12)] = _S2190;
_S2202[int(11)] = _S2191;
_S2202[int(10)] = _S2192;
_S2202[int(9)] = _S2193;
_S2202[int(8)] = _S2194;
_S2202[int(7)] = _S2195;
_S2202[int(6)] = _S2196;
_S2202[int(5)] = _S2197;
_S2202[int(4)] = _S2198;
_S2202[int(3)] = _S2199;
_S2202[int(2)] = _S2200;
_S2202[int(1)] = _S2201;
_S2202[int(0)] = _S2063.exposure_1;
_S2202[int(1)] = _S2201;
_S2202[int(2)] = _S2200;
_S2202[int(3)] = _S2199;
_S2202[int(4)] = _S2198;
_S2202[int(5)] = _S2197;
_S2202[int(6)] = _S2196;
_S2202[int(7)] = _S2195;
_S2202[int(8)] = _S2194;
_S2202[int(9)] = _S2193;
_S2202[int(10)] = _S2192;
_S2202[int(23)] = _S2186.color_params_1.n_0.y;
_S2202[int(12)] = _S2190;
_S2202[int(13)] = _S2189;
_S2202[int(14)] = _S2188;
_S2202[int(15)] = _S2187;
_S2202[int(16)] = _S2186.color_params_1.b_0.x;
_S2202[int(17)] = _S2186.color_params_1.b_0.y;
_S2202[int(18)] = _S2186.color_params_1.r_0.x;
_S2202[int(19)] = _S2186.color_params_1.r_0.y;
_S2202[int(20)] = _S2186.color_params_1.g_0.x;
_S2202[int(21)] = _S2186.color_params_1.g_0.y;
_S2202[int(22)] = _S2186.color_params_1.n_0.x;
dpparams_6->primal_0 = dpparams_6->primal_0;
dpparams_6->differential_0 = _S2202;
return;
+140 -63
View File
@@ -2744,6 +2744,70 @@ SS_MSG(background_noise_warmup_help,
TR("Arka plan gürültüsünün tam güce ulaşması için gereken adım sayısı. Yalnızca "
"`noise` / `pseudorandom` / `random` arka planlarıyla kullanılır."));
SS_MSG(background_match_luminance,
EN("Match background to image brightness"), JA("背景を画像の明るさに合わせる"),
ZH_HANS("背景亮度匹配图像"), ZH_HANT("背景亮度匹配圖像"),
KO("배경을 이미지 밝기에 맞춤"), DE("Hintergrund an Bildhelligkeit anpassen"),
FR("Adapter le fond à la luminosité de l'image"),
ES("Ajustar el fondo al brillo de la imagen"),
PT("Ajustar o fundo ao brilho da imagem"),
IT("Adatta lo sfondo alla luminosità dell'immagine"),
NL("Achtergrond aan beeldhelderheid aanpassen"),
RU("Подогнать фон под яркость снимка"),
TR("Arka planı görüntü parlaklığına uydur"));
SS_MSG(background_match_luminance_help,
EN("Raises the randomized background to a power so its typical brightness lands "
"on each photo's mean brightness, corrected for that photo's exposure. Keeps "
"dark scenes from being plugged with haze to hide a bright background. Only "
"used with the `noise` / `pseudorandom` / `random` backgrounds."),
JA("ランダム背景をべき乗して、その典型的な明るさが各写真の平均の明るさ(露出補正済み)"
"になるようにします。暗いシーンで明るい背景を隠すためにもやが詰め込まれるのを防ぎ"
"ます。背景が `noise` / `pseudorandom` / `random` のときだけ使われます。"),
ZH_HANS("对随机背景做幂运算,使其典型亮度落在每张照片的平均亮度上(已按该照片的曝光"
"校正)。避免暗场景为了遮住明亮背景而被雾状泼溅填满。仅在背景为 `noise` / "
"`pseudorandom` / `random` 时使用。"),
ZH_HANT("對隨機背景做冪運算,使其典型亮度落在每張照片的平均亮度上(已按該照片的曝光"
"校正)。避免暗場景為了遮住明亮背景而被霧狀潑濺填滿。僅在背景為 `noise` / "
"`pseudorandom` / `random` 時使用。"),
KO("무작위 배경을 거듭제곱하여 그 전형적인 밝기가 각 사진의 평균 밝기(노출 보정 후)에 "
"오도록 합니다. 어두운 장면이 밝은 배경을 가리려고 안개로 채워지는 것을 막습니다. "
"배경이 `noise` / `pseudorandom` / `random`일 때만 쓰입니다."),
DE("Potenziert den zufälligen Hintergrund, sodass seine typische Helligkeit auf "
"der mittleren Helligkeit jedes Fotos landet, um dessen Belichtung korrigiert. "
"Verhindert, dass dunkle Szenen mit Dunst zugesetzt werden, um einen hellen "
"Hintergrund zu verbergen. Wird nur mit den Hintergründen `noise` / "
"`pseudorandom` / `random` verwendet."),
FR("Élève le fond aléatoire à une puissance pour que sa luminosité typique tombe "
"sur la luminosité moyenne de chaque photo, corrigée de son exposition. Évite "
"que les scènes sombres se remplissent de brume pour cacher un fond clair. "
"Utilisé uniquement avec les arrière-plans `noise` / `pseudorandom` / `random`."),
ES("Eleva el fondo aleatorio a una potencia para que su brillo típico coincida con "
"el brillo medio de cada foto, corregido por su exposición. Evita que las "
"escenas oscuras se rellenen de neblina para ocultar un fondo claro. Solo se "
"usa con los fondos `noise` / `pseudorandom` / `random`."),
PT("Eleva o fundo aleatório a uma potência para que o seu brilho típico caia no "
"brilho médio de cada foto, corrigido pela exposição dela. Evita que cenas "
"escuras sejam preenchidas com névoa para esconder um fundo claro. Só é usado "
"com os fundos `noise` / `pseudorandom` / `random`."),
IT("Eleva lo sfondo casuale a una potenza così che la sua luminosità tipica "
"coincida con la luminosità media di ogni foto, corretta per la sua "
"esposizione. Evita che le scene scure si riempiano di foschia per nascondere "
"uno sfondo chiaro. Usato solo con gli sfondi `noise` / `pseudorandom` / "
"`random`."),
NL("Verheft de willekeurige achtergrond tot een macht zodat zijn typische "
"helderheid op de gemiddelde helderheid van elke foto valt, gecorrigeerd voor "
"de belichting ervan. Voorkomt dat donkere scènes met waas worden dichtgezet om "
"een lichte achtergrond te verbergen. Wordt alleen bij de achtergronden "
"`noise` / `pseudorandom` / `random` gebruikt."),
RU("Возводит случайный фон в степень, чтобы его типичная яркость совпала со "
"средней яркостью каждого снимка с поправкой на его экспозицию. Не даёт тёмным "
"сценам забиваться дымкой, скрывающей яркий фон. Используется только с фонами "
"`noise` / `pseudorandom` / `random`."),
TR("Rastgele arka planı bir kuvvete yükselterek tipik parlaklığının her "
"fotoğrafın pozlamasına göre düzeltilmiş ortalama parlaklığına denk gelmesini "
"sağlar. Karanlık sahnelerin parlak bir arka planı gizlemek için pusla "
"dolmasını önler. Yalnızca `noise` / `pseudorandom` / `random` arka "
"planlarıyla kullanılır."));
SS_MSG(background_noise_pre_warmup,
EN("Initial background noise"), JA("最初の背景ノイズの強さ"),
ZH_HANS("初始背景噪声强度"), ZH_HANT("初始背景雜訊強度"),
@@ -5391,73 +5455,86 @@ SS_MSG(loss_saturation_threshold_help,
"ikisi de kırpılmıştır, oradaki hata gerçek değildir. Negatif (varsayılan) "
"her pikseli tutar."));
SS_MSG(normalize_loss_by_luminance,
EN("Scale color error by brightness"), JA("明るさで色の誤差を調整"),
ZH_HANS("按亮度缩放颜色误差"), ZH_HANT("依亮度縮放顏色誤差"),
KO("밝기로 색 오차 조정"),
DE("Farbfehler nach Helligkeit skalieren"),
FR("Mettre l'erreur de couleur à l'échelle de la luminosité"),
ES("Escalar el error de color según el brillo"),
PT("Escalar o erro de cor pelo brilho"),
IT("Scalare l'errore di colore con la luminosità"),
NL("Kleurfout schalen met de helderheid"),
RU("Масштабировать ошибку цвета по яркости"),
TR("Renk hatasını parlaklığa göre ölçekle"));
SS_MSG(normalize_loss_by_luminance_help,
EN("Scales the color-error weights (L1, L2 and SSIM) by twice the mean "
"brightness of the photo, measured in sRGB, so a dark capture counts for "
"as much as a bright one. It is meant for training in linear light, where "
SS_MSG(loss_luminance_normalization,
EN("Brightness normalization of color error"), JA("色の誤差の明るさ正規化"),
ZH_HANS("颜色误差的亮度归一化"), ZH_HANT("顏色誤差的亮度正規化"),
KO("색 오차의 밝기 정규화"),
DE("Helligkeitsnormierung des Farbfehlers"),
FR("Normalisation de l'erreur de couleur par la luminosité"),
ES("Normalización del error de color por el brillo"),
PT("Normalização do erro de cor pelo brilho"),
IT("Normalizzazione dell'errore di colore per luminosità"),
NL("Helderheidsnormalisatie van de kleurfout"),
RU("Нормировка ошибки цвета по яркости"),
TR("Renk hatasının parlaklık normalizasyonu"));
SS_MSG(loss_luminance_normalization_help,
EN("Divides the color-error weights (L1, L2 and SSIM) by twice the photo's "
"mean brightness, measured in sRGB, raised to this power: 0 leaves them "
"alone, 1 makes a dark capture count for as much as a bright one, and "
"values in between soften that. Meant for training in linear light, where "
"the display curve already makes a dark pixel push harder."),
JA("色の誤差の重み(L1, L2, SSIM)を, sRGB で測った写真の平均的な明るさの二"
"倍で調整します。暗い写真も明るい写真と同じだけ効くようになります。線形"
"の光での学習を想定しています。そこでは表示曲線のせいで暗い画素のほうが"
"強く効いてしまいます。"),
ZH_HANS("把颜色误差的权重(L1、L2 和 SSIM)乘以照片在 sRGB 下平均亮度的两"
"倍,让偏暗的照片和明亮的照片起同样的作用。它是为线性光下的训练准备"
"的:在那里显示曲线本来就让暗像素推得更用力。"),
ZH_HANT("把顏色誤差的權重(L1、L2 和 SSIM)乘以照片在 sRGB 下平均亮度的兩"
"倍,讓偏暗的照片和明亮的照片起同樣的作用。它是為線性光下的訓練準備"
"的:在那裡顯示曲線本來就讓暗像素推得更用力。"),
KO("색 오차 가중치(L1, L2, SSIM)에 sRGB로 잰 사진의 평균 밝기의 두 배를 곱"
"합니다. 어두운 사진도 밝은 사진만큼 힘을 냅니다. 선형 광에서 학습할 때"
"를 위한 것으로, 거기서는 표시 곡선 때문에 어두운 픽셀이 더 세게 밀어붙"
"입니다."),
DE("Skaliert die Gewichte des Farbfehlers (L1, L2 und SSIM) mit der "
"doppelten mittleren Helligkeit des Fotos, gemessen in sRGB, sodass eine "
"dunkle Aufnahme so viel zählt wie eine helle. Gedacht für das Training "
"in linearem Licht, wo die Anzeigekurve ein dunkles Pixel ohnehin stärker "
"drücken lässt."),
FR("Met les poids de l'erreur de couleur (L1, L2 et SSIM) à l'échelle du "
"double de la luminosité moyenne de la photo, mesurée en sRGB, pour qu'une "
"prise sombre compte autant qu'une prise claire. Prévu pour "
"l'entraînement en lumière linéaire, où la courbe d'affichage fait déjà "
"pousser un pixel sombre plus fort."),
ES("Escala los pesos del error de color (L1, L2 y SSIM) por el doble del "
"brillo medio de la foto, medido en sRGB, para que una toma oscura cuente "
"tanto como una clara. Está pensado para el entrenamiento en luz lineal, "
"donde la curva de pantalla ya hace que un píxel oscuro empuje más fuerte."),
PT("Escala os pesos do erro de cor (L1, L2 e SSIM) pelo dobro do brilho "
"médio da foto, medido em sRGB, para que uma captura escura conte tanto "
"quanto uma clara. Destina-se ao treino em luz linear, onde a curva de "
"exibição já faz um pixel escuro empurrar com mais força."),
IT("Scala i pesi dell'errore di colore (L1, L2 e SSIM) per il doppio della "
"luminosità media della foto, misurata in sRGB, così uno scatto scuro "
"conta quanto uno chiaro. È pensato per l'addestramento in luce lineare, "
"dove la curva di visualizzazione fa già spingere di più un pixel scuro."),
NL("Schaalt de gewichten van de kleurfout (L1, L2 en SSIM) met tweemaal de "
"gemiddelde helderheid van de foto, gemeten in sRGB, zodat een donkere "
"opname net zo zwaar telt als een lichte. Bedoeld voor training in "
"lineair licht, waar de weergavecurve een donkere pixel toch al harder "
"倍のこの値乗で割ります。0 では何もせず, 1 では暗い写真も明るい写真と同"
"じだけ効き, その間の値ではそれを弱めます。線形の光での学習を想定してい"
"ます。そこでは表示曲線のせいで暗い画素のほうが強く効いてしまいます。"),
ZH_HANS("把颜色误差的权重(L1、L2 和 SSIM)除以照片在 sRGB 下平均亮度两倍的"
"此值次幂:0 不做处理,1 让偏暗的照片和明亮的照片起同样的作用,中间"
"的值则减弱这种效果。它是为线性光下的训练准备的:在那里显示曲线本来"
"就让暗像素推得更用力。"),
ZH_HANT("把顏色誤差的權重(L1、L2 和 SSIM)除以照片在 sRGB 下平均亮度兩倍的"
"此值次冪:0 不做處理,1 讓偏暗的照片和明亮的照片起同樣的作用,中間"
"的值則減弱這種效果。它是為線性光下的訓練準備的:在那裡顯示曲線本來"
"就讓暗像素推得更用力。"),
KO("색 오차 가중치(L1, L2, SSIM)를 sRGB로 잰 사진의 평균 밝기의 두 배를 이 "
"값으로 거듭제곱한 수로 나눕니다. 0은 아무것도 하지 않고, 1은 어두운 사진"
"도 밝은 사진만큼 힘을 내게 하며, 그 사이 값은 이를 완화합니다. 선형 광에"
"서 학습할 때를 위한 것으로, 거기서는 표시 곡선 때문에 어두운 픽셀이 더 "
"세게 밀어붙입니다."),
DE("Teilt die Gewichte des Farbfehlers (L1, L2 und SSIM) durch die doppelte "
"mittlere Helligkeit des Fotos, gemessen in sRGB, hoch diesen Wert: 0 lässt "
"sie unverändert, 1 lässt eine dunkle Aufnahme so viel zählen wie eine "
"helle, Werte dazwischen mildern das. Gedacht für das Training in linearem "
"Licht, wo die Anzeigekurve ein dunkles Pixel ohnehin stärker drücken "
"lässt."),
FR("Divise les poids de l'erreur de couleur (L1, L2 et SSIM) par le double de "
"la luminosité moyenne de la photo, mesurée en sRGB, élevé à cette "
"puissance : 0 ne change rien, 1 fait compter une prise sombre autant "
"qu'une prise claire, et les valeurs intermédiaires adoucissent cela. Prévu "
"pour l'entraînement en lumière linéaire, où la courbe d'affichage fait "
"déjà pousser un pixel sombre plus fort."),
ES("Divide los pesos del error de color (L1, L2 y SSIM) por el doble del "
"brillo medio de la foto, medido en sRGB, elevado a esta potencia: 0 no "
"cambia nada, 1 hace que una toma oscura cuente tanto como una clara, y los "
"valores intermedios lo suavizan. Está pensado para el entrenamiento en luz "
"lineal, donde la curva de pantalla ya hace que un píxel oscuro empuje más "
"fuerte."),
PT("Divide os pesos do erro de cor (L1, L2 e SSIM) pelo dobro do brilho médio "
"da foto, medido em sRGB, elevado a esta potência: 0 não muda nada, 1 faz "
"uma captura escura contar tanto quanto uma clara, e valores intermédios "
"suavizam isso. Destina-se ao treino em luz linear, onde a curva de exibição "
"já faz um pixel escuro empurrar com mais força."),
IT("Divide i pesi dell'errore di colore (L1, L2 e SSIM) per il doppio della "
"luminosità media della foto, misurata in sRGB, elevato a questa potenza: 0 "
"non cambia nulla, 1 fa contare uno scatto scuro quanto uno chiaro, e i "
"valori intermedi lo attenuano. È pensato per l'addestramento in luce "
"lineare, dove la curva di visualizzazione fa già spingere di più un pixel "
"scuro."),
NL("Deelt de gewichten van de kleurfout (L1, L2 en SSIM) door tweemaal de "
"gemiddelde helderheid van de foto, gemeten in sRGB, tot deze macht "
"verheven: 0 verandert niets, 1 laat een donkere opname net zo zwaar tellen "
"als een lichte, en waarden ertussen verzachten dat. Bedoeld voor training "
"in lineair licht, waar de weergavecurve een donkere pixel toch al harder "
"laat duwen."),
RU("Масштабирует веса ошибки цвета (L1, L2 и SSIM) на удвоенную среднюю "
"яркость снимка, измеренную в sRGB, чтобы тёмный кадр значил столько же, "
"сколько светлый. Предназначено для обучения в линейном свете, где кривая "
RU("Делит веса ошибки цвета (L1, L2 и SSIM) на удвоенную среднюю яркость "
"снимка, измеренную в sRGB, в этой степени: 0 ничего не меняет, 1 делает "
"тёмный кадр столь же весомым, как светлый, промежуточные значения "
"смягчают это. Предназначено для обучения в линейном свете, где кривая "
"отображения и так заставляет тёмный пиксель давить сильнее."),
TR("Renk hatası ağırlıklarını, fotoğrafın sRGB'de ölçülen ortalama "
"parlaklığının iki katıyla ölçekler (L1, L2 ve SSIM); böylece karanlık bir "
"çekim aydınlık olan kadar sayılır. Doğrusal ışıkta eğitim için "
"düşünülmüştür; orada görüntüleme eğrisi karanlık pikseli zaten daha sert "
"ittirir."));
TR("Renk hatası ağırlıklarını (L1, L2 ve SSIM), fotoğrafın sRGB'de ölçülen "
"ortalama parlaklığının iki katının bu kuvvetine böler: 0 hiçbir şey "
"değiştirmez, 1 karanlık bir çekimi aydınlık olan kadar saydırır, aradaki "
"değerler bunu yumuşatır. Doğrusal ışıkta eğitim için düşünülmüştür; orada "
"görüntüleme eğrisi karanlık pikseli zaten daha sert ittirir."));
// ===========================================================================
+23 -3
View File
@@ -169,13 +169,21 @@ __device__ __forceinline__ float3 _bg_sample(bool blocky, unsigned block_px,
return u;
}
// Per-image power on the display draw; 1 (identity) without a table.
__device__ __forceinline__ float _bg_exponent(unsigned bid,
const float* exponent_by_cam,
const int32_t* cam_indices) {
return exponent_by_cam ? exponent_by_cam[cam_indices[bid]] : 1.0f;
}
template<int Transfer, bool IsLinear>
__device__ __forceinline__ float3 _bg_color(bool blocky, unsigned block_px,
uint32_t seed, unsigned bid,
unsigned x, unsigned y, unsigned W,
float randomize_weight) {
float randomize_weight, float p) {
float3 background = _bg_sample(blocky, block_px, seed, bid, x, y, W);
background = 0.5 + 0.5*randomize_weight * background;
background = SlangPixelWise::background_apply_exponent(background, p);
return SlangPixelWise::display_to_working3(background, Transfer, IsLinear);
}
@@ -187,6 +195,8 @@ __global__ void blend_background_noise_forward_kernel(
const uint32_t seed,
const bool blocky,
const unsigned block_px,
const float* __restrict__ exponent_by_cam,
const int32_t* __restrict__ cam_indices,
TensorView<float, 4> out_rgb
) {
unsigned gid = blockIdx.x * blockDim.x + threadIdx.x;
@@ -201,7 +211,8 @@ __global__ void blend_background_noise_forward_kernel(
float transmittance = in_transmittance.load1(bid, y, x);
float3 background = _bg_color<Transfer, IsLinear>(
blocky, block_px, seed, bid, x, y, W, randomize_weight);
blocky, block_px, seed, bid, x, y, W, randomize_weight,
_bg_exponent(bid, exponent_by_cam, cam_indices));
rgb = SlangPixelWise::blend_background(rgb, transmittance, background);
@@ -216,6 +227,8 @@ __global__ void blend_background_noise_backward_kernel(
const uint32_t seed,
const bool blocky,
const unsigned block_px,
const float* __restrict__ exponent_by_cam,
const int32_t* __restrict__ cam_indices,
const float overexposure_scale,
const TensorView<float, 4> v_out_rgb,
TensorView<float, 4> v_in_rgb,
@@ -233,7 +246,8 @@ __global__ void blend_background_noise_backward_kernel(
float transmittance = in_transmittance.load1(bid, y, x);
float3 background = _bg_color<Transfer, IsLinear>(
blocky, block_px, seed, bid, x, y, W, randomize_weight);
blocky, block_px, seed, bid, x, y, W, randomize_weight,
_bg_exponent(bid, exponent_by_cam, cam_indices));
float3 v_out = v_out_rgb.load3(bid, y, x);
@@ -258,6 +272,8 @@ void blend_background_noise_forward(
DeviceTensor3D<float> transmittance, // [B, H, W, 1]
float randomize_weight,
uint32_t seed,
const float* exponent_by_cam, // power per camera slot; null = 1
const int32_t* cam_indices, // [B] slot per image
DeviceTensor3D<float3> out_rgb // [B, H, W, 3]
) {
long b = rgb.size<0>(), h = rgb.size<1>(), w = rgb.size<2>();
@@ -266,6 +282,7 @@ void blend_background_noise_forward(
<<<_LAUNCH_ARGS_2D(h*w, b, 256, 1)>>>(
_dt3d_to_tv4<float>(rgb), _dt3d_to_tv4<float>(transmittance),
randomize_weight, seed, blocky, block_px,
exponent_by_cam, cam_indices,
_dt3d_to_tv4<float>(out_rgb)
);
CHECK_DEVICE_ERROR(cudaGetLastError());
@@ -281,6 +298,8 @@ void blend_background_noise_backward(
DeviceTensor3D<float> transmittance, // [B, H, W, 1]
float randomize_weight,
uint32_t seed,
const float* exponent_by_cam, // as in the forward
const int32_t* cam_indices,
float overexposure_weight, // fused image-space reg, 0 = off
DeviceTensor3D<float3> v_out_rgb, // [B, H, W, 3]
DeviceTensor3D<float3> v_rgb, // [B, H, W, 3]
@@ -292,6 +311,7 @@ void blend_background_noise_backward(
<<<_LAUNCH_ARGS_2D(h*w, b, 256, 1)>>>(
_dt3d_to_tv4<float>(rgb), _dt3d_to_tv4<float>(transmittance),
randomize_weight, seed, blocky, block_px,
exponent_by_cam, cam_indices,
_overexposure_scale(b, h, w, overexposure_weight),
_dt3d_to_tv4<float>(v_out_rgb),
_dt3d_to_tv4<float>(v_rgb), _dt3d_to_tv4<float>(v_transmittance)
+4
View File
@@ -211,6 +211,8 @@ void blend_background_noise_forward(
DeviceTensor3D<float> transmittance, // [B, H, W, 1]
float randomize_weight,
uint32_t seed,
const float* exponent_by_cam, // power per camera slot; null = 1
const int32_t* cam_indices, // [B] slot per image
DeviceTensor3D<float3> out_rgb // [B, H, W, 3]
);
@@ -224,6 +226,8 @@ void blend_background_noise_backward(
DeviceTensor3D<float> transmittance, // [B, H, W, 1]
float randomize_weight,
uint32_t seed,
const float* exponent_by_cam, // as in the forward
const int32_t* cam_indices,
float overexposure_weight, // fused image-space reg, 0 = off
DeviceTensor3D<float3> v_out_rgb, // [B, H, W, 3]
DeviceTensor3D<float3> v_rgb, // [B, H, W, 3]
+14 -1
View File
@@ -239,7 +239,10 @@ float tone_encode(float x, no_diff int transfer) {
return linear_rgb_to_srgb(xfer_clamp01(xfer_uncharted2(x)));
if (transfer == kXferSrgbClamped)
return linear_rgb_to_srgb(xfer_clamp01(x));
return linear_rgb_to_srgb(x);
// Clipped at black, open above: a wide-gamut splat outside Rec.709 lands
// below zero here, and the PPISP chromaticity homography downstream has a
// pole there (measured: display values of 1e3..1e7 on an ACEScg run).
return linear_rgb_to_srgb(xfer_max0(x));
}
float tone_decode(float d, int transfer) {
@@ -287,6 +290,16 @@ float3 display_to_working3(float3 rgb, int transfer, bool is_linear) {
linear_rgb_to_srgb(lin.z));
}
// The randomized background's display draw raised to a per-image power (the
// host's _bg_luma_exponent). p == 1 returns the draw untouched, so a run
// without the option is bit-exact.
[CudaDeviceExport]
float3 background_apply_exponent(float3 display, float p) {
if (p == 1.0f) return display;
return float3(pow(max(display.x, 0.0f), p), pow(max(display.y, 0.0f), p),
pow(max(display.z, 0.0f), p));
}
// Unproject an undistorted, normalized image point to a ray direction.
// Handles all camera models (PINHOLE / FISHEYE / EQUISOLID); EQUIRECTANGULAR
// is currently unsupported and falls back to PINHOLE.