PDA

View Full Version : Intermittent orthographic stereo projection problems


AySz88
02-14-2012, 06:46 PM
I have an application where I am using both stereo and orthographic projection. For apparently arbitrary reasons, everything in the scene will suddenly have an incorrect disparity, all too far forward or too far back, and sometimes with a spurious vertical component - as if the projection has gotten changed somewhere. It seems to happen just before or after "important" events fire, such as timer events.

I have code that can reproduce the bug reliably for me, but very subtle changes (such as changing the frequencies of the aforementioned timers) will lead to the bug going in and out of existence, so I don't know if it would be helpful at all.

Any suggestions as to what could be going wrong? I also have been trying to narrow down what exactly changes - my hunch is the screen distance, but is there any way to query its value?

[edit] I do have trackers that can be linked to the viewpoint when I'm running in cave mode, but I break the links with the viewpoint when I switch to orthographic projection (by giving a None tracker to CaveView, IIRC). Are there any problems with doing it this way?

farshizzo
02-15-2012, 09:49 AM
Are you using the vizcave module? Depending on how you setup the Cave object, it will update the projection matrix every frame to account for the latest position of the head tracker. If you have other code running in a timer that tries to change the projection, then this could be the cause of the conflict. It's hard to say without seeing some sample code.

AySz88
02-15-2012, 03:10 PM
I do use the vizcave module, but even when I make sure no viewpoints are moving around (like by replacing the trackers with something that doesn't move), I get a lot of different disparity values. The disparity only seems to change when certain important code is run, too (it is when I re-apply the orthographic projection, for example).

Here's some code that reproduces the problem. Press 4 or 6 to have it repeatedly display the "stimulus". In case of no stereo equipment, the most obvious symptom is that the white box doesn't always stay in the same centered location from one trial to the next - it appears left or right by some unpredictable amount. (With stereo equipment, it's more apparent that what is happening is that the disparity is changing.) It usually starts happening by the 5th trial.

Here's the file for the stimulus part with as much code as I dared to remove taken out. I don't think most of this is too important, except that I do call orthoUpdateCaveAndView() in the restart() helper function, which is run each time you press 4 or 6.


import viz
import vizact
import vizshape
import sunyhardware

def makeCylinderSpheres(radius, height, vertexCount, vertexRadius, vac=None, dc=None):
# FIXME quite slow - profile for problems
import random
from math import sin, cos, pi

if vac and vertexRadius != 1.0:
print "WARNING: setting vertex radius to 1.0 meters"
print " for VisualAngleConstantizer to scale later"
vertexRadius = 1.0

cylinder = viz.addGroup()

# Distribution of verts in theta, stratifed, parameters by trial and error
# TODO parameterize
skipWidth = 4.0*pi/5.0
sliceWidth = pi/3.0
# in height
base = -height*0.5
climb = height/(vertexCount-1)

for vertNum in xrange(vertexCount):
# HACK Don't render the middle few to leave room for fixation dot
if abs(vertNum - vertexCount/2.0) <= 1.1:
continue;
theta = skipWidth*vertNum + sliceWidth*random.random()
pos = [radius*sin(theta), base + vertNum*climb, radius*cos(theta)]

sphere = vizshape.addSphere(radius=vertexRadius, parent=cylinder)
sphere.setPosition(*pos)

if dc:
dc.add(sphere)
if vac:
vac.add(sphere, vertexRadius*2)

return cylinder

# TODO error message if HiBall not available?

(METERS, DEGREES) = xrange(2) # valid units

(offX, offY, offZ) = sunyhardware.getValue(('xZero', 'yZero', 'zZero'))

DEFAULT_PARAMS = {
'vertexRadius': 0.01, # radius of spheres, in visual angle or length
'vertexUnits': METERS, # unit of the above radius ('degrees' only works if not orthographic)
# Rotation options
'rotateSpeed': 180.0, # Degrees per second, positive = front goes left
'spinAxis': (0,1,0), # Axis of rotation in world space, not local cube space

'fixationSize': 0.02,
'fixationColor': [0.5,0.5,0.5],
'fixationFrameSize': 0.005, # just the frame around fixationSize
'fixationFrameColor': [1.0,1.0,1.0],

# Graphical options
'orthographic': True,
'objectConstructor': makeCylinderSpheres,
# Define either 'dcTable' or a 'dc' DepthColorizer object; 'dc' takes priority over 'dcTable'
'dcTable': ( # (distance from head, [H,S,V])
( 1.7, [240, 0.75, 0.0]),
( 1.7, [240, 0.75, 1.0]),
( 2.3, [360, 0.75, 1.0]),
( 2.3, [360, 0.75, 0.0])
),

#Interface options
'interfaceParams': {
'usePosition': False, #True,
'centerThreshold': 180, #offX, # Dividing line between left and right (degrees or meters)
'centerIgnore': 5, #0.02 # Zone (in each direction) in which answer is ambiguous and ignored (degrees or meters)
'reverse': True, # Move or point rightwards for a 'correct' answer normally, leftwards for not
},
'indicParams': {
# visual indicators for pointing left/center/right
'leftObjPos': [-0.5, -0.25, 0], # Relative to center position
'ambigObjPos': [0.0, -0.5, 0], # Relative to center position
'rightObjPos': [0.5, -0.25, 0], # Relative to center position
}
}

DEFAULT_PARAMS.update({
'objectParams': {
'radius': 0.10, # of the circle around the cylinder
'height': 0.45,
'vertexCount': 24,
},
'startAngle': (0,0,0), # Euler angles of initial rotation
'centerPosition': [offX, offY, offZ]
})

class Stimulus(viz.EventClass):
""" TODO documentation """

TIMER_ID = 0

def __init__(self, stimParams):
viz.EventClass.__init__(self)

self.headTrkr = stimParams['headTrkr']

# Construct stimulus object to display
params = stimParams['objectParams'].copy()

params['dc']=None
params['vertexRadius'] = stimParams['vertexRadius']
self.obj = stimParams['objectConstructor'](**params)

# Apply actual spin to object
self.spinObject = viz.addGroup()
self.obj.parent(self.spinObject)

self.spinObject.setPosition(stimParams['centerPosition'])
spinAxis = stimParams['spinAxis']
spin = vizact.spin(spinAxis[0], spinAxis[1], spinAxis[2], stimParams['rotateSpeed'], viz.FOREVER)
self.spinObject.addAction(spin)
self.obj.visible(viz.OFF)
self.spinObject.disable(viz.ANIMATIONS)

# Create fixation block
self.fixGroup = viz.addGroup()

self.fixation = vizshape.addQuad(
size=(stimParams['fixationSize'], stimParams['fixationSize']),
parent = self.fixGroup)
self.fixation.color(stimParams['fixationColor'])
self.fixation.billboard(viz.BILLBOARD_VIEW)

totalFrameSize = stimParams['fixationSize'] + stimParams['fixationFrameSize']
self.fixationFrame = vizshape.addQuad(size=(totalFrameSize, totalFrameSize),
parent = self.fixGroup)
self.fixationFrame.color(stimParams['fixationFrameColor'])
self.fixationFrame.billboard(viz.BILLBOARD_VIEW)

self.fixGroup.setPosition(stimParams['centerPosition'])
self.fixGroup.visible(viz.OFF)

# Save parameters for later
self.stimParams = stimParams

def startFixation(self, trialParams):
self.obj.setEuler(*self.stimParams['startAngle'])

viz.ipd(trialParams['ipd'])

self.fixGroup.visible(viz.ON)

def startStimulus(self, trialParams):
self.spinObject.enable(viz.ANIMATIONS)
self.obj.visible(viz.ON)

def endStimulus(self, stairCallback):
self.fixGroup.visible(viz.OFF)
self.obj.visible(viz.OFF)
self.spinObject.disable(viz.ANIMATIONS)

#self.callback(viz.SENSOR_UP_EVENT, self.onButtonUp)
self.callback(viz.KEYUP_EVENT, self.onKeyUp)

self.stairCallback = stairCallback

def onKeyUp(self, key):
if key == '4' or key == '6':
self.callback(viz.KEYUP_EVENT, None)
return self.stairCallback(key == '4', False)
else:
pass # TODO 'try again' prompt

if __name__=='__main__':
viz.go()

stimParams = DEFAULT_PARAMS.copy()

trackers = sunyhardware.getAllSensors()
(stimParams['headTrkr'], stimParams['stylusTrkr'], stimParams['stylusBtn']) = trackers

# test multiple simultaneous stimulus objects
stim = Stimulus(stimParams)
#stim2 = Stimulus(stimParams)

RESTART_DELAY = 0.5

import random

def delayedRestart(*args):
viz.callback(viz.TIMER_EVENT, restart)
viz.starttimer(0, RESTART_DELAY)

def restart(*args):
print 'starting 1'
headTrkr = stimParams['headTrkr']
sunyhardware.orthoUpdateCaveAndView(
tracker = headTrkr.head,
cave = headTrkr.cave,
view = headTrkr.view,
ortho = stimParams['orthographic'])
stim.startFixation({'ipd': random.uniform(-0.05, 0.05)})
stim.startStimulus({})

viz.callback(viz.TIMER_EVENT, endStim)
viz.starttimer(0, 1.0)

def endStim(*args):
print 'ended 1'
viz.killtimer(0)
#stim.endStimulus(delayedRestart2)
stim.endStimulus(delayedRestart)

delayedRestart()


...and in the next post(s) are the potentially more-relevant hardware abstraction code. The most relevant functions are loadCave() (where I define helper functions makeOrthographic() and makePerspective()) and orthoUpdateCaveAndView(), which does the dirty work of turning off caves and such.

I edited the file so you'd get dummy VRPN sensors from localhost, which definitely don't move at all. It throws slightly-annoying "no response from server" warnings, though.

AySz88
02-15-2012, 03:12 PM
(The bulk of the file is mostly due to documentation, which I'm keeping intact in case it's useful.)

Part 1 of sunyhardware.py:

"""
This module provides functions and classes related to the utilization of
virtual reality hardware with Vizard in the lab of _______ at SUNY Optometry.

Variables: None.
Functions: getValue, loadCave, linkView,
isHiBallAvailable, getTracker, getAllSensors.
Classes: Keyboard, HiBall.

For additional information about a particular function or class, please see
the documentation string for that attribute.

Usage examples:
>>> (headTrkr, stylusTrkr, stylusBtn) = sunyhardware.getAllSensors()
>>> tracker = HiBall()
>>> screenWidth = getValue('screenWidth')
>>> print loadCave.__doc__

<removed>
August 4, 2008
edited by:
<removed>
July 29, 2011
"""

from __future__ import division #implements "/" as division without truncation regardless of its operands
import viz #standard Vizard module
import vizcave #Vizard virtual reality cave module

#private variables (measurements taken by hand [AEY 8/11/2011] in meters)
#1024x768, projector settings: 'size' = 1.060, 'vertical stretch' = 1.000
#most are a rough average of two measurements
_screenHeight = 1.779
_screenWidth = 2.396
_screenHeightOffset = (
0.448 #floor-to-frame
+ 0.034 #plus frame width
+ 0.030 #plus frame-to-image
)
_screenWidthOffset = (
-0.060 #origin-to-right-wall
+ 0.270 #plus wall-to-big-frame
+ 0.034 #plus frame width
+ 0.030 #plus frame-to-image
)
_screenDepthOffset = (
0.573 #origin-to-wall
+ 0.083 #plus wall-to-frame
+ 0.023 #plus frame width
+ 0.008 #plus glass-to-image (glass thickness)
)
_eyesHeightOffset = 0.140 #approx dist of eyes below HiBall (MAR+SJH 1/23/2009, RBM subject)
_eyesDepthOffset = 0.110 #approx dist of eyes in front of HiBall (MAR+SJH 1/23/2009, RBM subject)

_stylusOffset = [-0.00175, -0.1050, -0.1693] #TODO from HiBall stylus calibration procedure, currently 2003 numbers
# intended for link.preTrans(); see Help (Reference > Linking > Operators on Links)

def getValue(parameter):
"""
Returns the read-only value of the named private attribute.

Currently accepted parameter names include: 'screenHeight', 'screenWidth',
'screenHeightOffset', 'screenWidthOffset', 'screenDepthOffset',
'eyesHeightOffset', 'eyesDepthOffset'.

Definition:
getValue(parameter)

Usage:
>>> screenWidth = getValue('screenWidth')
"""

if parameter=='screenWidth':
return _screenWidth
elif parameter=='screenHeight':
return _screenHeight
elif parameter=='screenHeightOffset':
return _screenHeightOffset
elif parameter=='screenWidthOffset':
return _screenWidthOffset
elif parameter=='screenDepthOffset':
return _screenDepthOffset
elif parameter=='eyesHeightOffset':
return _eyesHeightOffset
elif parameter=='eyesDepthOffset':
return _eyesDepthOffset
elif parameter=='xZero':
return -_screenWidthOffset - _screenWidth/2
elif parameter=='yZero':
return _screenHeightOffset + _screenHeight/2
elif parameter=='zZero':
return _screenDepthOffset
elif parameter=='stylusOffset':
return _stylusOffset
elif isinstance(parameter,tuple): # must use tuples since strings are lists
return [getValue(x) for x in parameter]

def getVisualBounds(origin):
# Given HiBall coordinates of a viewpoint, return elevations and azimuths of each edge of screen (l,r,t,b)
from math import atan2, degrees
(x0, y0, z0) = getValue(('xZero', 'yZero', 'zZero'))
(w, h) = getValue(('screenWidth', 'screenHeight'))
[x,y,z] = origin
lrtb = (x0-w/2-x, x0+w/2-x, y0+h/2-y, y0-h/2-y)
angles = map(lambda opposite: atan2(opposite,z), lrtb)
print "bounds at", origin
print "... are", map(degrees, angles)
return angles

def loadCave(tracker=None,orthographic=False):
"""
Returns a Vizard cave optionally driven by tracker coordinates.

Creates a vizcave wall based on coordinates for the Christie Mirage S+4K
projection screen. Creates a stereo vizcave cave to which the wall and
optionally provided tracker are added. The cave can optionally be placed
in orthographic projection (defaults to perspective). The tracker, if
provided, sends position and orientation information to the cave, which
is used in the calculation of the viewing frustum. Note that the active
viewport is not updated with the tracking position; please use linkView
to do so.

Definition:
loadCave(tracker=None,orthographic=False)

Usage:
>>> cave = loadCave(tracker)
"""

#save default inter-pupilary distance (IPD)
ipd = viz.MainWindow.getIPD()
if not isinstance(tracker,viz.VizView):
ipd = -ipd #KLUDGE - this solves eye-swap problem, but still not sure where this problem comes from

def makeOrthographic():
# TODO Doesn't change IPD based on orientation of eyes with head
# Requires the wall to be in the X-Y plane
# to set up tracker so that stereo IPD will be correct?

# NOTE this is the "intended" way of doing this, but has drawbacks
viz.MainView.eyeheight(0)
viz.MainView.reset(viz.RESET_POS)
viz.MainWindow.ortho(
-_screenWidthOffset - _screenWidth,
-_screenWidthOffset,
_screenHeightOffset,
_screenHeightOffset + _screenHeight,
-1, -1, # automatic near and far clip
eye = viz.BOTH_EYE)
viz.MainWindow.screenDistance(_screenDepthOffset)

return None

cave = vizcave.Cave()

def makePerspective():
#setup wall using world coordinates
upperLeft = [-_screenWidthOffset-_screenWidth, _screenHeightOffset+_screenHeight, _screenDepthOffset]
upperRight = [-_screenWidthOffset, _screenHeightOffset+_screenHeight, _screenDepthOffset]
lowerLeft = [-_screenWidthOffset-_screenWidth, _screenHeightOffset, _screenDepthOffset]
lowerRight = [-_screenWidthOffset, _screenHeightOffset, _screenDepthOffset]
wall = vizcave.Wall('wall',upperLeft,upperRight,lowerLeft ,lowerRight)

#setup cave with wall and tracker (optional)
cave.setIPD(ipd)
cave.addWall(wall)
if tracker:
cave.setTracker(pos=tracker, ori=tracker)

tracker.makeOrthographic = makeOrthographic
tracker.makePerspective = makePerspective

if orthographic:
tracker.makeOrthographic()
else:
tracker.makePerspective()

return cave

# FAILED METHOD
# Below code **messes up stereo** when giving orthographic-like projection
"""
#setup wall using world coordinates
upperLeft = [-_screenWidthOffset-_screenWidth, _screenHeightOffset+_screenHeight, _screenDepthOffset]
upperRight = [-_screenWidthOffset, _screenHeightOffset+_screenHeight, _screenDepthOffset]
lowerLeft = [-_screenWidthOffset-_screenWidth, _screenHeightOffset, _screenDepthOffset]
lowerRight = [-_screenWidthOffset, _screenHeightOffset, _screenDepthOffset]
wall = vizcave.Wall('wall',upperLeft,upperRight,lowerLeft ,lowerRight)

#setup cave with wall and tracker (optional)
cave = vizcave.Cave()
cave.setIPD(ipd)
cave.addWall(wall)
if tracker:
# HACK if orthographic, shift viewpoint as if view is from far away (image as through a telescope)
cave.orthoTracker = viz.link(tracker, viz.NullLinkable)
cave.orthoTracker.postTrans([0,0,-100])

def makeOrthographic():
cave.setTracker(pos=cave.orthoTracker, ori=tracker)
def makePerspective():
cave.setTracker(pos=tracker, ori=tracker) #named arguments used for clarity (ori required for stereo)

cave.makeOrthographic = makeOrthographic
cave.makePerspective = makePerspective

if orthographic:
cave.makeOrthographic()
else:
cave.makePerspective()
return cave
"""

def linkView(tracker,withCave=False):
"""
Returns a view based on tracker coordinates.

Links the provided tracker to the Vizard main viewpoint. If
withCave=True, links the tracker to main view through the cave
view, otherwise links directly to the main view (default).

Definition:
linkView(tracker,withCave=False)

Usage:
>>> view = linkView(tracker,True)
"""

# HACK CaveView doesn't require a cave...at the moment
return vizcave.CaveView(tracker)
"""
if withCave:
#link tracker to CaveView
view = vizcave.CaveView(tracker) #added flexibility to change viewpoint manually
else:
#link tracker directly to viz.MainView
view = viz.link(tracker,viz.MainView)
view.setDstFlag(viz.LINK_POS_RAW) #use the raw tracker position coordinates
return view
"""

def orthoUpdateCaveAndView(tracker, cave, view, ortho):
if ortho:
tracker.makeOrthographic()
cave.setTracker(pos=None, ori=None)
view.setView(viz.NullLinkable) # disassociate CaveView and tracker
viz.cam.reset()
else:
tracker.makePerspective()
cave.setTracker(pos=tracker,ori=tracker)
view.setView(viz.MainView)

AySz88
02-15-2012, 03:13 PM
Part 2 of sunyhardware.py:

class Keyboard:
"""
Implements a simulated positional tracker using the keyboard.

This class is especially useful for debugging, when access to the
HiBall hardware is unavailable or undesirable. The key mapping is
displayed in the main window. An associated cave object, with
tracker-adjusted frustum, and link to the active viewpoint are
created by default.

Variables: head, cave, view.
Functions: write.

The variable head provides access to the Vizard tracker object, the
variable cave provides access to the Vizard cave object with which
the tracker is associated, and the variable view provides access to
the view to which the tracker is linked. For additional information
about a particular function, please see the documentation string
for that function.

Definition:
Keyboard(makeCave=True,orthographic=False,trackCav e=True,makeLink=True)

Usage:
>>> tracker = Keyboard()
>>> position = tracker.head.getPosition()
"""

head = cave = view = None

def __init__(self,makeCave=True,orthographic=False,tra ckCave=True,makeLink=True):
#keyboard simulated tracker
import viztracker
self.head = viztracker.add()
self.head.setPosition(-_screenWidth/2-_screenWidthOffset,_screenHeight/2+_screenHeightOffset+_eyesHeightOffset,-2) #start in the "middle" of the room

#display keyboard mapping
import vizinfo #Vizard 2D GUI commands
self.info = vizinfo.add('Left/Right = Q/E\n'+
'Up/Down = R/F\n'+
'Forwards/Backwards = W/S\n\n'+
'Roll Left/Right = G/J\n'+
'Pitch Up/Down = Y/H\n'+
'Yaw Left/Right = A/D')
self.info.title('Keyboard Tracker Mapping')

#add cave and link viewpoint
if makeCave:
if trackCave:
self.cave = loadCave(self.head,orthographic)
else:
self.cave = loadCave(None,orthographic)
if makeLink:
self.view = linkView(self.head,makeCave)

def write(self,timerID):
"""
Prints the simulated keyboard coordinates to standard output.

Accepts the timerID as specified by viz.callback. Prints the
current tracker position and orientation whenever a timer
expires.

Definition:
write(timerID)

Usage:
>>> viz.callback(viz.TIMER_EVENT,write)
>>> viz.starttimer(0,3,viz.FOREVER)
"""

print 'Keyboard [x, y, z] = [%.3f, %.3f, %.3f]'%tuple(self.head.getPosition())
print 'Keyboard [yaw, pitch, roll] = [%.3f, %.3f, %.3f]'%tuple(self.head.getEuler())

class HiBall:
"""
Implements a connection to the HiBall Wide Area Tracker via VRPN.

This class simplifies access to the HiBall hardware. It loads the
required VRPN plug-in and creates the tracker object(s). An
associated cave object, with tracker-adjusted frustum, and link
to the active viewpoint are created by default.

The HiBall sensor zero point (X=Y=Z=0) is located on floor below
front-right LED. The default directional vectors (+X=Left,
+Y=Backwards, +Z=Up) are remapped (+X=Right, +Y=Up, +Z=Forwards).
The default orientational vectors are correspondingly remapped.

For the headtracker, the location tracked is between the eyes of
a generic individual. For the stylus, the location tracked is at
the base of the HiBall sensor. (TODO calibrate for the stylus point)

Variables: head, cave, view.
Functions: write.

The variable head provides access to a viz.Linkable tracking the
appropriate location (not necessarily a VizExtensionSensor), the
variable cave provides access to the Vizard cave object with which
the tracker is associated, and the variable view provides access to
the view to which the tracker is linked. For additional information
about a particular function, please see the documentation string
for that function.

Definition:
HiBall(makeCave=True,orthographic=False,trackCave= True,makeLink=True,sensor=SENSOR_HEADTRACKER)

Usage:
>>> tracker = HiBall()
>>> position = tracker.head.getPosition()
"""

SENSOR_HEADTRACKER = 0;
SENSOR_STYLUS = 1; #handheld stylus (borrowed from UNC as of 7/2011)

SENSOR_DEFAULT = SENSOR_HEADTRACKER;

vrpn = viz.add('vrpn7.dle') #VRPN 7.15 plug-in

head = cave = view = None

def __init__(self,makeCave=True,orthographic=False,tra ckCave=True,makeLink=True,sensor=SENSOR_DEFAULT):
"""VRPN head tracker"""

# the 0 in 'Tracker0' is NOT the sensor number
self.raw = self.__class__.vrpn.addTracker('Tracker0@localhost ', sensor)
self.raw.swapPos([-1,3,-2])
self.raw.swapQuat([1,-3,2,4])

# Find appropriate offset in local coordinate space
if sensor == self.__class__.SENSOR_HEADTRACKER:
offset = (0,-_eyesHeightOffset,-_eyesDepthOffset)
elif sensor == self.__class__.SENSOR_STYLUS:
offset = _stylusOffset
else:
offset = (0,0,0)
print 'Unknown sensor %s; please update sunyhardware'%sensor

link = viz.link(self.raw, viz.NullLinkable)
link.preTrans(offset)
self.head = link # Links are themselves linkables

self.sensor = sensor; # just for .write()

#add cave and link viewpoint
if makeCave:
if trackCave:
self.cave = loadCave(self.head,orthographic)
else:
self.cave = loadCave(None,orthographic)
if makeLink:
self.view = linkView(self.head,makeCave)

def write(self,timerID):
"""
Prints the HiBall coordinates to standard output.

Accepts the timerID as specified by viz.callback. Prints the
current tracker position and orientation whenever any timer
expires.

Definition:
write(timerID)

Usage:
>>> viz.callback(viz.TIMER_EVENT,write)
>>> viz.starttimer(0,3,viz.FOREVER)
"""
print 'HiBall%s'%self.sensor, '[x, y, z] = [%.3f, %.3f, %.3f]'%tuple(self.head.getPosition())
print 'HiBall%s'%self.sensor, '[yaw, pitch, roll] = [%.3f, %.3f, %.3f]'%tuple(self.head.getEuler())

def isHiBallAvailable():
"""
Tries to connect to the HiBall server (computer name 'hiball')
on port 3883, and returns true when successful.
Doesn't actually do anything (similar to a ping).
"""

import socket #built-in Python socket module

try:
udpSock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #create UDP socket
udpSock.connect(('hiball',3883)) #try to connect
udpSock.close() #close socket test connection
return True
except socket.gaierror:
return False

def getTracker(makeCave=True,orthographic=False,trackC ave=True,makeLink=True,sensor=HiBall.SENSOR_DEFAUL T):
"""
Factory function to return a HiBall or Keyboard tracker.

This class is a factory function, which returns a HiBall tracker
object when the HiBall system is present, or a simulated Keyboard
tracker object otherwise. For additional information about the
HiBall and Keyboard classes, please see the class documentation
strings.

The boolean flags makeCave, orthographic, trackCave, and makeLink
specify the type of virtual reality environment to construct:
makeCave=True uses hard-coded cave parameters (see loadCave),
orthographic=True results in orthographic projection (object size
will need to be reduced by a factor of 100), trackCave=True allows
the tracker object to update the cave frustums, and makeLink=True
links the tracker object's position to the cave's view matrix.

Definition:
getTracker(makeCave=True,orthographic=False,trackC ave=True,makeLink=True,sensor=HiBall.SENSOR_DEFAUL T)

Usage:
>>> tracker = getTracker(sensor=HiBall.SENSOR_HEADTRACKER)
>>> position = tracker.head.getPosition()
"""

# return appropriate tracker object
if isHiBallAvailable():
return HiBall(makeCave,orthographic,trackCave,makeLink,se nsor)
else:
return Keyboard(makeCave,orthographic,trackCave,makeLink)

def getAllSensors():
"""
Returns all three sensors (head and stylus trackers, and stylus button)
after constructing them using 'reasonable' default settings. This sets
up the cave with perspective projection, with tracking, and links the
headtracker to the cave's view matrix.

The head tracker tracks the orientation of the head and the location of
the point between the eyes (of a generic subject). The stylus tracker
provides the position of the stylus at the bottom of the sensor and its
orientation.

Note that if the hiball server is not available, it will return a
keyboard tracker as the headtracker, and None for the stylus tracker
and stylus button.

Usage:
>>> (headTracker, stylusTracker, button) = getAllSensors()
>>> # EXAMPLES FOR DEBUGGING
>>> def writeAll(timerID):
headTracker.write(timerID)
if stylusTracker: stylusTracker.write(timerID)
if stylusButton: print 'Button =', stylusButton.buttonState(), '(buttons:' + str(stylusButton.buttonCount()) + ')'
>>> viz.callback(viz.TIMER_EVENT,writeAll)
>>> viz.starttimer(0,3,viz.FOREVER)

>>> def onButtonUp(e):
if e.object is stylusButton:
pass #YOUR CODE HERE
>>> viz.callback(viz.SENSOR_UP_EVENT,onButtonUp)

>>> def onButtonDown(e):
if e.object is stylusButton:
pass #YOUR CODE HERE
>>> viz.callback(viz.SENSOR_DOWN_EVENT,onButtonDown)
"""

if True:#isHiBallAvailable():
headTracker = HiBall(sensor=HiBall.SENSOR_HEADTRACKER)
stylusTracker = HiBall(makeCave=False,
makeLink=False,
sensor=HiBall.SENSOR_STYLUS)
button = HiBall.vrpn.addButton('Button0@localhost')
else:
headTracker = Keyboard()
stylusTracker = None
button = None

return (headTracker, stylusTracker, button)

if __name__=='__main__': #if module is run as a script
#display the documentation string
print __doc__

farshizzo
02-15-2012, 03:26 PM
As I mentioned, the Cave object will automatically call <window>.frustum every frame if you specify a tracker. I noticed your code will call <window>.ortho under certain circumstances. So one call will be overriding the other, depending on which is called first. Do any of your code paths allow for <window>.ortho to be called while a tracker is specified for the cave object?

AySz88
02-15-2012, 03:37 PM
The code always does the following at the same time as calling <window>.ortho(). (Testing it just now, doing it just before or just after makes no difference.)
cave.setTracker(pos=None, ori=None)
view.setView(viz.NullLinkable) # disassociate CaveView and tracker
viz.cam.reset()
This should cause the cave object to stop calling <window>.frustum, correct?

farshizzo
02-15-2012, 04:10 PM
That should stop the cave object from automatically changing the frustum. Try adding the following line as well:viz.MainWindow.ipdVector(None)
I noticed the IPD values changes drastically from trial to trial. Are you sure this isn't causing the problem?

AySz88
02-15-2012, 04:24 PM
Yes - the center of the object is at the same distance as the screendistance used for the orthographic projection. Thus the square mark should always have zero disparity, so it shouldn't move no matter what the IPD is.

Does that line of code force the IPD value to zero? That wouldn't work for my purposes, since I do need stereo - it is adjusting the IPD value so as to "turn up" or "turn down" the stereo cue on demand, as needed by the rest of the experiment.

farshizzo
02-15-2012, 05:13 PM
The IPD vector is used by vizcave to change the direction the IPD shift is applied to the view matrix. I think adding that line should fix your problem.

AySz88
02-17-2012, 01:50 PM
Just got a chance to try it, and it works! Thanks!