Skip to content

Image Viewer Example

This SightLab example demonstrates how to present images from a local folder in a virtual environment, either in random or sequential order, with gaze tracking and experiment summary logging.

Overview

Participants view a series of images placed in resources/images/. Each image is displayed for a fixed duration or until a key or trigger is pressed, and is automatically logged for later analysis. The order can be randomized or cycled sequentially.

Prerequisites

  • A folder resources/images/ containing .jpg, .png, or .jpeg files

Configuration Constants

Constant Description Default
IMAGE_POSITION Position of image quad in 3D space (x, y, z) [0, 1.0, 1.0]
DESKTOP_POSITION Position on desktop mirror view [0, 1.65, 1.1]
IMAGE_TIME Duration (seconds) each image is displayed 1.5
RANDOMIZE If True, images are chosen at random; otherwise cycled sequentially True
ENVIRONMENT_OBJECT Path to the environment .osgb file "resources/environments/whiteroom.osgb"
USE_PASSTHROUGH Enable device-specific passthrough (e.g. Meta Quest) for augmented reality False
TRIAL_COUNT Total number of trials (images to display) 12

Code Example

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

IMAGE_POSITION = [0,1.0,1.0]
#Desktop
DESKTOP_POSITION = [0,1.65,1.1]
IMAGE_TIME = 1.5
RANDOMIZE = True
ENVIRONMENT_OBJECT = "resources/environments/whiteroom.osgb"
USE_PASSTHROUGH = False
TRIAL_COUNT = 12

sightlab = sl.SightLab(gui=False)

if not USE_PASSTHROUGH:
    env = vizfx.addChild(ENVIRONMENT_OBJECT)
else:
    env = vizfx.addChild('sightlab_resources/environments/empty.osgb')
sightlab.setEnvironment(env)

sightlab.setTrialCount(TRIAL_COUNT)

# Get all image files from the resources/images/ directory
image_folder = "resources/images/"
image_files = [os.path.join(image_folder, f) for f in os.listdir(image_folder) if f.endswith(('.jpg', '.png', '.jpeg'))]

image_index = 0  # Initialize index for sequential selection

def sightLabExperiment():
    global image_index  # Allow modification of global index
    yield viztask.waitEvent(EXPERIMENT_START)

    for trial in range(sightlab.getTrialCount()):
        yield sightlab.startTrial(startExperimentText="Press Trigger to Start", textContinueEvent="triggerPress")   

        # Randomly choose an image and position
        if RANDOMIZE:
            selected_image = random.choice(image_files)
        else:
            selected_image = image_files[image_index]
            image_index = (image_index + 1) % len(image_files)  # Loop back after last image

        selected_filename = os.path.basename(selected_image)  # Extract only the filename
        if sightlab.getConfig()in "Desktop":
            selected_position = DESKTOP_POSITION
            sightlab.setAvatarRightHand(
                rightHand=vizfx.addChild(AVATAR_HANDS_RESOURCE_PATH + "/empty.osgb")
            )
        else:
            selected_position = IMAGE_POSITION

        if USE_PASSTHROUGH:
            import openxr
            xr = openxr.getClient()
            if sightlab.getConfig() in[ "Meta Quest Pro","Meta Quest 3"]:
                passthrough = xr.getPassthroughFB()
            elif sightlab.getConfig() == "Varjo":
                passthrough = xr.getPassthroughVarjo()
            elif sightlab.getConfig() == 'Vive Focus Vision':
                passthrough = xr.getPassthroughHTC()
            viz.clearcolor(viz.BLACK, 0.0)
            if passthrough:
                passthrough.setEnabled(True)

        # Save data to experiment summary
        sightlab.setExperimentSummaryData('image_file', selected_filename)
        sightlab.setExperimentSummaryData('image_position', selected_position)

        # Display instructions image
        pic = viz.addTexture(selected_image)
        instructionsImageQuad = viz.addTexQuad(texture=pic, pos=selected_position)
        instructionsImageQuad.drawOrder(125)

        # Get texture dimensions
        tex_w, tex_h, _ = pic.getSize()

        # Calculate aspect-ratio-preserving dimensions
        target_height = 1.0          # choose whatever height makes sense in meters
        if tex_h > 0:  # Prevent division by zero
            target_width = target_height * (tex_w / float(tex_h))
        else:
            target_width = target_height  # Fallback to square if height is 0

        instructionsImageQuad.setScale(target_width, target_height, 1)

        # Add the image quad to be tracked in SightLab
        sightlab.addSceneObject('image_quad', instructionsImageQuad, gaze=True)

        yield viztask.waitTime(IMAGE_TIME)
        yield sightlab.endTrial(endExperimentText="Thank You")
        instructionsImageQuad.visible(viz.OFF)

viztask.schedule(sightlab.runExperiment)
viztask.schedule(sightLabExperiment)
viz.callback(viz.getEventID('ResetPosition'), sightlab.resetViewPoint)

How It Works

  1. Initialization: Create a SightLab instance and load a .osgb environment into the scene.
  2. Trial Loop: Wait for the experiment start event, then iterate through the number of trials.
  3. Image Selection: Choose an image randomly or in sequence from the specified folder.
  4. Display & Tracking: Add a textured quad at the appropriate position, register it with SightLab for gaze tracking (addSceneObject), and display for a fixed duration.
  5. Data Logging: Use setExperimentSummaryData to record which image was shown and where.
  6. Cleanup: Hide the quad and proceed to the next trial.

Customization Options

  • Timing: Change IMAGE_TIME or use textContinueEvent to wait for user input.
  • Order: Toggle RANDOMIZE or implement custom ordering logic.
  • Interactive Events: Add grab tracking by setting grab=True in addSceneObject (see Code Reference) fileciteturn0file9.
  • Ratings & Inputs: Insert sightlab.showRatings() after a trial to collect responses (see Rating Scale example)

Running the Example

  1. Place your image files in resources/images/.
  2. Adjust constants at the top of the script to suit your experiment.
  3. Run in Vizard: open with Vizard 7/8 or use the SightLab GUI for integration.