In [35]:
%matplotlib inline

Handling Body Mobility in PyLayers


In [19]:
from pylayers.mobility.ban.body import *
from pylayers.mobility.trajectory import Trajectory
from IPython.display import Image
from matplotlib.pyplot import *

The body mobility is imported from motion capture files. This is the chosen manner to achieve a high degree of realism for the modeling of the human motion. Two kind of files exist :

  • c3d files are a set of point which are evolving in time
  • bvh files are a stuctured version of the motion capture.

Both type of file will be exploited in the following.

BodyCylinder data structure

To ease electromagnetic simulation a simplification of the motion capture data structure is necessary. Generally there is a large number of captutred points, not all of them being useful for our modeling.

The body model is a restriction of key body segments which are transformed into $K$ cylinders of radius $r_k$.

The chosen body model is made of 11 cylinders. 4 cylinders decribing the two arms, 4 cylinders decribing the two legs, 2 cylinders describing the trunk and 1 cylinder for the head.

The body cylinder model is handle by a dedicated Python class call Body

To create a void body, simply instantiate a Body object from the class


In [20]:
John = Body()

which is equivalent to :


In [21]:
John = Body(_filebody='John.ini',_filemocap='07_01.c3d')

The default body filename is John.ini and the default motion capture filename is '07_01.c3d'. The creation of a Body consists in reading a _filebody and a _filemocap

Description of a body file

An example of a body file is given below. It is a file in ini format with 4 sections.

  • [nodes]

    This section associates a node number to a c3d fils conventional node number

    NodeId = C3DNODE

  • [cylinder]

    This section associates a cylinder Id to a dictionnary wich contains cylinder tail head and radius information

    CylId = {'t',NodeId1,'h',NodeId2,'r',float (m),'name',}
  • [device]

    This section associates a device name to a dictionnary wich contains cylinder device related information

     DevId = {'typ' : {static|mobile} 
               'cyl': CylId 
               'l'  : length coordinate in ccs,
               'h'  : height coordinate in ccs, 
               'a'  : angle coordinate in ccs, 
               'file' : antenna file , 
               'T' : Rotation matrix }

In [22]:
#%load /home/uguen/Bureau/P1/ini/Francois.ini
[nodes] 0 = STRN 1 = CLAV 2 = RFHD 3 =RSHO 4 =LSHO 5 =RELB 6 =LELB 7 =RWRB 8 =LWRB 9 =RFWT 10 =LFWT 11 =RKNE 12 =LKNE 13 =RANK 14 =LANK 15 =BOTT [cylinder] ; sternum (STRN) - clavicule (CLAV) trunku = {'t':0,'h':1,'r':0.18,'i':0} ; bottom (BOTT) sternum (STRN) trunkb = {'t':15,'h':0,'r':0.17,'i':10} ; clavicule (CLAV) - tete (RFHD) headu = {'t':1,'h':2,'r':0.12,'i':1} ; coude droit (RELB) epaule droite (RSHO) armr = {'t':5,'h':3,'r':0.05,'i':2} ; coude gauche (LELB) epaule gauche (LSHO) arml = {'t':6,'h':4,'r':0.05,'i':3} ; poignet droit (RWRB) coude droit (RELB) forearmr = {'t':7,'h':5,'r':0.05,'i':4} ; left wrist (LWRB) left elbow (LELB) forearml = {'t':8,'h':6,'r':0.05,'i':5} ; knee droit (RKNE) hanche droit (RFWT) thighr = {'t':11,'h':9,'r':0.05,'i':6} ; knee left (LKNE) hanche left (LFWT) thighl = {'t':12,'h':10,'r':0.05,'i':7} ; cheville droit (RANK) genou droit (RKNE) calfr = {'t':13,'h':11,'r':0.05,'i':8} ; cheville droit (LANK) genou droit (LKNE) calfl = {'t':14,'h':12,'r':0.05,'i':9} [device] 0 = {'typ':'static','name':'BeSpoon Phone','cyl':'trunku','l':0.1,'h':0.01,'a':0,'file':'S2R2.sh3','T':np.array([[1,0,0],[0,1,0],[0,0,1]])} 1 = {'typ':'static','name':'Movea Accel','cyl':'trunku','l':0.1,'h':0.01,'a':180,'file':'S2R2.sh3','T':np.array([[1,0,0],[0,1,0],[0,0,1]])} 2 = {'typ':'static','name':'Optivent Glass','cyl':'head','l':0.7,'h':0.01,'a':0,'file':'S2R2.sh3','T':np.array([[1,0,0],[0,1,0],[0,0,1]])} 3 = {'typ':'static','name':'Geonaute Podo','cyl':'trunkb','l':0.1,'h':0.01,'a':45,'file':'S2R2.sh3','T':np.array([[1,0,0],[0,1,0],[0,0,1]])} 4 = {'typ':'static','name':'Breizh Watch','cyl':'forearmr','l':0.2,'h':0.01,'a':0,'file':'S2R2.sh3','T':np.array([[1,0,0],[0,1,0],[0,0,1]])} 5 = {'typ':'static','name':'Breizh Watch','cyl':'forearml','l':0.2,'h':0.01,'a':0,'file':'S2R2.sh3','T':np.array([[1,0,0],[0,1,0],[0,0,1]])} [mocap] walk = '07_01_c3d'

In [23]:
John


Out[23]:
My name is : John

I have a Galaxy Gear device on the left forearm
I have a cardio device on the upper part of trunk
I am nowhere yet

filename : 07_01.c3d
nframes : 300
Centered : True
Mocap Speed : 1.366 m/s 

In [24]:
Francois = Body(_filebody='Francois.ini',_filemocap='07_01.c3d')
Francois


Out[24]:
My name is : Francois

I have a BeSpoon Phone device on the upper part of trunk
I have a Breizh Watch device on the left forearm
I have a Breizh Watch device on the right forearm
I am nowhere yet

filename : 07_01.c3d
nframes : 300
Centered : True
Mocap Speed : 1.366 m/s 

Loading a Motion Capture File

A .c3d motion capture file is loaded with the method loadC3D with as arguments the motion capture file and the number of frames to load.

The motion is represented as a sequence of framef stored in the d variable member.

It is possible to get the information from the C3D header by using the verbose option of the read_c3d function


In [25]:
# Video Frame Rate 
Vrate = 120
# Inter Frame
Tframe = 1./120
# select a number of frame 
nframes = 300
# Time duration of the whole selected frame sequence
Tfseq = Tframe*nframes
#
# load a .c3dmotion capture file
# this update the g.pos 
#
#bc.loadC3D(filename='07_01.c3d',nframes=nframes,centered=True)

The duration of the capture is


In [26]:
print "Duration of the motion capture sequence", Tfseq," seconds"


Duration of the motion capture sequence 2.5  seconds

d is a MDA of shape (3,npoint,nframe). It contains all the possible configurations of the body. In general it is supposed to be a cyclic motion as an integer number of walking steps. This allows to instantiate the body configuration anywhere else in space in a given trajectory.

A specific space-time configuration of the body is called a topos.


In [27]:
np.shape(John.d)


Out[27]:
(3, 16, 300)

Defining a trajectory

A Trajectory is a class which :

  • derives from a pandas DataFrame
  • is container for time,position,velocity and acceleration.

To define a default trajectory :


In [28]:
traj = Trajectory()
t = traj.generate()

In [29]:
traj.head()


Out[29]:
x y z vx vy vz ax ay az s
1970-01-01 00:00:00 0.000000 0.000000 -1.072687 0.299813 1 -2.741473 -0.005505 0 51.856703 0.000000
1970-01-01 00:00:00.204082 0.061186 0.204082 -1.632171 0.298689 1 7.841528 -0.010989 0 -31.262875 0.213056
1970-01-01 00:00:00.408163 0.122143 0.408163 -0.031859 0.296446 1 1.461349 -0.016433 0 -22.214424 0.426047
1970-01-01 00:00:00.612245 0.182642 0.612245 0.266375 0.293093 1 -3.072207 -0.021814 0 55.250719 0.638907
1970-01-01 00:00:00.816327 0.242457 0.816327 -0.360606 0.288641 1 8.203450 -0.027114 0 -78.760639 0.851574

5 rows × 10 columns


In [30]:
f,a = traj.plot()

settopos () method

Once the trajectory has been defined it is possible to send the body at the position corresponding to any time of the trajectory with the settopos method.

settopos takes as argument

  • A trajectory
  • A time index

In [31]:
traj.__repr__()


Out[31]:
'void Trajectory'

In [32]:
John.settopos(traj,t=5)

In [36]:
plt.figure(figsize=(15,20))
for t in np.arange(traj.tmin+0.4,traj.tmax,0.5):
    John.settopos(traj,t=t)
    f,a=John.show(color='b',plane='yz',topos=True)
    axis('off')



In [37]:
John


Out[37]:
My name is : John

I have a Galaxy Gear device on the left forearm
I have a cardio device on the upper part of trunk
My centroid position is 
[ 0.31956097  9.3877551 ]
filename : 07_01.c3d
nframes : 300
Centered : True
Mocap Speed : 1.366 m/s 

In [38]:
Francois.settopos(traj,t=6)
Francois


Out[38]:
My name is : Francois

I have a BeSpoon Phone device on the upper part of trunk
I have a Breizh Watch device on the left forearm
I have a Breizh Watch device on the right forearm
My centroid position is 
[ 0.97911919  5.91836735]
filename : 07_01.c3d
nframes : 300
Centered : True
Mocap Speed : 1.366 m/s 
  • 3 : dimension of space
  • 16 : number of nodes
  • 300 : number of frames

The figure below shows the projection in a vertival plane of the body nodes.

Centering the motion


In [ ]:
John.centered

In order to translate the motion in any point in space-time, a distinction is made between the real motion or topos and the centered motion capture which acts as a virtual motion.

Let $\mathbf{p}^k$ denotes the center of gravity of the body in the (O,x,y) plane


In [ ]:
John.center()

In [ ]:
a = np.hstack((John.vg,John.vg[:,-1][:,newaxis]))

$\mathbf{v}_g$ is the velocity vector of the gravity center of the body.


In [ ]:
print np.shape(John.pg)
print np.shape(John.vg)

In [ ]:
print John.vg[:,145]
print John.vg[:,298]

At that point the body structure is centered.

The frame is centered in the xy plane by substracting from the configuration of points the projection of the body in the xy plane.


In [ ]:
np.shape(John.d)

In [ ]:
John.npoints

Each frame is centered above the origin. For example for a walk motion the effect of the centering is just like if the body was still walking but not moving forward exactly in the same manner as a walk on a conveyor belt.


In [ ]:
pgc = np.sum(John.d[:,:,0],axis=1)/16
pg0 = John.pg[:,0]
print "True center of gravity", pg0
print "Center of gravity of the centered frame",pgc

In [ ]:
np.shape(John.pg)

The current file contains 300 frames


In [ ]:
tframe = arange(John.nframes)

In [ ]:
np.shape(John.pg[0:-1,:])

In [ ]:
xg = John.pg[0,:]
yg = John.pg[1,:]
zg = John.pg[2,:]
figure(figsize=(8,8))
subplot(311)
plot(tframe,xg)
title('x component')
ylabel('m')
subplot(312)
xlabel('frame index')
title('y component')
ylabel('m')
plot(tframe,yg)
subplot(313)
xlabel('frame index')
title('Motion capture centroid trajectory')
ylabel('m')
plot(xg,yg,'.b')

d = John.pg[0:-1,1:]-John.pg[0:-1,0:-1]
smocap = cumsum(sqrt(sum(d*d,axis=0)))

Vmocap = smocap[-1]/Tfseq
title('Length = '+str(smocap[-1])+' V = '+str(Vmocap*3.6)+' km/h')
axis('scaled')
axis('off')
plt.tight_layout()

In [ ]:
plot(smocap)
title('evolution of curvilinear abscisse from motion capture centroid trajectory') 
xlabel('frame index')
ylabel('distance (meters)')

Defining a large scale trajectory

A large scale trajectory is defined in the $(O,x,y)$ plane.

traj is a data structure (Npt,2)


In [ ]:
v = Vmocap
print v*3.6,"Kmph"

In [ ]:
# time in seconds
time = np.arange(0,10,0.01)
x = v*time
y = np.zeros(len(time))
z = np.zeros(len(time))
traj = Trajectory()
traj.generate(time,np.vstack((x,y,y)).T)
traj.tmax

In [ ]:
fig ,ax = traj.plot()
traj.head()

Trajectory

posvel()

The posvel() function (position and velocity) takes as arguments the following parameters

  • traj a plane trajectory object.
  • $t_k$ time for evaluation of topos
  • $T_{fs}$ duration of the periodic motion frame sequence

and returns

  • the frame index $k_f = \lfloor \frac{t_k \pmod{T_{fs}}}{t_f} \rfloor$
  • the trajectory index $k_t = \lfloor t_k \rfloor$
  • velocity unitary vector along motion capture frame $\hat{\mathbf{v}}_s = \frac{\mathbf{p}^g[k_f]-\mathbf{p}^g[k_f-1]}{|\mathbf{p}^g[k_f]-\mathbf{p}^g[k_f-1]|}$
  • $\hat{\mathbf{w}}_s = \mathbf{\hat{z}} \times \hat{\mathbf{v}}_s $
  • velocity unitary vector along trajectory $\hat{\mathbf{v}}_t = \frac{\mathbf{p}^t[k_t]-\mathbf{p}^g[k_t-1]}{|\mathbf{p}^g[k_t]-\mathbf{p}^t[k_t-1]|}$
  • $\hat{\mathbf{w}}_t = \mathbf{\hat{z}} \times \hat{\mathbf{v}}_t $

$t_f = \frac{T_{fs}}{Nf}$ is the interframe time or frame sampling period, it is equal to the whole duration of the motion sequence $T_{fs}$ divided by the number of frames

settopos is a method which takes as argument :

  • traj a plane trajectory (Npt,2)
  • $t_k$ time for evaluation of topos

In futher version of the project, this function will be modified to be able to avoid passing the whole trajectory.


In [ ]:
John.settopos(traj=traj,t=3)

There is now a new data structure in the Body objet. This data structure is called a topos.


In [ ]:
print np.shape(John.topos)

In [ ]:
John.topos

In [ ]:
John.show3()

In [ ]:
John.settopos(traj=traj,t=1)
fig,ax=John.plot3d(topos=True,col='#87CEEB')
John.settopos(traj=traj,t=2)
John.plot3d(topos=True,fig=fig,ax=ax,col='#7EC0EE')
John.settopos(traj=traj,t=3)
John.plot3d(topos=True,fig=fig,ax=ax,col='#6A5ACD')
John.settopos(traj=traj,t=4)
John.plot3d(topos=True,fig=fig,ax=ax,col='#7A67EE')
John.settopos(traj=traj,t=5)
John.plot3d(topos=True,fig=fig,ax=ax,col='#473C8B')

Definition of Several Coordinates systems

Each cylinder of the Body model bears one specific coordinate system.

One or several cylinder coordinate systems can be chosen to define the Body Local Coordinates System (BLCS) which is required for motion capture (BLCS) applications.

In general, the origin will be chosen on a position which is the most time invariant as on the chest or the back.

Those frames of references are all defined in the Global Coordinate System (GCS) of the scene.

Construction of the Cylinder Coordinate System (CCS)

The method setccs() is used to associate a Cylinder Coordinate System (CCS) to each cylinder of the bodyCylinder model. Notice that those cylinders coordinates systems are not known by the localization application. The localization application will define the BLCS from the position of radiating devices placed on the body surface.

Each basis is constructed with the function from geomutil.onbfromaxe() : orthonormal bases from axes. This function takes 2 sets of $n$ points $\mathbf{p}_{A,n}$ and $\mathbf{p}_{B,n}$ as input and provides an orthonormal basis as output.

3 unitary vectors are constructed :

$$\hat{\mathbf{w}}_n = \frac{\mathbf{p}_B-\mathbf{p}_A}{| \mathbf{p}_B-\mathbf{p}_A |} $$$$\hat{\mathbf{u}}_n = \frac{\hat{\mathbf{v}}_g - (\hat{\mathbf{v}}_g.{\hat{\mathbf{w}}_n}) \mathbf{\hat{w}}_n}{|\hat{\mathbf{v}_g} - (\hat{\mathbf{v}_g}.{\hat{\mathbf{w}}_n}) \mathbf{\hat{w}}_n|} $$$$\hat{\mathbf{v}}_n = \mathbf{\hat{w}}_n \times \mathbf{\hat{u}}_n $$

Where $\hat{\mathbf{v}}_g$ is the unit velocity vector along actual trajectory.

The outpout of geomutil.onbframe is an MDA $(3\times n \times 3)$ of $n$ unitary matrices aggregated along axis 1 $$\mathbf{T}_n=[\hat{\mathbf{u}}_n, \hat{\mathbf{v}}_n, \hat{\mathbf{w}}_n]$$

To create the CCS :


In [ ]:
John.setccs()

In [ ]:
import scipy.linalg as la 
print "ccs dimensions : ",np.shape(John.ccs)
print John.ccs[0,:,:]
print "Check determinant : ", la.det(John.ccs[0,:,:])

Create a Wireframe body representation from the body graph model

Representation of frames associated with the cylinder


In [ ]:
John.show3()

On the figure below the wireframe model is shown associated with the 11 CCS (Cylinder coordinates systems)


In [ ]:
Image('CCS.png')

Placing a dcs (Device Coordinate System ) on the cylinder

A DCS is refered by 4 numbers $(Id,l,h,\alpha)$

  • Id : Cylinder Id
  • l : length along cylinder
  • h : height above cylinder generatrix
  • alpha : angle from front direction (degrees)

In [ ]:
Id = 4 # 4 Left Arm
l  = 0.1 # Longitudinal coordinates
h  = 0.03 # height 
alpha = 45 # angle degrees

In [ ]:
John.dcyl

Rotate Matrix around z


In [ ]:
John.settopos(traj=traj,t=6,cs=True)

In [ ]:
John.dcyl

In [ ]:
John.show3(topos=True,dcs=True)

In [ ]:
John.show3(topos=True,pattern=True)

In [ ]:
Image('acs.png')

In [40]:
from IPython.core.display import HTML

def css_styling():
    styles = open("../doc/notebookstyles/custom.css", "r").read()
    return HTML(styles)
css_styling()


---------------------------------------------------------------------------
IOError                                   Traceback (most recent call last)
<ipython-input-40-c7949896ef01> in <module>()
      4     styles = open("../styles/custom.css", "r").read()
      5     return HTML(styles)
----> 6 css_styling()

<ipython-input-40-c7949896ef01> in css_styling()
      2 
      3 def css_styling():
----> 4     styles = open("../styles/custom.css", "r").read()
      5     return HTML(styles)
      6 css_styling()

IOError: [Errno 2] Aucun fichier ou dossier de ce type: '../styles/custom.css'