View Single Post
  #2  
Old Today, 08:38 AM
sado_rabaudi sado_rabaudi is offline
WorldViz Team Member
 
Join Date: Jul 2016
Posts: 43
There's a few ways to approach this:

You could still use the GUI, but then would need to create copies of your environment with the stimuli rotated or moved and then add that modified environment to each trial. An example that uses this method is the Visual Search Single Object GUI SightLab Projects\ExampleScripts\VisualSearch\VisualSearch1 _SingleObject_GUI
https://help.worldviz.com/sightlab/v...e-manipulation
Also, using mostly the GUI you can just access the arrow objects if they are added in Inspector by using env,.getChild('nameOfObject'), so like this:
def sightLabExperiment():
yield viztask.waitEvent(EXPERIMENT_START)
viz.callback(viz.getEventID('ResetPosition'), sightlab.resetViewPoint)
env = sightlab.getEnvironment()
arrow1 = env.getChild('nameofArrow')
arrow1.setPosition([0,1,0])
arrow1.setEuler([180,0,0])
You could definitely use a STIM file, you would then also need either just the objects rotated or moved differently or the full environment and modify which one to present via the STIM file
https://help.worldviz.com/sightlab/stim-files/
You could fully create this with code. For help with coding I would recommend first trying with the SightLab GPT Assistant (can change this to GPT 5.2 for more thinking/better responses). You just need an OpenAI account (a free one works as well) and can access that here:
https://chatgpt.com/g/g-tgG8cOR5i-sightlab-vr-assistant

For example here is what I got from the Assistant. You just need to press trigger to start, then A or D for right or left and then trigger to start the next trial

import random
import math

import viz
import vizfx
import vizshape
import viztask
import vizact

import sightlab_utils.sightlab as sl
from sightlab_utils.settings import *

# ------------------------------------------------------------
# 1) Initialize SightLab
# ------------------------------------------------------------
# NOTE: vizshape objects need headlight=True so they render nicely.
sightlab = sl.SightLab(gui=False, headlight=True)
sightlab.setStartText('press Trigger to Start')

# If you're not using the GUI, you must set an environment in code:
env = vizfx.addChild('sightlab_resources/environments/whiteroom.osgb')
sightlab.setEnvironment(env)

# ------------------------------------------------------------
# 2) Helper: build a simple 3D "arrow" out of vizshape primitives
# ------------------------------------------------------------
def makeArrow(scale=0.12):
"""
Creates a right-pointing arrow (points +X by default).
Returns a viz.Group node you can position/rotate as one object.
"""
g = viz.addGroup()

# Shaft
shaft = vizshape.addBox(size=(0.35, 0.08, 0.08))
shaft.setParent(g)
shaft.setPosition(0.0, 0.0, 0.0)

# Head (cone)
head = vizshape.addCone(height=0.18, radius=0.09, slices=20)
head.setParent(g)
head.setPosition(0.22, 0.0, 0.0)
# Cone points +Z by default in many cases; rotate so it points +X
head.setEuler(0, 0, -90)

# Scale the whole arrow
g.setScale(scale, scale, scale)

# Optional: give a neutral color
g.color(viz.WHITE)

return g

def setArrowDirection(arrowNode, direction):
"""
direction: 'L' or 'R'
Arrow is modeled pointing +X (Right). Left is 180° about Y.
"""
if direction.upper() == 'R':
arrowNode.setEuler(0, 0, 0)
else:
arrowNode.setEuler(180, 0, 0)

# ------------------------------------------------------------
# 3) Create flanker layout: 4 flankers + 1 target (center)
# ------------------------------------------------------------
# Positions are in meters (SightLab uses meters)
Y_HEIGHT = 1.5 # roughly eye level in many setups
BASE_Z_NEAR = 1.0 # near depth
BASE_Z_FAR = 2.0 # far depth

# Horizontal spacing of arrows (meters)
X_SPACING = 0.18

# Make nodes
flanker1 = makeArrow()
flanker2 = makeArrow()
target = makeArrow()
flanker3 = makeArrow()
flanker4 = makeArrow()

# Parent them to the environment so their positions are in environment space
for n in [flanker1, flanker2, target, flanker3, flanker4]:
n.setParent(env)

# Base positions (Z will be set per trial)
flanker1.setPosition(-2*X_SPACING, Y_HEIGHT, BASE_Z_NEAR)
flanker2.setPosition(-1*X_SPACING, Y_HEIGHT, BASE_Z_NEAR)
target.setPosition(0*X_SPACING, Y_HEIGHT, BASE_Z_NEAR)
flanker3.setPosition(1*X_SPACING, Y_HEIGHT, BASE_Z_NEAR)
flanker4.setPosition(2*X_SPACING, Y_HEIGHT, BASE_Z_NEAR)

# IMPORTANT: Add objects to SightLab for data collection.
# If you want gaze data on them, set gaze=True.
sightlab.addSceneObject('flanker1', flanker1, gaze=True)
sightlab.addSceneObject('flanker2', flanker2, gaze=True)
sightlab.addSceneObject('target', target, gaze=True)
sightlab.addSceneObject('flanker3', flanker3, gaze=True)
sightlab.addSceneObject('flanker4', flanker4, gaze=True)

# ------------------------------------------------------------
# 4) Define "STIM-like" trial table in code
# (In a real project, you’d often read this from a CSV/STIM file.)
# ------------------------------------------------------------
TRIALS = []
n_trials = 20

for i in range(n_trials):
congruency = random.choice(['congruent', 'incongruent'])
depth = random.choice(['near', 'far'])
target_dir = random.choice(['L', 'R'])

if congruency == 'congruent':
flanker_dir = target_dir
else:
flanker_dir = 'L' if target_dir == 'R' else 'R'

z = BASE_Z_NEAR if depth == 'near' else BASE_Z_FAR

TRIALS.append({
'congruency': congruency,
'depth': depth,
'target_dir': target_dir,
'flanker_dir': flanker_dir,
'z': z
})

sightlab.setTrialCount(len(TRIALS))

# ------------------------------------------------------------
# 5) Optional: simple response collection + RT (keyboard)
# ------------------------------------------------------------
RESPONSE_LEFT_KEY = 'a'
RESPONSE_RIGHT_KEY = 'd'

def applyTrialConfig(cfg):
# Set directions
setArrowDirection(target, cfg['target_dir'])
for f in [flanker1, flanker2, flanker3, flanker4]:
setArrowDirection(f, cfg['flanker_dir'])

# Set depth (same z for all arrows in this simple example)
z = cfg['z']
flanker1.setPosition(-2*X_SPACING, Y_HEIGHT, z)
flanker2.setPosition(-1*X_SPACING, Y_HEIGHT, z)
target.setPosition(0*X_SPACING, Y_HEIGHT, z)
flanker3.setPosition(1*X_SPACING, Y_HEIGHT, z)
flanker4.setPosition(2*X_SPACING, Y_HEIGHT, z)

# ------------------------------------------------------------
# 6) Main trial loop
# ------------------------------------------------------------
def sightLabExperiment():
yield viztask.waitEvent(EXPERIMENT_START)

for t in range(sightlab.getTrialCount()):
cfg = TRIALS[t]
applyTrialConfig(cfg)

# Show trial-start text (SightLab will use trigger/left-click by default to continue)
msg = (
f"Flanker Task\n\n"
f"Respond to the CENTER arrow only.\n"
f"Press '{RESPONSE_LEFT_KEY.upper()}' for LEFT, '{RESPONSE_RIGHT_KEY.upper()}' for RIGHT.\n\n"
f"(Trial {t+1}/{len(TRIALS)} | {cfg['congruency']} | {cfg['depth']})\n\n"
f"Press Trigger to Start"
)

yield sightlab.startTrial(startTrialText=msg)

# Start RT timer at trial start
start_time = sightlab.hud.getCurrentTime()

# Wait for response
key = yield viztask.waitKeyDown([RESPONSE_LEFT_KEY, RESPONSE_RIGHT_KEY])
rt = sightlab.hud.getCurrentTime() - start_time

resp_dir = 'L' if key == RESPONSE_LEFT_KEY else 'R'
correct = (resp_dir == cfg['target_dir'])

# Log to console; SightLab will also log trial timing automatically.
print(f"Trial {t+1}: target={cfg['target_dir']} flankers={cfg['flanker_dir']} "
f"cong={cfg['congruency']} depth={cfg['depth']} resp={resp_dir} "
f"correct={correct} rt={rt:.3f}s")

yield sightlab.endTrial(endTrialText="")

# ------------------------------------------------------------
# 7) Schedule SightLab
# ------------------------------------------------------------
viztask.schedule(sightlab.runExperiment) # required for data collection loop
viztask.schedule(sightLabExperiment) # your experiment logic
viz.callback(viz.getEventID('ResetPosition'), sightlab.resetViewPoint) # 'r' to reset
Reply With Quote