Esempio n. 1
0
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)))
Esempio n. 2
0
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
Esempio n. 3
0
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])
Esempio n. 4
0
    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)
Esempio n. 5
0
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)
Esempio n. 6
0
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
Esempio n. 7
0
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
Esempio n. 8
0
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
Esempio n. 9
0
    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
Esempio n. 10
0
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