Skip to main content

A package for OpenGL drawing using Pygame.

Project description

TachyPy

TachyPy is a Python package designed for creating high-performance visual stimuli using OpenGL and Pygame. It provides a set of tools for screen management, texture handling, drawing visual elements, handling user responses, and generating psychophysical stimuli. TachyPy is ideal for researchers and developers working on psychophysics experiments, visualizations, or any application requiring precise control over visual presentations and user inputs.


Table of Contents


Features

  • Screen Management: Initialize and manage screens with OpenGL contexts, handle multiple monitors, and control refresh rates.
  • Texture Handling: Load and manage textures efficiently using OpenGL.
  • Drawing Utilities: Functions to draw rectangles, lines, fixation crosses, and stimuli on the screen.
  • User Input Handling: Capture keyboard and mouse events, including reaction times, without directly interacting with Pygame.
  • Psychophysical Stimuli Generation: Create stimuli such as Gabor patches with customizable parameters.

Installation

You can install TachyPy directly from GitHub:

pip install git+https://github.com/Charestlab/tachypy.git

Alternatively, clone the repository and install in editable mode:

git clone https://github.com/yourusername/tachypy.git
cd tachypy
pip install -e .

Dependencies

  • Python 3.6 or higher
  • NumPy (>=1.18.0)
  • Pygame (>=2.0.0)
  • PyOpenGL (>=3.1.0)
  • screeninfo (>=0.6.0)

You can install the dependencies using:

pip install -r requirements.txt

Usage

Here’s a quick example demonstrating how to use TachyPy to display a moving Gabor patch with a fixation cross and handle user input.

Example

import os
import time
import numpy as np
from tachypy import (
    Audio,
    Screen,
    Texture,
    Circle,
    FixationCross,
    center_rect_on_point,
    ResponseHandler,
    fabriquer_gabor,
    noisy_bit_dithering
)

def main():
    # which screen should we draw to?
    screen_number = 1
    screen = Screen(screen_number=screen_number, fullscreen=True, desired_refresh_rate=60)

    # get some relevant screen properties
    center_x = screen.width//2 
    center_y = screen.height//2 

    # let's initialise our FixationCross
    fixation_cross = FixationCross(center=[center_x, center_y], half_width=50, half_height=50, thickness=2.0, color=(255, 0, 0))  # Red cross

    # let's add a white circle
    circle = Circle(center=(320, 240), radius=50, fill=True, color=(0, 255, 0))  # Green circle

    # let's start our audio player
    audio_player = Audio(sample_rate=44100, channels=1)

    # make a sinewave for the sound
    duration = 1.0  # seconds
    frequency = 440.0  # Hz (A4 note)
    sample_rate = 44100
    t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
    amplitude = 0.5
    waveform = amplitude * np.sin(2 * np.pi * frequency * t).astype(np.float32)


    # set the screen's background colour to gray
    screen.fill([128, 128, 128])

    # flip the screen to make the background color visible
    screen.flip()

    # check for the screen's actual refresh rate
    frame_rate_actual = 1/screen.test_flip_intervals(num_frames=100)
    print(frame_rate_actual)

    # Initialize ResponseHandler
    response_handler = ResponseHandler()

    # create a moving Gabor patch animation
    nx = 750
    frequency_per_im = 10
    speed_in_cycles_per_s = 3
    nb_frames_per_cycle = int(frame_rate_actual / speed_in_cycles_per_s)

    rms_target = 0.08
    film = []
    for ii in range(nb_frames_per_cycle):
        phase = 2 * np.pi * ii / nb_frames_per_cycle
        gabor = fabriquer_gabor(nx, frequence=frequency_per_im, phase=phase, angle=np.pi/4, ecart_type=0.2)

        gabor = rms_target / np.std(gabor) * (gabor - 0.5) + 0.5
        gabor_dithered = noisy_bit_dithering(gabor)
        
        gabor_rgb = np.stack((gabor_dithered,)*3, axis=-1) # could be done in the OpenGL functions, like the colors
        # gabor_rgb[:,:,2] = 255 - gabor_rgb[:,:,2]
        film.append(gabor_rgb)


    # Load stimuli (example: red, green, blue squares)
    textures = [Texture(stimulus) for stimulus in film]

    # define the position in which the Texture will be mapped.
    dest_rect = center_rect_on_point([0, 0, nx-100, nx-100], [center_x, center_y])

    # Main loop
    running = True

    # Track frame timestamps to measure interval consistency
    frame_intervals = []

    # flip an initial screen and set initial time
    start_time = screen.flip()

    while running:
        for current_trial, texture in enumerate(textures):

            screen.fill([128, 128, 128])

            # draw a circle
            circle.draw()

            # draw the texture
            texture.draw(dest_rect)

            # draw the fixation cross
            fixation_cross.draw()

            time_stamp = screen.flip()

            frame_intervals.append(screen.get_flip_interval()) 

            # Handle events
            response_handler.get_events()
            if response_handler.should_quit():
                running = False
                break

            # Example: Check if the a key was pressed
            if response_handler.is_key_down('a'):
                print("a key pressed!")
                audio_player.play(waveform)
                # Do something in response to the spacebar press
            
    #time.sleep(0.01)
        
    # Analyze frame intervals after the loop ends
    frame_intervals = np.array(frame_intervals)
    average_interval = np.mean(frame_intervals) * 1000  # Convert to milliseconds
    std_deviation = np.std(frame_intervals) * 1000      # Convert to milliseconds

    print(f"Average frame interval: {average_interval:.4f} ms")
    print(f"Standard deviation: {std_deviation:.4f} ms")

    # one last flip
    screen.flip()

    # close the audio player
    audio_player.close()

    # close the screen
    screen.close()

if __name__ == "__main__":
    main()

Modules

Screen

Class: Screen

Manages screen initialization, handling multiple monitors, setting up OpenGL contexts, and controlling refresh rates.

Features:

  • Initialize fullscreen or windowed mode.
  • Control vsync and desired refresh rates.
  • Provide methods to flip the display and tick the clock.
  • Test flip intervals to measure actual frame rates.

Example:

from tachypy import Screen

screen = Screen(screen_number=0, fullscreen=True, desired_refresh_rate=60)
screen.fill((128, 128, 128))  # Fill the screen with gray color
screen.flip()

Texture

Class: Texture

Handles loading and managing OpenGL textures from images or NumPy arrays.

Features:

  • Load textures into OpenGL.
  • Bind and unbind textures for rendering.
  • Delete textures when no longer needed.

Example:

from tachypy import Texture
import numpy as np

# Create an example image
image = np.random.randint(0, 255, (256, 256, 3), dtype=np.uint8)
texture = Texture(image)
texture.bind()
# Render with the texture
texture.unbind()

Visuals

Provides utility functions for drawing basic shapes and stimuli.

Functions:

  • draw_rectangle(a_rect, fill=True, thickness=1.0, color=(255, 255, 255)): Draws a rectangle.
  • draw_line(pts1, pts2, thickness=1.0, color=(255, 0, 0)): Draws a line between two points.
  • draw_fixation_cross(center_pts, half_width, half_height, thickness=1.0, color=(255, 0, 0)): Draws a fixation cross.
  • center_rect_on_point(a_rect, a_point): Centers a rectangle on a given point.
  • draw_stimulus(texture, a_rect): Draws a texture on the screen within the specified rectangle.

Responses

Class: ResponseHandler

Handles user input events, including keyboard and mouse interactions, without exposing Pygame directly.

Features:

  • Capture key presses and releases with timestamps.
  • Detect mouse clicks and positions.
  • Check if specific keys are pressed or released.
  • Determine if the application should quit.

Contributing

Contributions are welcome! If you'd like to contribute to TachyPy, please follow these steps:

  1. Fork the repository on GitHub.
  2. Clone your forked repository.
  3. Create a new branch for your feature or bugfix.
  4. Make your changes and commit them with descriptive messages.
  5. Push your changes to your fork.
  6. Submit a pull request to the main repository.

Please ensure that your code follows the project’s coding standards and includes appropriate tests.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

  • Pygame for the multimedia library.
  • PyOpenGL for the OpenGL bindings.
  • NumPy for numerical computations.
  • screeninfo for monitor information.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tachypy-0.1.0.tar.gz (34.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tachypy-0.1.0-py3-none-any.whl (29.7 kB view details)

Uploaded Python 3

File details

Details for the file tachypy-0.1.0.tar.gz.

File metadata

  • Download URL: tachypy-0.1.0.tar.gz
  • Upload date:
  • Size: 34.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.12.5

File hashes

Hashes for tachypy-0.1.0.tar.gz
Algorithm Hash digest
SHA256 826f97e91d7fcafa4e952f5ce42d2cedddd91ebe0d5bb6e426c4caa09502a6f0
MD5 f23c459bc7a8e4deda0ad06674f2c692
BLAKE2b-256 896501f437d263134dc53b93e319da2391c53156d8b5fa3c0da7b60d0d65731e

See more details on using hashes here.

File details

Details for the file tachypy-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: tachypy-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 29.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.12.5

File hashes

Hashes for tachypy-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 af7f6956296b8732c296293421912d9f8f8236f44e9f3680e04abd566d523d5d
MD5 a18ddb53bca80c04defa827294a0282c
BLAKE2b-256 aff33bb05bfdb34f814ab473e49d0de22ab3f2547d6779907eca2d41ce390010

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page