Source code for eradiate.warp

import numpy as np
from numpy.typing import ArrayLike


[docs] def square_to_uniform_disk_concentric(sample: ArrayLike) -> np.ndarray: """ Low-distortion concentric square to disk mapping. Parameters ---------- sample : array-like A (N, 2) array of sample values. Returns ------- ndarray Sampled coordinates on the unit disk as a (N, 2) array. Notes ----- The function tries to be flexible with arrays with (N, 1) and (N,) arrays and attempts reshaping them to (N/2, 2). This, in particular, means that the following call will produce the expected result: .. code:: python square_to_uniform_disk_concentric((0.5, 0.5)) """ # Matches Mitsuba implementation sample = np.atleast_1d(sample) if sample.ndim < 2: sample = sample.reshape((sample.size // 2, 2)) if sample.ndim > 2 or sample.shape[1] != 2: raise ValueError(f"array must be of shape (N, 2), got {sample.shape}") x: ArrayLike = 2.0 * sample[..., 0] - 1.0 y: ArrayLike = 2.0 * sample[..., 1] - 1.0 is_zero = np.logical_and(x == 0.0, y == 0.0) quadrant_1_or_3 = np.abs(x) < np.abs(y) r = np.where(quadrant_1_or_3, y, x) rp = np.where(quadrant_1_or_3, x, y) phi = np.empty_like(r) phi[~is_zero] = 0.25 * np.pi * (rp[~is_zero] / r[~is_zero]) phi[quadrant_1_or_3] = 0.5 * np.pi - phi[quadrant_1_or_3] phi[is_zero] = 0.0 s, c = np.sin(phi), np.cos(phi) return np.vstack((r * c, r * s)).T
[docs] def square_to_uniform_hemisphere(sample: ArrayLike) -> np.ndarray: """ Uniformly sample a vector on the unit hemisphere with respect to solid angles. Parameters ---------- sample : array-like A (N, 2) array of sample values. Returns ------- ndarray Sampled coordinates on the unit hemisphere as a (N, 3) array. Notes ----- The function tries to be flexible with arrays with (N, 1) and (N,) arrays and attempts reshaping them to (N/2, 2). This, in particular, means that the following call will produce the expected result: .. code:: python square_to_uniform_hemisphere((0.5, 0.5)) """ # Matches Mitsuba implementation sample = np.atleast_1d(sample) if sample.ndim < 2: sample = sample.reshape((sample.size // 2, 2)) if sample.ndim > 2 or sample.shape[1] != 2: raise ValueError(f"array must be of shape (N, 2), got {sample.shape}") p = square_to_uniform_disk_concentric(sample) z = 1.0 - np.multiply(p, p).sum(axis=1) p *= np.sqrt(z + 1.0).reshape((len(p), 1)) return np.vstack((p[..., 0], p[..., 1], z)).T
[docs] def uniform_hemisphere_to_square(v: ArrayLike) -> np.ndarray: """ Inverse of the mapping square_to_uniform_hemisphere. Parameters ---------- v : array-like A (N, 3) array of vectors on the unit sphere. Returns ------- ndarray Corresponding coordinates on the [0, 1]² square as a (N, 2) array. Notes ----- The function tries to be flexible with arrays with (N, 1) and (N,) arrays and attempts reshaping them to (N/3, 3). This, in particular, means that the following call will produce the expected result: .. code:: python uniform_hemisphere_to_square((0, 0, 1)) """ # Matches Mitsuba implementation v = np.atleast_1d(v) if v.ndim < 2: v = v.reshape((v.size // 3, 3)) if v.ndim > 2 or v.shape[1] != 3: raise ValueError(f"array must be of shape (N, 3), got {v.shape}") p = v[..., 0:2] return uniform_disk_to_square_concentric( p / np.sqrt(v[..., 2] + 1.0).reshape((len(p), 1)) )
[docs] def uniform_disk_to_square_concentric(p: ArrayLike) -> np.ndarray: """ Inverse of the mapping square_to_uniform_disk_concentric. Parameters ---------- p : array-like A (N, 2) array of vectors on the unit disk. Returns ------- ndarray Corresponding coordinates on the [0, 1]² square as a (N, 2) array. Notes ----- The function tries to be flexible with arrays with (N, 1) and (N,) arrays and attempts reshaping them to (N/2, 2). This, in particular, means that the following call will produce the expected result: .. code:: python uniform_disk_to_square_concentric((0, 0)) """ # Matches Mitsuba implementation p = np.atleast_1d(p) if p.ndim < 2: p = p.reshape((p.size // 2, 2)) if p.ndim > 2 or p.shape[1] != 2: raise ValueError(f"array must be of shape (N, 2), got {p.shape}") quadrant_0_or_2 = np.abs(p[..., 0]) > np.abs(p[..., 1]) r_sign = np.where(quadrant_0_or_2, p[..., 0], p[..., 1]) r = np.copysign(np.linalg.norm(p, axis=-1), r_sign) phi = np.arctan2(p[..., 1] * np.sign(r_sign), p[..., 0] * np.sign(r_sign)) t = 4.0 / np.pi * phi t = np.where(quadrant_0_or_2, t, 2.0 - t) * r a = np.where(quadrant_0_or_2, r, t) b = np.where(quadrant_0_or_2, t, r) return np.vstack(((a + 1.0) * 0.5, (b + 1.0) * 0.5)).T