Grasshopper/MocapDemo¶
Data collection and analysis demonstration.
Much of the code was duplicated from Grasshopper/HouseOfCards:
CSV_Parser.py¶
This is an ad-hoc parser for CSV motion capture files generated by Motive 1.5. This is the Optitrack CSV 1.1 version, superseded in later versions of Motive. In particular, the IDeATe Optitrack motion capture system in Hunt A10A runs Motive 1.9 which uses an entirely different Optitrack CSV 1.21 version format.
Please note that this code permutes the dimensions of incoming point data to better match Rhino conventions in which Z is ‘up’.
## A single purpose script to parse motive tracker version 1.5 csv export data.
## Rigid bodies represented as GH planes using Rhino Script Syntax.
## Josh Bard, Carnegie Mellon University 01.20.17
import rhinoscriptsyntax as rs
def frameDataFromCSV(CSVPath):
""" creates a structured list of MOCAP frame data from a single
Motive Tracker session exported to CSV
Input: path to CSV file
Output: a list of frames with the following structure:
[timestamp, for each rb [id,[x,y,z],[roll,pitch,yaw] or
[None](for dropped rb), num dropped rbs in frame]"""
frames = []
with open (CSVPath) as MocapData: # open csv file and close when finished
for line in MocapData.readlines():
rawLine = line.split(',')
# determine total num of rbs tracked during session found in
# default "info" file header
if (rawLine[0] == "info" and rawLine[1] == "rigidbodycount"):
rb_Total = int(rawLine[2])
# construct data at each frame
elif (rawLine[0] == "frame"): # Motive convention for labeling rows
rb_Count = int(rawLine[3]) # num rbs tracked in frame
rb_Dropped = rb_Total - rb_Count # num dropped rbs in frame
# initialize single frame
frame = []
frame.append(float(rawLine[2])) # add timestamp
frame.extend([[None] for _ in range(rb_Total)]) # dropped rbs remain None
frame.append(rb_Dropped)
# for each tracked rigid body compile frame data
for i in range (rb_Count):
fos = i*11 # fixed offset per rigid body def
id = int(rawLine [4 + fos]) # motive assigned id
pos = [float(x) for x in rawLine[5+fos:8+fos]] # [x,y,z]
orient = [float(x) for x in rawLine[12+fos:15+fos]] #[roll,pitch,yaw]
frame[id] = [id]+[pos] + [orient]
frames.append(frame)
return frames
def movePlane(x,y,z,scale = True):
"""move and optionally scale a generic plane
Note: Motive units set to meters. scale if rhino units set to mm"""
basePlane = rs.WorldXYPlane()
movePoint = rs.AddPoint(x,y,z)
origin = (0,0,0)
if scale == True:
scale = (1000,1000,1000)
movePoint = rs.ScaleObject(movePoint,origin,scale)
movePlane = rs.MovePlane(basePlane,movePoint)
return movePlane
def rotatePlane (x,y,z,rz,rx,ry,scale):
"""Rotate base plane about rz,ry,rx(yaw,pitch,roll)"""
basePlane = movePlane(x,y,z,scale)
rzPlane = rs.RotatePlane(basePlane,rz,basePlane.ZAxis)
ryPlane = rs.RotatePlane(rzPlane,ry,rzPlane.YAxis)
rotatePlane = rs.RotatePlane(ryPlane,rx,ryPlane.XAxis)
return rotatePlane
def genRbPlane(rb_data,scale):
if (rb_data[0] != None):
x,y,z = rb_data[1][0],rb_data[1][1],rb_data[1][2]
rz,ry,rx = rb_data[2][0],rb_data[2][1],rb_data[2][2]
rb_plane = rotatePlane(x,y,z,rz,rx,ry,scale)
return rb_plane
else: return None
def compileRbPlanes(frame_data,scale):
rb_num = (len(frame_data[0])- 2)
rb_planes = [[] for _ in range(rb_num)]
for frame in frame_data:
for rb in range(rb_num):
rb_plane = genRbPlane(frame[rb+1],scale)
rb_planes[rb].append(rb_plane)
return rb_planes
def list_to_tree(input, none_and_holes=True, source=[0]):
"""Transforms nestings of lists or tuples to a Grasshopper DataTree
from https://gist.github.com/piac/ef91ac83cb5ee92a1294"""
from Grasshopper import DataTree as Tree
from Grasshopper.Kernel.Data import GH_Path as Path
from System import Array
def proc(input,tree,track):
path = Path(Array[int](track))
if len(input) == 0 and none_and_holes: tree.EnsurePath(path); return
for i,item in enumerate(input):
if hasattr(item, '__iter__'): #if list or tuple
track.append(i); proc(item,tree,track); track.pop()
else:
if none_and_holes: tree.Insert(item,path,i)
elif item is not None: tree.Add(item,path)
if input is not None: t=Tree[object]();proc(input,t,source[:]);return t
def printMOCAPSessionReport (frame_data):
frame_num = len(frame_data)
rb_num = (len(frame_data[0])- 2)
droppedRbCount = 0
droppedFrameCount = 0
captureDuration = frame_data[-1][0]
for frame in frame_data:
droppedRbCount += frame[-1]
if frame[-1] == rb_num: droppedFrameCount +=1
per_droppedFrames = droppedFrameCount/frame_num
per_droppedRbs = droppedRbCount/(rb_num * frame_num)
report = (
'There were %d rigid bodies tracked in the scene\n'
'There were %d frames captured during this session\n'
'There were %d (%.3f%%) dropped rigid bodies during the session\n'
'There were %d (%.3f%%) dropped frames during the session\n'
'capture duration %.2f seconds'
) %(rb_num,frame_num,droppedRbCount,per_droppedRbs,
droppedFrameCount,per_droppedFrames,captureDuration)
return report
################################################################################
## Generate data for GH Output
frame_data = frameDataFromCSV(CSVPath)
print (printMOCAPSessionReport(frame_data))
rb_planes = list_to_tree(compileRbPlanes(frame_data,scale))