text: """
  Parts of the code are adapted from https://github.com/akanazawa/hmr
  """
  from __future__ import absolute_import
  from __future__ import division
  from __future__ import print_function
  import numpy as np
  import torch
  
  
  def compute_similarity_transform(S1, S2):
      """
      Computes a similarity transform (sR, t) that takes
      a set of 3D points S1 (3 x N) closest to a set of 3D points S2,
      where R is an 3x3 rotation matrix, t 3x1 translation, s scale.
      i.e. solves the orthogonal Procrutes problem.
      """
      transposed = False
      if S1.shape[0] != 3 and S1.shape[0] != 2:
          S1 = S1.T
          S2 = S2.T
          transposed = True
      assert(S2.shape[1] == S1.shape[1])
  
      # 1. Remove mean.
      mu1 = S1.mean(axis=1, keepdims=True)
      mu2 = S2.mean(axis=1, keepdims=True)
      X1 = S1 - mu1
      X2 = S2 - mu2
  
      # 2. Compute variance of X1 used for scale.
      var1 = np.sum(X1**2)
  
      # 3. The outer product of X1 and X2.
      K = X1.dot(X2.T)
  
      # 4. Solution that Maximizes trace(R'K) is R=U*V', where U, V are
      # singular vectors of K.
      U, s, Vh = np.linalg.svd(K)
      V = Vh.T
      # Construct Z that fixes the orientation of R to get det(R)=1.
      Z = np.eye(U.shape[0])
      Z[-1, -1] *= np.sign(np.linalg.det(U.dot(V.T)))
      # Construct R.
      R = V.dot(Z.dot(U.T))
  
      # 5. Recover scale.
      scale = np.trace(R.dot(K)) / var1
  
      # 6. Recover translation.
      t = mu2 - scale*(R.dot(mu1))
  
      # 7. Error:
      S1_hat = scale*R.dot(S1) + t
  
      if transposed:
          S1_hat = S1_hat.T
  
      return S1_hat
  
  
  def procrustes_analysis_batch(S1, S2):
      """Batched version of compute_similarity_transform."""
      S1_hat = np.zeros_like(S1)
      for i in range(S1.shape[0]):
          S1_hat[i] = compute_similarity_transform(S1[i], S2[i])
      return S1_hat
  
  
  def scale_and_translation_transform_batch(P, T):
      """
      First Normalises batch of input 3D meshes P such that each mesh has mean (0, 0, 0) and
      RMS distance from mean = 1.
      Then transforms P such that it has the same mean and RMSD as T.
      :param P: (batch_size, N, 3) batch of N 3D meshes to transform.
      :param T: (batch_size, N, 3) batch of N reference 3D meshes.
      :return: P transformed
      """
      P_mean = np.mean(P, axis=1, keepdims=True)
      P_trans = P - P_mean
      P_scale = np.sqrt(np.sum(P_trans ** 2, axis=(1, 2), keepdims=True) / P.shape[1])
      P_normalised = P_trans / P_scale
  
      T_mean = np.mean(T, axis=1, keepdims=True)
      T_scale = np.sqrt(np.sum((T - T_mean) ** 2, axis=(1, 2), keepdims=True) / T.shape[1])
  
      P_transformed = P_normalised * T_scale + T_mean
  
      return P_transformed
  
  
  def scale_and_translation_transform_batch_torch(P, T):
      """
      First Normalises batch of input 3D meshes P such that each mesh has mean (0, 0, 0) and
      RMS distance from mean = 1.
      Then transforms P such that it has the same mean and RMSD as T.
      :param P: (batch_size, N, 3) batch of N 3D meshes to transform.
      :param T: (batch_size, N, 3) batch of N reference 3D meshes.
      :return: P transformed
      """
      P_mean = torch.mean(P, dim=1, keepdim=True)
      P_trans = P - P_mean
      P_scale = torch.sqrt(torch.sum(P_trans ** 2, dim=(1, 2), keepdim=True) / P.shape[1])
      P_normalised = P_trans / P_scale
  
      T_mean = torch.mean(T, dim=1, keepdim=True)
      T_scale = torch.sqrt(torch.sum((T - T_mean) ** 2, dim=(1, 2), keepdim=True) / T.shape[1])
  
      P_transformed = P_normalised * T_scale + T_mean
  
      return P_transformed
  
  
  def shape_parameters_to_a_pose(body_shape,
                                 smpl):
      """
      Return mesh of person in A-pose, given the body shape parameters.
      :param body_shape:
      :param smpl: SMPL model
      :return:
      """
      a_pose = torch.zeros((1, 69), device=body_shape.device)
      a_pose[:, 47] = -np.pi/3.0
      a_pose[:, 50] = np.pi/3.0
  
      a_pose_output = smpl(betas=body_shape,
                           body_pose=a_pose)
      a_pose_vertices = a_pose_output.vertices
      return a_pose_vertices
  
  
  def make_xz_ground_plane(vertices):
      """
      Given a vertex mesh, translates the mesh such that the lowest coordinate of the mesh
      lies on the x-z plane.
      :param vertices: (N, 6890, 3)
      :return:
      """
      lowest_y = vertices[:, :, 1].min(axis=-1, keepdims=True)
      vertices[:, :, 1] = vertices[:, :, 1] - lowest_y
      return vertices
  
  
  
  
  