code: def fit_circle_in_2d(
      points2d,
      *,
      n_points: int = 0,
      angles: Optional[torch.Tensor] = None,
  ) -> Circle2D:
      """
      Simple best fitting of a circle to 2D points. In particular, the circle which
      minimizes the sum of the squares of the squared-distances to the circle.
  
      Finds (a,b) and r to minimize the sum of squares (over the x,y pairs) of
          r**2 - [(x-a)**2+(y-b)**2]
      i.e.
          (2*a)*x + (2*b)*y + (r**2 - a**2 - b**2)*1 - (x**2 + y**2)
  
      In addition, generates points along the circle. If angles is None (default)
      then n_points around the circle equally spaced are given. These begin at the
      point closest to the first input point. They continue in the direction which
      seems to match the movement of points in points2d, as judged by its
      signed area. If `angles` are provided, then n_points is ignored, and points
      along the circle at the given angles are returned, with the starting point
      and direction as before.
  
      (Note that `generated_points` is affected by the order of the points in
      points2d, but the other outputs are not.)
  
      Args:
          points2d: N x 2 tensor of 2D points
          n_points: number of points to generate on the circle, if angles not given
          angles: optional angles in radians of points to generate.
  
      Returns:
          Circle2D object
      """
      design = torch.cat(
          [points2d, torch.ones_like(points2d[:, :1])], dim=1
      )
      rhs = (points2d**2).sum(1)
      n_provided = points2d.shape[0]
      if n_provided < 3:
          raise ValueError(
              f"{n_provided} points are not enough to determine a circle"
          )
      solution = torch.linalg.lstsq(design, rhs[:, None]).solution
      center = solution[:2, 0] / 2
      radius = torch.sqrt(solution[2, 0] + (center**2).sum())
      if n_points > 0:
          if angles is not None:
              warnings.warn("n_points ignored because angles provided")
          else:
              angles = torch.linspace(
                  0, 2 * pi, n_points, device=points2d.device
              )
  
      if angles is not None:
          initial_direction_xy = (points2d[0] - center).unbind()
          initial_angle = torch.atan2(
              initial_direction_xy[1], initial_direction_xy[0]
          )
          with torch.no_grad():
              anticlockwise = _signed_area(points2d) > 0
          if anticlockwise:
              use_angles = initial_angle + angles
          else:
              use_angles = initial_angle - angles
          generated_points = center[None] + radius * torch.stack(
              [torch.cos(use_angles), torch.sin(use_angles)], dim=-1
          )
      else:
          generated_points = points2d.new_zeros(0, 2)
      return Circle2D(
          center=center,
          radius=radius,
          generated_points=generated_points,
      )
  
file_path: pytorch3d/pytorch3d/implicitron/tools/circle_fitting.py
imports: import warnings
  from dataclasses import dataclass
  from math import pi
  from typing import Optional
  import torch
label_desc: Use image node_id 3 for calling a global function with example usage: fit_circle_in_2d(points2d) and returns: Circle2D