Files
kijai-ComfyUI-DiffusionLight/nodes.py
T
2024-04-02 19:38:51 +03:00

154 lines
5.3 KiB
Python

import torch
import numpy as np
import torch
import torch.nn.functional as F
from .relighting.tonemapper import TonemapHDR
def create_envmap_grid(size: int):
"""
BLENDER CONVENSION
Create the grid of environment map that contain the position in sperical coordinate
Top left is (0,0) and bottom right is (pi/2, 2pi)
"""
theta = torch.linspace(0, np.pi * 2, size * 2)
phi = torch.linspace(0, np.pi, size)
#use indexing 'xy' torch match vision's homework 3
theta, phi = torch.meshgrid(theta, phi ,indexing='xy')
theta_phi = torch.cat([theta[..., None], phi[..., None]], dim=-1)
theta_phi = theta_phi.numpy()
return theta_phi
def get_normal_vector(incoming_vector: np.ndarray, reflect_vector: np.ndarray):
"""
BLENDER CONVENSION
incoming_vector: the vector from the point to the camera
reflect_vector: the vector from the point to the light source
"""
#N = 2(R ⋅ I)R - I
N = (incoming_vector + reflect_vector) / np.linalg.norm(incoming_vector + reflect_vector, axis=-1, keepdims=True)
return N
def get_cartesian_from_spherical(theta: np.array, phi: np.array, r = 1.0):
"""
BLENDER CONVENSION
theta: vertical angle
phi: horizontal angle
r: radius
"""
x = r * np.sin(theta) * np.cos(phi)
y = r * np.sin(theta) * np.sin(phi)
z = r * np.cos(theta)
return np.concatenate([x[...,None],y[...,None],z[...,None]], axis=-1)
class chrome_ball_to_envmap:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"ball_images": ("IMAGE", ),
"envmap_height": ("INT", {"default": 256, "min": 1, "max": 2048, "step": 1}, ),
"scale": ("INT", {"default": 4, "min": 1, "max": 30, "step": 1}, ),
},
}
CATEGORY = "DiffusionLight"
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image", )
FUNCTION = "process"
def process(self, ball_images, envmap_height, scale):
I = np.array([1, 0, 0])
# compute normal map that create from reflect vector
env_grid = create_envmap_grid(envmap_height * scale)
reflect_vec = get_cartesian_from_spherical(env_grid[...,1], env_grid[...,0])
normal = get_normal_vector(I[None,None], reflect_vec)
# turn from normal map to position to lookup [Range: 0,1]
pos = (normal + 1.0) / 2
pos = 1.0 - pos
pos = pos[...,1:]
env_map = None
# convert position to pytorch grid look up
grid = torch.from_numpy(pos)[None].float()
grid = grid * 2 - 1 # convert to range [-1,1]
print(grid.shape)
ball_images = ball_images.permute(0,3,1,2) # [1,3,H,W]
env_maps_list = []
for ball in ball_images:
env_map = F.grid_sample(ball.unsqueeze(0), grid, mode='bilinear', padding_mode='border', align_corners=True)
env_map_default = F.interpolate(env_map, size=(envmap_height, envmap_height*2), mode='bilinear', align_corners=True)
env_map_default = env_map_default.permute(0,2,3,1).cpu().to(torch.float32)
env_maps_list.append(env_map_default)
env_maps_out = torch.cat(env_maps_list, dim=0)
return env_maps_out,
class exposure_to_hdr:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"images": ("IMAGE", ),
#"EV": ("FLOAT", {"default": 0, "min": 1, "max": 30, "step": 1}, ),
"gamma": ("FLOAT", {"default": 2.4, "min": 1, "max": 30, "step": 0.01}, ),
},
}
CATEGORY = "DiffusionLight"
RETURN_TYPES = ("IMAGE", "IMAGE",)
RETURN_NAMES = ("hrd_image", "ldr_image", )
FUNCTION = "exposuretohdr"
def exposuretohdr(self, images, gamma):
first_image = torch.pow(images[0], gamma)
evs = [0.0, -2.5, -5.0]
hdr2ldr = TonemapHDR(gamma=gamma, percentile=99, max_mapping=0.9)
scaler = torch.tensor([0.212671, 0.715160, 0.072169])
# read luminace for every image
luminances = []
for i in range(len(evs)):
linear_img = torch.pow(images[i], gamma)
linear_img = linear_img * 1 / (2** evs[i])
# compute luminace
lumi = linear_img @ scaler
luminances.append(lumi)
# start from darkest image
out_luminace = luminances[len(evs) - 1]
for i in range(len(evs) - 1, 0, -1):
# compute mask
maxval = 1 / (2 ** evs[i-1])
p1 = torch.clip((luminances[i-1] - 0.9 * maxval) / (0.1 * maxval), 0, 1)
p2 = out_luminace > luminances[i-1]
mask = (p1 * p2)
out_luminace = luminances[i-1] * (1-mask) + out_luminace * mask
hdr_rgb = first_image * (out_luminace / (luminances[0] + 1e-10)).unsqueeze(-1)
ldr_rgb, _, _ = hdr2ldr(hdr_rgb)
hrd_rgb = hdr_rgb.unsqueeze(0).cpu().to(torch.float32)
ldr_rgb = ldr_rgb.unsqueeze(0).cpu().to(torch.float32)
return (hrd_rgb, ldr_rgb,)
NODE_CLASS_MAPPINGS = {
"chrome_ball_to_envmap": chrome_ball_to_envmap,
"exposure_to_hdr": exposure_to_hdr,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"chrome_ball_to_envmap": "Chrome Ball to Envmap",
"exposure_to_hdr": "Exposure to HDR",
}