Tetrapod Project
utils.py
Go to the documentation of this file.
1 import collections
2 import numpy as np
3 
4 import matplotlib.pyplot as plt
5 
6 from IPython.display import HTML, display
7 
8 from matplotlib.animation import FuncAnimation
9 
10 from matplotlib.patches import Rectangle
11 
12 from matplotlib import RcParams, rcParams
13 rcParams['figure.figsize'] = (10, 5)
14 
15 from colour import Color
16 
17 import xml.etree.ElementTree as ET
18 
19 class SteppingStone(object):
20 
21  def __init__(self, center, width, height, name=None):
22 
23  # store arguments
24  self.centercenter = center
25  self.widthwidth = width
26  self.heightheight = height
27  self.namename = name
28 
29  # distance from center to corners
30  c2tr = np.array([width, height]) / 2
31  c2br = np.array([width, - height]) / 2
32 
33  # position of the corners
34  self.top_righttop_right = center + c2tr
35  self.bottom_rightbottom_right = center + c2br
36  self.top_lefttop_left = center - c2br
37  self.bottom_leftbottom_left = center - c2tr
38 
39  # halfspace representation of the stepping stone
40  self.AA = np.array([[1, 0], [0, 1], [-1, 0], [0, -1]])
41  self.bb = np.concatenate([c2tr] * 2) + self.AA.dot(center)
42 
43  def plot(self, **kwargs):
44  return plot_rectangle(self.centercenter, self.widthwidth, self.heightheight, **kwargs)
45  def add_to_sdf(self, super, indicator=True):
46  if indicator:
47  size_string = str(self.widthwidth) + ' ' + str(self.heightheight) + ' 0.001 0 0 0'
48  pose_string = str(self.centercenter[0]) + ' ' + str(self.centercenter[1]) + ' 0 0 0 0'
49  else:
50  size_string = str(self.widthwidth) + ' ' + str(self.heightheight) + ' 0.5 0 0 0'
51  pose_string = str(self.centercenter[0]) + ' ' + str(self.centercenter[1]) + ' -0.25 0 0 0'
52 
53  model = ET.SubElement(super, 'model')
54 
55  model.set('name', self.namename)
56 
57  static = ET.SubElement(model, 'static')
58  static.text = 'true'
59 
60  pose = ET.SubElement(model, 'pose')
61  pose.text = pose_string
62 
63  link = ET.SubElement(model, 'link')
64  link.set('name', 'plate_link')
65 
66  link_pose = ET.SubElement(link, 'pose')
67  link_pose.text = '0 0 0 0 0 0'
68 
69  visual = ET.SubElement(link, 'visual')
70  visual.set('name', 'plate_visual')
71 
72  visual_geometry = ET.SubElement(visual, 'geometry')
73  visual_box = ET.SubElement(visual_geometry, 'box')
74 
75  visual_size = ET.SubElement(visual_box, 'size')
76  visual_size.text = size_string
77  if indicator:
78  material = ET.SubElement(visual, 'material')
79 
80  ambient = ET.SubElement(material, 'ambient')
81  ambient.text = '0.8 0 0 1'
82 
83  diffuse = ET.SubElement(material, 'diffuse')
84  diffuse.text = '0.8 0 0 1'
85 
86  specular = ET.SubElement(material, 'specular')
87  specular.text = '0.1 0.1 0.1 1'
88 
89  emissive = ET.SubElement(material, 'emissive')
90  emissive.text = '0.8 0 0 1'
91  else:
92  collision = ET.SubElement(link, 'collision')
93  collision.set('name', 'plate_collision')
94 
95  collision_geometry = ET.SubElement(collision, 'geometry')
96  collision_box = ET.SubElement(collision_geometry, 'box')
97 
98  collision_size = ET.SubElement(collision_box, 'size')
99  collision_size.text = size_string
100 
101 
102 
103 
104 
105 # helper function that plots a rectangle with given center, width, and height
106 def plot_rectangle(center, width, height, ax=None, frame=.1, **kwargs):
107 
108  # make black the default edgecolor
109  if not 'edgecolor' in kwargs:
110  kwargs['edgecolor'] = 'black'
111 
112  # make transparent the default facecolor
113  if not 'facecolor' in kwargs:
114  kwargs['facecolor'] = 'none'
115 
116  # get current plot axis if one is not given
117  if ax is None:
118  ax = plt.gca()
119 
120  # get corners
121  c2c = np.array([width, height]) / 2
122  bottom_left = center - c2c
123  top_right = center + c2c
124 
125  # plot rectangle
126  rect = Rectangle(bottom_left, width, height, **kwargs)
127  ax.add_patch(rect)
128 
129  # scatter fake corners to update plot limits (bad looking but compact)
130  ax.scatter(*bottom_left, s=0)
131  ax.scatter(*top_right, s=0)
132 
133  # make axis scaling equal
134  ax.set_aspect('equal')
135 
136  return rect
137 
138 class Terrain(object):
139 
140  # parametric construction of the stepping stones
141  # the following code adapts the position of each stepping
142  # stone depending on the size and the sparsity of bool_bridge
143  def __init__(self, bool_bridge=None):
144 
145  self.stepping_stonesstepping_stones = []
146  if bool_bridge is None:
147  initial = self.add_stoneadd_stone([0, 0], 1.2, 1.2, 'initial')
148 
149  goal = self.add_stoneadd_stone([4, 0], 1.2, 1.2, 'goal')
150 
151  for i in range(2):
152  self.add_stoneadd_stone([1 + 2*i, 0], 1.2, 4.2, 'vertical' + str(i))
153  self.add_stoneadd_stone([2, -1.5 + 3*i], 3.2, 1.2, 'lateral' + str(i))
154  else:
155  # ensure that bool_bridge has only boolean entries
156  if any(i != bool(i) for i in bool_bridge):
157  raise ValueError('Entry bool_bridge must be a list of boolean value.')
158 
159  # initialize internal list of stepping stones
160 
161  # add initial stepping stone to the terrain
162  initial = self.add_stoneadd_stone([0, 0], 1.2, 1.2, 'initial')
163 
164  # add bridge stepping stones to the terrain
165  # gap between bridge stones equals bridge stone width
166  width_bridge = .24
167  center = initial.bottom_right + np.array([width_bridge * 1.5, initial.height / 2])
168  centers = [center + np.array([i * 2 * width_bridge, 0]) for i in np.where(bool_bridge)[0]]
169  self.add_stonesadd_stones(
170  centers,
171  [width_bridge*1.5] * sum(bool_bridge),
172  [initial.height] * sum(bool_bridge),
173  'bridge'
174  )
175 
176  # add goal stepping stone to the terrain
177  # same dimensions of the initial one
178  center = initial.center + np.array([initial.width + (len(bool_bridge) * 2 + 1) * width_bridge, 0])
179  goal = self.add_stoneadd_stone(center, initial.width, initial.height, 'goal')
180 
181  # add lateral stepping stone to the terrain
182  height = .8
183  clearance = .1
184  c2g = goal.center - initial.center
185  width = initial.width + c2g[0]
186  center = initial.center + c2g / 2 + np.array([0, (initial.height + height) / 2 + clearance])
187  self.add_stoneadd_stone(center, width, height, 'lateral')
188 
189  # adds a stone to the internal list stepping_stones
190  def add_stone(self, center, width, height, name=None):
191  stone = SteppingStone(center, width, height, name=name)
192  self.stepping_stonesstepping_stones.append(stone)
193  return stone
194 
195  # adds multiple stones to the internal list stepping_stones
196  def add_stones(self, centers, widths, heights, name=None):
197 
198  # ensure that inputs have coherent size
199  n_stones = len(centers)
200  if n_stones != len(widths) or n_stones != len(heights):
201  raise ValueError('Arguments have incoherent size.')
202 
203  # add one stone per time
204  stones = []
205  for i in range(n_stones):
206  stone_name = name if name is None else name + '_' + str(i)
207  stones.append(self.add_stoneadd_stone(centers[i], widths[i], heights[i], name=stone_name))
208 
209  return stones
210 
211  # returns the stone with the given name
212  # raise a ValueError if no stone has the given name
213  def get_stone_by_name(self, name):
214 
215  # loop through the stones
216  # select the first with the given name
217  for stone in self.stepping_stonesstepping_stones:
218  if stone.name == name:
219  return stone
220 
221  # raise error if there is no stone with the given name
222  raise ValueError(f'No stone in the terrain has name {name}.')
223 
224  # plots all the stones in the terrain
225  def plot(self, title=None, **kwargs):
226 
227  # make light green the default facecolor
228  if not 'facecolor' in kwargs:
229  kwargs['facecolor'] = [0, 1, 0, .1]
230 
231  # plot stepping stones disposition
232  labels = ['Stepping stone', None]
233  for i, stone in enumerate(self.stepping_stonesstepping_stones):
234  stone.plot(label=labels[min(i, 1)], **kwargs)
235 
236  # set title
237  plt.title(title)
238 
239  def write_to_sdf(self, fname='terrain_xml', with_height=True):
240  fend = '.sdf'
241  sdf = ET.Element('sdf')
242  sdf.set('version', '1.7')
243  model = ET.SubElement(sdf, 'model')
244  model.set('name', 'terrain')
245 
246  pose = ET.SubElement(model, 'pose')
247  pose.text = '0 0 0 0 0 0'
248 
249 
250  if with_height:
251  model_indicator = ET.SubElement(model, 'model')
252  model_indicator.set('name', 'terrain_indicator')
253 
254  pose_indicator = ET.SubElement(model, 'pose')
255  pose_indicator.text = '0 0 0 0 0 0'
256 
257  model_height = ET.SubElement(model, 'model')
258  model_height.set('name', 'terrain_height')
259 
260  pose_height = ET.SubElement(model, 'pose')
261  pose_height.text = '0 0 0 0 0 0'
262 
263  for s in self.stepping_stonesstepping_stones:
264  s.add_to_sdf(model_indicator, indicator=True)
265  s.add_to_sdf(model_height, indicator=False)
266  else:
267  for s in self.stepping_stonesstepping_stones:
268  s.add_to_sdf(model)
269 
270  mydata = ET.tostring(sdf, encoding='unicode', xml_declaration=True)
271  f = open(fname + fend, 'w')
272  f.write(mydata)
273  f.close()
274 
275 
276 def animate_footstep_plan(terrain, n_steps, step_span, positions, title=None, outname="footstep_planner", save_anim=False):
277 
278  # initialize figure for animation
279  fig, ax = plt.subplots()
280 
281  # plot stepping stones
282  terrain.plot(title=title, ax=ax)
283 
284  # initial position of the feet
285  colors = ['r', 'b', 'g', 'y']
286 
287  feet = [ax.scatter(0, 0, color=c, zorder=3, label='foot ' + str(i)) for (i, c) in enumerate(colors)]
288 
289  limits = [plot_rectangle(
290  [0, 0],
291  step_span,
292  step_span,
293  ax=ax,
294  edgecolor=c,
295  label='foot ' + str(i) + ' limits'
296  ) for (i, c) in enumerate(colors)]
297 
298  # left_foot = ax.scatter(0, 0, color='r', zorder=3, label='Left foot')
299  # right_foot = ax.scatter(0, 0, color='b', zorder=3, label='Right foot')
300 
301  # initial step limits
302  # left_limits = plot_rectangle(
303  # [0 ,0], # center
304  # step_span, # width
305  # step_span, # eight
306  # ax=ax,
307  # edgecolor='b',
308  # label='Left-foot limits'
309  # )
310  # right_limits = plot_rectangle(
311  # [0 ,0], # center
312  # step_span, # width
313  # step_span, # eight
314  # ax=ax,
315  # edgecolor='r',
316  # label='Right-foot limits'
317  # )
318 
319  # misc settings
320  #plt.close()
321  ax.legend(loc='upper left', bbox_to_anchor=(0, 1.3), ncol=2)
322 
323  def animate(n_steps):
324 
325  # scatter feet
326  #left_foot.set_offsets(position_left[n_steps])
327  #right_foot.set_offsets(position_right[n_steps])
328  for i in range(len(feet)):
329  feet[i].set_offsets(positions[i][n_steps])
330 
331  # limits of reachable set for each foot
332  c2c = np.ones(2) * step_span / 2
333  for i in range(len(limits)):
334  limits[i].set_xy(positions[i][n_steps] - c2c)
335  #right_limits.set_xy(position_left[n_steps] - c2c)
336  #left_limits.set_xy(position_right[n_steps] - c2c)
337 
338  # create ad display animation
339  ani = FuncAnimation(fig, animate, frames=n_steps, interval=1e3)
340 
341  if save_anim:
342  ani.save(outname + ".mp4", fps=30, extra_args=['-vcodec', 'libx264'])
343 
344  plt.show()
345 
346 def import_decision_variables_biped(base_name="footstep_planner"):
347  fend = ".csv"
348 
349  position_left = np.genfromtxt(base_name + "_position_left" + fend)
350 
351  position_right = np.genfromtxt(base_name + "_position_right" + fend)
352 
353  stone_left = np.genfromtxt(base_name + "_stone_left" + fend)
354 
355  stone_right = np.genfromtxt(base_name + "_stone_right" + fend)
356 
357  return (position_left, position_right, stone_left, stone_right, True)
358 
359 def import_decision_variables_quadruped(base_name="footstep_planner"):
360  fend = ".csv"
361 
362  position_front_left = np.genfromtxt(base_name + "_position_front_left" + fend)[:, 0:2]
363 
364  position_front_right = np.genfromtxt(base_name + "_position_front_right" + fend)[:, 0:2]
365 
366  position_rear_left = np.genfromtxt(base_name + "_position_rear_left" + fend)[:, 0:2]
367 
368  position_rear_right = np.genfromtxt(base_name + "_position_rear_right" + fend)[:, 0:2]
369 
370  return (position_front_left, position_front_right, position_rear_left, position_rear_right, True)
371 
372 def import_and_animate_footstep_plan(terrain, step_span, title=None, base_name="footstep_planner"):
373 
374  # import footstep_planner results
375  footsteps = import_decision_variables_quadruped(base_name)
376  n_steps = footsteps[0].shape[0]
377  # animate result
378  animate_footstep_plan(terrain, n_steps, step_span, footsteps[:4], title, base_name)
379 
380 def write_footsteps_to_sdf(steps, name='footsteps_xml'):
381  fend = '.sdf'
382  sdf = ET.Element('sdf')
383  sdf.set('version', '1.7')
384 
385  model = ET.SubElement(sdf, 'model')
386  model.set('name', 'footsteps')
387 
388  static = ET.SubElement(model, 'static')
389  static.text = '1'
390 
391  pose = ET.SubElement(model, 'pose')
392  pose.text = '0 0 0 0 0 0'
393 
394  yellow = Color('yellow')
395  colors = list(yellow.range_to(Color('green'), len(steps)))
396 
397  for i, (color, step) in enumerate(zip(colors, steps)):
398  footstep_model = ET.SubElement(model, 'model')
399  footstep_model.set('name', 'footstep ' + str(i))
400 
401  static = ET.SubElement(footstep_model, 'static')
402  static.text = '1'
403 
404  rgb = color.get_rgb()
405  color_string = str(rgb[0]) + ' ' + str(rgb[1]) + ' ' + str(rgb[2]) + ' 1'
406 
407  link = ET.SubElement(footstep_model, 'link')
408  link.set('name', 'footstep_link')
409 
410  link_pose = ET.SubElement(link, 'pose')
411  link_pose.text = str(step[0]) + ' ' + str(step[1]) + ' 0 0 0 0'
412 
413  visual = ET.SubElement(link, 'visual')
414  visual.set('name', 'footstep_visual')
415 
416  visual_geometry = ET.SubElement(visual, 'geometry')
417 
418  visual_cylinder = ET.SubElement(visual_geometry, 'cylinder')
419  radius = ET.SubElement(visual_cylinder, 'radius')
420  radius.text = '0.02'
421 
422  length = ET.SubElement(visual_cylinder, 'length')
423  length.text = '0.01'
424 
425  #visual_cylinder.set('radius', '0.05')
426  #visual_cylinder.set('length', '0.01')
427 
428  material = ET.SubElement(visual, 'material')
429  #material.set('name', 'red')
430 
431  ambient = ET.SubElement(material, 'ambient')
432  ambient.text = color_string
433 
434  diffuse = ET.SubElement(material, 'diffuse')
435  diffuse.text = color_string
436 
437  specular = ET.SubElement(material, 'specular')
438  specular.text = '0.1 0.1 0.1 1'
439 
440  emissive = ET.SubElement(material, 'emissive')
441  emissive.text = color_string
442 
443  #collision = ET.SubElement(link, 'collision')
444  #collision.set('name', 'plate_collision')
445 
446  #collision_geometry = ET.SubElement(collision, 'geometry')
447  #collision_box = ET.SubElement(collision_geometry, 'box')
448 
449  #collision_size = ET.SubElement(collision_box, 'size')
450  #collision_size.text = "0 0 0 0 0 0"
451  data = ET.tostring(sdf, encoding='unicode', xml_declaration=True)
452  f = open(name + fend, 'w')
453  f.write(data)
454  f.close()
455 
456 
457 def transcribe_footsteps_to_sdf(infname = 'footstep_planner', outfname = 'footsteps_xml'):
458  infend = '.csv'
459  footsteps = np.genfromtxt(infname + '_position_ts' + infend)
460  write_footsteps_to_sdf(footsteps, outfname)
def __init__(self, center, width, height, name=None)
Definition: utils.py:21
def plot(self, **kwargs)
Definition: utils.py:43
def add_to_sdf(self, super, indicator=True)
Definition: utils.py:45
def plot(self, title=None, **kwargs)
Definition: utils.py:225
def __init__(self, bool_bridge=None)
Definition: utils.py:143
def add_stones(self, centers, widths, heights, name=None)
Definition: utils.py:196
def get_stone_by_name(self, name)
Definition: utils.py:213
def write_to_sdf(self, fname='terrain_xml', with_height=True)
Definition: utils.py:239
def add_stone(self, center, width, height, name=None)
Definition: utils.py:190
def import_decision_variables_quadruped(base_name="footstep_planner")
Definition: utils.py:359
def import_and_animate_footstep_plan(terrain, step_span, title=None, base_name="footstep_planner")
Definition: utils.py:372
def import_decision_variables_biped(base_name="footstep_planner")
Definition: utils.py:346
def transcribe_footsteps_to_sdf(infname='footstep_planner', outfname='footsteps_xml')
Definition: utils.py:457
def plot_rectangle(center, width, height, ax=None, frame=.1, **kwargs)
Definition: utils.py:106
def write_footsteps_to_sdf(steps, name='footsteps_xml')
Definition: utils.py:380
def animate_footstep_plan(terrain, n_steps, step_span, positions, title=None, outname="footstep_planner", save_anim=False)
Definition: utils.py:276