View Single Post
  #3  
Old 02-15-2012, 02:10 PM
AySz88 AySz88 is offline
Member
 
Join Date: Aug 2011
Posts: 13
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.
Reply With Quote