| import enum |
| from internal import configs |
| from internal import stepfun |
| from internal import utils |
| import numpy as np |
| import scipy |
|
|
|
|
| def convert_to_ndc(origins, |
| directions, |
| pixtocam, |
| near: float = 1.): |
| """Converts a set of rays to normalized device coordinates (NDC). |
| |
| Args: |
| origins: ndarray(float32), [..., 3], world space ray origins. |
| directions: ndarray(float32), [..., 3], world space ray directions. |
| pixtocam: ndarray(float32), [3, 3], inverse intrinsic matrix. |
| near: float, near plane along the negative z axis. |
| |
| Returns: |
| origins_ndc: ndarray(float32), [..., 3]. |
| directions_ndc: ndarray(float32), [..., 3]. |
| |
| This function assumes input rays should be mapped into the NDC space for a |
| perspective projection pinhole camera, with identity extrinsic matrix (pose) |
| and intrinsic parameters defined by inputs focal, width, and height. |
| |
| The near value specifies the near plane of the frustum, and the far plane is |
| assumed to be infinity. |
| |
| The ray bundle for the identity pose camera will be remapped to parallel rays |
| within the (-1, -1, -1) to (1, 1, 1) cube. Any other ray in the original |
| world space can be remapped as long as it has dz < 0 (ray direction has a |
| negative z-coord); this allows us to share a common NDC space for "forward |
| facing" scenes. |
| |
| Note that |
| projection(origins + t * directions) |
| will NOT be equal to |
| origins_ndc + t * directions_ndc |
| and that the directions_ndc are not unit length. Rather, directions_ndc is |
| defined such that the valid near and far planes in NDC will be 0 and 1. |
| |
| See Appendix C in https://arxiv.org/abs/2003.08934 for additional details. |
| """ |
|
|
| |
| |
| t = -(near + origins[..., 2]) / directions[..., 2] |
| origins = origins + t[..., None] * directions |
|
|
| dx, dy, dz = np.moveaxis(directions, -1, 0) |
| ox, oy, oz = np.moveaxis(origins, -1, 0) |
|
|
| xmult = 1. / pixtocam[0, 2] |
| ymult = 1. / pixtocam[1, 2] |
|
|
| |
| |
| origins_ndc = np.stack([xmult * ox / oz, ymult * oy / oz, |
| -np.ones_like(oz)], axis=-1) |
|
|
| |
| |
| infinity_ndc = np.stack([xmult * dx / dz, ymult * dy / dz, |
| np.ones_like(oz)], |
| axis=-1) |
|
|
| |
| directions_ndc = infinity_ndc - origins_ndc |
|
|
| return origins_ndc, directions_ndc |
|
|
|
|
| def pad_poses(p): |
| """Pad [..., 3, 4] pose matrices with a homogeneous bottom row [0,0,0,1].""" |
| bottom = np.broadcast_to([0, 0, 0, 1.], p[..., :1, :4].shape) |
| return np.concatenate([p[..., :3, :4], bottom], axis=-2) |
|
|
|
|
| def unpad_poses(p): |
| """Remove the homogeneous bottom row from [..., 4, 4] pose matrices.""" |
| return p[..., :3, :4] |
|
|
|
|
| def recenter_poses(poses): |
| """Recenter poses around the origin.""" |
| cam2world = average_pose(poses) |
| transform = np.linalg.inv(pad_poses(cam2world)) |
| poses = transform @ pad_poses(poses) |
| return unpad_poses(poses), transform |
|
|
|
|
| def average_pose(poses): |
| """New pose using average position, z-axis, and up vector of input poses.""" |
| position = poses[:, :3, 3].mean(0) |
| z_axis = poses[:, :3, 2].mean(0) |
| up = poses[:, :3, 1].mean(0) |
| cam2world = viewmatrix(z_axis, up, position) |
| return cam2world |
|
|
|
|
| def viewmatrix(lookdir, up, position): |
| """Construct lookat view matrix.""" |
| vec2 = normalize(lookdir) |
| vec0 = normalize(np.cross(up, vec2)) |
| vec1 = normalize(np.cross(vec2, vec0)) |
| m = np.stack([vec0, vec1, vec2, position], axis=1) |
| return m |
|
|
|
|
| def normalize(x): |
| """Normalization helper function.""" |
| return x / np.linalg.norm(x) |
|
|
|
|
| def focus_point_fn(poses): |
| """Calculate nearest point to all focal axes in poses.""" |
| directions, origins = poses[:, :3, 2:3], poses[:, :3, 3:4] |
| m = np.eye(3) - directions * np.transpose(directions, [0, 2, 1]) |
| mt_m = np.transpose(m, [0, 2, 1]) @ m |
| focus_pt = np.linalg.inv(mt_m.mean(0)) @ (mt_m @ origins).mean(0)[:, 0] |
| return focus_pt |
|
|
|
|
| |
| NEAR_STRETCH = .9 |
| FAR_STRETCH = 5. |
| FOCUS_DISTANCE = .75 |
|
|
|
|
| def generate_spiral_path(poses, bounds, n_frames=120, n_rots=2, zrate=.5): |
| """Calculates a forward facing spiral path for rendering.""" |
| |
| |
| near_bound = bounds.min() * NEAR_STRETCH |
| far_bound = bounds.max() * FAR_STRETCH |
| |
| focal = 1 / (((1 - FOCUS_DISTANCE) / near_bound + FOCUS_DISTANCE / far_bound)) |
|
|
| |
| positions = poses[:, :3, 3] |
| radii = np.percentile(np.abs(positions), 90, 0) |
| radii = np.concatenate([radii, [1.]]) |
|
|
| |
| render_poses = [] |
| cam2world = average_pose(poses) |
| up = poses[:, :3, 1].mean(0) |
| for theta in np.linspace(0., 2. * np.pi * n_rots, n_frames, endpoint=False): |
| t = radii * [np.cos(theta), -np.sin(theta), -np.sin(theta * zrate), 1.] |
| position = cam2world @ t |
| lookat = cam2world @ [0, 0, -focal, 1.] |
| z_axis = position - lookat |
| render_poses.append(viewmatrix(z_axis, up, position)) |
| render_poses = np.stack(render_poses, axis=0) |
| return render_poses |
|
|
|
|
| def transform_poses_pca(poses): |
| """Transforms poses so principal components lie on XYZ axes. |
| |
| Args: |
| poses: a (N, 3, 4) array containing the cameras' camera to world transforms. |
| |
| Returns: |
| A tuple (poses, transform), with the transformed poses and the applied |
| camera_to_world transforms. |
| """ |
| t = poses[:, :3, 3] |
| t_mean = t.mean(axis=0) |
| t = t - t_mean |
|
|
| eigval, eigvec = np.linalg.eig(t.T @ t) |
| |
| inds = np.argsort(eigval)[::-1] |
| eigvec = eigvec[:, inds] |
| rot = eigvec.T |
| if np.linalg.det(rot) < 0: |
| rot = np.diag(np.array([1, 1, -1])) @ rot |
|
|
| transform = np.concatenate([rot, rot @ -t_mean[:, None]], -1) |
| poses_recentered = unpad_poses(transform @ pad_poses(poses)) |
| transform = np.concatenate([transform, np.eye(4)[3:]], axis=0) |
|
|
| |
| if poses_recentered.mean(axis=0)[2, 1] < 0: |
| poses_recentered = np.diag(np.array([1, -1, -1])) @ poses_recentered |
| transform = np.diag(np.array([1, -1, -1, 1])) @ transform |
|
|
| |
| scale_factor = 1. / np.max(np.abs(poses_recentered[:, :3, 3])) |
| poses_recentered[:, :3, 3] *= scale_factor |
| transform = np.diag(np.array([scale_factor] * 3 + [1])) @ transform |
|
|
| return poses_recentered, transform |
|
|
|
|
| def generate_ellipse_path(poses, n_frames=120, const_speed=True, z_variation=0., z_phase=0.): |
| """Generate an elliptical render path based on the given poses.""" |
| |
| center = focus_point_fn(poses) |
| |
| offset = np.array([center[0], center[1], 0]) |
|
|
| |
| sc = np.percentile(np.abs(poses[:, :3, 3] - offset), 90, axis=0) |
| |
| low = -sc + offset |
| high = sc + offset |
| |
| z_low = np.percentile((poses[:, :3, 3]), 10, axis=0) |
| z_high = np.percentile((poses[:, :3, 3]), 90, axis=0) |
|
|
| def get_positions(theta): |
| |
| |
| return np.stack([ |
| low[0] + (high - low)[0] * (np.cos(theta) * .5 + .5), |
| low[1] + (high - low)[1] * (np.sin(theta) * .5 + .5), |
| z_variation * (z_low[2] + (z_high - z_low)[2] * |
| (np.cos(theta + 2 * np.pi * z_phase) * .5 + .5)), |
| ], -1) |
|
|
| theta = np.linspace(0, 2. * np.pi, n_frames + 1, endpoint=True) |
| positions = get_positions(theta) |
|
|
| if const_speed: |
| |
| lengths = np.linalg.norm(positions[1:] - positions[:-1], axis=-1) |
| theta = stepfun.sample_np(None, theta, np.log(lengths), n_frames + 1) |
| positions = get_positions(theta) |
|
|
| |
| positions = positions[:-1] |
|
|
| |
| avg_up = poses[:, :3, 1].mean(0) |
| avg_up = avg_up / np.linalg.norm(avg_up) |
| ind_up = np.argmax(np.abs(avg_up)) |
| up = np.eye(3)[ind_up] * np.sign(avg_up[ind_up]) |
|
|
| return np.stack([viewmatrix(p - center, up, p) for p in positions]) |
|
|
|
|
| def generate_interpolated_path(poses, n_interp, spline_degree=5, |
| smoothness=.03, rot_weight=.1): |
| """Creates a smooth spline path between input keyframe camera poses. |
| |
| Spline is calculated with poses in format (position, lookat-point, up-point). |
| |
| Args: |
| poses: (n, 3, 4) array of input pose keyframes. |
| n_interp: returned path will have n_interp * (n - 1) total poses. |
| spline_degree: polynomial degree of B-spline. |
| smoothness: parameter for spline smoothing, 0 forces exact interpolation. |
| rot_weight: relative weighting of rotation/translation in spline solve. |
| |
| Returns: |
| Array of new camera poses with shape (n_interp * (n - 1), 3, 4). |
| """ |
|
|
| def poses_to_points(poses, dist): |
| """Converts from pose matrices to (position, lookat, up) format.""" |
| pos = poses[:, :3, -1] |
| lookat = poses[:, :3, -1] - dist * poses[:, :3, 2] |
| up = poses[:, :3, -1] + dist * poses[:, :3, 1] |
| return np.stack([pos, lookat, up], 1) |
|
|
| def points_to_poses(points): |
| """Converts from (position, lookat, up) format to pose matrices.""" |
| return np.array([viewmatrix(p - l, u - p, p) for p, l, u in points]) |
|
|
| def interp(points, n, k, s): |
| """Runs multidimensional B-spline interpolation on the input points.""" |
| sh = points.shape |
| pts = np.reshape(points, (sh[0], -1)) |
| k = min(k, sh[0] - 1) |
| tck, _ = scipy.interpolate.splprep(pts.T, k=k, s=s) |
| u = np.linspace(0, 1, n, endpoint=False) |
| new_points = np.array(scipy.interpolate.splev(u, tck)) |
| new_points = np.reshape(new_points.T, (n, sh[1], sh[2])) |
| return new_points |
|
|
| points = poses_to_points(poses, dist=rot_weight) |
| new_points = interp(points, |
| n_interp * (points.shape[0] - 1), |
| k=spline_degree, |
| s=smoothness) |
| return points_to_poses(new_points) |
|
|
|
|
| def interpolate_1d(x, n_interp, spline_degree, smoothness): |
| """Interpolate 1d signal x (by a factor of n_interp times).""" |
| t = np.linspace(0, 1, len(x), endpoint=True) |
| tck = scipy.interpolate.splrep(t, x, s=smoothness, k=spline_degree) |
| n = n_interp * (len(x) - 1) |
| u = np.linspace(0, 1, n, endpoint=False) |
| return scipy.interpolate.splev(u, tck) |
|
|
|
|
| def create_render_spline_path(config, image_names, poses, exposures): |
| """Creates spline interpolation render path from subset of dataset poses. |
| |
| Args: |
| config: configs.Config object. |
| image_names: either a directory of images or a text file of image names. |
| poses: [N, 3, 4] array of extrinsic camera pose matrices. |
| exposures: optional list of floating point exposure values. |
| |
| Returns: |
| spline_indices: list of indices used to select spline keyframe poses. |
| render_poses: array of interpolated extrinsic camera poses for the path. |
| render_exposures: optional list of interpolated exposures for the path. |
| """ |
| if utils.isdir(config.render_spline_keyframes): |
| |
| keyframe_names = sorted(utils.listdir(config.render_spline_keyframes)) |
| else: |
| |
| with utils.open_file(config.render_spline_keyframes, 'r') as fp: |
| |
| keyframe_names = fp.read().decode('utf-8').splitlines() |
| |
| spline_indices = np.array( |
| [i for i, n in enumerate(image_names) if n in keyframe_names]) |
| keyframes = poses[spline_indices] |
| render_poses = generate_interpolated_path( |
| keyframes, |
| n_interp=config.render_spline_n_interp, |
| spline_degree=config.render_spline_degree, |
| smoothness=config.render_spline_smoothness, |
| rot_weight=.1) |
| if config.render_spline_interpolate_exposure: |
| if exposures is None: |
| raise ValueError('config.render_spline_interpolate_exposure is True but ' |
| 'create_render_spline_path() was passed exposures=None.') |
| |
| log_exposure = np.log(exposures[spline_indices]) |
| |
| log_exposure_interp = interpolate_1d( |
| log_exposure, |
| config.render_spline_n_interp, |
| spline_degree=5, |
| smoothness=20) |
| render_exposures = np.exp(log_exposure_interp) |
| else: |
| render_exposures = None |
| return spline_indices, render_poses, render_exposures |
|
|
|
|
| def intrinsic_matrix(fx, fy, cx, cy): |
| """Intrinsic matrix for a pinhole camera in OpenCV coordinate system.""" |
| return np.array([ |
| [fx, 0, cx], |
| [0, fy, cy], |
| [0, 0, 1.], |
| ]) |
|
|
|
|
| def get_pixtocam(focal, width, height): |
| """Inverse intrinsic matrix for a perfect pinhole camera.""" |
| camtopix = intrinsic_matrix(focal, focal, width * .5, height * .5) |
| return np.linalg.inv(camtopix) |
|
|
|
|
| def pixel_coordinates(width, height): |
| """Tuple of the x and y integer coordinates for a grid of pixels.""" |
| return np.meshgrid(np.arange(width), np.arange(height), indexing='xy') |
|
|
|
|
| def _compute_residual_and_jacobian(x, y, xd, yd, |
| k1=0.0, k2=0.0, k3=0.0, |
| k4=0.0, p1=0.0, p2=0.0, ): |
| """Auxiliary function of radial_and_tangential_undistort().""" |
| |
| |
| |
| |
| r = x * x + y * y |
| d = 1.0 + r * (k1 + r * (k2 + r * (k3 + r * k4))) |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| fx = d * x + 2 * p1 * x * y + p2 * (r + 2 * x * x) - xd |
| fy = d * y + 2 * p2 * x * y + p1 * (r + 2 * y * y) - yd |
|
|
| |
| d_r = (k1 + r * (2.0 * k2 + r * (3.0 * k3 + r * 4.0 * k4))) |
| d_x = 2.0 * x * d_r |
| d_y = 2.0 * y * d_r |
|
|
| |
| fx_x = d + d_x * x + 2.0 * p1 * y + 6.0 * p2 * x |
| fx_y = d_y * x + 2.0 * p1 * x + 2.0 * p2 * y |
|
|
| |
| fy_x = d_x * y + 2.0 * p2 * y + 2.0 * p1 * x |
| fy_y = d + d_y * y + 2.0 * p2 * x + 6.0 * p1 * y |
|
|
| return fx, fy, fx_x, fx_y, fy_x, fy_y |
|
|
|
|
| def _radial_and_tangential_undistort(xd, yd, k1=0, k2=0, |
| k3=0, k4=0, p1=0, |
| p2=0, eps=1e-9, max_iterations=10): |
| """Computes undistorted (x, y) from (xd, yd).""" |
| |
| |
| x = np.copy(xd) |
| y = np.copy(yd) |
|
|
| for _ in range(max_iterations): |
| fx, fy, fx_x, fx_y, fy_x, fy_y = _compute_residual_and_jacobian( |
| x=x, y=y, xd=xd, yd=yd, k1=k1, k2=k2, k3=k3, k4=k4, p1=p1, p2=p2) |
| denominator = fy_x * fx_y - fx_x * fy_y |
| x_numerator = fx * fy_y - fy * fx_y |
| y_numerator = fy * fx_x - fx * fy_x |
| step_x = np.where( |
| np.abs(denominator) > eps, x_numerator / denominator, |
| np.zeros_like(denominator)) |
| step_y = np.where( |
| np.abs(denominator) > eps, y_numerator / denominator, |
| np.zeros_like(denominator)) |
|
|
| x = x + step_x |
| y = y + step_y |
|
|
| return x, y |
|
|
|
|
| class ProjectionType(enum.Enum): |
| """Camera projection type (standard perspective pinhole or fisheye model).""" |
| PERSPECTIVE = 'perspective' |
| FISHEYE = 'fisheye' |
|
|
|
|
| def pixels_to_rays(pix_x_int, pix_y_int, pixtocams, |
| camtoworlds, |
| distortion_params=None, |
| pixtocam_ndc=None, |
| camtype=ProjectionType.PERSPECTIVE): |
| """Calculates rays given pixel coordinates, intrinisics, and extrinsics. |
| |
| Given 2D pixel coordinates pix_x_int, pix_y_int for cameras with |
| inverse intrinsics pixtocams and extrinsics camtoworlds (and optional |
| distortion coefficients distortion_params and NDC space projection matrix |
| pixtocam_ndc), computes the corresponding 3D camera rays. |
| |
| Vectorized over the leading dimensions of the first four arguments. |
| |
| Args: |
| pix_x_int: int array, shape SH, x coordinates of image pixels. |
| pix_y_int: int array, shape SH, y coordinates of image pixels. |
| pixtocams: float array, broadcastable to SH + [3, 3], inverse intrinsics. |
| camtoworlds: float array, broadcastable to SH + [3, 4], camera extrinsics. |
| distortion_params: dict of floats, optional camera distortion parameters. |
| pixtocam_ndc: float array, [3, 3], optional inverse intrinsics for NDC. |
| camtype: camera_utils.ProjectionType, fisheye or perspective camera. |
| |
| Returns: |
| origins: float array, shape SH + [3], ray origin points. |
| directions: float array, shape SH + [3], ray direction vectors. |
| viewdirs: float array, shape SH + [3], normalized ray direction vectors. |
| radii: float array, shape SH + [1], ray differential radii. |
| imageplane: float array, shape SH + [2], xy coordinates on the image plane. |
| If the image plane is at world space distance 1 from the pinhole, then |
| imageplane will be the xy coordinates of a pixel in that space (so the |
| camera ray direction at the origin would be (x, y, -1) in OpenGL coords). |
| """ |
|
|
| |
| def pix_to_dir(x, y): |
| return np.stack([x + .5, y + .5, np.ones_like(x)], axis=-1) |
|
|
| |
| pixel_dirs_stacked = np.stack([ |
| pix_to_dir(pix_x_int, pix_y_int), |
| pix_to_dir(pix_x_int + 1, pix_y_int), |
| pix_to_dir(pix_x_int, pix_y_int + 1) |
| ], axis=0) |
|
|
| matmul = np.matmul |
| mat_vec_mul = lambda A, b: matmul(A, b[..., None])[..., 0] |
|
|
| |
| camera_dirs_stacked = mat_vec_mul(pixtocams, pixel_dirs_stacked) |
|
|
| if distortion_params is not None: |
| |
| x, y = _radial_and_tangential_undistort( |
| camera_dirs_stacked[..., 0], |
| camera_dirs_stacked[..., 1], |
| **distortion_params) |
| camera_dirs_stacked = np.stack([x, y, np.ones_like(x)], -1) |
|
|
| if camtype == ProjectionType.FISHEYE: |
| theta = np.sqrt(np.sum(np.square(camera_dirs_stacked[..., :2]), axis=-1)) |
| theta = np.minimum(np.pi, theta) |
|
|
| sin_theta_over_theta = np.sin(theta) / theta |
| camera_dirs_stacked = np.stack([ |
| camera_dirs_stacked[..., 0] * sin_theta_over_theta, |
| camera_dirs_stacked[..., 1] * sin_theta_over_theta, |
| np.cos(theta), |
| ], axis=-1) |
|
|
| |
| camera_dirs_stacked = matmul(camera_dirs_stacked, |
| np.diag(np.array([1., -1., -1.]))) |
|
|
| |
| imageplane = camera_dirs_stacked[0, ..., :2] |
|
|
| |
| directions_stacked = mat_vec_mul(camtoworlds[..., :3, :3], |
| camera_dirs_stacked) |
| |
| directions, dx, dy = directions_stacked |
|
|
| origins = np.broadcast_to(camtoworlds[..., :3, -1], directions.shape) |
| viewdirs = directions / np.linalg.norm(directions, axis=-1, keepdims=True) |
|
|
| if pixtocam_ndc is None: |
| |
| dx_norm = np.linalg.norm(dx - directions, axis=-1) |
| dy_norm = np.linalg.norm(dy - directions, axis=-1) |
|
|
| else: |
| |
| origins_dx, _ = convert_to_ndc(origins, dx, pixtocam_ndc) |
| origins_dy, _ = convert_to_ndc(origins, dy, pixtocam_ndc) |
| origins, directions = convert_to_ndc(origins, directions, pixtocam_ndc) |
|
|
| |
| dx_norm = np.linalg.norm(origins_dx - origins, axis=-1) |
| dy_norm = np.linalg.norm(origins_dy - origins, axis=-1) |
|
|
| |
| |
| radii = (0.5 * (dx_norm + dy_norm))[..., None] * 2 / np.sqrt(12) |
| return origins, directions, viewdirs, radii, imageplane |
|
|
|
|
| def cast_ray_batch(cameras, pixels, camtype): |
| """Maps from input cameras and Pixel batch to output Ray batch. |
| |
| `cameras` is a Tuple of four sets of camera parameters. |
| pixtocams: 1 or N stacked [3, 3] inverse intrinsic matrices. |
| camtoworlds: 1 or N stacked [3, 4] extrinsic pose matrices. |
| distortion_params: optional, dict[str, float] containing pinhole model |
| distortion parameters. |
| pixtocam_ndc: optional, [3, 3] inverse intrinsic matrix for mapping to NDC. |
| |
| Args: |
| cameras: described above. |
| pixels: integer pixel coordinates and camera indices, plus ray metadata. |
| These fields can be an arbitrary batch shape. |
| camtype: camera_utils.ProjectionType, fisheye or perspective camera. |
| |
| Returns: |
| rays: Rays dataclass with computed 3D world space ray data. |
| """ |
| pixtocams, camtoworlds, distortion_params, pixtocam_ndc = cameras |
|
|
| |
| cam_idx = pixels['cam_idx'][..., 0] |
| batch_index = lambda arr: arr if arr.ndim == 2 else arr[cam_idx] |
|
|
| |
| origins, directions, viewdirs, radii, imageplane = pixels_to_rays( |
| pixels['pix_x_int'], |
| pixels['pix_y_int'], |
| batch_index(pixtocams), |
| batch_index(camtoworlds), |
| distortion_params=distortion_params, |
| pixtocam_ndc=pixtocam_ndc, |
| camtype=camtype) |
|
|
| |
| return dict( |
| origins=origins, |
| directions=directions, |
| viewdirs=viewdirs, |
| radii=radii, |
| imageplane=imageplane, |
| lossmult=pixels.get('lossmult'), |
| near=pixels.get('near'), |
| far=pixels.get('far'), |
| cam_idx=pixels.get('cam_idx'), |
| exposure_idx=pixels.get('exposure_idx'), |
| exposure_values=pixels.get('exposure_values'), |
| ) |
|
|
|
|
| def cast_pinhole_rays(camtoworld, height, width, focal, near, far): |
| """Wrapper for generating a pinhole camera ray batch (w/o distortion).""" |
|
|
| pix_x_int, pix_y_int = pixel_coordinates(width, height) |
| pixtocam = get_pixtocam(focal, width, height) |
|
|
| origins, directions, viewdirs, radii, imageplane = pixels_to_rays(pix_x_int, pix_y_int, pixtocam, camtoworld) |
|
|
| broadcast_scalar = lambda x: np.broadcast_to(x, pix_x_int.shape)[..., None] |
| ray_kwargs = { |
| 'lossmult': broadcast_scalar(1.), |
| 'near': broadcast_scalar(near), |
| 'far': broadcast_scalar(far), |
| 'cam_idx': broadcast_scalar(0), |
| } |
|
|
| return dict(origins=origins, |
| directions=directions, |
| viewdirs=viewdirs, |
| radii=radii, |
| imageplane=imageplane, |
| **ray_kwargs) |
|
|
|
|
| def cast_spherical_rays(camtoworld, height, width, near, far): |
| """Generates a spherical camera ray batch.""" |
|
|
| theta_vals = np.linspace(0, 2 * np.pi, width + 1) |
| phi_vals = np.linspace(0, np.pi, height + 1) |
| theta, phi = np.meshgrid(theta_vals, phi_vals, indexing='xy') |
|
|
| |
| directions = np.stack([ |
| -np.sin(phi) * np.sin(theta), |
| np.cos(phi), |
| np.sin(phi) * np.cos(theta), |
| ], axis=-1) |
|
|
| matmul = np.matmul |
| directions = matmul(camtoworld[:3, :3], directions[..., None])[..., 0] |
|
|
| dy = np.diff(directions[:, :-1], axis=0) |
| dx = np.diff(directions[:-1, :], axis=1) |
| directions = directions[:-1, :-1] |
| viewdirs = directions |
|
|
| origins = np.broadcast_to(camtoworld[:3, -1], directions.shape) |
|
|
| dx_norm = np.linalg.norm(dx, axis=-1) |
| dy_norm = np.linalg.norm(dy, axis=-1) |
| radii = (0.5 * (dx_norm + dy_norm))[..., None] * 2 / np.sqrt(12) |
|
|
| imageplane = np.zeros_like(directions[..., :2]) |
|
|
| broadcast_scalar = lambda x: np.broadcast_to(x, radii.shape[:-1])[..., None] |
| ray_kwargs = { |
| 'lossmult': broadcast_scalar(1.), |
| 'near': broadcast_scalar(near), |
| 'far': broadcast_scalar(far), |
| 'cam_idx': broadcast_scalar(0), |
| } |
|
|
| return dict(origins=origins, |
| directions=directions, |
| viewdirs=viewdirs, |
| radii=radii, |
| imageplane=imageplane, |
| **ray_kwargs) |
|
|