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)))
def direction_to_angles(v: ArrayLike) -> np.ndarray: """ Convert a cartesian unit vector to a zenith-azimuth pair. Parameters ---------- v : array-like A sequence of 3-vectors (shape (N, 3)) [unitless]. Returns ------- array-like A sequence of 2-vectors containing zenith and azimuth angles, where zenith = 0 corresponds to +z direction (shape (N, 2)) [rad]. """ 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}") v = v / np.linalg.norm(v, axis=-1).reshape(len(v), 1) theta = np.arccos(v[..., 2]) phi = np.arctan2(v[..., 1], v[..., 0]) return np.vstack((theta, phi)).T
def angles_to_direction(angles: ArrayLike) -> np.ndarray: r""" Convert a zenith and azimuth angle pair to a direction unit vector. Parameters ---------- theta : array-like Zenith angle [radian]. 0 corresponds to zenith, :math:`\pi/2` corresponds to the XY plane, :math:`\pi` corresponds to nadir. Negative values are allowed; :math:`(\theta, \varphi)` then maps to :math:`(| \theta |, \varphi + \pi)`. phi : array-like Azimuth angle [radian]. 0 corresponds to the X axis, :math:`\pi / 2` corresponds to the Y axis (*i.e.* rotation is counter-clockwise). Returns ------- ndarray Direction corresponding to the angular parameters [unitless]. """ angles = np.atleast_1d(angles) if angles.ndim < 2: angles = angles.reshape((angles.size // 2, 2)) if angles.ndim > 2 or angles.shape[1] != 2: raise ValueError(f"array must be of shape (N, 2), got {angles.shape}") negative_zenith = angles[:, 0] < 0 angles[negative_zenith, 0] *= -1 angles[negative_zenith, 1] += np.pi return cos_angle_to_direction(np.cos(angles[:, 0]), angles[:, 1])
def pmf(self, X:npt.ArrayLike) -> np.ndarray: X = np.array(X) try: return np.apply_along_axis(self._prob_of, 1, X) except np.AxisError: # Deal with 0-d arrays X = X.reshape(-1,1) return np.apply_along_axis(self._prob_of, 1, X)
def findZero(matrix: npt.ArrayLike): n, m = matrix.shape flatMatrix = matrix.reshape(n * m) # 1. find the zero zeroPosition = np.where(flatMatrix == 0)[0][0] zeroPosition = [0, zeroPosition] while zeroPosition[1] > n - 1: zeroPosition[1] = zeroPosition[1] - n zeroPosition[0] += 1 return tuple(zeroPosition)
def matrix_to_tex_string(matrix: npt.ArrayLike) -> str: matrix = np.array(matrix).astype("str") if matrix.ndim == 1: matrix = matrix.reshape((matrix.size, 1)) n_rows, n_cols = matrix.shape prefix = "\\left[ \\begin{array}{%s}" % ("c" * n_cols) suffix = "\\end{array} \\right]" rows = [ " & ".join(row) for row in matrix ] return prefix + " \\\\ ".join(rows) + suffix
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
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
def pdf_given_y(self, x:npt.ArrayLike, y:npt.ArrayLike) -> np.ndarray: x = np.array(x) y = np.array(y) if y.ndim == 2 or y.ndim == 0: y = y.reshape(-1) p_x_given_y = np.empty(shape=x.shape[0]) labels = np.unique(y, axis=0) for label in labels: try: kde = self._get_cond_kde(label) p_x_given_y[y == label] = np.exp(kde.score_samples(x[y == label])) except KeyError: # if no kde for the label is present, the entries can remain 0. p_x_given_y[y == label] = 0 return p_x_given_y
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