code: def warp_samples_to_canonical(pts, verts, faces, T):
      assert (
          len(pts.shape) == 3
      ), "pts should have shape [num_rays, num_samples, 3]"
      assert pts.shape[-1] == 3
      num_rays, num_samples, _ = pts.shape
      pts = pts.reshape(-1, 3)
      dist2, f_id, closest = igl.point_mesh_squared_distance(
          pts, verts, faces[:, :3]
      )
      closest_tri = verts[faces[:, :3][f_id]]
      barycentric = igl.barycentric_coordinates_tri(
          closest,
          closest_tri[:, 0, :].copy(),
          closest_tri[:, 1, :].copy(),
          closest_tri[:, 2, :].copy(),
      )
      T_interp = (
          T[faces[:, :3][f_id]] * barycentric[..., None, None]
      ).sum(axis=1)
      T_interp_inv = np.linalg.inv(T_interp)
      can_pts = (T_interp_inv @ to_homogeneous(pts)[..., None])[
          :, :3, 0
      ]
      # reshape back and compute ray directions in canonical space
      can_pts = can_pts.reshape(num_rays, num_samples, 3)
      closest = closest.reshape(num_rays, num_samples, 3)
      can_dirs = can_pts[:, 1:] - can_pts[:, :-1]
      can_dirs = np.concatenate([can_dirs, can_dirs[:, -1:]], axis=1)
      can_dirs = can_dirs / np.linalg.norm(
          can_dirs, axis=2, keepdims=True
      )
  
      return can_pts, can_dirs, closest
  
file_path: ml-neuman/utils/ray_utils.py
imports: import numpy
  import torch
  import igl
  from geometry.pcd_projector import PointCloudProjectorNp
  from utils.constant import DEFAULT_GEO_THRESH, PERTURB_EPSILON
label_desc: Use image node_id 5 for calling a global function with example usage: warp_samples_to_canonical(pts, verts, faces, T) and returns: can_pts,  can_dirs,  closest