/udom_utils/udom_geometric_transformation/src/udom_geometric_transformation/pose_extractor_utils.py

https://gitlab.com/jsanch/udom
Python | 265 lines | 228 code | 13 blank | 24 comment | 3 complexity | 7d02482595684091ff3365f12f17bf8c MD5 | raw file
  1. #!/usr/bin/env python
  2. # -*- encoding: utf-8 -*-
  3. """
  4. This modules defines utility functions for the pose extractor node.
  5. """
  6. import copy
  7. import numpy as np
  8. import geometry_msgs.msg
  9. import udom_modeling_msgs.msg
  10. import tf
  11. import tf.transformations as transformations
  12. def extract_points(mesh, indices, zero_based=True):
  13. """
  14. Extracts the nodes' positions based on the mesh and indices.
  15. :param mesh: The mesh from which the pose will be extracted.
  16. :type mesh: udom_modeling_msgs.msg.Mesh
  17. :param indices: Indices of the node to return.
  18. :type indices: list of int
  19. :param zero_based: If true, it assumes the indices are zero-based.
  20. Otherwise, it assumes the indices start from one.
  21. :type zero_based: bool
  22. :return: The extracted nodes' positions, if there is an error it returns None.
  23. :rtype: list of geometry_msgs.msg.Point or None
  24. """
  25. try:
  26. if zero_based:
  27. return [mesh.vertices[index] for index in indices]
  28. else:
  29. return [mesh.vertices[index - 1] for index in indices]
  30. except IndexError:
  31. return None
  32. def list_to_mesh(mesh, points, nodes=None):
  33. """
  34. Updates the nodes in a mesh based on a list which follows this order:
  35. [p1_x, p1_y, p_1z, p2_x, p2_y, p2_z....]
  36. :param mesh: The mesh to update.
  37. :type mesh: udom_modeling_msgs.msg.Mesh
  38. :param points: The points representing the mesh's nodes position.
  39. :type points: list
  40. :param nodes: The indices of the nodes (in order) that are represented by the points.
  41. :type nodes: list
  42. :return: The mesh with the updated position of its nodes.
  43. :rtype: udom_modeling_msgs.msg.Mesh
  44. """
  45. if nodes:
  46. mesh_out = udom_modeling_msgs.msg.Mesh()
  47. for ii, _ in enumerate(nodes):
  48. mesh_out.vertices.append(geometry_msgs.msg.Point(
  49. x=points[ii * 3], y=points[ii * 3 + 1], z=points[ii * 3 + 2]))
  50. else:
  51. mesh_out = copy.deepcopy(mesh)
  52. for ii, _ in enumerate(mesh_out.vertices):
  53. mesh_out.vertices[ii] = geometry_msgs.msg.Point(
  54. x=points[ii * 3], y=points[ii * 3 + 1], z=points[ii * 3 + 2])
  55. return mesh_out
  56. def mesh_to_list(mesh, indices=None):
  57. """
  58. Converts the nodes in a mesh into a list.
  59. The order follows: [p1_x, p1_y, p_1z, p2_x, p2_y, p2_z....]
  60. :param mesh: The mesh from which the points will be converted to a list.
  61. :type mesh: udom_modeling_msgs.msg.Mesh
  62. :param indices: If specified, it returns only the position for the
  63. nodes represented by the indices.
  64. :type indices: list
  65. :return: The extracted nodes' positions.
  66. :rtype: list
  67. """
  68. points = []
  69. for ii, vertex in enumerate(mesh.vertices):
  70. if indices:
  71. if ii in indices:
  72. p_x = vertex.x
  73. p_y = vertex.y
  74. p_z = vertex.z
  75. points.extend([p_x, p_y, p_z])
  76. else:
  77. p_x = vertex.x
  78. p_y = vertex.y
  79. p_z = vertex.z
  80. points.extend([p_x, p_y, p_z])
  81. return points
  82. def extract_forces(force_array, indices):
  83. """
  84. Extracts the force values on the nodes as represented by the indices.
  85. :param force_array: An array representing the three-dimensional forces applied
  86. to each node on a mesh.
  87. :type force_array: std_msgs.msg.Float32MultiArray
  88. :param indices: Indices of the nodes where the force is applied.
  89. :type indices: list
  90. :return: The extracted forces.
  91. :rtype: list
  92. """
  93. forces = []
  94. try:
  95. for ii in indices:
  96. f_x = force_array.data[ii * 3]
  97. f_y = force_array.data[ii * 3 + 1]
  98. f_z = force_array.data[ii * 3 + 2]
  99. forces.extend([f_x, f_y, f_z])
  100. return forces
  101. except IndexError:
  102. return None
  103. def fill_forces(forces, indices, number_of_nodes):
  104. """
  105. Fills a force array of length number_of_nodes*3 with the forces,
  106. where the indices represent the nodes where the forces are applied.
  107. The forces are assumed to have the same order as the indices and
  108. they are arranged as 'fx, fy, fz'.
  109. :param forces: An array representing the three-dimensional forces applied
  110. on the nodes (as specified by the indices) of the mesh.
  111. :type forces: list
  112. :param indices: Indices of the nodes where the force is applied.
  113. :type indices: list
  114. :param number_of_nodes: Number of nodes of the mesh.
  115. :type number_of_nodes: int
  116. :return: The filled force array.
  117. :rtype: list
  118. """
  119. force_array = np.zeros(number_of_nodes * 3).tolist()
  120. try:
  121. for ii, index in enumerate(indices):
  122. force_array[index * 3] = forces[ii * 3]
  123. force_array[index * 3 + 1] = forces[ii * 3 + 1]
  124. force_array[index * 3 + 2] = forces[ii * 3 + 2]
  125. return force_array
  126. except IndexError:
  127. return None
  128. def compute_centroid(points):
  129. """
  130. Computes the centroid of the points based on the arithmetic mean.
  131. :param points: The points on which the centroid will be computed.
  132. :type points: list of geometry_msgs.msg.Point
  133. :return: The centroid of the points.
  134. :rtype: geometry_msgs.msg.Point
  135. """
  136. centroid = geometry_msgs.msg.Point()
  137. points = [[point.x, point.y, point.z] for point in points]
  138. c = np.mean(points, axis=0)
  139. centroid.x = c[0]
  140. centroid.y = c[1]
  141. centroid.z = c[2]
  142. return centroid
  143. def compute_normal_as_quaternion(points, rotation=90.0):
  144. """
  145. Computes the normal, as a quaternion, based on three points.
  146. :param points: The points on which the normal will be computed.
  147. :type points: list of geometry_msgs.msg.Point
  148. :param rotation: Rotates the pose around the normal axis by the specified angle (in degrees).
  149. :type rotation: float
  150. :return: The normal of the three points as a quaternion.
  151. :rtype: geometry_msgs.msg.Quaternion
  152. """
  153. quaternion = geometry_msgs.msg.Quaternion()
  154. p_1 = np.array([points[0].x, points[0].y, points[0].z])
  155. p_2 = np.array([points[1].x, points[1].y, points[1].z])
  156. p_3 = np.array([points[2].x, points[2].y, points[2].z])
  157. normal = np.cross((p_2 - p_1), (p_3 - p_1))
  158. quat = tf.transformations.quaternion_about_axis(np.radians(rotation), normal)
  159. quaternion.x = quat[0]
  160. quaternion.y = quat[1]
  161. quaternion.z = quat[2]
  162. quaternion.w = quat[3]
  163. return quaternion
  164. def rotate_pose(pose, angle=0.0, reference_axis='z'):
  165. """
  166. Rotates the orientation of a pose, for a single rotation axis (reference_axis),
  167. by the specified angle.
  168. :param pose: The pose to be modified.
  169. :type pose: geometry_msgs.msg.PoseStamped
  170. :param angle: The angle to rotate the pose (in degrees).
  171. :type angle: float
  172. :param reference_axis: The rotation axis of the pose to be modified (e.g. x, y, z).
  173. :type reference_axis: str
  174. :return: The modified pose.
  175. :rtype: geometry_msgs.msg.PoseStamped
  176. """
  177. assert reference_axis.lower() in ['x', 'y', 'z'], \
  178. "'reference_axis' must be 'x', 'y', or 'z'."
  179. pose_out = copy.deepcopy(pose)
  180. orientation_in = (
  181. pose_out.pose.orientation.x, pose_out.pose.orientation.y,
  182. pose_out.pose.orientation.z, pose_out.pose.orientation.w)
  183. angles_out = np.array(transformations.euler_from_quaternion(orientation_in))
  184. # Ensure the offset is not more than 360 degrees
  185. angle %= 360
  186. if reference_axis.lower() == 'x':
  187. angles_out[0] += np.radians(angle)
  188. elif reference_axis.lower() == 'y':
  189. angles_out[1] += np.radians(angle)
  190. elif reference_axis.lower() == 'z':
  191. angles_out[2] += np.radians(angle)
  192. orientation_out = transformations.quaternion_from_euler(
  193. angles_out[0], angles_out[1], angles_out[2])
  194. pose_out.pose.orientation.x = orientation_out[0]
  195. pose_out.pose.orientation.y = orientation_out[1]
  196. pose_out.pose.orientation.z = orientation_out[2]
  197. pose_out.pose.orientation.w = orientation_out[3]
  198. return pose_out