class_docstr: Class to manage spatial ray sampling.
  
  Args:
      min_depth: sampled rays minimal depth from cameras: float
      max_depth: sampled rays maximal depth from cameras: float
      ndc: convert ray parameters to normalized device coordinates: bool
      device: device for ray tensors: Union[str, torch.device]
code: def _calc_ray_directions_cam(
      self, cameras: PinholeCamera, points_2d: Tensor
  ) -> Tensor:
      # FIXME: This function should call perspective.unproject_points or, implement in PinholeCamera unproject to
      # camera coordinates that will call perspective.unproject_points
      fx = cameras.fx
      fy = cameras.fy
      cx = cameras.cx
      cy = cameras.cy
      directions_x = (points_2d[..., 0] - cx[..., None]) / fx[..., None]
      directions_y = (points_2d[..., 1] - cy[..., None]) / fy[..., None]
      directions_z = torch.ones_like(directions_x)
      directions_cam = torch.stack(
          [directions_x, directions_y, directions_z], dim=-1
      )
      return directions_cam.reshape(-1, 3)
  
imports: import math
  from typing import Dict, List, Tuple
  import torch
  from kornia.core import Device, Tensor, tensor
  from kornia.geometry.camera import PinholeCamera
  from kornia.nerf.camera_utils import cameras_for_ids
  from kornia.utils._compat import torch_meshgrid
  from kornia.utils.helpers import _torch_inverse_cast
label_desc: Use image node_id 7 for calling the RaySampler obj's underlying member method code with example usage: obj._calc_ray_directions_cam(cameras, points_2d) and returns: directions_cam