Threading Motion Capture at a High Rate.

using windows xp, panda 1.8.0

Im using PhaseSpace to capture motion and import to “virtual reality” set up. The capture freq of the MoCap is about 150 Hz while the game updates at a much slower rate. However, this is causing the framerate to lag.

I NEED to store the MoCap at the high frequency for post-game data analysis, so I cant slow the server down.

Preface: Im very new to python programming. if you reference certain calls, please include what their function is briefly, it would be much appreciated.

Ive tried threading, but cant quite get it working. I feel I need to have the collection function threaded versus the game, but dont know how to thread the entire game? if i even need to? just the task manager? just the one task that updates the frame?

Any ideas would be great, but keep in mind I need to keep data collection as high as possible, which I think should be kept outside of the main game, so it doesnt slow it down too much.

Thanks!

here is the code without any threading built in. is there a better way of handing the server information being brought in and the game updating?

import sys,os
from OWL import *
from math import pi, sin, cos, acos, atan, tan
from random import *
import time


from direct.task import Task
from direct.stdpy import thread
from direct.actor.Actor import Actor
from direct.gui.DirectGui import OnscreenText
from direct.showbase.ShowBase import ShowBase
from direct.showbase.DirectObject import DirectObject
from direct.interval.IntervalGlobal import *

from panda3d.core import *
from pandac.PandaModules import *


#-----------------------------------------
os.system('cls')
print Thread.isThreadingSupported()

MARKER_COUNT = 5
print "MARKER_COUNT: " , MARKER_COUNT
SERVER_NAME = "192.168.2.7"
print "Server: ", SERVER_NAME
INIT_FLAGS = 0

#quits if cant access the server
if(owlInit(SERVER_NAME, INIT_FLAGS) < 0):
    print "init error: ", owlGetError()
    sys.exit(0)

# create tracker 0
tracker = 0
owlTrackeri(tracker, OWL_CREATE, OWL_POINT_TRACKER)

# set markers
for i in range(MARKER_COUNT):
    owlMarkeri(MARKER(tracker, i), OWL_SET_LED, i)

# activate tracker
owlTracker(tracker, OWL_ENABLE)

# flush requests and check for errors
if(owlGetStatus() == 0):
    owl_print_error("error in point tracker setup", owlGetError())
    sys.exit(0)

# set define frequency
owlSetFloat(OWL_FREQUENCY, OWL_MAX_FREQUENCY)

# start streaming
owlSetInteger(OWL_STREAMING, OWL_ENABLE)

global markers, streaming, n, xCal, yCal, zCal, dtext
markers = []
digits={}
streaming=0
n=[]
xCal=0
yCal=0
zCal=0
camMove=1
updateTrackers=0
updateText=0
DO=DirectObject()
#---------------------------
class MyApp(ShowBase):

    
 
    def __init__(self):
        ShowBase.__init__(self)
        self.dtext={'digitNumber':'text'}
        base.disableMouse()
        for i in range(MARKER_COUNT):
            self.dtext[i]=self.genLabelText("Digit_"+str(i+1)+": ",3+i)
        self.calText=self.genLabelText("xCal:" + str(xCal)+ ", yCal: " + str(yCal) +", zCal: " + str(zCal),2)    
        self.genLabelText("Press 'esc' to quit",0)
        self.genLabelText("q: streamPS, w:updateText e:loadFingers, r:calibrate, t:fixCamera",1)
        self.fpstext=self.genLabelText("FPS: ",10)
        DO.accept("escape", self.endProgram)
        DO.acceptOnce("q", self.startstreamPS)
        DO.accept("w", self.startupdateText)
        DO.accept("e", self.loadFingers,[MARKER_COUNT])
        DO.accept("r", self.calibrate)
        DO.accept("t", self.fixCamera)
        self.timeS=time.time()

        
        
    def genLabelText(self, text, i):
        return OnscreenText(text = text, pos = (-1.2, .95-.05*i), fg=(1,1,0,1), align = TextNode.ALeft, scale = .05)



    def startstreamPS(self):
        print("stream started")
        self.taskMgr.add(self.streamPS,"streamPS")
    def startupdateText(self):
        global updateText
        updateText=1
    def loadFingers(self, MARKER_COUNT):
        global  updateTrackers, digits
        tip = loader.loadModel("models/ball.egg.pz")
        
        for i in range(MARKER_COUNT):
            tipN=NodePath('tipN')
            tipN.reparentTo(render)
            digits[i]=tip.copyTo(tipN)
            digits[i].setPos(i,0,0)
            digits[i].setScale(.5,.5,.5)
        updateTrackers=1



        
        
    def calibrate(self):
        global markers, xCal, yCal, zCal, n
        
        if n >0:
            xCal=markers[1].x
            yCal=-markers[1].z
            zCal=markers[1].y
            self.calText.setText("xCal:" + str(xCal)+ ", yCal: " + str(yCal) +", zCal: " + str(zCal))

    def fixCamera(self):
        global camMove
        camMove=0
        self.camera.setPos(0,-15,10)
        self.camera.lookAt(0,0,0)
    
    def streamPS(self, task):
        global xCal, yCal, zCal, markers, n, MARKER_COUNT, updateTrackers, updateText, camMove
        markers = []
        n=owlGetMarkers(markers, 32)
        if n==MARKER_COUNT:
            color=(0,255,0)
        

        if updateTrackers>0:
            for i in range(n):
                if markers[i].cond>0:
                
                    digits[i].setPos((markers[i].x-xCal)/50,(-markers[i].z-yCal)/50,(markers[i].y-zCal)/50)
                if camMove>0:
                    self.camera.setPos(digits[1].getX(),-digits[1].getZ()-50,digits[1].getY()+50)
                    self.camera.lookAt(digits[1].getPos())
        

        if updateText>0:
            for i in range(n):
                self.dtext[i].setText("Digit_"+str(i+1)+": "+str(['%.2f' % markers[i].x]) + ' ' + str(['%.2f' % markers[i].y]) + ' ' + str(['%.2f' % markers[i].z]) )

        fps=1/(time.time()-self.timeS+0.000000001)
        self.timeS=time.time()
        self.fpstext.setText("FPS: " + str(fps))
        
        return Task.cont


    def endProgram(self):
        
        print("end")
        owlDone()
        sys.exit()
    
  





    

app = MyApp()
app.run()

If speed is an issue, have you considered moving to C++? I think that you’ll find Python to be rather slower overall, and switching to C++ might improve performance (presuming that you’re not bound by your hardware, of course).

I don’t really understand what you need, but tasks will run once per frame. What do you need panda to do? Render at 150 fps or give some information 150 times per second?

Also, is your FPS metter working? IIRC the text node by default is not editable. BTW, it is a lot easier to use:

base.setFrameRateMeter(True)

@thaumaturge: getting my server running in c++ won’t be a problem, but how would I draw that data in for the game?

@knolancross: I need panda to update the game at whatever rate, doesn’t really matter. But I need to save my mocap data at as fast a rate as possible. What I need to do is separate the capture rate and the framerate so that they are independent.

Tasks won’t save you then, because they run as fast as the framerate.

I believe you may create a separate taskChain, setting the threadsNumber to 1, turn off the tickClock and the frameSync, to have a thread that run faster than the framerate. This may work, but be warned that you may run into race conditions.

Adding the task to a threaded taskChain, as KnolanCross suggests, is good advice and will do the trick. The task will run independently of the frame rate and can be much faster.

KC’s warning about race conditions is also worth noting. Whenever you write threaded code, you run the risk of introducing race conditions unless you are very careful to protect all critical sections within mutexes or similar threading constructs. There is a whole science to writing correct multithreaded code; and it can take a while to learn it well.

Another approach is to use the subprocess module to poll the tracker data in a completely isolated process. This has no risk of race conditions, but communication with the main process is more difficult and usually requires the use of sockets or some such.

David

Ill play around with the additional taskchain first. It was also suggested I use C++ to do the server code, to make it faster. Would it be possible to have a seperate task chain for a C++ function?

Also, Im a little confused on ‘race condition’ and was hoping for a quick explanation? How I see it is multiple processes assign to one variable and its a fight to which assignment is first/prevalent? Would that matter with only one function assigning it?

A race condition is a coding error in which the result of an algorithm varies according to the random order in which multiple threads operate. One common example of a race condition is multiple assignment to the same variable, as you mention. There are many others. The main point of a race condition is that it’s hard to detect, hard to diagnose, and leads to random failures of your program even after you’ve thoroughly tested it.

All that said, you’re more safe from race conditions in Python than you would be in a language like C++, since in Python each individual statement is guaranteed to be atomic, so you can (for instance) add things to a dictionary in one thread at the same time another thread is reading the same dictionary, and you won’t royally hose the dictionary index.

You can add C++ functions to a threaded task chain, but I don’t see any reasons for you to use C++ to solve this particular problem. I don’t think the language choice is relevant, since your fundamental problem is about decoupling rendering from socket manipulation, which is different from simple performance optimization.

David

well, i need my data stream to be as fast as possible, depending on the rate i get from python vs c++, im not sure which i’ll use. I think the data just gets streamed at about 150 Hz, but thats an issue i need to sort out.

You can write slow code in Python or in C++; you can write fast code in Python or in C++. 150Hz is not a problem for Python.

David

the task chains seems to work well. THanks! However, two little issues that I was hoping to resolve.

Im also saving the data, but the “timestamp” part of the data line seems to only update for every game frame, rather than the loop its within, the one added to the threaded task chain.

creating the chain:

        self.taskMgr.setupTaskChain('streamChain', numThreads = 1, tickClock = False, frameBudget=0.005)

adding the method to the taskChain:

app.taskMgr.add(app.savePS,"savePS",taskChain='streamChain')

heres the method for saving the PhaseSpace data:

def savePS(self, task):
        markers,n=getTrackers()
        dataStr=""
        if(n > 0):
            dataStr="%d %.4f"%(markers[1].frame, time.time()-self.timeS)
            for i in range(n):
                                   
                dataStr= dataStr+" %d %d %.3f %.3f %.3f %.3f %.3f %.3f  "%(i+1,markers[i].cond, markers[i].x,markers[i].y,markers[i].z,self.xCal, self.yCal, self.zCal)
            if self.record:
                self.file.write('\n'+dataStr)
        return Task.cont

and heres an example of the data generated:
[frame# timestamp marker# markerCondition X Y Z]

69 5.6870 1 4 -218.788 580.536 -161.277   
70 5.6870 1 4 -218.787 580.538 -161.270     
71 5.6870 1 4 -218.802 580.553 -161.297    
72 5.6870 1 4 -218.805 580.559 -161.301   
73 5.6870 1 4 -218.800 580.522 -161.304    
74 5.6870 1 4 -218.801 580.516 -161.308     
75 5.6870 1 4 -218.814 580.534 -161.306    
76 5.6870 1 4 -218.819 580.535 -161.307    
77 5.6870 1 4 -218.808 580.533 -161.272    
78 5.6870 1 4 -218.808 580.533 -161.263    
79 5.6870 1 4 -218.814 580.532 -161.295     
80 5.7030 1 4 -218.816 580.532 -161.298    
81 5.7030 1 4 -218.811 580.516 -161.267    
82 5.7030 1 4 -218.810 580.512 -161.261   
83 5.7030 1 4 -218.820 580.537 -161.307   
84 5.7030 1 4 -218.823 580.541 -161.315    
85 5.7030 1 4 -218.824 580.527 -161.279   
86 5.7030 1 4 -218.826 580.525 -161.275   
87 5.7030 1 4 -218.816 580.537 -161.297   
88 5.7030 1 4 -218.815 580.539 -161.300  
89 5.7030 1 4 -218.819 580.547 -161.304    
90 5.7030 1 4 -218.819 580.551 -161.307   
91 5.7030 1 4 -218.819 580.549 -161.309    
92 5.7030 1 4 -218.819 580.552 -161.312    
93 5.7030 1 4 -218.817 580.537 -161.280    
94 5.7030 1 4 -218.816 580.535 -161.273    
95 5.7030 1 4 -218.817 580.548 -161.301   
96 5.7030 1 4 -218.817 580.550 -161.304    
97 5.7030 1 4 -218.824 580.528 -161.275   
98 5.7030 1 4 -218.826 580.524 -161.269   
99 5.7030 1 4 -218.835 580.525 -161.298  

so the XYZ data is changing, the frame is updating, but thats all being drawn from the server. the LOOP creates the time stamp. I think its something from when “time.time()” in the savePS() method actually is called, but dont know how to fix/troubleshoot that.


the other issue is just that if I dont tax the savePS method, the game lags the MotionCapture terribly. I had it so that the entire method’s code was conditional on self.record, rather than just the file.write call. Now it works fine, but i dont really know why. I suspect its because the default gameLoop is going to fast, but dont know how to govern that to go slower?
[/code]

Using time.time() is unreliable in a 3-D application. Instead, use globalClock.getRealTime() if you need a counter that increases continuously.

You can delay the main thread if you need to by using Thread.sleep(0.01) or whatever value works for you. You can put this call into its own task or call it as part of some other task. You can also just use base.setSleep(0.01) which automatically starts a task for this purpose.

David