WorldViz User Forum

WorldViz User Forum (https://forum.worldviz.com/index.php)
-   Vizard (https://forum.worldviz.com/forumdisplay.php?f=17)
-   -   Intermittent orthographic stereo projection problems (https://forum.worldviz.com/showthread.php?t=4138)

AySz88 02-14-2012 05:46 PM

Intermittent orthographic stereo projection problems
 
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 08: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 02: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.

Code:

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 02: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:

Code:

"""
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__


August 4, 2008
        edited by:
       
        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 02:13 PM

Part 2 of sunyhardware.py:
Code:

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,trackCave=True,makeLink=True)
       
        Usage:
        >>> tracker = Keyboard()
        >>> position = tracker.head.getPosition()
        """
       
        head = cave = view = None
       
        def __init__(self,makeCave=True,orthographic=False,trackCave=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,trackCave=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,trackCave=True,makeLink=True,sensor=HiBall.SENSOR_DEFAULT):
        """
        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,trackCave=True,makeLink=True,sensor=HiBall.SENSOR_DEFAULT)
       
        Usage:
        >>> tracker = getTracker(sensor=HiBall.SENSOR_HEADTRACKER)
        >>> position = tracker.head.getPosition()
        """
       
        # return appropriate tracker object
        if isHiBallAvailable():
                return HiBall(makeCave,orthographic,trackCave,makeLink,sensor)
        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 02:26 PM

As I mentioned, the Cave object will automatically call .frustum every frame if you specify a tracker. I noticed your code will call .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 .ortho to be called while a tracker is specified for the cave object?

AySz88 02-15-2012 02:37 PM

The code always does the following at the same time as calling .ortho(). (Testing it just now, doing it just before or just after makes no difference.)
Code:

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 .frustum, correct?

farshizzo 02-15-2012 03:10 PM

That should stop the cave object from automatically changing the frustum. Try adding the following line as well:
Code:

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 03: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 04: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 12:50 PM

Just got a chance to try it, and it works! Thanks!


All times are GMT -7. The time now is 02:13 AM.

Powered by vBulletin® Version 3.8.7
Copyright ©2000 - 2024, vBulletin Solutions, Inc.
Copyright 2002-2023 WorldViz LLC