# -*- coding: utf-8 -*-
# Copyright (c) 2019 Stephen Wasilewski
# =======================================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# =======================================================================
"""functions for translating from mappers to hdr"""
import numpy as np
import clasp.script_tools as cst
from raytraverse import translate, io
from raytraverse.evaluate import MetricSet, retina
from raytraverse.mapper.viewmapper import ViewMapper
[docs]def hdr_uv2ang(imgf, useview=None):
imarray = io.hdr2carray(imgf)
outf = imgf.rsplit(".", 1)[0] + "_ang.hdr"
res = imarray.shape[-1]
vm = ViewMapper(viewangle=180)
pixelxyz = vm.pixelrays(res)
uv = vm.xyz2uv(pixelxyz.reshape(-1, 3))
mask = vm.in_view(pixelxyz, indices=False)
ij = translate.uv2ij(uv[mask], res)
img = np.zeros((3, res*res))
img[:, mask] = imarray[:, ij[:, 1], ij[:, 0]]
io.carray2hdr(img.reshape(3, res, res)[:, ::-1], outf)
[docs]def hdr_ang2uv(imgf, useview=True):
vm = None
if useview:
vm = hdr2vm(imgf)
if vm is None:
vm = ViewMapper(viewangle=180)
imarray = io.hdr2carray(imgf)
outf = imgf.rsplit(".", 1)[0] + "_uv.hdr"
res = imarray.shape[-1]
uv = translate.bin2uv(np.arange(res*res), res)
vm2 = ViewMapper(viewangle=180)
xyz = vm.uv2xyz(uv)
pxy = vm.ray2pixel(xyz, imarray.shape[-1])
# img = np.take_along_axis(imarray, pxy.T, 0)
img = imarray[:, pxy[:, 1], pxy[:, 0]].reshape(3, res, res)
io.carray2hdr(img[:, ::-1], outf)
[docs]def uvarray2hdr(uvarray, imgf, header=None):
res = uvarray.shape[0]
vm = ViewMapper(viewangle=180)
pixelxyz = vm.pixelrays(res)
uv = vm.xyz2uv(pixelxyz.reshape(-1, 3))
mask = vm.in_view(pixelxyz, indices=False)
ij = translate.uv2ij(uv[mask], res)
img = np.zeros(res*res)
img[mask] = uvarray[ij[:, 0], ij[-1:None:-1, 1]]
io.array2hdr(img.reshape(res, res), imgf, header)
[docs]def hdr2uvarray(imgf, vm=None, res=None):
if vm is None:
vm = hdr2vm(imgf)
imarray = io.hdr2array(imgf)
if res is None:
res = imarray.shape[0]
uv = translate.bin2uv(np.arange(res*res), res)
xyz = vm.uv2xyz(uv)
pxy = vm.ray2pixel(xyz, imarray.shape[0])
return imarray[pxy[:, 1], pxy[:, 0]].reshape(res, res)
[docs]def hdr2vol(imgf, vm=None):
ar = io.hdr2array(imgf).T
if vm is None:
vm = hdr2vm(imgf)
vecs = vm.pixelrays(ar.shape[-1]).reshape(-1, 3)
oga = vm.pixel2omega(vm.pixels(ar.shape[-1]), ar.shape[-1]).ravel()
return vecs, oga, ar.ravel()
[docs]def vf_to_vm(view):
"""view file to ViewMapper"""
vl = [i for i in open(view).readlines() if "-vta" in i]
if len(vl) == 0:
raise ValueError(f"no valid -vta view in file {view}")
vp = vl[-1].split()
view_angle = float(vp[vp.index("-vh") + 1])
vd = vp.index("-vd")
view_dir = [float(vp[i]) for i in range(vd + 1, vd + 4)]
return ViewMapper(view_dir, view_angle)
[docs]def hdr2vm(imgf, vpt=False):
"""hdr to ViewMapper"""
header, err = cst.pipeline([f"getinfo {imgf}"], caperr=True)
try:
err = err.decode("utf-8")
except AttributeError:
pass
if "bad header!" in err:
raise IOError(f"{err} - wrong file type?")
elif "cannot open" in header:
raise FileNotFoundError(f"{imgf} not found")
vp = None
if "VIEW= -vta" in header:
vp = header.rsplit("VIEW= -vta", 1)[-1].splitlines()[0].split()
elif "rvu -vta" in header:
vp = header.rsplit("rvu -vta", 1)[-1].splitlines()[0].split()
if vp is not None:
view_angle = float(vp[vp.index("-vh") + 1])
try:
vd = vp.index("-vd")
except ValueError:
view_dir = [0.0, 1.0, 0.0]
else:
view_dir = [float(vp[i]) for i in range(vd + 1, vd + 4)]
try:
vpi = vp.index("-vp")
except ValueError:
view_pt = [0.0, 0.0, 0.0]
else:
view_pt = [float(vp[i]) for i in range(vpi + 1, vpi + 4)]
hd = cst.pipeline([f"getinfo -d {imgf}"]).strip().split()
x = 1
y = 1
for i in range(2, len(hd)):
if 'X' in hd[i - 1]:
x = float(hd[i])
elif 'Y' in hd[i - 1]:
y = float(hd[i])
vm = ViewMapper(view_dir, view_angle * x / y)
else:
view_pt = None
vm = None
if vpt:
return vm, view_pt
else:
return vm
[docs]def normalize_peak(v, o, l, scale=179, peaka=6.7967e-05, peakt=1e5, peakr=4,
blursun=False):
"""consolidate the brightest pixels represented by v, o, l up into a single
source, correcting the area while maintaining equal energy
Parameters
----------
v: np.array
shape (N, 3), direction vectors of pixels (x, y, z) normalized
o: np.array
shape (N,), solid angles of pixels (steradians)
l: np.array
shape (N,), luminance of pixels
scale: Union[float, int], optional
scale factor for l to convert to cd/m^2, default assumes radiance units
peaka: float, optional
area to aggregate to
peakt: Union[float, int], optional
lower threshold for possible bright pixels
peakr: Union[float, int], optional
ratio, from peak pixel value to lowest value to include when aggregating
partially visible sun.
blursun: bool, optional
whether to correct area and luminance according to human PSF
Returns
-------
v: np.array
shape (N, 3), direction vectors of pixels (x, y, z) normalized
o: np.array
shape (N,), solid angles of pixels (steradians)
l: np.array
shape (N,), luminance of pixels
"""
pc = np.nonzero(l > peakt / scale)[0]
# only do something if some pixels above peakt
if pc.size > 0:
# first sort descending by luminance
pc = pc[np.argsort(-l[pc])]
pvol = np.hstack((v[pc], o[pc, None], l[pc, None]))
# establish maximum radius for grouping
cosrad = np.cos((peaka/np.pi)**.5*4)
# calculate angular distance from peak ray and filter strays
pd = np.einsum("i,ji->j", pvol[0, 0:3], pvol[:, 0:3])
dm = pd > cosrad
pc = pc[dm]
pvol = pvol[dm]
# calculate expected energy assuming full visibility:
esun = pvol[0, 4]*peaka
# sum up to peak energy
cume = np.cumsum(pvol[:, 3]*pvol[:, 4])
# when there is enough energy, treat as full sun
if cume[-1] > esun:
stop = np.argmax(cume > esun)
if stop == 0:
stop = len(cume)
peakl = cume[stop - 1]/peaka
# otherwise treat as partial sun (needs to use peak ratio)
else:
stop = np.argmax(pvol[:, 4] < pvol[0, 4]/peakr)
if stop == 0:
stop = len(cume)
peakl = pvol[0, 4]
peaka = cume[stop - 1]/peakl
pc = pc[:stop]
pvol = pvol[:stop]
# new source vector weight by L*omega of source rarys
pv = translate.norm(np.average(pvol[:, 0:3], axis=0,
weights=pvol[:, 3]*pvol[:, 4]))
# filter out source rays
vol = np.delete(np.hstack((v, o[:, None], l[:, None])), pc, axis=0)
# then add new consolidated ray back to output v, o, l
v = np.vstack((vol[:, 0:3], pv))
if blursun:
cf = np.atleast_1d(retina.blur_sun(peaka, peakl))[0]
else:
cf = 1
o = np.concatenate((vol[:, 3], [peaka*cf]))
l = np.concatenate((vol[:, 4], [peakl/cf]))
return v, o, l
[docs]def imgmetric(imgf, metrics, peakn=False, scale=179, threshold=2000., lowlight=False,
**peakwargs):
vm = hdr2vm(imgf)
if vm is None:
vm = ViewMapper(viewangle=180)
v, o, l = hdr2vol(imgf, vm)
if peakn:
v, o, l = normalize_peak(v, o, l, scale, **peakwargs)
return MetricSet(v, o, l, vm, metrics, scale=scale, threshold=threshold, lowlight=lowlight)()
# from raytraverse.lightpoint import LightPointKD
# from raytraverse.sampler import draw
# from raytraverse.sampler.basesampler import filterdict
# def img2lf(imga, imgb, src, scn):
#
# accuracy = 0.5
# t0 = 0
# t1 = .6667
# levels = 6
#
# def _threshold(idx, acc):
# """threshold for determining sample count"""
# return acc * _linear(idx, t0, t1)
#
# def _linear(x, x1, x2):
# if levels <= 2:
# return (x1, x2)[x]
# else:
# return (x2 - x1)/(levels - 1) * x + x1
#
# vm, vp = hdr2vm(imga, vpt=True)
# uva = hdr2uvarray(imga, vm, 1024)
# if imgb is not None:
# vmb = ViewMapper(vm.dxyz*np.array((-1, -1, -1)), vm.viewangle)
# uvb = hdr2uvarray(imgb, vmb, 1024)
# uva = np.stack((uvb, uva), 0).reshape(-1, uvb.shape[1])
# vm = ViewMapper(vm.dxyz)
# accuracy *= np.average(uva)
# uvt = translate.resample(uva, uva.shape, radius=2)
# ar = int(uva.shape[0]/uva.shape[1])
# available = np.full(uvt.shape, True)
# rays = None
# vals = None
# for i in range(1, levels+1):
# res = 2**(levels-i)*1024/2**levels
# uvt = translate.resample(uvt, (res*ar, res))
# available = translate.resample(available.astype(float), uvt.shape) > 0.0
# p = draw.get_detail(uvt, *filterdict['wav']).reshape(uvt.shape)
# t = _threshold(levels-i, accuracy)
# p[np.logical_not(available)] = 0
# mi = p > t
# available[mi] = False
# miu = translate.resample(mi, (res*2*ar, res*2), False)
# uv = vm.idx2uv(np.arange(miu.size)[miu.ravel()], miu.shape, False)
# uv[:, 0] *= ar
# ray = vm.uv2xyz(uv)
# if rays is None:
# rays = ray
# vals = uva[miu]
# else:
# rays = np.concatenate((rays, ray))
# vals = np.concatenate((vals, uva[miu]))
# uva = translate.resample(uva, (res*ar, res))
# lp = LightPointKD(scn, rays, vals, vm, vp, src=src)
# lp.direct_view(512)