├── .gitignore ├── Arduino ├── QDSpy_DIO │ └── QDSpy_DIO.ino └── QDSpy_DIO_wTrigger │ └── QDSpy_DIO_wTrigger.ino ├── Devices ├── __init__.py ├── api │ ├── dlpc343x_xpr4.py │ ├── dlpc343x_xpr4_evm.py │ └── packer.py ├── camera.py ├── digital_io.py ├── digital_io_UL_const.py ├── dldcr230 │ ├── api │ │ ├── ScriptAPIDoc.html │ │ ├── dlpc343x_xpr4.py │ │ ├── dlpc343x_xpr4_evm.py │ │ └── packer.py │ ├── dlpdldcr230npevm_python_support_software_manifest.html │ ├── i2c.py │ ├── init_fpdlink_mode.py │ ├── init_parallel.py │ ├── init_parallel_mode.py │ ├── linuxi2c.py │ ├── pcb │ │ ├── 2024-09-28_connector_BOM_v1.xlsx │ │ └── 2024-09-28_connector_Gerber_v1.zip │ ├── pics │ │ ├── connecting_board.jpg │ │ ├── connector_circuit.png │ │ ├── connector_pcb.png │ │ └── warnung.png │ ├── sample00_template.py │ ├── sample01_tpg.py │ ├── sample02_splash.py │ ├── sample03_display.py │ ├── sample04_looks.py │ ├── sample05_led.py │ └── sample06_status.py ├── dlplcr203_config │ ├── INFO.md │ ├── config_rpi4B-64bit-bookworm_1920x1080_58Hz.txt │ ├── config_rpi4B-64bit-bookworm_1920x1080_60Hz_simpler.txt │ └── older │ │ ├── config_rpi4b-32bit-bullseye.txt │ │ ├── config_rpi4b-64bit-bullseye_v2.txt │ │ ├── config_rpi4b-64bit-bullseye_v3.txt │ │ └── config_rpi5b-64bit-bullseye.txt ├── hid.cp311-win_amd64.pyd ├── hid.cp312-win_amd64.pyd ├── hid.cp313-win_amd64.pyd ├── hid.cp34-win_amd64.pyd ├── hid.cp35-win_amd64.pyd ├── hid.cp36-win_amd64.pyd ├── hid.cp37-win_amd64.pyd ├── hid.cp38-win_amd64.pyd ├── hid.cp39-win_amd64.pyd ├── lightcrafter_230np.py └── lightcrafter_4500.py ├── Disable_ExtensiveGammaChanges.reg ├── Enable_ExtensiveGammaChanges.reg ├── Graphics ├── __init__.py ├── distort_barrel.frag ├── distort_barrel_rp5.frag ├── distort_vertex_shader.glsl ├── distort_vertex_shader_rp5.glsl ├── renderer_opengl.py ├── shader_opengl.py └── sounds.py ├── LICENSE ├── Libraries ├── __init__.py ├── i2c.py ├── i2c_linux.py ├── log_helper.py ├── mqtt_client.py ├── mqtt_globals.py ├── multiprocess_helper.py └── tetrachroma.py ├── QDS.py ├── QDSpy.bat ├── QDSpy.ico ├── QDSpy.png ├── QDSpy_16x16.png ├── QDSpy_32x32.png ├── QDSpy_GUI_cam.py ├── QDSpy_GUI_cam.ui ├── QDSpy_GUI_main.py ├── QDSpy_GUI_main.ui ├── QDSpy_MQTT_main.py ├── QDSpy_app.py ├── QDSpy_checks.py ├── QDSpy_config.py ├── QDSpy_core.py ├── QDSpy_core_presenter.py ├── QDSpy_core_shader.py ├── QDSpy_core_support.py ├── QDSpy_core_view.py ├── QDSpy_file_support.py ├── QDSpy_gamma.py ├── QDSpy_global.py ├── QDSpy_probeCenter.py ├── QDSpy_stage.py ├── QDSpy_stim.py ├── QDSpy_stim_draw.py ├── QDSpy_stim_movie.py ├── QDSpy_stim_support.py ├── QDSpy_stim_video.py ├── README.md ├── Release_notes ├── ReleaseNotes_v090.md ├── ReleaseNotes_v091.md └── ReleaseNotes_v092.md ├── Shader ├── CircSineWaveGrating.cl ├── SineGrating.cl ├── SineWaveGrating.cl ├── SineWaveGratingMix.cl ├── SineWaveGratingMix2.cl ├── SquareWaveGrating.cl ├── SquareWaveGratingMix2.cl ├── SquareWaveGratingMix3.cl ├── SquareWaveGratingMix4.cl ├── SquareWaveGratingMix_rp5.cl └── StillSquareWaveGrating.cl ├── Sounds ├── error.mp3 ├── ok.mp3 ├── stim_end.mp3 └── stim_start.mp3 ├── Stimuli ├── Cricket_1.py ├── Cricket_2.py ├── Cricket_3.py ├── New Stimuli │ ├── Neu_Polar_1.pickle │ ├── Neu_Polar_1.py │ ├── Neu_Ripple_1.pickle │ ├── Neu_Ripple_1.py │ ├── Neu_Scribble_1.pickle │ ├── Neu_Scribble_1.py │ ├── RGC_BG_3s_3.pickle │ ├── RGC_BG_3s_3.py │ ├── RGC_BWNoise_3.py │ ├── RGC_BWNoise_official.txt │ ├── RGC_Chirp_3.py │ ├── RGC_MovingBar_3.pickle │ ├── RGC_MovingBar_3.py │ ├── Ripple_0.py │ ├── Scribble_0.py │ ├── Scribble_01.pickle │ └── Scribble_01.py ├── Perlin16.avi ├── Perlin32.avi ├── Perlin48.avi ├── RGC_BG_3s_2.py ├── RGC_BWNoise_2.py ├── RGC_BWNoise_official.txt ├── RGC_Chirp_2.py ├── RGC_MovingBar_2.py ├── RGC_MovingBar_2_RGBU.py ├── Test1.py ├── Test2.py ├── Test2a.py ├── Test2a_RGBU.py ├── Test2b.py ├── Test6.py ├── Test6_rp5.py ├── Test6a.py ├── Test_grating1.py ├── __autorun.py ├── __toGB_8bit_patternMode.py_template ├── __toVideoMode.py_template ├── dome_grid.py ├── dome_grid_sphere.py ├── dome_optokinetic_1.py ├── movie.py ├── movie_water.py ├── noise_Colored1.py ├── noise_Colored1_RGBU.py ├── noise_Colored_Wait.py ├── rabbit.jpg ├── rabbit.png ├── rabbit.txt ├── video_perlin16.py ├── video_perlin32.py ├── video_perlin32_RGBU.py ├── video_perlin48.py ├── video_water.py ├── water.avi ├── water1_flipped.jpg └── water1_flipped.txt ├── __autorun_default_DO_NOT_DELETE.pickle ├── __autorun_default_DO_NOT_DELETE.py ├── defaultGammaLUT.txt ├── defaultGammaLUT_linear.txt ├── grating.jpg ├── requirements.txt └── test_mqtt.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | Logs 3 | QDSpy.ini 4 | Stimuli/*.pickle 5 | Stimuli/New Stimuli/*.pickle 6 | Stimuli/Challenge 7 | Other/ 8 | qdspy/ 9 | .idea/* 10 | .vscode/* 11 | RecordedStimuli/* 12 | Stimuli/StimChallenge/* 13 | -------------------------------------------------------------------------------- /Arduino/QDSpy_DIO/QDSpy_DIO.ino: -------------------------------------------------------------------------------- 1 | int pin_LED = 13; 2 | 3 | void setup() 4 | { 5 | Serial.begin(115200); 6 | pinMode(pin_LED, OUTPUT); 7 | } 8 | 9 | void loop() 10 | { 11 | delayMicroseconds(500); 12 | } 13 | 14 | void serialEvent() 15 | { 16 | char ch; 17 | 18 | while (Serial.available()) { 19 | ch = (char)Serial.read(); 20 | if(ch == '1') 21 | digitalWrite(pin_LED, true); 22 | else 23 | digitalWrite(pin_LED, false); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Arduino/QDSpy_DIO_wTrigger/QDSpy_DIO_wTrigger.ino: -------------------------------------------------------------------------------- 1 | /* QDSpy simple digital interface 2 | * Notes: 3 | * - To make sure that the connection to the PC is as fast as possible, 4 | * the BAUD rate is set to `230400`. Lower it, if the communication is 5 | * not reliable 6 | * - If you want to test this using the Arduino IDE's serial monitor, 7 | * make sure that the monitor does not add anything to your command 8 | * (non LF and/or CR) 9 | * - Check that the Arduino board you are using supports interrupts at 10 | * `pin_INT`; for the Nano, this is only pins 2,3 11 | * 12 | * Function: 13 | * - To signal a trigger/marker event, QDSpy sends a `1` via the serial 14 | * port; this signal can be picked up at pin `pin_LED` (usually 13) 15 | */ 16 | 17 | #define pin_TRIG 13 // Trigger out pin 18 | #define pin_LED 12 // LED that signals interrupt detected 19 | #define pin_INT 2 // Interrupt pin 20 | #define INT_EVENT RISING // Event that triggers the interrupt 21 | #define DEBOUNCE_MS 10 22 | 23 | String sParam = ""; 24 | bool LEDon = false; 25 | unsigned long t_lastEvent_ms = 0; 26 | 27 | void setup() 28 | { 29 | Serial.begin(230400); 30 | pinMode(pin_TRIG, OUTPUT); 31 | pinMode(pin_LED, OUTPUT); 32 | pinMode(pin_INT, INPUT_PULLUP); 33 | attachInterrupt(digitalPinToInterrupt(pin_INT), triggerEvent, INT_EVENT); 34 | /* 35 | Serial.setTimeout(5000); 36 | Serial.println("Waiting for configuration ..."); 37 | sParam = Serial.readStringUntil('#'); 38 | if(sParam.length() == 0) { 39 | // Default 40 | pinMode(pin_TRIG, OUTPUT); 41 | pinMode(pin_LED, OUTPUT); 42 | pinMode(pin_INT, INPUT_PULLUP); 43 | attachInterrupt(digitalPinToInterrupt(pin_INT), triggerEvent, INT_EVENT); 44 | Serial.println("Using default pins"); 45 | } 46 | else { 47 | //... 48 | Serial.println("Using user pins"); 49 | } 50 | Serial.setTimeout(); 51 | */ 52 | } 53 | 54 | 55 | void loop() 56 | { 57 | delayMicroseconds(1000); 58 | if(LEDon and (millis() -t_lastEvent_ms > 1000)) { 59 | digitalWrite(pin_LED, false); 60 | LEDon = false; 61 | } 62 | } 63 | 64 | void triggerEvent() 65 | { 66 | unsigned long t_ms = millis(); 67 | if (t_ms -t_lastEvent_ms > DEBOUNCE_MS) { 68 | Serial.print('1'); 69 | digitalWrite(pin_LED, true); 70 | LEDon = true; 71 | } 72 | t_lastEvent_ms = t_ms; 73 | } 74 | 75 | void serialEvent() 76 | { 77 | char ch; 78 | 79 | while (Serial.available()) { 80 | ch = (char)Serial.read(); 81 | if(ch == '1') 82 | digitalWrite(pin_TRIG, true); 83 | else 84 | digitalWrite(pin_TRIG, false); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Devices/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Sat Jun 27 19:14:35 2015 4 | 5 | @author: Eulerlab 6 | """ 7 | 8 | -------------------------------------------------------------------------------- /Devices/api/dlpc343x_xpr4_evm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # --------------------------------------------------------------------- 4 | # Texas Instruments DLP LightCrafter 230NP EVM Python Support Code 5 | # - dlpc343x_xpr4_evm.py - Last Updated May 27th, 2020 6 | # 7 | # This script is intended to be used with the DLPDLCR230NP EVM on a 8 | # Raspberry Pi 4 (or compatible). It is recommended to consult the 9 | # DLPDLCR230NPEVM User's Guide for more detailed information on the 10 | # operation of this EVM. For technical support beyond what is provided 11 | # in the above documentation, direct inquiries to TI E2E Forums 12 | # (http://e2e.ti.com/support/dlp) 13 | # --------------------------------------------------------------------- 14 | import time 15 | import sys 16 | import os.path 17 | 18 | python_dir = (os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) 19 | sys.path.append(python_dir) 20 | 21 | # --------------------------------------------------------------------- 22 | # This module cycles provides basic functionality for the DLPDLCR230NP 23 | # EVM. To adjust the default GPIO drive strength, set a value below. 24 | # GPIO drive strength 5-7 is recommended. 25 | # 26 | # TE: GPIO strength does not seem to be supported by later Raspbian 27 | # versions 28 | ''' 29 | gpio_drive_strength = 5 30 | ''' 31 | 32 | # --------------------------------------------------------------------- 33 | def reg2Dict(data): 34 | """Helper function; converts the contents of a register data block 35 | into a dictionary. 36 | """ 37 | return [ 38 | (key, value) for key, value in vars(data).items() 39 | if not (key.startswith("__") and key.endswith("__")) 40 | ] 41 | 42 | 43 | def printReg(data): 44 | """Helper function; prints the contents of a register data block 45 | read via I2C. 46 | """ 47 | fdict = reg2Dict(data) 48 | print( 49 | "{" + "\n".join("{!r}: {!r},".format(k, v) for k, v in fdict) + "}" 50 | ) 51 | 52 | # --------------------------------------------------------------------- 53 | def initGPIO(): 54 | """Initializes the GPIO pins on the Raspberry Pi for use with the 55 | DLPDLCR230NP EVM in Video mode. 56 | 57 | Standard GPIO functionality is as follows: 58 | BCM #0 - 21 --> ALT2 (RGB666 DPI Output) 59 | BCM #22 - 23 --> I2C-7 (SW-based I2C Bus) 60 | BCM #24 --> SPI_SELECT_ASIC (Flash select line, Tri-Stated 61 | for Video mode) 62 | BCM #25 --> RGB_BUFFER_SEL (RGB666 Buffer Enable, Drive 63 | High for Video mode) 64 | BCM #26 --> SPI_SELECT_FPGA (Flash select line, Tri-Stated 65 | for Video mode) 66 | 67 | NOTE: Do NOT attempt to enable RGB666 buffers and access ASIC/FPGA 68 | flash devices simultaneously. Damage to flash devices may occur. 69 | """ 70 | ''' TE 2024-08-20 71 | pins 26 and 27 have to be excluded, as they serve as QDSpy's 72 | digital I/O 73 | os.system("pinctrl set 1-27 ip pn") 74 | ''' 75 | cmd = "gpio" if False else "pinctrl" 76 | os.system(f"{cmd} set 1-25 ip pn") 77 | os.system(f"{cmd} set 0 op pn") 78 | time.sleep(1) 79 | ''' TE 2024-07-25 80 | Not sure how that ever worked; wrong command? 81 | In any case, the drive strength seems not to be critical here 82 | os.system("gpio drive 0 {0}".format(gpio_drive_strength)) 83 | ''' 84 | os.system(f"{cmd} set 22 op pn") 85 | os.system(f"{cmd} set 23 op pn") 86 | os.system(f"{cmd} set 0-21 a2 pn") 87 | os.system(f"{cmd} set 25 op dh") 88 | time.sleep(1) 89 | 90 | # --------------------------------------------------------------------- 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Devices/api/packer.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | _packervalue = 0 4 | 5 | def packerinit(initvalue = 0): 6 | global _packervalue 7 | _packervalue = initvalue 8 | 9 | def setbits (newvalue, numbits, startindex): 10 | global _packervalue 11 | if (newvalue != 0): 12 | assert math.ceil(math.log(newvalue) / math.log(2)) <= (numbits) 13 | # set new value 14 | _packervalue = _packervalue | (newvalue<> startindex) & (2**(numbits) - 1) 20 | 21 | def convertfloattofixed (value, scale): 22 | return value * scale 23 | 24 | def convertfixedtofloat (value, scale): 25 | return value / scale 26 | -------------------------------------------------------------------------------- /Devices/dldcr230/api/ScriptAPIDoc.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Devices/dldcr230/api/ScriptAPIDoc.html -------------------------------------------------------------------------------- /Devices/dldcr230/api/dlpc343x_xpr4_evm.py: -------------------------------------------------------------------------------- 1 | ########################################################################################################################################################################### 2 | # Texas Instruments DLP LightCrafter 230NP EVM Python Support Code - dlpc343x_xpr4_evm.py - Last Updated May 27th, 2020 3 | # This script is intended to be used with the DLPDLCR230NP EVM on a Raspberry Pi 4 (or compatible) 4 | # It is recommended to consult the DLPDLCR230NPEVM User's Guide for more detailed information on the operation of this EVM 5 | # For technical support beyond what is provided in the above documentation, direct inquiries to TI E2E Forums (http://e2e.ti.com/support/dlp) 6 | ########################################################################################################################################################################### 7 | import struct 8 | import time 9 | 10 | from enum import Enum 11 | 12 | import sys, os.path 13 | python_dir = (os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) 14 | sys.path.append(python_dir) 15 | 16 | ''' 17 | This module cycles provides basic functionality for the DLPDLCR230NP EVM. 18 | To adjust the default GPIO drive strength, set a value below. 19 | GPIO drive strength 5-7 is recommended. 20 | ''' 21 | 22 | gpio_drive_strength = 5 23 | 24 | def PrintRegister(ReadData): 25 | ''' 26 | Helper function; prints the contents of a register data block read via I2C. 27 | ''' 28 | filtered_dict = [ (key, value) for key, value in vars(ReadData).items() if not (key.startswith("__") and key.endswith("__"))] 29 | print("{" + "\n".join("{!r}: {!r},".format(k, v) for k, v in filtered_dict) + "}") 30 | 31 | def InitGPIO(): 32 | ''' 33 | Initializes the GPIO pins on the Raspberry Pi for use with the DLPDLCR230NP EVM in Video mode. 34 | Standard GPIO functionality is as follows: 35 | BCM #0 - 21 --> ALT2 (RGB666 DPI Output) 36 | BCM #22 - 23 --> I2C-7 (SW-based I2C Bus) 37 | BCM #24 --> SPI_SELECT_ASIC (Flash select line, Tri-Stated for Video mode) 38 | BCM #25 --> RGB_BUFFER_SEL (RGB666 Buffer Enable, Drive High for Video mode) 39 | BCM #26 --> SPI_SELECT_FPGA (Flash select line, Tri-Stated for Video mode) 40 | NOTE: Do NOT attempt to enable RGB666 buffers and access ASIC/FPGA flash devices simultaneously. Damage to flash devices may occur. 41 | ''' 42 | print("Initializing Raspberry Pi Default Settings for DLPC3436...") 43 | os.system("pinctrl set 0 op pn") 44 | os.system("pinctrl set 1-27 ip pn") 45 | time.sleep(1) 46 | ''' TE 2024-07-25 47 | Not sure how that ever worked; wrong command? 48 | In any case, the drive strength seems not to be critical here 49 | os.system("gpio drive 0 {0}".format(gpio_drive_strength)) 50 | ''' 51 | os.system("pinctrl set 22 op pn") 52 | os.system("pinctrl set 23 op pn") 53 | os.system("pinctrl set 0-21 a2 pn") 54 | os.system("pinctrl set 25 op dh") 55 | time.sleep(1) 56 | 57 | 58 | if __name__ == "__main__" : main() 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /Devices/dldcr230/api/packer.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | _packervalue = 0 4 | 5 | def packerinit(initvalue = 0): 6 | global _packervalue 7 | _packervalue = initvalue 8 | 9 | def setbits (newvalue, numbits, startindex): 10 | global _packervalue 11 | if (newvalue != 0): 12 | assert math.ceil(math.log(newvalue) / math.log(2)) <= (numbits) 13 | # set new value 14 | _packervalue = _packervalue | (newvalue<> startindex) & (2**(numbits) - 1) 20 | 21 | def convertfloattofixed (value, scale): 22 | return value * scale 23 | 24 | def convertfixedtofloat (value, scale): 25 | return value / scale 26 | -------------------------------------------------------------------------------- /Devices/dldcr230/i2c.py: -------------------------------------------------------------------------------- 1 | # i2c.py 2 | # 3 | # I2C interface that runs on Windows (using DeVaSys Adapter) or Linux 4 | # 5 | # Copyright (C) 2017 Texas Instruments Incorporated - http://www.ti.com/ 6 | # 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions 10 | # are met: 11 | # 12 | # Redistributions of source code must retain the above copyright 13 | # notice, this list of conditions and the following disclaimer. 14 | # 15 | # Redistributions in binary form must reproduce the above copyright 16 | # notice, this list of conditions and the following disclaimer in the 17 | # documentation and/or other materials provided with the distribution. 18 | # 19 | # Neither the name of Texas Instruments Incorporated nor the names of 20 | # its contributors may be used to endorse or promote products derived 21 | # from this software without specific prior written permission. 22 | # 23 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 26 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 27 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 28 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 29 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 30 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 31 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 33 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | 35 | import sys 36 | from logging import log, DEBUG 37 | 38 | DEFAULT_SLAVE_ADDRESS = 0x36 # 8-bit I2C slave address 39 | DEFAULT_I2C_BUS = 22 # 7 for SW-based I2C bus 40 | 41 | _slave_address = DEFAULT_SLAVE_ADDRESS 42 | _i2c = None 43 | _debug = False 44 | 45 | 46 | def initialize(slave_address=DEFAULT_SLAVE_ADDRESS, i2c_bus=DEFAULT_I2C_BUS): 47 | """ 48 | :param slave_address: 8-bit I2C slave address. For DPP2607, should be 0x34 or 0x36. 49 | :param i2c_bus: I2C bus number, for Linux only. 50 | """ 51 | global _i2c, _slave_address 52 | if sys.platform == 'win32': 53 | import devasys 54 | _i2c = devasys.DeVaSys(slave_address) 55 | _i2c.open() 56 | else: 57 | import linuxi2c 58 | _i2c = linuxi2c.LinuxI2C(i2c_bus, slave_address) 59 | _i2c.open() 60 | _slave_address = slave_address 61 | 62 | 63 | def terminate(): 64 | global _i2c 65 | if _i2c: 66 | _i2c.close() 67 | _i2c = None 68 | 69 | 70 | def write(data): 71 | """ 72 | write to I2C port 73 | :type data: list[int] 74 | :rtype: None 75 | """ 76 | if _debug: 77 | print(DEBUG, 'I2C.write: %s', _hexlist(data)) 78 | _i2c.write(data) 79 | 80 | 81 | def read(numbytes): 82 | """ 83 | read from I2C port 84 | :type numbytes: int 85 | :rtype: list[int] 86 | """ 87 | data = _i2c.read(numbytes) 88 | if _debug: 89 | print(DEBUG, 'I2C.read: %s', _hexlist(data)) 90 | return data 91 | 92 | 93 | def get_slave_address(): 94 | return _slave_address 95 | 96 | 97 | def set_debug(debug): 98 | global _debug 99 | _debug = debug 100 | 101 | 102 | def get_debug(): 103 | return _debug 104 | 105 | 106 | def _hexlist(data): 107 | return '[%s]' % ', '.join([hex(b) for b in data]) 108 | -------------------------------------------------------------------------------- /Devices/dldcr230/init_parallel.py: -------------------------------------------------------------------------------- 1 | import time 2 | import Devices.lightcrafter_230np as _lcr 3 | import Devices.api.dlpc343x_xpr4 as dlp 4 | 5 | lcr = _lcr.Lightcrafter(_initGPIO=True) 6 | 7 | # Connect ... 8 | res = lcr.connect(_bus=None) 9 | 10 | if res[0] == _lcr.ERROR.OK: 11 | try: 12 | # Get LED status ... 13 | res = lcr.getLEDEnabled() 14 | res = lcr.getLEDCurrents() 15 | time.sleep(1.0) 16 | 17 | # Set LED currents t0 20 % 18 | res = lcr.setLEDCurrents([0.2, 0.2, 0.2]) 19 | res = lcr.getLEDCurrents() 20 | time.sleep(1.0) 21 | 22 | # Setting input source to parallel from RPi ... 23 | ''' 24 | res = lcr.setInputSource( 25 | _lcr.SourceSel.Parallel, 26 | _width=1920, _height=1080, 27 | _hsync=dlp.Polarity.ActiveLow, 28 | _vsync=dlp.Polarity.ActiveLow 29 | ) 30 | ''' 31 | ''' 32 | res = lcr.setInputSource( 33 | _lcr.SourceSel.Parallel, 34 | _width=1280, _height=720, 35 | _hsync=dlp.Polarity.ActiveHigh, 36 | _vsync=dlp.Polarity.ActiveHigh 37 | ''' 38 | res = lcr.setInputSource( 39 | _lcr.SourceSel.Parallel, 40 | _width=960, _height=540, 41 | _hsync=dlp.Polarity.ActiveHigh, 42 | _vsync=dlp.Polarity.ActiveLow 43 | ) 44 | 45 | 46 | time.sleep(1.0) 47 | 48 | except _lcr.LCException as excp: 49 | s = _lcr.ErrorStr[excp.value] 50 | print(f"Exception `{s}` ocurred") 51 | 52 | # Disconnect ... 53 | lcr.disconnect() -------------------------------------------------------------------------------- /Devices/dldcr230/linuxi2c.py: -------------------------------------------------------------------------------- 1 | # linuxi2c.py 2 | # 3 | # Linux-compatible I2C interface using ioctl 4 | # 5 | # Copyright (C) 2017 Texas Instruments Incorporated - http://www.ti.com/ 6 | # 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions 10 | # are met: 11 | # 12 | # Redistributions of source code must retain the above copyright 13 | # notice, this list of conditions and the following disclaimer. 14 | # 15 | # Redistributions in binary form must reproduce the above copyright 16 | # notice, this list of conditions and the following disclaimer in the 17 | # documentation and/or other materials provided with the distribution. 18 | # 19 | # Neither the name of Texas Instruments Incorporated nor the names of 20 | # its contributors may be used to endorse or promote products derived 21 | # from this software without specific prior written permission. 22 | # 23 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 26 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 27 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 28 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 29 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 30 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 31 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 33 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | 35 | import os 36 | import fcntl 37 | 38 | 39 | class LinuxI2C(object): 40 | """Simple Linux I2C port access""" 41 | I2C_SLAVE = 0x0703 42 | I2C_TENBIT = 0x0704 43 | 44 | def __init__(self, busnum, slave_address): 45 | super(LinuxI2C, self).__init__() 46 | self.busnum = busnum 47 | self.slave_address = slave_address 48 | self.fd = None 49 | 50 | def open(self, ): 51 | self.fd = os.open('/dev/i2c-%d' % self.busnum, os.O_RDWR) 52 | if self.fd < 0: 53 | raise IOError('could not open I2C interface') 54 | self.set_slave_address(self.slave_address) 55 | 56 | def close(self, ): 57 | if self.fd > 0: 58 | os.close(self.fd) 59 | self.fd = None 60 | 61 | def set_slave_address(self, slave_address): 62 | if self.fd > 0: 63 | if fcntl.ioctl(self.fd, self.I2C_TENBIT, 0) < 0: 64 | raise IOError('cannot set 7 bit I2C addressing') 65 | if fcntl.ioctl(self.fd, self.I2C_SLAVE, slave_address >> 1) < 0: 66 | raise IOError('cannot set slave address') 67 | print('set slave address:', slave_address >> 1) 68 | else: 69 | raise IOError('I2C interface is not open!') 70 | 71 | def write(self, data): 72 | if self.fd > 0: 73 | wrbuff = bytearray(data) 74 | if os.write(self.fd, wrbuff) < 0: 75 | raise IOError('cannot write to I2C interface') 76 | else: 77 | raise IOError('I2C interface is not open!') 78 | 79 | def read(self, numbytes): 80 | if self.fd > 0: 81 | rdbuff = os.read(self.fd, numbytes) 82 | return list(bytearray(rdbuff)) 83 | else: 84 | raise IOError('I2C interface is not open!') 85 | -------------------------------------------------------------------------------- /Devices/dldcr230/pcb/2024-09-28_connector_BOM_v1.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Devices/dldcr230/pcb/2024-09-28_connector_BOM_v1.xlsx -------------------------------------------------------------------------------- /Devices/dldcr230/pcb/2024-09-28_connector_Gerber_v1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Devices/dldcr230/pcb/2024-09-28_connector_Gerber_v1.zip -------------------------------------------------------------------------------- /Devices/dldcr230/pics/connecting_board.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Devices/dldcr230/pics/connecting_board.jpg -------------------------------------------------------------------------------- /Devices/dldcr230/pics/connector_circuit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Devices/dldcr230/pics/connector_circuit.png -------------------------------------------------------------------------------- /Devices/dldcr230/pics/connector_pcb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Devices/dldcr230/pics/connector_pcb.png -------------------------------------------------------------------------------- /Devices/dldcr230/pics/warnung.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Devices/dldcr230/pics/warnung.png -------------------------------------------------------------------------------- /Devices/dlplcr203_config/INFO.md: -------------------------------------------------------------------------------- 1 | - vc4-kms-dpi-generic 2 | - https://forums.raspberrypi.com/viewtopic.php?t=363795 3 | 4 | - xrandr 5 | - https://medium.com/@AbhiXpert/add-change-the-custom-resolution-of-your-display-using-xrandr-on-ubuntu-18-04-in-a-minute-338caec6e29 6 | 7 | - Custom HDMI mode 8 | - https://forums.raspberrypi.com/viewtopic.php?t=352841 9 | - https://forum-raspberrypi.de/forum/thread/57962-hilfe-bei-timings-erstellen-vom-datenblatt-des-displays/ 10 | - https://github.com/raspberrypi/documentation/blob/develop/documentation/asciidoc/computers/raspberry-pi/display-parallel-interface.adoc 11 | - https://www.raspberrypi.com/documentation/computers/legacy_config_txt.html#dpi_group-dpi_mode-dpi_output_format 12 | - https://www.elektronik-kompendium.de/sites/raspberry-pi/1907021.htm 13 | 14 | - Video mode calculator 15 | - https://tomverbeure.github.io/video_timings_calculator 16 | 17 | - RPi configuration parameters 18 | - https://elinux.org/RPiconfig#Video_mode_options 19 | 20 | - RPi pinout 21 | - https://learn.sparkfun.com/tutorials/raspberry-gpio/gpio-pinout -------------------------------------------------------------------------------- /Devices/dlplcr203_config/config_rpi4B-64bit-bookworm_1920x1080_58Hz.txt: -------------------------------------------------------------------------------- 1 | # For more options and information see 2 | # http://rptl.io/configtxt 3 | # Some settings may impact device functionality. See link above for details 4 | 5 | # Uncomment some or all of these to enable the optional hardware interfaces 6 | dtparam=i2c_arm=on 7 | #dtparam=i2s=on 8 | dtparam=spi=on 9 | 10 | # Enable audio (loads snd_bcm2835) 11 | dtparam=audio=on 12 | 13 | # Additional overlays and parameters are documented 14 | # /boot/firmware/overlays/README 15 | 16 | # Automatically load overlays for detected cameras 17 | camera_auto_detect=1 18 | 19 | # Automatically load overlays for detected DSI displays 20 | display_auto_detect=1 21 | 22 | # Automatically load initramfs files, if found 23 | auto_initramfs=1 24 | 25 | # Enable DRM VC4 V3D driver 26 | dtoverlay=vc4-kms-v3d 27 | max_framebuffers=2 28 | 29 | # Don't have the firmware create an initial video= setting in cmdline.txt. 30 | # Use the kernel's default instead. 31 | disable_fw_kms_setup=1 32 | 33 | # Run in 64-bit mode 34 | arm_64bit=1 35 | 36 | # Disable compensation for displays with overscan 37 | disable_overscan=1 38 | 39 | # Run as fast as firmware / board allows 40 | arm_boost=1 41 | 42 | # Configure Raspberry PI for SSH over USB 43 | dtoverlay=dwc2 44 | 45 | [cm4] 46 | # Enable host mode on the 2711 built-in XHCI USB controller. 47 | # This line should be removed if the legacy DWC2 controller is required 48 | # (e.g. for USB device mode) or if USB support is not required. 49 | otg_mode=1 50 | 51 | [all] 52 | # Configure I2C on GPIO Pins #22 and #23 53 | dtoverlay=i2c-gpio,i2c-gpio_sda=23,i2c_gpio_scl=22,i2c_gpio_delay_us=2 54 | 55 | # **** 56 | # Comment the following [none] to activate the DLP 57 | # *** 58 | #[none] 59 | dtoverlay=vc4-kms-dpi-generic 60 | dtparam=hactive=1920,hfp=20,hsync=10,hbp=10 61 | dtparam=vactive=1080,vfp=10,vsync=10,vbp=10 62 | dtparam=clock-frequency=125000000 63 | 64 | dtparam=hsync-invert,vsync-invert 65 | #dtparam=de-invert 66 | dtparam=rgb666 67 | 68 | #[all] 69 | # DLDCR230-related 70 | # -> TE added 71 | hdmi_force_hotplug=1 72 | hdmi_group=2 73 | hdmi_mode=82 74 | config_hdmi_boost=7 75 | 76 | # Configure DPI on GPIO Pins #0 through #21 77 | gpio=0=op 78 | gpio=0=pn 79 | gpio=1-27=ip 80 | gpio=1-27=pn 81 | #gpio=1-25=ip 82 | #gpio=1-25=pn 83 | 84 | # Enable DPI18 Overlay 85 | enable_dpi_lcd=1 86 | display_default_lcd=1 87 | dpi_group=2 88 | dpi_mode=87 89 | 90 | # Configuration of 1920 x 1080, 58-60 Hz video output via 18-bit DPI lines (RGB666). 91 | # Configure DPI Video Timings 92 | # RGB 666 CFG 1 (MODE 5) 93 | dpi_output_format=458773 94 | 95 | # 58 Hz Timings (Low-End Spec) 96 | # Works at GPIO DRIVE 5-7 97 | hdmi_timings=1920 0 20 10 10 1080 0 10 10 10 0 0 0 58 0 125000000 3 98 | # <- 99 | 100 | -------------------------------------------------------------------------------- /Devices/dlplcr203_config/config_rpi4B-64bit-bookworm_1920x1080_60Hz_simpler.txt: -------------------------------------------------------------------------------- 1 | # For more options and information see 2 | # http://rptl.io/configtxt 3 | # Some settings may impact device functionality. See link above for details 4 | 5 | # Uncomment some or all of these to enable the optional hardware interfaces 6 | dtparam=i2c_arm=on 7 | #dtparam=i2s=on 8 | dtparam=spi=on 9 | 10 | # Enable audio (loads snd_bcm2835) 11 | dtparam=audio=on 12 | 13 | # Additional overlays and parameters are documented 14 | # /boot/firmware/overlays/README 15 | 16 | # Automatically load overlays for detected cameras 17 | camera_auto_detect=1 18 | 19 | # Automatically load overlays for detected DSI displays 20 | # -> TE disabled 21 | display_auto_detect=1 22 | # <- 23 | 24 | # Automatically load initramfs files, if found 25 | auto_initramfs=1 26 | 27 | # Enable DRM VC4 V3D driver 28 | # -> TE disabled 29 | dtoverlay=vc4-kms-v3d 30 | max_framebuffers=2 31 | # <- 32 | 33 | # Don't have the firmware create an initial video= setting in cmdline.txt. 34 | # Use the kernel's default instead. 35 | disable_fw_kms_setup=1 36 | 37 | # Run in 64-bit mode 38 | arm_64bit=1 39 | 40 | # Disable compensation for displays with overscan 41 | disable_overscan=1 42 | 43 | # Run as fast as firmware / board allows 44 | arm_boost=1 45 | 46 | # Configure Raspberry PI for SSH over USB 47 | dtoverlay=dwc2 48 | 49 | [cm4] 50 | # Enable host mode on the 2711 built-in XHCI USB controller. 51 | # This line should be removed if the legacy DWC2 controller is required 52 | # (e.g. for USB device mode) or if USB support is not required. 53 | otg_mode=1 54 | 55 | [all] 56 | # Configure I2C on GPIO Pins #22 and #23 57 | dtoverlay=i2c-gpio,i2c-gpio_sda=23,i2c_gpio_scl=22,i2c_gpio_delay_us=2 58 | 59 | # **** 60 | # Comment the following [none] to activate the DLP 61 | # *** 62 | #[none] 63 | dtoverlay=vc4-kms-dpi-generic 64 | #dtparam=rgb666 65 | # 1920x1080/60Hz 66 | #dtparam=hactive=1920,hfp=20,hsync=10,hbp=10 67 | #dtparam=vactive=1080,vfp=10,vsync=10,vbp=10 68 | #dtparam=clock-frequency=125000000 69 | #dtparam=hsync-invert,vsync-invert 70 | ##dtparam=de-invert 71 | # 72 | # -- 800x600/60Hz 73 | #dtparam=hactive=800,hfp=40,hsync=128,hbp=88 74 | #dtparam=vactive=600,vfp=1,vsync=4,vbp=23 75 | #dtparam=clock-frequency=40000000 76 | ##dtparam=hsync-invert,vsync-invert 77 | # 78 | # -- 1024x768/60Hz 79 | #dtparam=hactive=1024,hfp=20,hsync=10,hbp=10 80 | #dtparam=vactive=768,vfp=10,vsync=10,vbp=10 81 | #dtparam=clock-frequency=50944000 82 | 83 | # ===> DLDCR230 via pin header 84 | # Configure DPI on GPIO Pins #0 through #21 85 | # (Note that pins 26 and 27 will be re-configured 86 | # and used for other I/O fucntions) 87 | gpio=0=op 88 | gpio=0=pn 89 | gpio=1-27=ip 90 | gpio=1-27=pn 91 | 92 | # --> TE: the following seems not to be needed 93 | hdmi_force_hotplug=1 94 | config_hdmi_boost=10 95 | #hdmi_group=2 96 | # 82=1080p/60Hz -> works 97 | #hdmi_mode=82 98 | #hdmi_mode=9 99 | # <-- 100 | 101 | # Enable DPI18 Overlay 102 | enable_dpi_lcd=1 103 | display_default_lcd=1 104 | # group=2, mode=87 -> user timings 105 | dpi_group=2 106 | dpi_mode=87 107 | 108 | # Configuration of 1920x1080, 58-60 Hz video output via 18-bit DPI lines (RGB666). 109 | # Configure DPI Video Timings 110 | # RGB 666 CFG 1 (MODE 5) 111 | dpi_output_format=458773 112 | ##hdmi_timings=1920 0 20 10 10 1080 0 10 10 10 0 0 0 58 0 125000000 3 113 | hdmi_timings=1920 0 20 10 10 1080 0 10 10 10 0 0 0 60 0 130000000 3 114 | # 115 | # Other timings (experimental): 116 | # 1280x720 60 Hz, v+, h+ 117 | #dpi_output_format=483349 118 | #hdmi_timings=1280 1 110 40 220 720 1 5 5 20 0 0 0 60 0 74250000 2 119 | # ... 120 | #dpi_output_format=483349 121 | #hdmi_timings=800 1 40 128 88 600 1 1 4 23 0 0 0 60 0 40000000 2 122 | # <=== 123 | -------------------------------------------------------------------------------- /Devices/dlplcr203_config/older/config_rpi4b-32bit-bullseye.txt: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # TE: 2024-07-27 3 | # This configuration works with the RPi-4B under Raspbian 32bit bullseye 4 | # See section [DLP] 5 | # ------------------------------------------------------------------------- 6 | # For more options and information see 7 | # http://rpf.io/configtxt 8 | # Some settings may impact device functionality. See link above for details 9 | 10 | # uncomment if you get no picture on HDMI for a default "safe" mode 11 | #hdmi_safe=1 12 | 13 | # uncomment the following to adjust overscan. Use positive numbers if console 14 | # goes off screen, and negative if there is too much border 15 | #overscan_left=16 16 | #overscan_right=16 17 | #overscan_top=16 18 | #overscan_bottom=16 19 | 20 | # uncomment to force a console size. By default it will be display's size minus 21 | # overscan. 22 | #framebuffer_width=1280 23 | #framebuffer_height=720 24 | 25 | # uncomment if hdmi display is not detected and composite is being output 26 | #hdmi_force_hotplug=1 27 | 28 | # uncomment to force a specific HDMI mode (this will force VGA) 29 | #hdmi_group=1 30 | #hdmi_mode=1 31 | 32 | # uncomment to force a HDMI mode rather than DVI. This can make audio work in 33 | # DMT (computer monitor) modes 34 | #hdmi_drive=2 35 | 36 | # uncomment to increase signal to HDMI, if you have interference, blanking, or 37 | # no display 38 | #config_hdmi_boost=4 39 | 40 | # uncomment for composite PAL 41 | #sdtv_mode=2 42 | 43 | #uncomment to overclock the arm. 700 MHz is the default. 44 | #arm_freq=800 45 | 46 | # Uncomment some or all of these to enable the optional hardware interfaces 47 | dtparam=i2c_arm=on 48 | #dtparam=i2s=on 49 | dtparam=spi=on 50 | 51 | # Uncomment this to enable infrared communication. 52 | #dtoverlay=gpio-ir,gpio_pin=17 53 | #dtoverlay=gpio-ir-tx,gpio_pin=18 54 | 55 | # Additional overlays and parameters are documented /boot/overlays/README 56 | 57 | # Enable audio (loads snd_bcm2835) 58 | dtparam=audio=on 59 | 60 | # Automatically load overlays for detected cameras 61 | camera_auto_detect=1 62 | 63 | # Automatically load overlays for detected DSI displays 64 | display_auto_detect=1 65 | 66 | # Enable DRM VC4 V3D driver 67 | # --> TE: To enable the standard HDMI display, uncomment this 68 | # (note that this deactivates the output to the DLP, somehow) 69 | #dtoverlay=vc4-kms-v3d 70 | #max_framebuffers=2 71 | # <-- 72 | 73 | # Disable compensation for displays with overscan 74 | disable_overscan=1 75 | 76 | [cm4] 77 | # Enable host mode on the 2711 built-in XHCI USB controller. 78 | # This line should be removed if the legacy DWC2 controller is required 79 | # (e.g. for USB device mode) or if USB support is not required. 80 | otg_mode=1 81 | 82 | [pi4] 83 | # Run as fast as firmware / board allows 84 | arm_boost=1 85 | 86 | # --> TE: This is for the DLP 87 | [DLP] 88 | hdmi_force_hotplug=1 89 | hdmi_group=2 90 | hdmi_mode=82 91 | config_hdmi_boost=4 92 | 93 | dtparam=spi=on 94 | dtoverlay=dwc2 95 | 96 | # Configure I2C on GPIO Pins #22 and #23 97 | dtoverlay=i2c-gpio,i2c-gpio_sda=23,i2c_gpio_scl=22,i2c_gpio_delay_us=2 98 | 99 | # Configure DPI on GPIO Pins #0 through #21 100 | gpio=0=op 101 | gpio=0=pn 102 | gpio=1-27=ip 103 | gpio=1-27=pn 104 | 105 | # Enable DPI18 Overlay 106 | enable_dpi_lcd=1 107 | display_default_lcd=1 108 | dpi_group=2 109 | dpi_mode=87 110 | 111 | # Configure DPI Video Timings 112 | # RGB 666 CFG 1 (MODE 5) 113 | dpi_output_format=458773 114 | 115 | # 58 Hz Timings (Low-End Spec) 116 | # Works at GPIO DRIVE 5-7 117 | hdmi_timings=1920 0 20 10 10 1080 0 10 10 10 0 0 0 58 0 125000000 3 118 | # <--- -------------------------------------------------------------------------------- /Devices/dlplcr203_config/older/config_rpi4b-64bit-bullseye_v2.txt: -------------------------------------------------------------------------------- 1 | # For more options and information see 2 | # http://rptl.io/configtxt 3 | # Some settings may impact device functionality. See link above for details 4 | 5 | # Uncomment some or all of these to enable the optional hardware interfaces 6 | #dtparam=i2c_arm=on 7 | #dtparam=i2s=on 8 | dtparam=spi=on 9 | 10 | # Enable audio (loads snd_bcm2835) 11 | dtparam=audio=on 12 | 13 | # Additional overlays and parameters are documented 14 | # /boot/firmware/overlays/README 15 | 16 | # Automatically load overlays for detected cameras 17 | camera_auto_detect=1 18 | 19 | # Automatically load overlays for detected DSI displays 20 | # -> TE disabled 21 | display_auto_detect=1 22 | # <- 23 | 24 | # Automatically load initramfs files, if found 25 | auto_initramfs=1 26 | 27 | # Enable DRM VC4 V3D driver 28 | # -> TE disabled 29 | dtoverlay=vc4-kms-v3d 30 | max_framebuffers=2 31 | # <- 32 | 33 | # Don't have the firmware create an initial video= setting in cmdline.txt. 34 | # Use the kernel's default instead. 35 | disable_fw_kms_setup=1 36 | 37 | # Run in 64-bit mode 38 | arm_64bit=1 39 | 40 | # Disable compensation for displays with overscan 41 | disable_overscan=1 42 | 43 | # Run as fast as firmware / board allows 44 | arm_boost=1 45 | 46 | # Configure Raspberry PI for SSH over USB 47 | dtoverlay=dwc2 48 | 49 | [cm4] 50 | # Enable host mode on the 2711 built-in XHCI USB controller. 51 | # This line should be removed if the legacy DWC2 controller is required 52 | # (e.g. for USB device mode) or if USB support is not required. 53 | otg_mode=1 54 | 55 | [all] 56 | # Configure I2C on GPIO Pins #22 and #23 57 | dtoverlay=i2c-gpio,i2c-gpio_sda=23,i2c_gpio_scl=22,i2c_gpio_delay_us=2 58 | 59 | dtoverlay=vc4-kms-dpi-generic 60 | dtparam=hactive=1920,hfp=20,hsync=10,hbp=10 61 | dtparam=vactive=1080,vfp=10,vsync=10,vbp=10 62 | dtparam=clock-frequency=125000000 63 | 64 | dtparam=hsync-invert,vsync-invert 65 | #dtparam=de-invert 66 | dtparam=rgb666 67 | 68 | #[all] 69 | # DLDCR230-related 70 | # -> TE added 71 | hdmi_force_hotplug=1 72 | hdmi_group=2 73 | hdmi_mode=82 74 | config_hdmi_boost=4 75 | 76 | # Configure I2C on GPIO Pins #22 and #23 77 | #dtoverlay=i2c-gpio,i2c-gpio_sda=23,i2c_gpio_scl=22,i2c_gpio_delay_us=2 78 | 79 | # Configure DPI on GPIO Pins #0 through #21 80 | #gpio=0=op 81 | #gpio=0=pn 82 | #gpio=1-27=ip 83 | #gpio=1-27=pn 84 | 85 | # Enable DPI18 Overlay 86 | enable_dpi_lcd=1 87 | display_default_lcd=1 88 | dpi_group=2 89 | dpi_mode=87 90 | 91 | # Configuration of 1920 x 1080, 58-60 Hz video output via 18-bit DPI lines (RGB666). 92 | # Configure DPI Video Timings 93 | # RGB 666 CFG 1 (MODE 5) 94 | dpi_output_format=458773 95 | 96 | # 58 Hz Timings (Low-End Spec) 97 | # Works at GPIO DRIVE 5-7 98 | hdmi_timings=1920 0 20 10 10 1080 0 10 10 10 0 0 0 58 0 125000000 3 99 | # <- 100 | 101 | -------------------------------------------------------------------------------- /Devices/dlplcr203_config/older/config_rpi4b-64bit-bullseye_v3.txt: -------------------------------------------------------------------------------- 1 | # For more options and information see 2 | # http://rptl.io/configtxt 3 | # Some settings may impact device functionality. See link above for details 4 | 5 | # Uncomment some or all of these to enable the optional hardware interfaces 6 | dtparam=i2c_arm=on 7 | #dtparam=i2s=on 8 | dtparam=spi=on 9 | 10 | # Enable audio (loads snd_bcm2835) 11 | dtparam=audio=on 12 | 13 | # Additional overlays and parameters are documented 14 | # /boot/firmware/overlays/README 15 | 16 | # Automatically load overlays for detected cameras 17 | camera_auto_detect=1 18 | 19 | # Automatically load overlays for detected DSI displays 20 | # -> TE disabled 21 | display_auto_detect=1 22 | # <- 23 | 24 | # Automatically load initramfs files, if found 25 | auto_initramfs=1 26 | 27 | # Enable DRM VC4 V3D driver 28 | # -> TE disabled 29 | dtoverlay=vc4-kms-v3d 30 | max_framebuffers=2 31 | # <- 32 | 33 | # Don't have the firmware create an initial video= setting in cmdline.txt. 34 | # Use the kernel's default instead. 35 | disable_fw_kms_setup=1 36 | 37 | # Run in 64-bit mode 38 | arm_64bit=1 39 | 40 | # Disable compensation for displays with overscan 41 | disable_overscan=1 42 | 43 | # Run as fast as firmware / board allows 44 | arm_boost=1 45 | 46 | # Configure Raspberry PI for SSH over USB 47 | dtoverlay=dwc2 48 | 49 | [cm4] 50 | # Enable host mode on the 2711 built-in XHCI USB controller. 51 | # This line should be removed if the legacy DWC2 controller is required 52 | # (e.g. for USB device mode) or if USB support is not required. 53 | otg_mode=1 54 | 55 | [all] 56 | # Configure I2C on GPIO Pins #22 and #23 57 | dtoverlay=i2c-gpio,i2c-gpio_sda=23,i2c_gpio_scl=22,i2c_gpio_delay_us=2 58 | 59 | # **** 60 | # Comment the following [none] to activate the DLP 61 | # **** 62 | [none] 63 | dtoverlay=vc4-kms-dpi-generic 64 | dtparam=hactive=1920,hfp=20,hsync=10,hbp=10 65 | dtparam=vactive=1080,vfp=10,vsync=10,vbp=10 66 | dtparam=clock-frequency=132000000 67 | 68 | dtparam=hsync-invert,vsync-invert 69 | #dtparam=de-invert 70 | dtparam=rgb666 71 | 72 | #[all] 73 | # DLDCR230-related 74 | # -> TE added 75 | hdmi_force_hotplug=1 76 | hdmi_group=2 77 | hdmi_mode=82 78 | config_hdmi_boost=7 79 | 80 | # Configure I2C on GPIO Pins #22 and #23 81 | #dtoverlay=i2c-gpio,i2c-gpio_sda=23,i2c_gpio_scl=22,i2c_gpio_delay_us=2 82 | 83 | # Configure DPI on GPIO Pins #0 through #21 84 | #gpio=0=op 85 | #gpio=0=pn 86 | #gpio=1-27=ip 87 | #gpio=1-27=pn 88 | 89 | # Enable DPI18 Overlay 90 | enable_dpi_lcd=1 91 | display_default_lcd=1 92 | dpi_group=2 93 | dpi_mode=87 94 | 95 | # Configuration of 1920 x 1080, 58-60 Hz video output via 18-bit DPI lines (RGB666). 96 | # Configure DPI Video Timings 97 | # RGB 666 CFG 1 (MODE 5) 98 | dpi_output_format=458773 99 | 100 | # 58 Hz Timings (Low-End Spec) 101 | # Works at GPIO DRIVE 5-7 102 | hdmi_timings=1920 0 20 10 10 1080 0 10 10 10 0 0 0 58 0 132000000 3 103 | # <- 104 | 105 | -------------------------------------------------------------------------------- /Devices/dlplcr203_config/older/config_rpi5b-64bit-bullseye.txt: -------------------------------------------------------------------------------- 1 | # --------------------------------------------------------------------------- 2 | # TE: 2024-08-05 3 | # This configuration is aimed at the RPi-5 under Raspbian 64bit bookworm 4 | # --------------------------------------------------------------------------- 5 | # For more options and information see 6 | # http://rpf.io/configtxt 7 | # Some settings may impact device functionality. See link above for details 8 | # Additional overlays and parameters are documented /boot/overlays/README 9 | 10 | # TE: The following is standard 11 | # uncomment if you get no picture on HDMI for a default "safe" mode 12 | #hdmi_safe=1 13 | 14 | # uncomment if hdmi display is not detected and composite is being output 15 | #hdmi_force_hotplug=1 16 | 17 | # uncomment to force a specific HDMI mode (this will force VGA) 18 | #hdmi_group=1 19 | #hdmi_mode=1 20 | 21 | # uncomment to force a HDMI mode rather than DVI. This can make audio work in 22 | # DMT (computer monitor) modes 23 | #hdmi_drive=2 24 | 25 | # uncomment to increase signal to HDMI, if you have interference, blanking, or 26 | # no display 27 | #config_hdmi_boost=5 28 | 29 | # uncomment for composite PAL 30 | #sdtv_mode=2 31 | 32 | # Uncomment some or all of these to enable the optional hardware interfaces 33 | dtparam=i2c_arm=on 34 | #dtparam=i2s=on 35 | dtparam=spi=on 36 | 37 | # Additional overlays and parameters are documented /boot/overlays/README 38 | 39 | # Enable audio (loads snd_bcm2835) 40 | dtparam=audio=on 41 | 42 | # Automatically load overlays for detected cameras 43 | camera_auto_detect=1 44 | 45 | # Automatically load overlays for detected DSI displays 46 | display_auto_detect=1 47 | 48 | # Enable DRM VC4 V3D driver 49 | # --> TE: To enable the standard HDMI display, uncomment this 50 | # (note that this deactivates the output to the DLP, somehow) 51 | #dtoverlay=vc4-kms-v3d 52 | #max_framebuffers=2 53 | # <-- 54 | 55 | # Disable compensation for displays with overscan 56 | disable_overscan=1 57 | 58 | [cm4] 59 | # Enable host mode on the 2711 built-in XHCI USB controller. 60 | # This line should be removed if the legacy DWC2 controller is required 61 | # (e.g. for USB device mode) or if USB support is not required. 62 | otg_mode=1 63 | 64 | [pi4] 65 | # Run as fast as firmware / board allows 66 | arm_boost=1 67 | 68 | # --> TE: This is for the DLP 69 | [DLP] 70 | hdmi_force_hotplug=1 71 | hdmi_group=2 72 | hdmi_mode=82 73 | config_hdmi_boost=4 74 | 75 | dtparam=spi=on 76 | dtoverlay=dwc2 77 | 78 | # Configure I2C on GPIO Pins #22 and #23 79 | dtoverlay=i2c-gpio,i2c-gpio_sda=23,i2c_gpio_scl=22,i2c_gpio_delay_us=2 80 | 81 | # Configure DPI on GPIO Pins #0 through #21 82 | gpio=0=op 83 | gpio=0=pn 84 | gpio=1-27=ip 85 | gpio=1-27=pn 86 | 87 | # Enable DPI18 Overlay 88 | enable_dpi_lcd=1 89 | display_default_lcd=1 90 | dpi_group=2 91 | dpi_mode=87 92 | 93 | # Configure DPI Video Timings 94 | # RGB 666 CFG 1 (MODE 5) 95 | dpi_output_format=458773 96 | 97 | # TE: legacy definitions: 98 | # 58 Hz Timings (Low-End Spec) 99 | # Works at GPIO DRIVE 5-7 100 | # hdmi_timings=1920 0 20 10 10 1080 0 10 10 10 0 0 0 58 0 125000000 3 101 | hdmi_timings= 102 | [h_active_pixels] 1920 103 | [h_sync_polarity] 0 104 | [h_front_porch] 20 105 | [h_sync_pulse] 10 106 | [h_back_porch] 10 107 | [v_active_lines] 1080 108 | [v_sync_polarity] 0 109 | [v_front_porch] 10 110 | [v_sync_pulse] 10 111 | [v_back_porch] 10 112 | [v_sync_offset_a] 0 113 | [v_sync_offset_b] 0 114 | [pixel_rep] 0 115 | [frame_rate] 58 116 | [interlaced] 0 117 | [pixel_freq] 125000000 118 | [aspect_ratio] 3 119 | 120 | # TE: new (bookworm) 121 | dpi_timings= 122 | 123 | 124 | dtoverlay=vc4-kms-dpi-generic 125 | dtparam=hactive=1920,hfp=20,hsync=16,hbp=59 126 | dtparam=vactive=800,vfp=15,vsync=113,vbp=15 127 | dtparam=clock-frequency=32000000 128 | 129 | Params: 130 | clock-frequency Display clock frequency (Hz) 131 | hactive Horizontal active pixels 132 | hfp Horizontal front porch 133 | hsync Horizontal sync pulse width 134 | hbp Horizontal back porch 135 | vactive Vertical active lines 136 | vfp Vertical front porch 137 | vsync Vertical sync pulse width 138 | vbp Vertical back porch 139 | hsync-invert Horizontal sync active low 140 | vsync-invert Vertical sync active low 141 | de-invert Data Enable active low 142 | pixclk-invert Negative edge pixel clock 143 | width-mm Define the screen width in mm 144 | height-mm Define the screen height in mm 145 | rgb565 Change to RGB565 output on GPIOs 0-19 146 | rgb666-padhi Change to RGB666 output on GPIOs 0-9, 12-17, and 147 | 20-25 148 | rgb888 Change to RGB888 output on GPIOs 0-27 149 | bus-format Override the bus format for a MEDIA_BUS_FMT_* 150 | value. NB also overridden by rgbXXX overrides. 151 | backlight-gpio Defines a GPIO to be used for backlight control 152 | (default of none). 153 | 154 | 155 | # <--- -------------------------------------------------------------------------------- /Devices/hid.cp311-win_amd64.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Devices/hid.cp311-win_amd64.pyd -------------------------------------------------------------------------------- /Devices/hid.cp312-win_amd64.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Devices/hid.cp312-win_amd64.pyd -------------------------------------------------------------------------------- /Devices/hid.cp313-win_amd64.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Devices/hid.cp313-win_amd64.pyd -------------------------------------------------------------------------------- /Devices/hid.cp34-win_amd64.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Devices/hid.cp34-win_amd64.pyd -------------------------------------------------------------------------------- /Devices/hid.cp35-win_amd64.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Devices/hid.cp35-win_amd64.pyd -------------------------------------------------------------------------------- /Devices/hid.cp36-win_amd64.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Devices/hid.cp36-win_amd64.pyd -------------------------------------------------------------------------------- /Devices/hid.cp37-win_amd64.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Devices/hid.cp37-win_amd64.pyd -------------------------------------------------------------------------------- /Devices/hid.cp38-win_amd64.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Devices/hid.cp38-win_amd64.pyd -------------------------------------------------------------------------------- /Devices/hid.cp39-win_amd64.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Devices/hid.cp39-win_amd64.pyd -------------------------------------------------------------------------------- /Disable_ExtensiveGammaChanges.reg: -------------------------------------------------------------------------------- 1 | Windows Registry Editor Version 5.00 2 | 3 | [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ICM] 4 | "GdiIcmGammaRange"=dword:00000000 -------------------------------------------------------------------------------- /Enable_ExtensiveGammaChanges.reg: -------------------------------------------------------------------------------- 1 | Windows Registry Editor Version 5.00 2 | 3 | [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ICM] 4 | "GdiIcmGammaRange"=dword:00000100 -------------------------------------------------------------------------------- /Graphics/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Sat Jun 27 19:14:35 2015 4 | 5 | @author: Eulerlab 6 | """ 7 | 8 | -------------------------------------------------------------------------------- /Graphics/distort_barrel.frag: -------------------------------------------------------------------------------- 1 | #version 120 2 | 3 | uniform sampler2D framebuffer_texture; 4 | uniform vec2 resolution; 5 | 6 | void main() { 7 | vec2 uv = gl_TexCoord[0].xy; 8 | 9 | // Convert to centered coordinates (-1 to 1) 10 | vec2 p = (uv * 2.0 - 1.0); 11 | 12 | // Calculate distance from center 13 | float r = length(p); 14 | 15 | // Barrel distortion parameters 16 | float k1 = 0.1; // Primary distortion 17 | float k2 = 0.1; // Secondary distortion 18 | 19 | // Apply distortion 20 | float distortion = 1.0 + k1 * r * r + k2 * r * r * r * r; 21 | vec2 distorted_uv = p * distortion; 22 | 23 | // Convert back to texture coordinates (0 to 1) 24 | distorted_uv = (distorted_uv + 1.0) * 0.5; 25 | 26 | // Sample the texture 27 | vec4 color = texture2D(framebuffer_texture, distorted_uv); 28 | 29 | // Debug: mix with coordinate visualization 30 | vec4 debugColor = vec4(uv.x, uv.y, 0.0, 1.0); 31 | float debugMix = 0.5; // 0.0 for no debug overlay, 0.5 for 50% mix 32 | 33 | gl_FragColor = mix(color, debugColor, debugMix); 34 | } -------------------------------------------------------------------------------- /Graphics/distort_barrel_rp5.frag: -------------------------------------------------------------------------------- 1 | #version 310 es 2 | 3 | uniform sampler2D framebuffer_texture; 4 | uniform vec2 resolution; 5 | 6 | void main() { 7 | vec2 uv = gl_TexCoord[0].xy; 8 | 9 | // Convert to centered coordinates (-1 to 1) 10 | vec2 p = (uv * 2.0 - 1.0); 11 | 12 | // Calculate distance from center 13 | float r = length(p); 14 | 15 | // Barrel distortion parameters 16 | float k1 = 0.1; // Primary distortion 17 | float k2 = 0.1; // Secondary distortion 18 | 19 | // Apply distortion 20 | float distortion = 1.0 + k1 * r * r + k2 * r * r * r * r; 21 | vec2 distorted_uv = p * distortion; 22 | 23 | // Convert back to texture coordinates (0 to 1) 24 | distorted_uv = (distorted_uv + 1.0) * 0.5; 25 | 26 | // Sample the texture 27 | vec4 color = texture2D(framebuffer_texture, distorted_uv); 28 | 29 | // Debug: mix with coordinate visualization 30 | vec4 debugColor = vec4(uv.x, uv.y, 0.0, 1.0); 31 | float debugMix = 0.5; // 0.0 for no debug overlay, 0.5 for 50% mix 32 | 33 | gl_FragColor = mix(color, debugColor, debugMix); 34 | } -------------------------------------------------------------------------------- /Graphics/distort_vertex_shader.glsl: -------------------------------------------------------------------------------- 1 | #version 120 2 | 3 | void main() { 4 | gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; 5 | gl_TexCoord[0] = gl_MultiTexCoord0; 6 | } -------------------------------------------------------------------------------- /Graphics/distort_vertex_shader_rp5.glsl: -------------------------------------------------------------------------------- 1 | #version 310 es 2 | 3 | void main() { 4 | gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; 5 | gl_TexCoord[0] = gl_MultiTexCoord0; 6 | } -------------------------------------------------------------------------------- /Graphics/sounds.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Classes for sounds in QDSpy 5 | 6 | Copyright (c) 2024 Thomas Euler 7 | All rights reserved. 8 | 9 | 2022-08-06 - First version 10 | """ 11 | # --------------------------------------------------------------------- 12 | __author__ = "code@eulerlab.de" 13 | 14 | import time 15 | import pygame 16 | from pathlib import Path 17 | from enum import Enum 18 | 19 | class Sounds(Enum): 20 | OK = 0 21 | ERROR = -1 22 | STIM_START = 1 23 | STIM_END = 2 24 | 25 | # --------------------------------------------------------------------- 26 | # Sound-related functions 27 | # --------------------------------------------------------------------- 28 | class SoundPlayer(): 29 | """ Play predefined audio signals 30 | """ 31 | def __init__(self): 32 | self._sounds = {} 33 | pygame.mixer.init() 34 | self._ready = True 35 | self.setVol(0.1) 36 | 37 | def __del__(self): 38 | self._sounds = None 39 | ''' 40 | if self._ready: 41 | pygame.mixer.music.unload() 42 | ''' 43 | 44 | def add(self, id, fname): 45 | if id in Sounds: 46 | sndfile = Path(fname) 47 | if sndfile.is_file(): 48 | self._sounds.update({id: fname}) 49 | 50 | def play(self, id): 51 | if not self._ready: 52 | return 53 | try: 54 | pygame.mixer.music.load(self._sounds[id]) 55 | pygame.mixer.music.play() 56 | while pygame.mixer.music.get_busy(): 57 | time.sleep(0.02) 58 | 59 | except KeyError: 60 | pass 61 | 62 | def setVol(self, vol): 63 | if not self._ready: 64 | return 65 | vol = min(max(0, vol), 1) 66 | pygame.mixer.music.set_volume(vol) 67 | 68 | 69 | # --------------------------------------------------------------------- 70 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2024 Thomas Euler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Libraries/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Sat Jun 27 19:14:35 2015 4 | 5 | @author: Eulerlab 6 | """ 7 | 8 | -------------------------------------------------------------------------------- /Libraries/i2c.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | QDSpy module - I2C class 5 | 6 | Copyright (c) 2024 Thomas Euler 7 | All rights reserved. 8 | 9 | 2024-08-03 - Initial version 10 | """ 11 | # --------------------------------------------------------------------- 12 | __author__ = "code@eulerlab.de" 13 | 14 | import platform 15 | import os 16 | 17 | # --------------------------------------------------------------------- 18 | # fmt: off 19 | I2C_ADDRESS = 0x36 # 8-bit I2C slave address 20 | I2C_BUS = 22 # 11 21 | 22 | # fmt: on 23 | 24 | # --------------------------------------------------------------------- 25 | def get_I2C_busses(): 26 | if not platform.system() == "Linux": 27 | return [] 28 | 29 | busses = [] 30 | for bus in os.listdir("/dev"): 31 | if bus.startswith("i2c-"): 32 | busses.append(int(bus[4:])) 33 | return busses 34 | 35 | # --------------------------------------------------------------------- 36 | class I2C(object): 37 | 38 | def __init__(self, debug: bool = True): 39 | # Initialize 40 | self._i2c = None 41 | self._isReady = False 42 | self._addr = 0 43 | self._bus = 0 44 | self._debug = debug 45 | 46 | 47 | def connect(self, _addr: int = I2C_ADDRESS, _bus: int = I2C_BUS) -> None: 48 | """Connect to I2C bus device at `_addr` 49 | """ 50 | self._i2c = None 51 | self._addr = _addr 52 | self._bus = _bus 53 | 54 | # Try opening I2C port 55 | if platform.system() == "Linux": 56 | import Libraries.i2c_linux as i2c_linux 57 | self._i2c = i2c_linux.LinuxI2C(self._bus, self._addr) 58 | self._i2c.open() 59 | else: 60 | print("i2c|Error: Only implemented for Linux") 61 | 62 | 63 | def terminate(self) -> None: 64 | """Terminate connection to I2C bus 65 | """ 66 | if self._i2c: 67 | self._i2c.close() 68 | self._i2c = None 69 | 70 | 71 | @property 72 | def is_connected(self) -> bool: 73 | return self._i2c is not None 74 | 75 | @property 76 | def address(self) -> int: 77 | return self._addr 78 | 79 | @property 80 | def debug(self) -> bool: 81 | return self._debug 82 | 83 | @debug.setter 84 | def debug(self, value: bool): 85 | self._debug = value 86 | 87 | 88 | def write(self, data: list[int]) -> None: 89 | """Write data 90 | """ 91 | if self._debug: 92 | print(f"i2c|Debug, write: {self._get_hexlist(data)}") 93 | self._i2c.write(data) 94 | 95 | 96 | def read(self, _n_bytes: int) -> list[int]: 97 | """Read data 98 | """ 99 | data = self._i2c.read(_n_bytes) 100 | if self._debug: 101 | print(f"i2c|Debug, read: {self._get_hexlist(data)}") 102 | return data 103 | 104 | 105 | @staticmethod 106 | def _get_hexlist(data): 107 | return '[%s]' % ', '.join([hex(b) for b in data]) 108 | 109 | # --------------------------------------------------------------------- 110 | -------------------------------------------------------------------------------- /Libraries/i2c_linux.py: -------------------------------------------------------------------------------- 1 | # --------------------------------------------------------------------- 2 | # linuxi2c.py 3 | # 4 | # Linux-compatible I2C interface using ioctl 5 | # 6 | # Copyright (C) 2017 Texas Instruments Incorporated - http://www.ti.com/ 7 | # 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions 11 | # are met: 12 | # 13 | # Redistributions of source code must retain the above copyright 14 | # notice, this list of conditions and the following disclaimer. 15 | # 16 | # Redistributions in binary form must reproduce the above copyright 17 | # notice, this list of conditions and the following disclaimer in the 18 | # documentation and/or other materials provided with the distribution. 19 | # 20 | # Neither the name of Texas Instruments Incorporated nor the names of 21 | # its contributors may be used to endorse or promote products derived 22 | # from this software without specific prior written permission. 23 | # 24 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 25 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 26 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 27 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 28 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 29 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 30 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 31 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 32 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 33 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 34 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35 | # --------------------------------------------------------------------- 36 | import os 37 | import fcntl 38 | 39 | # --------------------------------------------------------------------- 40 | class LinuxI2C(object): 41 | """Simple Linux I2C port access 42 | """ 43 | # fmt: off 44 | I2C_ADDR = 0x0703 45 | I2C_TENBIT = 0x0704 46 | # fmt: on 47 | 48 | def __init__(self, bus: int, addr: int) -> None: 49 | super(LinuxI2C, self).__init__() 50 | self._bus = bus 51 | self._addr = addr 52 | self._fd = None 53 | 54 | def open(self) -> None: 55 | self._fd = os.open(f"/dev/i2c-{self._bus:d}", os.O_RDWR) 56 | if self._fd < 0: 57 | raise IOError("Could not open I2C interface") 58 | self._set_addr(self._addr) 59 | 60 | def close(self) -> None: 61 | if self._fd > 0: 62 | os.close(self._fd) 63 | self._fd = None 64 | 65 | def _set_addr(self, _addr: int) -> None: 66 | if self._fd > 0: 67 | if fcntl.ioctl(self._fd, self.I2C_TENBIT, 0) < 0: 68 | raise IOError("Cannot set 7 bit I2C addressing") 69 | if fcntl.ioctl(self._fd, self.I2C_ADDR, _addr >> 1) < 0: 70 | raise IOError("Cannot set server address") 71 | else: 72 | raise IOError("I2C interface is not open") 73 | 74 | def write(self, data: list[int]) -> None: 75 | if self._fd > 0: 76 | wrbuff = bytearray(data) 77 | try: 78 | if os.write(self._fd, wrbuff) < 0: 79 | raise IOError("Cannot write to I2C interface") 80 | except OSError as err: 81 | raise IOError(f"OSError ({err})") 82 | else: 83 | raise IOError("I2C interface is not open") 84 | 85 | def read(self, n_bytes: int) -> None: 86 | if self._fd > 0: 87 | try: 88 | rdbuff = os.read(self._fd, n_bytes) 89 | return list(bytearray(rdbuff)) 90 | except OSError as err: 91 | raise IOError(f"OSError occurred ({err})") 92 | else: 93 | raise IOError("I2C interface is not open") 94 | 95 | # --------------------------------------------------------------------- 96 | 97 | -------------------------------------------------------------------------------- /Libraries/mqtt_globals.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | QDSpy module - MQTT-related global definitions 5 | 6 | Copyright (c) 2024-2025 Thomas Euler 7 | All rights reserved. 8 | 9 | 2024-08-03 - Initial version 10 | """ 11 | # --------------------------------------------------------------------- 12 | __author__ = "code@eulerlab.de" 13 | 14 | from enum import Enum 15 | 16 | # fmt: off 17 | # MQTT broker settings 18 | broker_address = "test.mosquitto.org" 19 | broker_port = 1883 20 | broker_timeout_s = 60 21 | topic_root = "qds" 22 | topic_serv = ["cli", "srv"] 23 | 24 | # UUID must be unique for each QDSpy MQTT client 25 | UUID = 'd6d09beb68a3448a' 26 | 27 | class Command(Enum): 28 | LOAD = "load" 29 | COMPILE = "compile" 30 | PLAY = "play" 31 | STOP = "stop" 32 | STATE = "state" 33 | EXIT = "exit" 34 | OPEN_LCR = "open_lcr" 35 | CLOSE_LCR = "close_lcr" 36 | GET_LEDS = "get_leds" 37 | SET_LEDS = "set_leds" 38 | ERROR = "error" 39 | OK = "ok" 40 | 41 | # fmt: on 42 | # --------------------------------------------------------------------- 43 | -------------------------------------------------------------------------------- /Libraries/multiprocess_helper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | QDSpy module - to synchronize QDSpy processes 5 | 6 | 'Sync' 7 | Class to synchronize states and data (via a pipe) between the GUI process 8 | and the stimulus presenter process 9 | 10 | Copyright (c) 2013-2024 Thomas Euler 11 | All rights reserved. 12 | 13 | 2024-06-11 - Reformatted (using Ruff) 14 | - Suport sending stimulus time info back to client 15 | 2024-08-04 - Moved into `Libraries` 16 | """ 17 | # --------------------------------------------------------------------- 18 | __author__ = "code@eulerlab.de" 19 | 20 | import time 21 | from multiprocessing import Pipe, Value 22 | 23 | # --------------------------------------------------------------------- 24 | # Multiprocessing support 25 | # --------------------------------------------------------------------- 26 | UNDEFINED = 0 27 | PRESENTING = 1 28 | COMPILING = 2 29 | CANCELING = 3 30 | TERMINATING = 4 31 | IDLE = 5 32 | PROBING = 6 33 | 34 | StateStr = dict( 35 | [ 36 | (UNDEFINED, "Undefined"), 37 | (PRESENTING, "Presenting"), 38 | (COMPILING, "Compiling"), 39 | (CANCELING, "Canceling"), 40 | (TERMINATING, "Terminating"), 41 | (IDLE, "Idle"), 42 | (PROBING, "Probing"), 43 | ] 44 | ) 45 | 46 | 47 | class PipeValType: 48 | toCli_log = 0 49 | toCli_displayInfo = 1 50 | toCli_IODevInfo = 2 51 | toCli_TEMP = 3 52 | toCli_time = 4 53 | toCli_playEndInfo = 5 54 | # ... 55 | toSrv_None = 9 56 | toSrv_fileName = 10 57 | toSrv_changedStage = 11 58 | toSrv_changedLEDs = 12 59 | toSrv_probeParams = 13 60 | toSrv_checkIODev = 14 61 | toSrv_setIODevPins = 15 62 | 63 | 64 | # --------------------------------------------------------------------- 65 | # Sync class 66 | # --------------------------------------------------------------------- 67 | class Sync: 68 | def __init__(self): 69 | """ Initializing 70 | """ 71 | self.Request = Value("i", IDLE) 72 | self.State = Value("i", UNDEFINED) 73 | self.pipeCli, self.pipeSrv = Pipe() 74 | 75 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 76 | def setStateSafe(self, _newState): 77 | self.State.value = _newState 78 | 79 | def setRequestSafe(self, _newReq): 80 | self.Request.value = _newReq 81 | 82 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 83 | def waitForState(self, _targetState, _timeout_s=0.0, _updateProc=None): 84 | """ Wait for "mpState" to reach a certain state or for timeout, if given. 85 | Returns True, if stage was reached within the timeout period 86 | """ 87 | timeout = False 88 | t_s = time.time() 89 | while not (self.State.value == _targetState) and not (timeout): 90 | if _updateProc is not None: 91 | _updateProc() 92 | time.sleep(0.05) 93 | if _timeout_s > 0.0: 94 | timeout = (time.time() - t_s) >= _timeout_s 95 | return not (timeout) 96 | 97 | 98 | # -------------------------------------------------------------------- 99 | -------------------------------------------------------------------------------- /Libraries/tetrachroma.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | tetrachromatic stimulus API 5 | 6 | Copyright (c) 2017-2024 Thomas Euler 7 | All rights reserved. 8 | """ 9 | # --------------------------------------------------------------------- 10 | __author__ = "code@eulerlab.de" 11 | 12 | 13 | # --------------------------------------------------------------------- 14 | class Tetrachroma: 15 | """Methods to program and sync two lightcrafters and for mapping 16 | tetrachromatic stimuli into RGB 17 | """ 18 | 19 | def __init__(self, _bitDepths, _bitOffsets, _blackBit): 20 | """ 21 | '_bitDepths' is a 4-value tuple, giving the number of bits 22 | (1..8) for the respective tetrachromatic color channel r,g,b,uv. 23 | The bit sum must be 23 for '_blackBit'> 0 and 24 for 24 | '_blackBit' == 0 25 | 26 | '_bitOffsets' ... 27 | 28 | '_blackBit' is a bit mask that indicates which bit is always 29 | set to zero (may be needed for dual LCr configuration); if 0 30 | then ignored. 31 | """ 32 | self.bitDepths = _bitDepths 33 | self.bitOffsets = _bitOffsets 34 | self.blackBit = _blackBit 35 | 36 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 37 | def toRGB(self, _rgbu): 38 | """Converts a tetrachromatic color r,g,b,uv ("_rgbu, 0..1") 39 | into a 24 bit RGB value and returns it as a 3-value tuple 40 | (0..255 per gun). 41 | """ 42 | newRGB = 0 43 | for iVal, val in enumerate(_rgbu): 44 | if self.bitDepths[iVal] > 0: 45 | depth = 2 ** self.bitDepths[iVal] 46 | newVal = min(depth - 1, int(val * depth)) 47 | newRGB = newRGB | (newVal << self.bitOffsets[iVal]) 48 | # print("0x{:06X}".format(newRGB)) 49 | 50 | if self.blackBit > 0: 51 | newRGB = newRGB & ~self.blackBit 52 | 53 | rgb = [] 54 | rgb.append(int(newRGB & 0x0000FF)) 55 | rgb.append(int((newRGB & 0x00FF00) >> 8)) 56 | rgb.append(int((newRGB & 0xFF0000) >> 16)) 57 | 58 | # print(_rgbu, "0x{:024b}".format(newRGB), rgb) 59 | return (tuple(rgb), newRGB) 60 | 61 | 62 | # --------------------------------------------------------------------- 63 | -------------------------------------------------------------------------------- /QDSpy.bat: -------------------------------------------------------------------------------- 1 | start pythonw QDSpy_GUI_main.py --gui 2 | -------------------------------------------------------------------------------- /QDSpy.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/QDSpy.ico -------------------------------------------------------------------------------- /QDSpy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/QDSpy.png -------------------------------------------------------------------------------- /QDSpy_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/QDSpy_16x16.png -------------------------------------------------------------------------------- /QDSpy_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/QDSpy_32x32.png -------------------------------------------------------------------------------- /QDSpy_GUI_cam.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | QDSpy module - camera dialog 5 | 6 | Copyright (c) 2013-2024 Thomas Euler 7 | All rights reserved. 8 | 9 | 2024-06-19 - Ported from `PyQt5` to `PyQt6` 10 | """ 11 | # --------------------------------------------------------------------- 12 | __author__ = "code@eulerlab.de" 13 | 14 | from PyQt6 import QtGui, QtCore, uic 15 | from PyQt6.QtCore import QTimer 16 | from PyQt6.QtWidgets import QDialog 17 | import QDSpy_core_support as csp 18 | 19 | if csp.module_exists("cv2"): 20 | import Devices.camera as cam 21 | 22 | # --------------------------------------------------------------------- 23 | form_class = uic.loadUiType("QDSpy_GUI_cam.ui")[0] 24 | grab_interval_ms = 50 25 | 26 | # --------------------------------------------------------------------- 27 | # Camera dialog window 28 | # --------------------------------------------------------------------- 29 | class CamWinClass(QDialog, form_class): 30 | def __init__(self, parent=None, _funcUpdate=None, _funcLog=None): 31 | QtGui.QDialog.__init__(self, parent) 32 | 33 | # Initialize GUI ... 34 | # 35 | self.setupUi(self) 36 | self.btnPause.clicked.connect(self.OnClick_btnPause) 37 | self.funcUpdate = _funcUpdate 38 | 39 | # Generate camera object 40 | # 41 | self.cam = cam.Camera(_funcLog) 42 | 43 | # Generate timer object for continous video grabbing 44 | # 45 | self.timer = QTimer() 46 | self.timer.setInterval(grab_interval_ms) 47 | self.timer.timeout.connect(self.grab) 48 | 49 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 50 | def showEvent(self, event): 51 | # Camera window is (re)shown 52 | # 53 | super().showEvent(event) 54 | 55 | # Connect a camera and start timer to grab continously new frames 56 | # 57 | self.cam.connect(0) 58 | self.timer.start() 59 | 60 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 61 | def closeEvent(self, event): 62 | # Camera window is being closed 63 | # 64 | # Stop timer and disconnect camera 65 | # 66 | self.timer.stop() 67 | self.cam.disconnect() 68 | 69 | # Update parent 70 | # 71 | if self.parent is not None and self.funcUpdate is not None: 72 | self.funcUpdate() 73 | 74 | self.accept() 75 | 76 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 77 | def grab(self): 78 | frame = self.cam.grab() 79 | if len(frame) > 0: 80 | height, width, channel = frame.shape 81 | bytesPerLine = 3 * width 82 | qImg = QtGui.QImage(frame.data, width, height, bytesPerLine, QtGui.QImage.Format_RGB888) 83 | pix = QtGui.QPixmap(qImg) 84 | self.label_CamFrame.setPixmap(pix.scaled(self.label_CamFrame.size(), 85 | QtCore.Qt.KeepAspectRatio, 86 | QtCore.Qt.FastTransformation)) 87 | 88 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 89 | def OnClick_btnPause(self): 90 | if self.timer.isActive(): 91 | self.timer.stop() 92 | self.btnPause.setText("Play") 93 | else: 94 | self.timer.start() 95 | self.btnPause.setText("Pause") 96 | 97 | # --------------------------------------------------------------------- -------------------------------------------------------------------------------- /QDSpy_GUI_cam.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 320 10 | 240 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | 21 | 320 22 | 240 23 | 24 | 25 | 26 | 27 | Segoe UI 28 | 9 29 | 30 | 31 | 32 | QDSpy - Camera 33 | 34 | 35 | 36 | 2 37 | 38 | 39 | 40 | 41 | 1 42 | 43 | 44 | 45 | 46 | 47 | 0 48 | 0 49 | 50 | 51 | 52 | 53 | 0 54 | 0 55 | 56 | 57 | 58 | 59 | 60 | 61 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 0 70 | 40 71 | 72 | 73 | 74 | 75 | 16777215 76 | 40 77 | 78 | 79 | 80 | QFrame::StyledPanel 81 | 82 | 83 | QFrame::Raised 84 | 85 | 86 | 87 | 88 | 4 89 | 4 90 | 57 91 | 33 92 | 93 | 94 | 95 | Pause 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /QDSpy_checks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | QDSpy module - to check critical stuff while being imported 5 | 6 | Copyright (c) 2024 Thomas Euler 7 | All rights reserved. 8 | 9 | 2024-07-28 - First version 10 | """ 11 | # --------------------------------------------------------------------- 12 | __author__ = "code@eulerlab.de" 13 | 14 | import sys 15 | import platform 16 | import pyglet 17 | 18 | PLATFORM_WINDOWS = platform.system() == "Windows" 19 | if not PLATFORM_WINDOWS: 20 | # Check if screens exist under Linux 21 | try: 22 | import pyglet.gl as GL 23 | except pyglet.canvas.xlib.NoSuchDisplayException: 24 | print("FATAL ERROR: No display devices detected") 25 | sys.exit() 26 | 27 | # --------------------------------------------------------------------- -------------------------------------------------------------------------------- /QDSpy_gamma.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | QDSpy module - gamma correction functions 5 | 6 | Copyright (c) 2013-2024 Thomas Euler 7 | All rights reserved. 8 | 9 | 2021-10-15 - Adapt to LINUX 10 | """ 11 | # --------------------------------------------------------------------- 12 | __author__ = "code@eulerlab.de" 13 | 14 | import numpy 15 | import time 16 | import platform 17 | import QDSpy_global as glo 18 | import QDSpy_stim as stm 19 | import Libraries.log_helper as _log 20 | 21 | PLATFORM_WINDOWS = platform.system == "Windows" 22 | if PLATFORM_WINDOWS: 23 | from ctypes import windll 24 | from ctypes.wintypes import LPCVOID 25 | 26 | # --------------------------------------------------------------------- 27 | def generateLinearLUT(): 28 | '''Return a linear LUT (see setGammaLUT for details) 29 | ''' 30 | tempLUT = [] 31 | for j in range(3): 32 | temp = list(range(256)) 33 | temp = [float(v) / 255.0 for v in temp] 34 | tempLUT.append(temp) 35 | 36 | newLUT = numpy.array(tempLUT) 37 | newLUT = (255 * newLUT).astype(numpy.uint16) 38 | newLUT.byteswap(True) 39 | return newLUT 40 | 41 | 42 | # --------------------------------------------------------------------- 43 | def generateInverseLUT(): 44 | '''.. for testing purposes 45 | ''' 46 | tempLUT = [] 47 | for j in range(3): 48 | temp = list(range(255, -1, -1)) 49 | temp = [float(v) / 255.0 for v in temp] 50 | tempLUT.append(temp) 51 | 52 | newLUT = numpy.array(tempLUT) 53 | newLUT = (255 * newLUT).astype(numpy.uint16) 54 | newLUT.byteswap(True) 55 | return newLUT 56 | 57 | 58 | # --------------------------------------------------------------------- 59 | def setGammaLUT(_winDC, _LUT): 60 | '''Set a look-up table (LUT) that allows to correct all presented color 61 | values, e.g. if the presentation device is not linear. 62 | `_LUT` has to be an uint16-type 3x256 numpy array. 63 | ''' 64 | if not PLATFORM_WINDOWS: 65 | return stm.StimErrC.notYetImplemented 66 | 67 | if (len(_LUT) != 3) or (len(_LUT[0]) != 256): 68 | return stm.StimErrC.invalidDimensions 69 | 70 | # For some unclear reason it fails to return a valid result when called 71 | # for the first time ... 72 | for j in range(5): 73 | try: 74 | res = windll.gdi32.SetDeviceGammaRamp( 75 | _winDC & LPCVOID(0xFFFFFFFF), _LUT.ctypes 76 | ) 77 | except TypeError: 78 | res = windll.gdi32.SetDeviceGammaRamp(_winDC, _LUT.ctypes) 79 | time.sleep(0.1) 80 | if res: 81 | break 82 | 83 | if res: 84 | _log.Log.write("OK", "SetDeviceGammaRamp worked") 85 | return stm.StimErrC.ok 86 | else: 87 | _log.Log.write("ERROR", "SetDeviceGammaRamp failed") 88 | return stm.StimErrC.SetGammaLUTFailed 89 | 90 | 91 | # --------------------------------------------------------------------- 92 | def restoreGammaLUT(_winDC): 93 | return setGammaLUT(_winDC, generateLinearLUT()) 94 | 95 | 96 | # --------------------------------------------------------------------- 97 | def loadGammaLUT(_fName): 98 | _log.Log.write(" ", "Loading user-defined gamma LUT file ...") 99 | 100 | try: 101 | rgb = [] 102 | r = [] 103 | g = [] 104 | b = [] 105 | fName = _fName + glo.QDSpy_LUTFileExt 106 | with open(fName, "r") as LUTFile: 107 | for line in LUTFile: 108 | rgb = [int(v.strip()) for v in line.split(",")] 109 | r.append(rgb[0]) 110 | g.append(rgb[1]) 111 | b.append(rgb[2]) 112 | 113 | newLUT = numpy.array([r, g, b]) 114 | newLUT = newLUT.astype(numpy.uint16) 115 | _log.Log.write("ok", "... done") 116 | return newLUT 117 | 118 | except IOError: 119 | _log.Log.write("ERROR", "gamma LUT file `{0}` not found".format(fName)) 120 | return generateLinearLUT() 121 | 122 | 123 | # --------------------------------------------------------------------- 124 | -------------------------------------------------------------------------------- /QDSpy_stim_support.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | QDSpy module - support functions for stimulus generation and compilation 5 | 6 | 'rotateTranslate()' 7 | Rotate the coordinates by the given angle 8 | 9 | 'scaleRGB()' 10 | 'scaleRGBShader()' 11 | Scale color as RGBA depending on bit depth and color mode 12 | 13 | 'getHashStr()' 14 | 'getHashStrForFile()' 15 | Get hashes for strings or files 16 | 17 | 'Log' 18 | Class that allows program-wide flexible logging. Only one instance that 19 | is defined in this module. Writes log messages to stdout and/or sends 20 | messages via a pipe to the GUI process. 21 | 22 | Copyright (c) 2013-2024 Thomas Euler 23 | All rights reserved. 24 | 25 | 2021-10-15 - Account for LINUX console text coloring 26 | 2024-08-13 - Move file-related functions to own module 27 | """ 28 | # --------------------------------------------------------------------- 29 | __author__ = "code@eulerlab.de" 30 | 31 | import numpy as np 32 | import QDSpy_stim as stm 33 | 34 | # --------------------------------------------------------------------- 35 | def rotateTranslate(_coords, _rot_deg, _posxy): 36 | # Rotate the coordinates in the list ([x1,y1,x2,y2, ...]) by the 37 | # given angle 38 | # 39 | coords = [] 40 | a_rad = np.radians(_rot_deg) 41 | for i in range(0, len(_coords), 2): 42 | x = _coords[i] * np.cos(a_rad) + _coords[i + 1] * np.sin(a_rad) + _posxy[0] 43 | y = -_coords[i] * np.sin(a_rad) + _coords[i + 1] * np.cos(a_rad) + _posxy[1] 44 | coords += [x, y] 45 | return coords 46 | 47 | 48 | # --------------------------------------------------------------------- 49 | def toInt(_coords): 50 | # Convert the coordinates in the list to integers 51 | # 52 | coords = [] 53 | for v in _coords: 54 | coords.append(int(v)) 55 | return coords 56 | 57 | 58 | # --------------------------------------------------------------------- 59 | def completeRGBList(_RGBs): 60 | # Complete each RGBx2 tuple if incomplete in the given list 61 | # 62 | return [tuple(list(rgb) + [0] * (stm.RGB_MAX - len(rgb))) for rgb in _RGBs] 63 | 64 | 65 | def completeRGBAList(_RGBAs): 66 | # Complete each RGBAx2 tuple if incomplete in the given list 67 | # 68 | res = [] 69 | for obj in _RGBAs: 70 | res.append([rgba + (0,) * (stm.RGBA_MAX - len(rgba)) for rgba in obj]) 71 | return res 72 | 73 | 74 | # --------------------------------------------------------------------- 75 | def scaleRGB(_Stim, _inRGBA): 76 | # Scale color (RGBA) depending on bit depth and color mode (format) 77 | # 78 | if _Stim.colorMode >= stm.ColorMode.LC_first: 79 | # One of the lightcrafter specific modes ... 80 | # 81 | if _Stim.colorMode == stm.ColorMode.LC_G9B9: 82 | RGBA = np.clip(_inRGBA, 0, 255) 83 | g = 2 ** int(RGBA[1] / 255.0 * 8) - 1 84 | b = 2 ** int(RGBA[2] / 255.0 * 8) - 1 85 | return 0, g, b, int(RGBA[3]) 86 | 87 | else: 88 | return 255, 255, 255, 255 89 | 90 | else: 91 | # One of the "normal" modes ... 92 | # 93 | if _Stim.colorMode == stm.ColorMode.range0_255: 94 | RGBA = np.clip(_inRGBA, 0, 255) 95 | dv = 255.0 96 | elif _Stim.colorMode == stm.ColorMode.range0_1: 97 | RGBA = np.clip(_inRGBA, 0, 1) 98 | dv = 1.0 99 | 100 | r = int(RGBA[0] / dv * (2 ** _Stim.bitDepth[0] - 1)) << _Stim.bitShift[0] 101 | g = int(RGBA[1] / dv * (2 ** _Stim.bitDepth[1] - 1)) << _Stim.bitShift[1] 102 | b = int(RGBA[2] / dv * (2 ** _Stim.bitDepth[2] - 1)) << _Stim.bitShift[2] 103 | RGB = tuple(np.clip((r, g, b), 0, 255)) 104 | return RGB + (int(RGBA[3]),) 105 | 106 | 107 | # --------------------------------------------------------------------- 108 | def scaleRGBShader(_Stim, _inRGBA): 109 | # Scale color (RGBA) depending on bit depth and color mode (format) 110 | # into shader compatible values (0...1) 111 | # 112 | r = _inRGBA[0] / 255.0 113 | g = _inRGBA[1] / 255.0 114 | b = _inRGBA[2] / 255.0 115 | a = _inRGBA[3] / 255.0 116 | RGBA = tuple(np.clip((r, g, b, a), 0.0, 1.0)) 117 | return RGBA 118 | 119 | 120 | # --------------------------------------------------------------------- 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QDSpy 2 | 3 | __v0.94beta__ 4 | 5 | QDSpy is a software for generating and presenting stimuli for visual neuroscience. It is based on QDS, developped in the former Dept. of Biomedical Optics at the MPI for Medical Research in Heidelberg. QDSpy is written in Python, uses OpenGL and primarly targets Windows 7 and above. 6 | 7 | *Note, this is still a beta version.* 8 | *For disclaimer, see [here](http://qdspy.eulerlab.de/disclaimer.html#disclaimer-of-warranty).* 9 | 10 | ## Documentation 11 | 12 | For documentation, see [manual](http://qdspy.eulerlab.de). 13 | 14 | To jump to installation, see [Wiki](https://github.com/eulerlab/QDSpy/wiki/Installation-under-Windows) or [manual](http://qdspy.eulerlab.de/installation.html#installation). 15 | 16 | For details on versions, see [release notes](https://github.com/eulerlab/QDSpy/wiki/Release-notes). 17 | -------------------------------------------------------------------------------- /Release_notes/ReleaseNotes_v090.md: -------------------------------------------------------------------------------- 1 | ## Release notes v0.90 (June 2024) 2 | 3 | ### Fixed issues 4 | - Spot probe tool works again 5 | - `movie_water.py` stimulus fixed and move error handling improved 6 | - Issues with Python v3.12 and `configparser` fixed 7 | - `hid.cp312-win_amd64.pyd` added to `Devices\` 8 | 9 | ### Changes: 10 | - `QDSpy_GUI_main.py` 11 | - Reformatted (using Ruff) 12 | - Small fixes for PEP violations 13 | - `int()` where Qt5 does not accept float 14 | 15 | - `QDSpy_global.py` 16 | - Fix for breaking change in `configparser` (see `QDSpy_config.py`): `QDSpy_rec_setup_id` cannot be `None` -> changed to -1 for no setup defined 17 | 18 | - `QDSpy_stim.py` 19 | - Reformatted (using Ruff) 20 | - Small fixes for PEP violations 21 | - Bug fix related to correct error handling for movie compilation errors 22 | 23 | - `QDSpy_config.py` 24 | - Reformatted (using Ruff) 25 | - Fix for breaking change in `configparser`; now using `ConfigParser` instead of `RawConfigParser` 26 | - Small fixes for PEP violations 27 | - Set `rec_setup_id` to `None` if < 0 28 | 29 | - `QDSpy_core.py` 30 | - Reformatted (using Ruff) 31 | - Catch if `conda` is not installed 32 | 33 | - `QDSpy_core_presenter.py` 34 | - Reformatted (using Ruff) 35 | - Improved error message for movie errors 36 | 37 | - `Graphics/renderer_opengl.py` 38 | - Reformatted (using Ruff) 39 | - Fixed a bug that prevented using the probe spot tool 40 | 41 | ### Open issues: 42 | - Stimulus containing a wait raises an uncaught error if no respective hardware is connected (e.g., stimulus `noise_Colored_Wait.py`) 43 | 44 | 45 | -------------------------------------------------------------------------------- /Release_notes/ReleaseNotes_v091.md: -------------------------------------------------------------------------------- 1 | ## Release notes v0.91 (July 2024) 2 | 3 | ### New features 4 | - GUI migrated to `PyQt6`, now supporting dark mode. 5 | - Progress shown in status bar during stimulus presentation. 6 | - Added the option to play sounds, e.g., at the start and end of a stimulus presentation (see `QDSpy_global.py`, for settings) 7 | 8 | ### Fixed issues 9 | - Fixed an issue with loading gamma LUTs 10 | - Fixed finding shader files when run from the command line 11 | - Running stimuli from the command line (w/o GUI) fixed 12 | - Now works with a newer release of `pyglet` version 1 (v1.5.5 instead of v1.4.x) 13 | - GUI migrated to `PyQt6` 14 | - More modules reformatted using `ruff` 15 | 16 | ### Changes: 17 | - Additional dependency: `pygame` 18 | - `Graphics/sounds.py` w/ folder `Sounds` now supports audio signals. 19 | - `QDSpy_GUI_main.py`, `QDSpy_GUI_main.ui`, `QDSpy_GUI_support.py` 20 | - Migrated to `PyQt6` 21 | - Fix for font color in log window for dark mode 22 | - `QDSpy_global.py`, 23 | - `QDSpy_useGUIScalingForHD` introduced to control if QDSpy attempts to rescale the GUI for high screen resolutions. If off for `PyQt6`. 24 | - `QDSpy_multiprocessing.py` 25 | - Small bug fix 26 | - `Graphics/renderer_opengl.py` 27 | - Fixed a bug when using `pyglet` v1.5.x (still fixed to v1.5.5, see below) 28 | 29 | ### Open issues: 30 | - Linux version: Cannot abort qith `Q` 31 | - Error message `QWindowsContext: OleInitialize() failed: "COM error 0x80010106: Der Threadmodus kann nicht nach dem Einstellen geändert werden."` appears when starting QDSpy. It appears to result from some interaction between `pyglet` v1.5.6 and higher and `PyQt6`. I first thought it has no consequences but then saw that now the file dialog is not working anymore. For now, make sure that `pyglet` v1.5.5 is used. 32 | - Stimulus containing a wait raises an uncaught error if no respective hardware is connected (e.g., stimulus `noise_Colored_Wait.py`) 33 | 34 | ### Notes on the 35 | - at [240 Hz](https://e2e.ti.com/support/dlp-products-group/dlp/f/dlp-products-forum/1054640/dlpdlcr230npevm-can-i-use-960x640-240hz-with-the-provided-firmware) - untested 36 | - regarding running the DLPDLCR2000EVM (a different DLP) with [bookworm](https://e2e.ti.com/support/dlp-products-group/dlp/f/dlp-products-forum/1281455/dlpdlcr2000evm-raspberry-pi-configuration-using-dtoverlay-vc4-kms-dpi-generic-raspberry-pi-os-12-bookworm) 37 | 38 | -------------------------------------------------------------------------------- /Release_notes/ReleaseNotes_v092.md: -------------------------------------------------------------------------------- 1 | ## Release notes v0.92 (August 2024) 2 | 3 | ### New features 4 | - Version that can be controlled via MQTT (work in progress) 5 | - New digital I/O device added: "RaspberryPi". It uses by default pins GPIO26 for trigger-in and GPIO27 for marker-out. These pins cannot be changed as these are the only two free pins when using the DLP Lightcrafter 230NP EVM (dldcr203). 6 | 7 | ### Fixed issues 8 | 9 | ### Changes: 10 | - Use `colorama` for console colors 11 | - Messages from the worker thread (the one that presents the stimuli) and the host thread (the GUI or the MQTT client) can now be distinguished; worker thread messages start with a `|` character. 12 | - `QDSpy_stim_movie.py` and `QDSpy_stim_video.py` are not anymore directly dependent on `pyglet` by adding helper functions to `renderer_opengl.py`. 13 | - Log instance (`Log`) moved into own module `Libraries\log.helper.py` 14 | - Multiprocessing support now in `Libraries\multiprocess_helper.py` 15 | 16 | ### Open issues: 17 | - _Linux version:_ When running QDSpy via SSH, `q` does not abort stimulus presentation. 18 | - Error message _`QWindowsContext: OleInitialize() failed: "COM error 0x80010106: Der Threadmodus kann nicht nach dem Einstellen geändert werden."`_ appears when starting QDSpy. It appears to result from some interaction between `pyglet` v1.5.6 and higher and `PyQt6`. I first thought it has no consequences but then saw that now the file dialog is not working anymore. For now, __make sure that `pyglet` v1.5.5 is used__.- Stimulus containing a wait raises an uncaught error if no respective hardware is connected (e.g., stimulus `noise_Colored_Wait.py`) 19 | - __Gamma LUT functionality does not work reliably__. It works with a single screen, but not for all multiple screen configurations. The respective code has not changed, therefore I am unsure of the cause (changed packages, e.g. `pyglet`, `pyqt6` seem not to be responsible). 20 | - Stimulus containing a __wait__ raises an uncaught error if no respective hardware is connected (e.g., stimulus `noise_Colored_Wait.py`) 21 | 22 | ## Feature requests 23 | - Being able to save the background color explicitly in the `.pickle` file. 24 | -------------------------------------------------------------------------------- /Shader/CircSineWaveGrating.cl: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | //#QDS ShaderName =CIRC_SINE_WAVE_GRATING 4 | //#QDS ShaderParamNames =perLen_um; perDur_s; minRGB; maxRGB 5 | //#QDS ShaderParamLengths =1;1;4;4 6 | //#QDS ShaderParamDefaults =0.0; 1.0; (0,0,0,255); (255,255,255,255) 7 | // 8 | //#QDS ShaderVertexStart 9 | // ---------------------------------------------------------------------- 10 | void main() 11 | { 12 | gl_Position = ftransform(); 13 | } 14 | // ---------------------------------------------------------------------- 15 | //#QDS ShaderVertexEnd 16 | 17 | //#QDS ShaderFragmentStart 18 | // ---------------------------------------------------------------------- 19 | #define pi 3.141592653589 20 | #define pi2 6.283185307179 21 | 22 | uniform float time_s; 23 | uniform vec3 obj_xy_rot; 24 | uniform float perLen_um; 25 | uniform float perDur_s; 26 | uniform vec4 minRGB; 27 | uniform vec4 maxRGB; 28 | 29 | float inten, r; 30 | vec4 b; 31 | 32 | void main() { 33 | vec4 a = gl_FragCoord; 34 | a.x = a.x -obj_xy_rot.x; 35 | a.y = a.y -obj_xy_rot.y; 36 | r = sqrt(a.x*a.x +a.y*a.y); 37 | inten = (sin((r/perLen_um +time_s/perDur_s) *pi2) +1.0)/2.0; 38 | gl_FragColor = mix(minRGB, maxRGB, inten); 39 | } 40 | // ---------------------------------------------------------------------- 41 | //#QDS ShaderFragmentEnd 42 | -------------------------------------------------------------------------------- /Shader/SineGrating.cl: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | //#QDS ShaderName =SINE_GRATING 4 | //#QDS ShaderParamNames =perLen_um; phase; minRGB; maxRGB 5 | //#QDS ShaderParamLengths =1;1;4;4 6 | //#QDS ShaderParamDefaults =0.0; 0.0; (0,0,0,255); (255,255,255,255) 7 | // 8 | //#QDS ShaderVertexStart 9 | // ---------------------------------------------------------------------- 10 | void main() 11 | { 12 | gl_Position = ftransform(); 13 | } 14 | // ---------------------------------------------------------------------- 15 | //#QDS ShaderVertexEnd 16 | 17 | //#QDS ShaderFragmentStart 18 | // ---------------------------------------------------------------------- 19 | #define pi 3.141592653589 20 | #define pi2 6.283185307179 21 | 22 | uniform vec3 obj_xy_rot; 23 | uniform float perLen_um; 24 | uniform float phase; 25 | uniform vec4 minRGB; 26 | uniform vec4 maxRGB; 27 | 28 | float inten; 29 | vec4 b; 30 | 31 | void main() { 32 | vec4 a = gl_FragCoord; 33 | a.x = a.x -obj_xy_rot.x; 34 | a.y = a.y -obj_xy_rot.y; 35 | b.x = a.x*cos(obj_xy_rot[2]) -a.y*sin(obj_xy_rot[2]); 36 | b.y = a.y*cos(obj_xy_rot[2]) +a.x*sin(obj_xy_rot[2]); 37 | inten = (sin(((b.x)/perLen_um + phase/pi) *pi2) +1.0)/2.0; 38 | gl_FragColor = mix(minRGB, maxRGB, inten); 39 | } 40 | // ---------------------------------------------------------------------- 41 | //#QDS ShaderFragmentEnd 42 | -------------------------------------------------------------------------------- /Shader/SineWaveGrating.cl: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | //#QDS ShaderName =SINE_WAVE_GRATING 4 | //#QDS ShaderParamNames =perLen_um; perDur_s; minRGB; maxRGB 5 | //#QDS ShaderParamLengths =1;1;4;4 6 | //#QDS ShaderParamDefaults =0.0; 1.0; (0,0,0,255); (255,255,255,255) 7 | // 8 | //#QDS ShaderVertexStart 9 | // ---------------------------------------------------------------------- 10 | void main() 11 | { 12 | gl_Position = ftransform(); 13 | } 14 | // ---------------------------------------------------------------------- 15 | //#QDS ShaderVertexEnd 16 | 17 | //#QDS ShaderFragmentStart 18 | // ---------------------------------------------------------------------- 19 | #define pi 3.141592653589 20 | #define pi2 6.283185307179 21 | 22 | uniform float time_s; 23 | uniform vec3 obj_xy_rot; 24 | uniform float perLen_um; 25 | uniform float perDur_s; 26 | uniform vec4 minRGB; 27 | uniform vec4 maxRGB; 28 | 29 | float inten; 30 | vec4 b; 31 | 32 | void main() { 33 | vec4 a = gl_FragCoord; 34 | a.x = a.x -obj_xy_rot.x; 35 | a.y = a.y -obj_xy_rot.y; 36 | b.x = a.x*cos(obj_xy_rot[2]) -a.y*sin(obj_xy_rot[2]); 37 | b.y = a.y*cos(obj_xy_rot[2]) +a.x*sin(obj_xy_rot[2]); 38 | inten = (sin(((b.x)/perLen_um +time_s/perDur_s) *pi2) +1.0)/2.0; 39 | gl_FragColor = mix(minRGB, maxRGB, inten); 40 | } 41 | // ---------------------------------------------------------------------- 42 | //#QDS ShaderFragmentEnd 43 | -------------------------------------------------------------------------------- /Shader/SineWaveGratingMix.cl: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | //#QDS ShaderName =SINE_WAVE_GRATING_MIX 4 | //#QDS ShaderParamNames =perLen_um; perDur_s; minRGB; maxRGB 5 | //#QDS ShaderParamLengths =1;1;4;4 6 | //#QDS ShaderParamDefaults =0.0; 1.0; (0,0,0,255); (255,255,255,255) 7 | // 8 | //#QDS ShaderVertexStart 9 | // ---------------------------------------------------------------------- 10 | varying vec4 vertex_color; 11 | 12 | void main() 13 | { 14 | gl_Position = ftransform(); 15 | vertex_color = gl_Color; 16 | } 17 | // ---------------------------------------------------------------------- 18 | //#QDS ShaderVertexEnd 19 | 20 | //#QDS ShaderFragmentStart 21 | // ---------------------------------------------------------------------- 22 | #define pi 3.141592653589 23 | #define pi2 6.283185307179 24 | 25 | in vec4 vertex_color; 26 | 27 | uniform float time_s; 28 | uniform vec3 obj_xy_rot; 29 | uniform float perLen_um; 30 | uniform float perDur_s; 31 | uniform vec4 minRGB; 32 | uniform vec4 maxRGB; 33 | 34 | float inten; 35 | vec4 b; 36 | 37 | void main() { 38 | vec4 a = gl_FragCoord; 39 | a.x = a.x -obj_xy_rot.x; 40 | a.y = a.y -obj_xy_rot.y; 41 | b.x = a.x*cos(obj_xy_rot[2]) -a.y*sin(obj_xy_rot[2]); 42 | b.y = a.y*cos(obj_xy_rot[2]) +a.x*sin(obj_xy_rot[2]); 43 | inten = (sin(((b.x)/perLen_um +time_s/perDur_s) *pi2) +1.0)/2.0; 44 | gl_FragColor = mix(minRGB, maxRGB, inten); 45 | gl_FragColor = mix(gl_FragColor, vertex_color, 0.5); 46 | } 47 | // ---------------------------------------------------------------------- 48 | //#QDS ShaderFragmentEnd 49 | -------------------------------------------------------------------------------- /Shader/SineWaveGratingMix2.cl: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | //#QDS ShaderName =SINE_WAVE_GRATING_MIX2 4 | //#QDS ShaderParamNames =perLen_um; perDur_s; minRGB; maxRGB; mixA 5 | //#QDS ShaderParamLengths =1;1;4;4;1 6 | //#QDS ShaderParamDefaults =0.0; 1.0; (0,0,0,255); (255,255,255,255); 0.5 7 | // 8 | //#QDS ShaderVertexStart 9 | // ---------------------------------------------------------------------- 10 | varying vec4 vertex_color; 11 | 12 | void main() 13 | { 14 | gl_Position = ftransform(); 15 | vertex_color = gl_Color; 16 | } 17 | // ---------------------------------------------------------------------- 18 | //#QDS ShaderVertexEnd 19 | 20 | //#QDS ShaderFragmentStart 21 | // ---------------------------------------------------------------------- 22 | #define pi 3.141592653589 23 | #define pi2 6.283185307179 24 | 25 | in vec4 vertex_color; 26 | 27 | uniform float time_s; 28 | uniform vec3 obj_xy_rot; 29 | uniform float perLen_um; 30 | uniform float perDur_s; 31 | uniform vec4 minRGB; 32 | uniform vec4 maxRGB; 33 | uniform float mixA; 34 | 35 | float inten; 36 | vec4 b; 37 | 38 | void main() { 39 | vec4 a = gl_FragCoord; 40 | a.x = a.x -obj_xy_rot.x; 41 | a.y = a.y -obj_xy_rot.y; 42 | b.x = a.x*cos(obj_xy_rot[2]) -a.y*sin(obj_xy_rot[2]); 43 | b.y = a.y*cos(obj_xy_rot[2]) +a.x*sin(obj_xy_rot[2]); 44 | inten = (sin(((b.x)/perLen_um +time_s/perDur_s) *pi2) +1.0)/2.0; 45 | gl_FragColor = mix(minRGB, maxRGB, inten); 46 | gl_FragColor = mix(gl_FragColor, vertex_color, mixA); 47 | } 48 | // ---------------------------------------------------------------------- 49 | //#QDS ShaderFragmentEnd 50 | -------------------------------------------------------------------------------- /Shader/SquareWaveGrating.cl: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | //#QDS ShaderName =SQUARE_WAVE_GRATING 4 | //#QDS ShaderParamNames =perLen_um; perDur_s; minRGB; maxRGB 5 | //#QDS ShaderParamLengths =1;1;4;4 6 | //#QDS ShaderParamDefaults =0.0; 1.0; (0,0,0,255); (255,255,255,255) 7 | // 8 | //#QDS ShaderVertexStart 9 | // ---------------------------------------------------------------------- 10 | void main() 11 | { 12 | gl_Position = ftransform(); 13 | } 14 | // ---------------------------------------------------------------------- 15 | //#QDS ShaderVertexEnd 16 | 17 | //#QDS ShaderFragmentStart 18 | // ---------------------------------------------------------------------- 19 | #define pi 3.141592653589 20 | #define pi2 6.283185307179 21 | 22 | uniform float time_s; 23 | uniform vec3 obj_xy_rot; 24 | uniform float perLen_um; 25 | uniform float perDur_s; 26 | uniform vec4 minRGB; 27 | uniform vec4 maxRGB; 28 | 29 | float inten; 30 | vec4 b; 31 | 32 | void main() { 33 | vec4 a = gl_FragCoord; 34 | a.x = a.x -obj_xy_rot.x; 35 | a.y = a.y -obj_xy_rot.y; 36 | b.x = a.x*cos(obj_xy_rot[2]) -a.y*sin(obj_xy_rot[2]); 37 | b.y = a.y*cos(obj_xy_rot[2]) +a.x*sin(obj_xy_rot[2]); 38 | inten = ((ceil(sin(((b.x)/perLen_um +time_s/perDur_s) *pi2))+floor(sin(((b.x)/perLen_um +time_s/perDur_s) *pi2)))+1)/2; 39 | gl_FragColor = mix(minRGB, maxRGB, inten); 40 | } 41 | // ---------------------------------------------------------------------- 42 | //#QDS ShaderFragmentEnd 43 | -------------------------------------------------------------------------------- /Shader/SquareWaveGratingMix2.cl: -------------------------------------------------------------------------------- 1 | #version 300 2 | 3 | //#QDS ShaderName =SQUARE_WAVE_GRATING_MIX2 4 | //#QDS ShaderParamNames =perLen_um; perDur_s; minRGB; maxRGB; mixA 5 | //#QDS ShaderParamLengths =1;1;4;4;1 6 | //#QDS ShaderParamDefaults =0.0; 1.0; (0,0,0,255); (255,255,255,255); 0.5 7 | // 8 | //#QDS ShaderVertexStart 9 | // ---------------------------------------------------------------------- 10 | varying vec4 vertex_color; 11 | 12 | void main() 13 | { 14 | gl_Position = ftransform(); 15 | vertex_color = gl_Color; 16 | } 17 | // ---------------------------------------------------------------------- 18 | //#QDS ShaderVertexEnd 19 | 20 | //#QDS ShaderFragmentStart 21 | // ---------------------------------------------------------------------- 22 | #define pi 3.141592653589 23 | #define pi2 6.283185307179 24 | 25 | in vec4 vertex_color; 26 | 27 | uniform float time_s; 28 | uniform vec3 obj_xy_rot; 29 | uniform float perLen_um; 30 | uniform float perDur_s; 31 | uniform vec4 minRGB; 32 | uniform vec4 maxRGB; 33 | uniform float mixA; 34 | 35 | float inten; 36 | vec4 b; 37 | 38 | void main() { 39 | vec4 a = gl_FragCoord; 40 | a.x = a.x -obj_xy_rot.x; 41 | a.y = a.y -obj_xy_rot.y; 42 | b.x = a.x*cos(obj_xy_rot[2]) -a.y*sin(obj_xy_rot[2]); 43 | b.y = a.y*cos(obj_xy_rot[2]) +a.x*sin(obj_xy_rot[2]); 44 | inten = round((sin(((b.x)/perLen_um +time_s/perDur_s) *pi2) +1.0)/2.0); 45 | gl_FragColor = mix(minRGB, maxRGB, inten); 46 | gl_FragColor = mix(gl_FragColor, vertex_color, mixA); 47 | } 48 | // ---------------------------------------------------------------------- 49 | //#QDS ShaderFragmentEnd 50 | -------------------------------------------------------------------------------- /Shader/SquareWaveGratingMix3.cl: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | //#QDS ShaderName =SQUARE_WAVE_GRATING_MIX3 4 | //#QDS ShaderParamNames =perLen_um; perDur_s; minRGB; maxRGB; mixA 5 | //#QDS ShaderParamLengths =1;1;4;4;1 6 | //#QDS ShaderParamDefaults =0.0; 1.0; (0,0,0,255); (255,255,255,255); 0.5 7 | // 8 | //#QDS ShaderVertexStart 9 | // ---------------------------------------------------------------------- 10 | varying vec4 vertex_color; 11 | 12 | void main() 13 | { 14 | gl_Position = ftransform(); 15 | vertex_color = gl_Color; 16 | } 17 | // ---------------------------------------------------------------------- 18 | //#QDS ShaderVertexEnd 19 | 20 | //#QDS ShaderFragmentStart 21 | // ---------------------------------------------------------------------- 22 | #define pi 3.141592653589 23 | #define pi2 6.283185307179 24 | // aspect ratio y/x 25 | #define ar_xy 2.0 26 | 27 | in vec4 vertex_color; 28 | 29 | uniform float time_s; 30 | uniform vec3 obj_xy_rot; 31 | uniform float perLen_um; 32 | uniform float perDur_s; 33 | uniform vec4 minRGB; 34 | uniform vec4 maxRGB; 35 | uniform float mixA; 36 | 37 | float inten; 38 | float l, rot_deg; 39 | vec4 b; 40 | 41 | void main() { 42 | vec4 a = gl_FragCoord; 43 | a.x = a.x -obj_xy_rot.x; 44 | a.y = a.y -obj_xy_rot.y; 45 | b.x = a.x *cos(obj_xy_rot[2]) -a.y*sin(obj_xy_rot[2]); 46 | b.y = a.y *cos(obj_xy_rot[2]) +a.x*sin(obj_xy_rot[2]); 47 | rot_deg = obj_xy_rot[2] /pi2 *360; 48 | l = perLen_um *(ar_xy +(1.-ar_xy) *abs(mod(rot_deg, 180.) -90.) /90.); 49 | inten = round((sin(((b.x)/l +time_s/perDur_s) *pi2) +1.0)/2.0); 50 | gl_FragColor = mix(minRGB, maxRGB, inten); 51 | gl_FragColor = mix(gl_FragColor, vertex_color, mixA); 52 | } 53 | // ---------------------------------------------------------------------- 54 | //#QDS ShaderFragmentEnd 55 | -------------------------------------------------------------------------------- /Shader/SquareWaveGratingMix4.cl: -------------------------------------------------------------------------------- 1 | #version 310 es 2 | // --> TE 2024-08-01 3 | // Modified to run on a RPi 5 w/ Rasbian 64bit bookworm 4 | // - requires shader version 310 es 5 | // <-- 6 | //precision highp float; 7 | 8 | //#QDS ShaderName =SQUARE_WAVE_GRATING_MIX4 9 | //#QDS ShaderParamNames =perLen_um; perDur_s; minRGB; maxRGB; mixA 10 | //#QDS ShaderParamLengths =1;1;4;4;1 11 | //#QDS ShaderParamDefaults =0.0; 1.0; (0,0,0,255); (255,255,255,255); 0.5 12 | // 13 | //#QDS ShaderVertexStart 14 | // ---------------------------------------------------------------------- 15 | varying vec4 vertex_color; 16 | 17 | void main() 18 | { 19 | gl_Position = ftransform(); 20 | vertex_color = gl_Color; 21 | } 22 | // ---------------------------------------------------------------------- 23 | //#QDS ShaderVertexEnd 24 | 25 | //#QDS ShaderFragmentStart 26 | // ---------------------------------------------------------------------- 27 | #define pi 3.141592653589 28 | #define pi2 6.283185307179 29 | 30 | varying vec4 vertex_color; 31 | 32 | uniform float time_s; 33 | uniform vec3 obj_xy_rot; 34 | uniform float perLen_um; 35 | uniform float perDur_s; 36 | uniform vec4 minRGB; 37 | uniform vec4 maxRGB; 38 | uniform float mixA; 39 | 40 | float inten; 41 | vec4 b; 42 | 43 | void main() { 44 | vec4 a = gl_FragCoord; 45 | a.x = a.x -obj_xy_rot.x; 46 | a.y = a.y -obj_xy_rot.y; 47 | b.x = a.x*cos(obj_xy_rot[2]) -a.y*sin(obj_xy_rot[2]); 48 | b.y = a.y*cos(obj_xy_rot[2]) +a.x*sin(obj_xy_rot[2]); 49 | inten = floor((sin(((b.x)/perLen_um +time_s/perDur_s) *pi2) +1.0)/2.0 +0.5); 50 | gl_FragColor = mix(minRGB, maxRGB, inten); 51 | gl_FragColor = mix(gl_FragColor, vertex_color, mixA); 52 | } 53 | // ---------------------------------------------------------------------- 54 | //#QDS ShaderFragmentEnd 55 | -------------------------------------------------------------------------------- /Shader/SquareWaveGratingMix_rp5.cl: -------------------------------------------------------------------------------- 1 | #version 310 es 2 | // --> TE 2024-08-01 3 | // Modified to run on a RPi 5 w/ Rasbian 64bit bookworm 4 | // - requires shader version 310 es 5 | // <-- 6 | //precision highp float; 7 | 8 | //#QDS ShaderName =SQUARE_WAVE_GRATING_MIX_RP5 9 | //#QDS ShaderParamNames =perLen_um; perDur_s; minRGB; maxRGB; mixA 10 | //#QDS ShaderParamLengths =1;1;4;4;1 11 | //#QDS ShaderParamDefaults =0.0; 1.0; (0,0,0,255); (255,255,255,255); 0.5 12 | // 13 | //#QDS ShaderVertexStart 14 | // ---------------------------------------------------------------------- 15 | varying vec4 vertex_color; 16 | 17 | void main() 18 | { 19 | gl_Position = ftransform(); 20 | vertex_color = gl_Color; 21 | } 22 | // ---------------------------------------------------------------------- 23 | //#QDS ShaderVertexEnd 24 | 25 | //#QDS ShaderFragmentStart 26 | // ---------------------------------------------------------------------- 27 | #define pi 3.141592653589 28 | #define pi2 6.283185307179 29 | 30 | varying vec4 vertex_color; 31 | 32 | uniform float time_s; 33 | uniform vec3 obj_xy_rot; 34 | uniform float perLen_um; 35 | uniform float perDur_s; 36 | uniform vec4 minRGB; 37 | uniform vec4 maxRGB; 38 | uniform float mixA; 39 | 40 | float inten; 41 | vec4 b; 42 | 43 | void main() { 44 | vec4 a = gl_FragCoord; 45 | a.x = a.x -obj_xy_rot.x; 46 | a.y = a.y -obj_xy_rot.y; 47 | b.x = a.x*cos(obj_xy_rot[2]) -a.y*sin(obj_xy_rot[2]); 48 | b.y = a.y*cos(obj_xy_rot[2]) +a.x*sin(obj_xy_rot[2]); 49 | inten = floor((sin(((b.x)/perLen_um +time_s/perDur_s) *pi2) +1.0)/2.0 +0.5); 50 | gl_FragColor = mix(minRGB, maxRGB, inten); 51 | gl_FragColor = mix(gl_FragColor, vertex_color, mixA); 52 | } 53 | // ---------------------------------------------------------------------- 54 | //#QDS ShaderFragmentEnd 55 | -------------------------------------------------------------------------------- /Shader/StillSquareWaveGrating.cl: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | //#QDS ShaderName =STILL_SQUARE_WAVE_GRATING 4 | //#QDS ShaderParamNames =perLen_um; phase_um; minRGB; maxRGB 5 | //#QDS ShaderParamLengths =1;1;4;4 6 | //#QDS ShaderParamDefaults =0.0; 0.0; (0,0,0,255); (255,255,255,255) 7 | // 8 | //#QDS ShaderVertexStart 9 | // ---------------------------------------------------------------------- 10 | void main() 11 | { 12 | gl_Position = ftransform(); 13 | } 14 | // ---------------------------------------------------------------------- 15 | //#QDS ShaderVertexEnd 16 | 17 | //#QDS ShaderFragmentStart 18 | // ---------------------------------------------------------------------- 19 | #define pi 3.141592653589 20 | #define pi2 6.283185307179 21 | 22 | uniform float time_s; 23 | uniform vec3 obj_xy_rot; 24 | uniform float perLen_um; 25 | uniform float phase_um; 26 | uniform vec4 minRGB; 27 | uniform vec4 maxRGB; 28 | 29 | float inten; 30 | vec4 b; 31 | 32 | void main() { 33 | vec4 a = gl_FragCoord; 34 | a.x = a.x -obj_xy_rot.x; 35 | a.y = a.y -obj_xy_rot.y; 36 | b.x = a.x*cos(obj_xy_rot[2]) -a.y*sin(obj_xy_rot[2]); 37 | b.y = a.y*cos(obj_xy_rot[2]) +a.x*sin(obj_xy_rot[2]); 38 | inten = ((ceil(sin((((b.x)+phase_um)/perLen_um) *pi2))+floor(sin((((b.x)+phase_um)/perLen_um) *pi2)))+1)/2; 39 | gl_FragColor = mix(minRGB, maxRGB, inten); 40 | } 41 | // ---------------------------------------------------------------------- 42 | //#QDS ShaderFragmentEnd 43 | -------------------------------------------------------------------------------- /Sounds/error.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Sounds/error.mp3 -------------------------------------------------------------------------------- /Sounds/ok.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Sounds/ok.mp3 -------------------------------------------------------------------------------- /Sounds/stim_end.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Sounds/stim_end.mp3 -------------------------------------------------------------------------------- /Sounds/stim_start.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Sounds/stim_start.mp3 -------------------------------------------------------------------------------- /Stimuli/Cricket_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # -------------------------------------------------------------------------- 4 | import QDS 5 | import math 6 | 7 | # -------------------------------------------------------------------------- 8 | def MoveBarSeq(): 9 | # A function that presents the moving bar in the given number of 10 | # directions (= moving bar sequence) 11 | for rot_deg in p["DirList"]: 12 | # Calculate rotation angle and starting position of bar 13 | rot_rad = (rot_deg -90)/360.0 *2 *math.pi 14 | x = math.cos(rot_rad) *( moveDist_um /2.0) 15 | y = math.sin(rot_rad) *(-moveDist_um /2.0) 16 | 17 | # Move the bar stepwise across the screen (as smooth as permitted 18 | # by the refresh frequency) 19 | QDS.Scene_Clear(durMarker_s, 1) 20 | for iStep in range(round(nFrToMove)): 21 | QDS.Scene_RenderEx( 22 | p["durFr_s"], [1], [(x,y)], [(1.0,1.0)], [rot_deg], 23 | iStep < p["nFrPerMarker"] 24 | ) 25 | x -= math.cos(rot_rad) *umPerFr 26 | y += math.sin(rot_rad) *umPerFr 27 | 28 | # -------------------------------------------------------------------------- 29 | # Main script 30 | # -------------------------------------------------------------------------- 31 | QDS.Initialize("Cricket_1", "") 32 | 33 | # Define global stimulus parameters 34 | # 35 | p = { 36 | "nTrials" : 3, 37 | "DirList" : [0,180, 45,225, 90,270, 135,315], 38 | "tMoveDur_s" : 3.0, # duration of movement (-> traveled distance) 39 | "barDx_um" : 75.0, # "cricket" dimensions in um (Johnson et al., 2021) 40 | "barDy_um" : 195.0, 41 | "vel_umSec" : 650.0, # speed of "cricket" in um/sec 42 | "bkgColor" : (10,20,30, 30,20,10), 43 | "barColor" : (255,255,255), 44 | "durFr_s" : 1/60.0, 45 | "nFrPerMarker" : 3 46 | } 47 | QDS.LogUserParameters(p) 48 | 49 | # Do some calculations 50 | durMarker_s = p["durFr_s"]*p["nFrPerMarker"] 51 | freq_Hz = round(1.0 /p["durFr_s"]) 52 | umPerFr = float(p["vel_umSec"]) /freq_Hz 53 | moveDist_um = p["vel_umSec"] *p["tMoveDur_s"] 54 | nFrToMove = float(moveDist_um) /umPerFr 55 | 56 | # Define stimulus object(s) 57 | # "cricket" 58 | QDS.DefObj_Box(1, p["barDx_um"], p["barDy_um"]) 59 | 60 | # Start of stimulus run 61 | QDS.StartScript() 62 | 63 | QDS.SetObjColor(1, [1], [p["barColor"]]) 64 | QDS.SetBkgColor(p["bkgColor"]) 65 | QDS.Scene_Clear(3.0, 0) 66 | 67 | # Loop the moving bar sequence nTrial times 68 | QDS.Loop(p["nTrials"], MoveBarSeq) 69 | 70 | # Finalize stimulus 71 | QDS.Scene_Clear(1.0, 0) 72 | QDS.EndScript() 73 | 74 | # -------------------------------------------------------------------------- 75 | -------------------------------------------------------------------------------- /Stimuli/Cricket_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # -------------------------------------------------------------------------- 4 | import QDS 5 | import math, random 6 | 7 | # -------------------------------------------------------------------------- 8 | def MoveBarSeq(): 9 | """ A function that presents the moving bar in the given number of 10 | directions (= moving bar sequence) 11 | """ 12 | for rot_deg in p["DirList"]: 13 | # Calculate rotation angle and starting position of bar 14 | rot_rad = (rot_deg -90)/360.0 *2 *math.pi 15 | x = math.cos(rot_rad) *( moveDist_um /2.0) 16 | y = math.sin(rot_rad) *(-moveDist_um /2.0) 17 | 18 | # Move the bar stepwise across the screen (as smooth as permitted 19 | # by the refresh frequency) 20 | #QDS.Scene_Clear(durMarker_s, 1) 21 | iFr = 0 22 | for iStep in range(round(nFrToMove)): 23 | if iFr % nFrPerNoise == 0: 24 | updateNoise() 25 | QDS.Scene_RenderEx( 26 | p["durFr_s"], BoxIndList +[1], 27 | BoxPosList +[(x,y)], 28 | [(1.0,1.0)]*(nB+1), 29 | [0]*nB +[rot_deg], 30 | iStep < p["nFrPerMarker"] 31 | ) 32 | x -= math.cos(rot_rad) *umPerFr 33 | y += math.sin(rot_rad) *umPerFr 34 | iFr += 1 35 | 36 | def updateNoise(): 37 | """ Draw background noise 38 | """ 39 | #global BoxIndList, BoxPosList, nB, p 40 | BoxColList = [] 41 | for iB in range(2, nB+2): 42 | r = 0 43 | g = random.randint(p["noiseG"][0], p["noiseG"][1]) 44 | b = random.randint(p["noiseUV"][0], p["noiseUV"][1]) 45 | a = int(max(min(255, 255*p["noiseAlpha"]), 0)) 46 | if (p["noiseVAtten"] != 1) and (iB-2 < nB/2): 47 | g = int(g *p["noiseVAtten"]) 48 | b = int(b *p["noiseVAtten"]) 49 | BoxColList.append((r, g, b)) 50 | QDS.SetObjColorEx(BoxIndList, BoxColList, [a]*nB) 51 | 52 | # -------------------------------------------------------------------------- 53 | # Main script 54 | # -------------------------------------------------------------------------- 55 | QDS.Initialize("Cricket_2", "") 56 | 57 | # Define global stimulus parameters 58 | # 59 | p = { 60 | "nTrials" : 5, 61 | "DirList" : [0,180, 45,225, 90,270, 135,315], 62 | "tMoveDur_s" : 3.0, # duration of movement (-> traveled distance) 63 | "barDx_um" : 75.0, # "cricket" dimensions in um (Johnson et al., 2021) 64 | "barDy_um" : 195.0, 65 | "vel_umSec" : 650.0, # speed of "cricket" in um/sec 66 | "bkgColor" : (10,20,30, 30,20,10), 67 | "barColor" : (255,255,255), 68 | "durFr_s" : 1/60.0, 69 | "nFrPerMarker" : 3, 70 | "noiseRows" : 8, # Noise grid dimensions 71 | "noiseCols" : 12, 72 | "noisePixDx" : 50, # Noise pixel size in um 73 | "noisePixDy" : 50, 74 | "noiseUV" : [5,200], 75 | "noiseG" : [5,200], 76 | "noiseAlpha" : 0.5, 77 | "noiseFreq_Hz" : 5, 78 | "noiseVAtten" : 0.1 79 | } 80 | QDS.LogUserParameters(p) 81 | 82 | # Do some calculations 83 | durMarker_s = p["durFr_s"]*p["nFrPerMarker"] 84 | freq_Hz = round(1.0 /p["durFr_s"]) 85 | umPerFr = float(p["vel_umSec"]) /freq_Hz 86 | moveDist_um = p["vel_umSec"] *p["tMoveDur_s"] 87 | nFrToMove = float(moveDist_um) /umPerFr 88 | nFrPerNoise = freq_Hz /p["noiseFreq_Hz"] 89 | 90 | # Define stimulus objects 91 | # "cricket" 92 | QDS.DefObj_Box(1, p["barDx_um"], p["barDy_um"]) 93 | 94 | # Background noise 95 | BoxIndList = [] 96 | BoxPosList = [] 97 | nB = p['noiseRows']*p['noiseCols'] 98 | for iB in range(2, nB+2): 99 | BoxIndList.append(iB) 100 | QDS.DefObj_Box(iB, p['noisePixDx'], p['noisePixDy']) 101 | 102 | for iY in range(p['noiseRows']): 103 | for iX in range(p['noiseCols']): 104 | iB = 1 +iX +iY*p['noiseCols'] 105 | dx = p['noisePixDx'] 106 | dy = p['noisePixDy'] 107 | x = iX*dx +dx/2.0 -dx*p['noiseCols']/2.0 108 | y = iY*dy +dy/2.0 -dy*p['noiseRows']/2.0 109 | BoxPosList.append((x,y)) 110 | 111 | 112 | # Start of stimulus run 113 | QDS.StartScript() 114 | 115 | QDS.SetObjColor(1, [1], [p["barColor"]]) 116 | QDS.SetBkgColor(p["bkgColor"]) 117 | QDS.Scene_Clear(3.0, 0) 118 | 119 | # Loop the moving bar sequence nTrial times 120 | QDS.Loop(p["nTrials"], MoveBarSeq) 121 | 122 | # Finalize stimulus 123 | QDS.Scene_Clear(1.0, 0) 124 | QDS.EndScript() 125 | 126 | # -------------------------------------------------------------------------- 127 | -------------------------------------------------------------------------------- /Stimuli/Cricket_3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # -------------------------------------------------------------------------- 4 | import QDS 5 | import math, random 6 | 7 | # -------------------------------------------------------------------------- 8 | def MoveBarSeq(): 9 | """ A function that presents the moving bar in the given number of 10 | directions (= moving bar sequence) 11 | """ 12 | for rot_deg in p["DirList"]: 13 | # Calculate rotation angle and starting position of bar 14 | rot_rad = (rot_deg -90)/360.0 *2 *math.pi 15 | x = math.cos(rot_rad) *( moveDist_um /2.0) 16 | y = math.sin(rot_rad) *(-moveDist_um /2.0) 17 | 18 | # Move the bar stepwise across the screen (as smooth as permitted 19 | # by the refresh frequency) 20 | #QDS.Scene_Clear(durMarker_s, 1) 21 | iFr = 0 22 | for iStep in range(round(nFrToMove)): 23 | if iFr % nFrPerNoise == 0: 24 | updateNoise() 25 | QDS.Scene_RenderEx( 26 | p["durFr_s"], BoxIndList +[1], 27 | BoxPosList +[(x,y)], 28 | [(1.0,1.0)]*(nB+1), 29 | [0]*nB +[rot_deg], 30 | iStep < p["nFrPerMarker"] 31 | ) 32 | x -= math.cos(rot_rad) *umPerFr 33 | y += math.sin(rot_rad) *umPerFr 34 | iFr += 1 35 | 36 | def updateNoise(): 37 | """ Draw background noise 38 | """ 39 | #global BoxIndList, BoxPosList, nB, p 40 | BoxColList = [] 41 | for iB in range(2, nB+2): 42 | r = 0 43 | g = random.randint(p["noiseG"][0], p["noiseG"][1]) 44 | b = random.randint(p["noiseUV"][0], p["noiseUV"][1]) 45 | a = int(max(min(255, 255*p["noiseAlpha"]), 0)) 46 | if (p["noiseVAtten"] != 1) and (iB-2 < nB/2): 47 | g = int(g *p["noiseVAtten"]) 48 | b = int(b *p["noiseVAtten"]) 49 | BoxColList.append((r, g, b)) 50 | QDS.SetObjColorEx(BoxIndList, BoxColList, [a]*nB) 51 | 52 | # -------------------------------------------------------------------------- 53 | # Main script 54 | # -------------------------------------------------------------------------- 55 | QDS.Initialize("Cricket_2", "") 56 | 57 | # Define global stimulus parameters 58 | # 59 | p = { 60 | "nTrials" : 5, 61 | "DirList" : [0,180, 45,225, 90,270, 135,315], 62 | "tMoveDur_s" : 3.0, # duration of movement (-> traveled distance) 63 | "barDx_um" : 75.0, # "cricket" dimensions in um (Johnson et al., 2021) 64 | "barDy_um" : 195.0, 65 | "vel_umSec" : 650.0, # speed of "cricket" in um/sec 66 | "bkgColor" : (10,20,30, 30,20,10), 67 | "barColor" : (0,0,0), 68 | "durFr_s" : 1/60.0, 69 | "nFrPerMarker" : 3, 70 | "noiseRows" : 8, # Noise grid dimensions 71 | "noiseCols" : 12, 72 | "noisePixDx" : 50, # Noise pixel size in um 73 | "noisePixDy" : 50, 74 | "noiseUV" : [50,255], 75 | "noiseG" : [50,255], 76 | "noiseAlpha" : 1, 77 | "noiseFreq_Hz" : 5, 78 | "noiseVAtten" : 0.3 79 | } 80 | QDS.LogUserParameters(p) 81 | 82 | # Do some calculations 83 | durMarker_s = p["durFr_s"]*p["nFrPerMarker"] 84 | freq_Hz = round(1.0 /p["durFr_s"]) 85 | umPerFr = float(p["vel_umSec"]) /freq_Hz 86 | moveDist_um = p["vel_umSec"] *p["tMoveDur_s"] 87 | nFrToMove = float(moveDist_um) /umPerFr 88 | nFrPerNoise = freq_Hz /p["noiseFreq_Hz"] 89 | 90 | # Define stimulus objects 91 | # "cricket" 92 | QDS.DefObj_Box(1, p["barDx_um"], p["barDy_um"]) 93 | 94 | # Background noise 95 | BoxIndList = [] 96 | BoxPosList = [] 97 | nB = p['noiseRows']*p['noiseCols'] 98 | for iB in range(2, nB+2): 99 | BoxIndList.append(iB) 100 | QDS.DefObj_Box(iB, p['noisePixDx'], p['noisePixDy']) 101 | 102 | for iY in range(p['noiseRows']): 103 | for iX in range(p['noiseCols']): 104 | iB = 1 +iX +iY*p['noiseCols'] 105 | dx = p['noisePixDx'] 106 | dy = p['noisePixDy'] 107 | x = iX*dx +dx/2.0 -dx*p['noiseCols']/2.0 108 | y = iY*dy +dy/2.0 -dy*p['noiseRows']/2.0 109 | BoxPosList.append((x,y)) 110 | 111 | 112 | # Start of stimulus run 113 | QDS.StartScript() 114 | 115 | QDS.SetObjColor(1, [1], [p["barColor"]]) 116 | QDS.SetBkgColor(p["bkgColor"]) 117 | QDS.Scene_Clear(3.0, 0) 118 | 119 | # Loop the moving bar sequence nTrial times 120 | QDS.Loop(p["nTrials"], MoveBarSeq) 121 | 122 | # Finalize stimulus 123 | QDS.Scene_Clear(1.0, 0) 124 | QDS.EndScript() 125 | 126 | # -------------------------------------------------------------------------- 127 | -------------------------------------------------------------------------------- /Stimuli/New Stimuli/Neu_Polar_1.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Stimuli/New Stimuli/Neu_Polar_1.pickle -------------------------------------------------------------------------------- /Stimuli/New Stimuli/Neu_Polar_1.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on April 19th 2016 @author: Luke Edward Rogerson, AG Euler 4 | """ 5 | import collections 6 | from functools import partial 7 | import matplotlib.pyplot as plt 8 | import numpy as np 9 | import QDS 10 | import seaborn as sns 11 | 12 | p = {'_sName' : "Polar_flicker_0", 13 | '_sDescr' : "Noise flicker distributed over a polar grid", 14 | 'rng_seed' : 1, 15 | 'frameRate' : 60, 16 | 'duration' : 10, 17 | 'radius_max' : 200, 18 | 'radius_itx' : 4, #2, 19 | 'azimuth_max' : 2*np.pi, 20 | 'azimuth_itx' : 5, #3 21 | 'iFull' : 255, 22 | } 23 | 24 | def buildStimulus(p): 25 | radius = np.linspace(0,p['radius_max'],p['radius_itx']+1) 26 | azimuth = np.linspace(0,p['azimuth_max'],p['azimuth_itx']+1) 27 | rad_to_degree = lambda rad: rad*180/np.pi 28 | np.random.seed(seed=p['rng_seed']) 29 | 30 | domain = [] 31 | for a in range(p['radius_itx']): 32 | for b in range(p['azimuth_itx']): 33 | domain.append((radius[a], 34 | radius[a+1], 35 | rad_to_degree(azimuth[b]), 36 | rad_to_degree(azimuth[b+1] -azimuth[b]))) 37 | 38 | p['domain'] = domain 39 | p['rand_streams'] = np.random.rand(len(domain),p['duration']*p['frameRate']) 40 | 41 | for itx in range(len(domain)): 42 | QDS.DefObj_Sector(itx,domain[itx][1],domain[itx][0],domain[itx][2],domain[itx][3], _astep=10) 43 | print(itx,domain[itx][1],domain[itx][0],domain[itx][2],domain[itx][3]) 44 | 45 | def iterateStimulus(p): 46 | for t_pnt in range(p['rand_streams'].shape[1]): 47 | IND,RGB,POS,MAG,ANG = [],[],[],[],[] 48 | for itx in range(len(p['domain'])): 49 | IND.append(itx,) 50 | RGB.append(3*(int(p['rand_streams'][itx,t_pnt]*p['iFull']),)) 51 | POS.append((0,0),) 52 | MAG.append((1,1),) 53 | ANG.append(0,) 54 | QDS.SetObjColor(itx,[IND[itx]],[RGB[itx]]) 55 | 56 | QDS.Scene_RenderEx(1/p["frameRate"],IND,POS,MAG,ANG,0) 57 | 58 | # -------------------------------------------------------------------------- 59 | dispatcher = collections.OrderedDict([ 60 | ('init', partial(QDS.Initialize,p['_sName'],p['_sDescr'])), 61 | ('log', partial(QDS.LogUserParameters,p)), 62 | ('build', partial(buildStimulus,p)), 63 | ('start', QDS.StartScript), 64 | ('clear1', partial(QDS.Scene_Clear,1.0, 0)), 65 | ('iter', partial(iterateStimulus,p)), 66 | ('clear2', partial(QDS.Scene_Clear,1.0, 0)), 67 | ('stop', QDS.EndScript)] 68 | ) 69 | 70 | [dispatcher[process]() for process in list(dispatcher.keys())] -------------------------------------------------------------------------------- /Stimuli/New Stimuli/Neu_Ripple_1.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Stimuli/New Stimuli/Neu_Ripple_1.pickle -------------------------------------------------------------------------------- /Stimuli/New Stimuli/Neu_Ripple_1.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu Apr 7 11:32:23 2016 4 | 5 | @author: Luke 6 | """ 7 | import collections 8 | from functools import partial 9 | import QDS 10 | import numpy as np 11 | import random 12 | 13 | # Define global stimulus parameters 14 | p = {'_sName' : "Ripple_0", 15 | '_sDescr' : "Sine wave ripple", 16 | 'seed' : 1, 17 | "nTrials" : 1, 18 | "iHalf" : 127, 19 | "iFull" : 254, 20 | "dxStim_um" : 100, # Stimulus size 21 | "durFr_s" : 1/60.0, # Frame duration 22 | 'durRipple' : 2, 23 | 'contrast_max' : 0.9, 24 | 'contrast_min' : 0.1, 25 | 'contrast_n' : 5, 26 | 'frequency_max' : 12, 27 | 'frequency_min' : 2, 28 | 'frequency_n' : 10, 29 | "tSteadyMID_s" : 2, # Light at 50% for steps 30 | } 31 | 32 | def buildStimulus(p): 33 | '''Set random seed. Generate sets of contrast and frequency values. Generate 34 | all permutations of these sets. Shuffle the order of the permutation set. 35 | Create single elliptical object, for which the freqeuency is modulated.''' 36 | 37 | random.seed(p['seed']) 38 | contrast_values = np.linspace(p['contrast_min'],p['contrast_max'],p['contrast_n']) 39 | frequency_values = np.linspace(p['frequency_min'],p['frequency_max'],p['frequency_n']) 40 | p['contfreq_pairs'] = [(x,y) for x in frequency_values for y in contrast_values] 41 | random.shuffle(p['contfreq_pairs']) 42 | 43 | # Define stimulus objects 44 | QDS.DefObj_Ellipse(1, p["dxStim_um"], p["dxStim_um"]) 45 | 46 | def iterateStimulus(p): 47 | '''Define function for single sine ripple, and conversion function to 48 | generate RGB values. Generate timeseries for each value pair instance. 49 | Iterate over all value pairs, calculating the intensity change at each 50 | timepoint. Render the intensity change of the ellipse.''' 51 | 52 | for (frequency,contrast) in p['contfreq_pairs']: 53 | RGB = 3 *(p["iHalf"],) 54 | QDS.SetObjColor (1, [1], [RGB]) 55 | QDS.Scene_Render(p["tSteadyMID_s"], 1, [1], [(0,0)], 0) 56 | 57 | for t_pnt in range(int(p['durRipple']/p['durFr_s'])): 58 | Intensity = np.sin(np.pi*frequency*t_pnt)*contrast*p["iHalf"]+p["iHalf"] 59 | RGB = 3 *(int(Intensity),) 60 | QDS.SetObjColor(1, [1], [RGB]) 61 | QDS.Scene_Render(p["durFr_s"], 1, [1], [(0,0)], 0) 62 | # -------------------------------------------------------------------------- 63 | dispatcher = collections.OrderedDict([ 64 | ('init', partial(QDS.Initialize,p['_sName'],p['_sDescr'])), 65 | ('log', partial(QDS.LogUserParameters,p)), 66 | ('build', partial(buildStimulus,p)), 67 | ('start', QDS.StartScript), 68 | ('clear1', partial(QDS.Scene_Clear,1.0, 0)), 69 | ('iter', partial(iterateStimulus,p)), 70 | ('clear2', partial(QDS.Scene_Clear,1.0, 0)), 71 | ('stop', QDS.EndScript)] 72 | ) 73 | 74 | [dispatcher[process]() for process in list(dispatcher.keys())] -------------------------------------------------------------------------------- /Stimuli/New Stimuli/Neu_Scribble_1.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Stimuli/New Stimuli/Neu_Scribble_1.pickle -------------------------------------------------------------------------------- /Stimuli/New Stimuli/Neu_Scribble_1.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created April 2016 @author: Luke Edward Rogerson, AG Euler 4 | 5 | """ 6 | import collections 7 | from functools import partial 8 | import numpy as np 9 | import QDS 10 | import scipy as sp 11 | import scipy.interpolate 12 | import seaborn as sns 13 | 14 | p = {'_sName' : "Scribble_0", 15 | '_sDescr' : "Continuously varying sine wave", 16 | 'rng_seed' : 1, 17 | 'pnt_seed' : 500, 18 | 'velocity' : 0.33, 19 | 'duration' : 60, 20 | 'frameRate' : 60, 21 | 'iHalf' : 127, 22 | 'contrast_max' : 1, 23 | 'contrast_min' : 0, 24 | 'frequency_max' : 12, 25 | 'frequency_min' : 2, 26 | 'dot_um' : 100 27 | } 28 | 29 | def buildStimulus(p): 30 | # http://mathworld.wolfram.com/SpherePointPicking.html 31 | np.random.seed(seed=p['rng_seed']) 32 | theta = 2*np.pi*np.random.rand(p['pnt_seed']) 33 | phi = np.arccos(2*np.random.rand(p['pnt_seed'])-1) 34 | 35 | dX = np.cos(theta[1:])*np.sin(phi[1:]) - np.cos(theta[:-1])*np.sin(phi[:-1]) 36 | dY = np.sin(theta[1:])*np.sin(phi[1:]) - np.sin(theta[:-1])*np.sin(phi[:-1]) 37 | dT = np.cumsum(np.append(0,1/np.sqrt(dX**2 + dY**2))) # <- distance correction? 38 | 39 | # if p['velocity']*p['duration']/p['frameRate'] > dT.shape[0]: 40 | # raise ValueError('Target trajectory exceeds seed trajectory') 41 | 42 | coord = np.append(np.expand_dims(theta,axis=1),np.expand_dims(phi,axis=1),axis=1) 43 | timepoints = np.linspace(0,p['duration'],p['duration']*p['frameRate']) 44 | coord_interp = sp.interpolate.interp1d(dT,coord,kind='cubic',axis=0)(timepoints) 45 | 46 | spherical_to_cartesian = lambda theta,phi: [np.cos(theta)*np.sin(phi),np.sin(theta)*np.sin(phi)] 47 | coord_xy = np.asarray(spherical_to_cartesian(coord_interp[:,0],coord_interp[:,1])).T 48 | 49 | coord_to_value = lambda value,parMax,parMin: (value+1)/2*(parMax-parMin)+parMin 50 | frequency = coord_to_value(coord_xy[:,0],p['frequency_max'],p['frequency_min']) 51 | contrast = coord_to_value(coord_xy[:,1],p['contrast_max'],p['contrast_min']) 52 | 53 | intensity = np.zeros((frequency.shape[0])) 54 | for itx in range(frequency.shape[0]): 55 | intensity[itx] = np.sin(2*np.pi*frequency[itx])*contrast[itx]*p['iHalf'] +p['iHalf'] 56 | 57 | p['intensity'] = intensity 58 | # Define stimulus objects 59 | QDS.DefObj_Ellipse(1, p['dot_um'], p['dot_um']) 60 | 61 | def iterateStimulus(p): 62 | for t_pnt in range(p['intensity'].shape[0]): 63 | RGB = 3 *(int(p['intensity'][t_pnt]),) 64 | QDS.SetObjColor(1, [1], [RGB]) 65 | QDS.Scene_Render(1/p["frameRate"], 1, [1], [(0,0)], 0) 66 | 67 | # -------------------------------------------------------------------------- 68 | dispatcher = collections.OrderedDict([ 69 | ('init', partial(QDS.Initialize,p['_sName'],p['_sDescr'])), 70 | ('log', partial(QDS.LogUserParameters,p)), 71 | ('build', partial(buildStimulus,p)), 72 | ('start', QDS.StartScript), 73 | ('clear1', partial(QDS.Scene_Clear,1.0, 0)), 74 | ('iter', partial(iterateStimulus,p)), 75 | ('clear2', partial(QDS.Scene_Clear,1.0, 0)), 76 | ('stop', QDS.EndScript)] 77 | ) 78 | 79 | [dispatcher[process]() for process in list(dispatcher.keys())] -------------------------------------------------------------------------------- /Stimuli/New Stimuli/RGC_BG_3s_3.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Stimuli/New Stimuli/RGC_BG_3s_3.pickle -------------------------------------------------------------------------------- /Stimuli/New Stimuli/RGC_BG_3s_3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # --------------------------------------------------------------------- 4 | import collections 5 | from functools import partial 6 | import QDS 7 | 8 | # Define global stimulus parameters 9 | p = {'_sName' : 'RGC_BG_3s', 10 | '_sDescr' : "'B/G' in fingerprinting stimulus set", 11 | 'nTrials' : 3, 12 | 'dxStim_um' : 1000, # Stimulus size 13 | 'durFr_s' : 1/60.0, # Frame duration 14 | 'nFrPerMarker' : 3, 15 | 'RGB_green' : (0,255,0), 16 | 'RGB_blue' : (0,0,255)} 17 | 18 | def buildStimulus(p): 19 | # Do some calculations 20 | p['durMarker_s'] = p['durFr_s'] *p['nFrPerMarker'] 21 | 22 | # Define stimulus objects 23 | QDS.DefObj_Ellipse(1, p['dxStim_um'], p['dxStim_um']) 24 | 25 | def iterateStimulus(p): 26 | # Generate stimulus by iterating over function calls 27 | for iT in range(p['nTrials']): 28 | QDS.SetObjColor (1, [1], [p['RGB_green']]) 29 | QDS.Scene_Render(p['durMarker_s'], 1, [1], [(0,0)], 1) 30 | QDS.Scene_Render(3.0 -p['durMarker_s'], 1, [1], [(0,0)], 0) 31 | QDS.Scene_Clear(3.0, 0) 32 | 33 | QDS.SetObjColor (1, [1], [p['RGB_blue']]) 34 | QDS.Scene_Render(p['durMarker_s'], 1, [1], [(0,0)], 1) 35 | QDS.Scene_Render(3.0-p['durMarker_s'], 1, [1], [(0,0)], 0) 36 | QDS.Scene_Clear(3.0, 0) 37 | 38 | # -------------------------------------------------------------------------- 39 | dispatcher = collections.OrderedDict([ 40 | ('init', partial(QDS.Initialize,p['_sName'],p['_sDescr'])), 41 | ('log', partial(QDS.LogUserParameters,p)), 42 | ('build', partial(buildStimulus,p)), 43 | ('start', QDS.StartScript), 44 | ('clear1', partial(QDS.Scene_Clear,1.0, 0)), 45 | ('iter', partial(iterateStimulus,p)), 46 | ('clear2', partial(QDS.Scene_Clear,1.0, 0)), 47 | ('stop', QDS.EndScript)] 48 | ) 49 | 50 | [dispatcher[process]() for process in list(dispatcher.keys())] -------------------------------------------------------------------------------- /Stimuli/New Stimuli/RGC_BWNoise_3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # --------------------------------------------------------------------- 4 | import collections 5 | from functools import partial 6 | import QDS 7 | 8 | # Define global stimulus parameters 9 | p = {'_sName' : "RGC_Noise", 10 | '_sDescr' : "'noise' in fingerprinting stimulus set", 11 | 'durStim_s' : 0.200, 12 | 'boxDx_um' : 40, 13 | 'boxDy_um' : 40, 14 | 'fIntenW' : 255, # intensity factor (pixel value(0,1) x fIntenW) 15 | 'fNameNoise' : 'RGC_BWNoise_official', 16 | 'durFr_s' : 1/60.0, # Frame duration 17 | 'nFrPerMarker' : 3 18 | } 19 | 20 | def buildStimulus(p): 21 | p['fPath'] = QDS.GetStimulusPath() 22 | p['durMarker_s'] = p['durFr_s'] *p['nFrPerMarker'] 23 | 24 | '''Read file with M sequence''' 25 | try: 26 | f = open(p['fPath'] +'\\' +p['fNameNoise'] +'.txt', 'r') 27 | iLn = 0 28 | p['Frames'] = [] 29 | 30 | while 1: 31 | line = f.readline() 32 | if not line: 33 | break 34 | parts = line.split(',') 35 | if iLn == 0: 36 | p['nX'], p['nY'], p['nFr'] = int(parts[0]),int(parts[1]),int(parts[2]) 37 | p['nB'] = p['nX']*p['nY'] 38 | else: 39 | Frame = [] 40 | for iB in range(1, p['nB']+1): 41 | r = int(parts[iB-1]) *p['fIntenW'] 42 | Frame.append((r,r,r)) 43 | p['Frames'].append(Frame) 44 | iLn += 1 45 | finally: 46 | f.close() 47 | 48 | '''Define objects 49 | Create box objects, one for each field of the checkerboard, such that we 50 | can later just change their color to let them appear or disappear''' 51 | 52 | for iB in range(1, p['nB']+1): 53 | QDS.DefObj_Box(iB, p['boxDx_um'], p['boxDy_um'], 0) 54 | 55 | '''Create two lists, one with the indices of the box objects and one with 56 | their positions in the checkerboard; these lists later facilitate using 57 | the Scene_Render() command to display the checkerboard''' 58 | 59 | p['BoxIndList'],p['BoxPosList'] = [], [] 60 | for iX in range(p['nX']): 61 | for iY in range(p['nY']): 62 | iB = 1 +iX +iY*p['nX'] 63 | x = iX*p['boxDx_um'] -(p['boxDx_um']*p['nX']/2) 64 | y = iY*p['boxDy_um'] -(p['boxDy_um']*p['nY']/2) 65 | p['BoxIndList'].append(iB) 66 | p['BoxPosList'].append((x,y)) 67 | 68 | def iterateStimulus(p): 69 | for iF in range(p['nFr']): 70 | QDS.SetObjColor(p['nB'], p['BoxIndList'], p['Frames'][iF]) 71 | QDS.Scene_Render(p['durMarker_s'], p['nB'], p['BoxIndList'], p['BoxPosList'], 1) 72 | QDS.Scene_Render(p['durStim_s'] -p['durMarker_s'], p['nB'], p['BoxIndList'], p['BoxPosList'], 0) 73 | 74 | # -------------------------------------------------------------------------- 75 | dispatcher = collections.OrderedDict([ 76 | ('init', partial(QDS.Initialize,p['_sName'],p['_sDescr'])), 77 | ('log', partial(QDS.LogUserParameters,p)), 78 | ('build', partial(buildStimulus,p)), 79 | ('start', QDS.StartScript), 80 | ('clear1', partial(QDS.Scene_Clear,1.0, 0)), 81 | ('iter', partial(iterateStimulus,p)), 82 | ('clear2', partial(QDS.Scene_Clear,1.0, 0)), 83 | ('stop', QDS.EndScript)] 84 | ) 85 | 86 | [dispatcher[process]() for process in list(dispatcher.keys())] 87 | -------------------------------------------------------------------------------- /Stimuli/New Stimuli/RGC_Chirp_3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # --------------------------------------------------------------------- 4 | import collections 5 | from functools import partial 6 | import QDS 7 | import math 8 | 9 | # Define global stimulus parameters 10 | p = {'_sName' : "RGC_Chirp", 11 | '_sDescr' : "'chirp' in fingerprinting stimulus set", 12 | "nTrials" : 1, 13 | "chirpDur_s" : 8.0, # Rising chirp phase 14 | "chirpMaxFreq_Hz" : 8.0, # Peak frequency of chirp (Hz) 15 | "ContrastFreq_Hz" : 2.0, # Freqency at which contrast 16 | # is modulated 17 | "tSteadyOFF_s" : 3.0, # Light OFF at beginning ... 18 | "tSteadyOFF2_s" : 2.0, # ... and end of stimulus 19 | "tSteadyON_s" : 3.0, # Light 100% ON before and 20 | # after chirp 21 | "tSteadyMID_s" : 2.0, # Light at 50% for steps 22 | "IHalf" : 127, 23 | "IFull" : 254, 24 | "dxStim_um" : 1000, # Stimulus size 25 | "StimType" : 2, # 1 = Box, 2 = Circle/ 26 | "durFr_s" : 1/60.0, # Frame duration 27 | "nFrPerMarker" : 3} 28 | 29 | def buildStimulus(p): 30 | '''Chirp formula is f(t) = sin( 2pi*F0 + pi*K*t^2)), where: 31 | F0 is Starting Frequency 32 | K = Acceleration (Hz / s) 33 | t = time (s)''' 34 | 35 | p['nPntChirp'] = int(p["chirpDur_s"] /p["durFr_s"]) 36 | p['K_HzPerSec'] = p["chirpMaxFreq_Hz"] /p["chirpDur_s"] # acceleration in Hz/s 37 | p['durMarker_s'] = p["durFr_s"]*p["nFrPerMarker"] 38 | p['RGB_IHalf'] = 3 *(p["IHalf"],) 39 | p['RGB_IFull'] = 3 *(p["IFull"],) 40 | 41 | # Define stimulus objects 42 | QDS.DefObj_Box(1, p["dxStim_um"], p["dxStim_um"], 0) 43 | QDS.DefObj_Ellipse(2, p["dxStim_um"], p["dxStim_um"]) 44 | 45 | def iterateStimulus(p): 46 | for iL in range(p["nTrials"]): 47 | # Steady steps 48 | QDS.Scene_Clear(p['durMarker_s'], 1) 49 | QDS.Scene_Clear(p["tSteadyOFF2_s"] -p['durMarker_s'], 0) 50 | 51 | QDS.SetObjColor(1, [p["StimType"]], [p['RGB_IFull']]) 52 | QDS.Scene_Render(p["tSteadyON_s"], 1, [p["StimType"]], [(0,0)], 0) 53 | 54 | QDS.Scene_Clear(p['durMarker_s'], 1) 55 | QDS.Scene_Clear(p["tSteadyOFF_s"] -p['durMarker_s'], 0) 56 | 57 | QDS.SetObjColor(1, [p["StimType"]], [p['RGB_IHalf']]) 58 | QDS.Scene_Render(p["tSteadyMID_s"], 1, [p["StimType"]], [(0,0)], 0) 59 | 60 | # Frequency chirp 61 | for iT in range(p['nPntChirp']): 62 | t_s = iT*p["durFr_s"] # in ms 63 | Intensity = math.sin(math.pi *p['K_HzPerSec'] *t_s**2) *p["IHalf"] +p["IHalf"] 64 | RGB = 3 *(int(Intensity),) # -> RGB tuple 65 | QDS.SetObjColor (1, [p["StimType"]], [RGB]) 66 | QDS.Scene_Render(p["durFr_s"], 1, [p["StimType"]], [(0,0)], 0) 67 | 68 | # Gap between frequency chirp and contrast chirp 69 | QDS.SetObjColor (1, [p["StimType"]], [p['RGB_IHalf']]) 70 | QDS.Scene_Render(p["tSteadyMID_s"], 1, [p["StimType"]], [(0,0)], 0) 71 | 72 | # Contrast chirp 73 | for iPnt in range(p['nPntChirp']): 74 | t_s = iPnt*p["durFr_s"] 75 | IRamp = int(p["IHalf"] *t_s /p["chirpDur_s"]) 76 | 77 | Intensity = math.sin(2*math.pi *p["ContrastFreq_Hz"] *t_s) *IRamp +p["IHalf"] 78 | RGB = 3 *(int(Intensity),) 79 | QDS.SetObjColor(1, [p["StimType"]], [RGB]) 80 | QDS.Scene_Render(p["durFr_s"], 1, [p["StimType"]], [(0,0)], 0) 81 | 82 | # Gap after contrast chirp 83 | QDS.SetObjColor(1, [p["StimType"]], [p['RGB_IHalf']]) 84 | QDS.Scene_Render(p["tSteadyMID_s"], 1, [p["StimType"]], [(0,0)], 0) 85 | 86 | QDS.Scene_Clear(p["tSteadyOFF_s"], 0) 87 | 88 | 89 | # -------------------------------------------------------------------------- 90 | dispatcher = collections.OrderedDict([ 91 | ('init', partial(QDS.Initialize,p['_sName'],p['_sDescr'])), 92 | ('log', partial(QDS.LogUserParameters,p)), 93 | ('build', partial(buildStimulus,p)), 94 | ('start', QDS.StartScript), 95 | ('clear1', partial(QDS.Scene_Clear,1.0, 0)), 96 | ('iter', partial(iterateStimulus,p)), 97 | ('clear2', partial(QDS.Scene_Clear,1.0, 0)), 98 | ('stop', QDS.EndScript)] 99 | ) 100 | 101 | [dispatcher[process]() for process in list(dispatcher.keys())] -------------------------------------------------------------------------------- /Stimuli/New Stimuli/RGC_MovingBar_3.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Stimuli/New Stimuli/RGC_MovingBar_3.pickle -------------------------------------------------------------------------------- /Stimuli/New Stimuli/RGC_MovingBar_3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # -------------------------------------------------------------------------- 4 | import collections 5 | from functools import partial 6 | import QDS 7 | import math 8 | 9 | # Define global stimulus parameters 10 | p = {'_sName' : "RGC_MovingBar", 11 | '_sDescr' : "'moving bar' in fingerprinting stimulus set", 12 | "nTrials" : 2, 13 | "DirList" : [0,180, 45,225, 90,270, 135,315], 14 | 15 | "vel_umSec" : 1000.0, # speed of moving bar in um/sec 16 | "tMoveDur_s" : 4.0, # duration of movement (defines distance 17 | # the bar travels, not its speed) 18 | "barDx_um" : 300.0, # bar dimensions in um 19 | "barDy_um" : 1000.0, 20 | "bkgColor" : (0,0,0), # background color 21 | "barColor" : (255,255,255), # bar color 22 | "durFr_s" : 1/60.0, # Frame duration 23 | "nFrPerMarker" : 3 24 | } 25 | 26 | def buildStimulus(p): 27 | p['durMarker_s'] = p["durFr_s"]*p["nFrPerMarker"] 28 | p['freq_Hz'] = round(1.0 /p["durFr_s"]) 29 | p['umPerFr'] = float(p["vel_umSec"]) /p['freq_Hz'] 30 | p['moveDist_um'] = p["vel_umSec"] *p["tMoveDur_s"] 31 | p['nFrToMove'] = float(p['moveDist_um']) /p['umPerFr'] 32 | 33 | # Define stimulus objects 34 | QDS.DefObj_Box(1, p["barDx_um"], p["barDy_um"]) 35 | 36 | def MoveBarSeq(): 37 | # A function that presents the moving bar in the given number of 38 | # directions (= moving bar sequence) 39 | for rot_deg in p["DirList"]: 40 | # Calculate rotation angle and starting position of bar 41 | rot_rad = (rot_deg -90)/360.0 *2 *math.pi 42 | x = math.cos(rot_rad) *( p['moveDist_um'] /2.0) 43 | y = math.sin(rot_rad) *(-p['moveDist_um'] /2.0) 44 | 45 | # Move the bar stepwise across the screen (as smooth as permitted 46 | # by the refresh frequency) 47 | QDS.Scene_Clear(p['durMarker_s'], 1) 48 | for iStep in range(int(p['nFrToMove'])): 49 | QDS.Scene_RenderEx(p["durFr_s"], [1], [(x,y)], [(1.0,1.0)], [rot_deg], 0) 50 | x -= math.cos(rot_rad) *p['umPerFr'] 51 | y += math.sin(rot_rad) *p['umPerFr'] 52 | 53 | def iterateStimulus(p): 54 | QDS.SetObjColor(1, [1], [p["barColor"]]) 55 | QDS.SetBkgColor(p["bkgColor"]) 56 | QDS.Scene_Clear(3.0, 0) 57 | QDS.Loop(p["nTrials"], MoveBarSeq) 58 | 59 | # -------------------------------------------------------------------------- 60 | dispatcher = collections.OrderedDict([ 61 | ('init', partial(QDS.Initialize,p['_sName'],p['_sDescr'])), 62 | ('log', partial(QDS.LogUserParameters,p)), 63 | ('build', partial(buildStimulus,p)), 64 | ('start', QDS.StartScript), 65 | ('clear1', partial(QDS.Scene_Clear,1.0, 0)), 66 | ('iter', partial(iterateStimulus,p)), 67 | ('clear2', partial(QDS.Scene_Clear,1.0, 0)), 68 | ('stop', QDS.EndScript)] 69 | ) 70 | 71 | [dispatcher[process]() for process in list(dispatcher.keys())] -------------------------------------------------------------------------------- /Stimuli/New Stimuli/Ripple_0.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu Apr 7 11:32:23 2016 4 | 5 | @author: Luke 6 | """ 7 | import collections 8 | from functools import partial 9 | import QDS 10 | import numpy as np 11 | import random 12 | 13 | # Define global stimulus parameters 14 | p = {'_sName' : "Ripple_0", 15 | '_sDescr' : "Sine wave ripple", 16 | 'seed' : 1, 17 | "nTrials" : 1, 18 | "iHalf" : 127, 19 | "iFull" : 254, 20 | "dxStim_um" : 100, # Stimulus size 21 | "durFr_s" : 1/60.0, # Frame duration 22 | 'durRipple' : 2, 23 | 'contrast_max' : 1, 24 | 'contrast_min' : 0.2, 25 | 'contrast_n' : 5, 26 | 'frequency_max' : 6, 27 | 'frequency_min' : 2, 28 | 'frequency_n' : 10, 29 | "tSteadyMID_s" : 2, # Light at 50% for steps 30 | } 31 | 32 | def buildStimulus(p): 33 | '''Set random seed. Generate sets of contrast and frequency values. Generate 34 | all permutations of these sets. Shuffle the order of the permutation set. 35 | Create single elliptical object, for which the freqeuency is modulated.''' 36 | 37 | random.seed(p['seed']) 38 | contrast_values = np.linspace(p['contrast_min'],p['contrast_max'],p['contrast_n']) 39 | frequency_values = np.linspace(p['frequency_min'],p['frequency_max'],p['frequency_n']) 40 | p['contfreq_pairs'] = [(x,y) for x in frequency_values for y in contrast_values] 41 | random.shuffle(p['contfreq_pairs']) 42 | 43 | # Define stimulus objects 44 | QDS.DefObj_Ellipse(1, p["dxStim_um"], p["dxStim_um"]) 45 | 46 | def iterateStimulus(p): 47 | '''Define function for single sine ripple, and conversion function to 48 | generate RGB values. Generate timeseries for each value pair instance. 49 | Iterate over all value pairs, calculating the intensity change at each 50 | timepoint. Render the intensity change of the ellipse.''' 51 | 52 | for (frequency,contrast) in p['contfreq_pairs']: 53 | RGB = 3 *(p["iHalf"],) 54 | QDS.SetObjColor (1, [1], [RGB]) 55 | QDS.Scene_Render(p["tSteadyMID_s"], 1, [1], [(0,0)], 0) 56 | 57 | for t_pnt in range(int(p['durRipple']/p['durFr_s'])): 58 | Intensity = np.sin(np.pi*frequency*t_pnt)*contrast*p["iHalf"]+p["iHalf"] 59 | RGB = 3 *(int(Intensity),) 60 | QDS.SetObjColor(1, [1], [RGB]) 61 | QDS.Scene_Render(p["durFr_s"], 1, [1], [(0,0)], 0) 62 | # -------------------------------------------------------------------------- 63 | dispatcher = collections.OrderedDict([ 64 | ('init', partial(QDS.Initialize,p['_sName'],p['_sDescr'])), 65 | ('log', partial(QDS.LogUserParameters,p)), 66 | ('build', partial(buildStimulus,p)), 67 | ('start', QDS.StartScript), 68 | ('clear1', partial(QDS.Scene_Clear,1.0, 0)), 69 | ('iter', partial(iterateStimulus,p)), 70 | ('clear2', partial(QDS.Scene_Clear,1.0, 0)), 71 | ('stop', QDS.EndScript)] 72 | ) 73 | 74 | [dispatcher[process]() for process in list(dispatcher.keys())] -------------------------------------------------------------------------------- /Stimuli/New Stimuli/Scribble_01.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Stimuli/New Stimuli/Scribble_01.pickle -------------------------------------------------------------------------------- /Stimuli/New Stimuli/Scribble_01.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created April 2016 @author: Luke Edward Rogerson, AG Euler 4 | 5 | """ 6 | import collections 7 | from functools import partial 8 | import numpy as np 9 | import QDS 10 | import scipy as sp 11 | import scipy.interpolate 12 | import seaborn as sns 13 | 14 | p = {'_sName' : "Scribble_0", 15 | '_sDescr' : "Continuously varying sine wave", 16 | 'rng_seed' : 1, 17 | 'pnt_seed' : 500, 18 | 'velocity' : 0.33, 19 | 'duration' : 60, 20 | 'frameRate' : 60, 21 | 'iHalf' : 127, 22 | 'contrast_max' : 1, 23 | 'contrast_min' : 0, 24 | 'frequency_max' : 12, 25 | 'frequency_min' : 2, 26 | 'dot_um' : 100 27 | } 28 | 29 | def buildStimulus(p): 30 | # http://mathworld.wolfram.com/SpherePointPicking.html 31 | np.random.seed(seed=p['rng_seed']) 32 | theta = 2*np.pi*np.random.rand(p['pnt_seed']) 33 | phi = np.arccos(2*np.random.rand(p['pnt_seed'])-1) 34 | 35 | dX = np.cos(theta[1:])*np.sin(phi[1:]) - np.cos(theta[:-1])*np.sin(phi[:-1]) 36 | dY = np.sin(theta[1:])*np.sin(phi[1:]) - np.sin(theta[:-1])*np.sin(phi[:-1]) 37 | dT = np.cumsum(np.append(0,1/np.sqrt(dX**2 + dY**2))) # <- distance correction? 38 | 39 | # if p['velocity']*p['duration']/p['frameRate'] > dT.shape[0]: 40 | # raise ValueError('Target trajectory exceeds seed trajectory') 41 | 42 | coord = np.append(np.expand_dims(theta,axis=1),np.expand_dims(phi,axis=1),axis=1) 43 | timepoints = np.linspace(0,p['duration'],p['duration']*p['frameRate']) 44 | coord_interp = sp.interpolate.interp1d(dT,coord,kind='cubic',axis=0)(timepoints) 45 | 46 | spherical_to_cartesian = lambda theta,phi: [np.cos(theta)*np.sin(phi),np.sin(theta)*np.sin(phi)] 47 | coord_xy = np.asarray(spherical_to_cartesian(coord_interp[:,0],coord_interp[:,1])).T 48 | 49 | coord_to_value = lambda value,parMax,parMin: (value+1)/2*(parMax-parMin)+parMin 50 | frequency = coord_to_value(coord_xy[:,0],p['frequency_max'],p['frequency_min']) 51 | contrast = coord_to_value(coord_xy[:,1],p['contrast_max'],p['contrast_min']) 52 | 53 | intensity = np.zeros((frequency.shape[0])) 54 | for itx in range(frequency.shape[0]): 55 | intensity[itx] = np.sin(2*np.pi*frequency[itx])*contrast[itx]*p['iHalf'] +p['iHalf'] 56 | 57 | p['intensity'] = intensity 58 | # Define stimulus objects 59 | QDS.DefObj_Ellipse(1, p['dot_um'], p['dot_um']) 60 | 61 | def iterateStimulus(p): 62 | for t_pnt in range(p['intensity'].shape[0]): 63 | RGB = 3 *(int(p['intensity'][t_pnt]),) 64 | QDS.SetObjColor(1, [1], [RGB]) 65 | QDS.Scene_Render(1/p["frameRate"], 1, [1], [(0,0)], 0) 66 | 67 | # -------------------------------------------------------------------------- 68 | dispatcher = collections.OrderedDict([ 69 | ('init', partial(QDS.Initialize,p['_sName'],p['_sDescr'])), 70 | ('log', partial(QDS.LogUserParameters,p)), 71 | ('build', partial(buildStimulus,p)), 72 | ('start', QDS.StartScript), 73 | ('clear1', partial(QDS.Scene_Clear,1.0, 0)), 74 | ('iter', partial(iterateStimulus,p)), 75 | ('clear2', partial(QDS.Scene_Clear,1.0, 0)), 76 | ('stop', QDS.EndScript)] 77 | ) 78 | 79 | [dispatcher[process]() for process in list(dispatcher.keys())] -------------------------------------------------------------------------------- /Stimuli/Perlin16.avi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Stimuli/Perlin16.avi -------------------------------------------------------------------------------- /Stimuli/Perlin32.avi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Stimuli/Perlin32.avi -------------------------------------------------------------------------------- /Stimuli/Perlin48.avi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Stimuli/Perlin48.avi -------------------------------------------------------------------------------- /Stimuli/RGC_BG_3s_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # --------------------------------------------------------------------- 4 | import QDS 5 | 6 | QDS.Initialize("RGC_BG_3s", "'B/G' in fingerprinting stimulus set") 7 | 8 | # Define global stimulus parameters 9 | # 10 | p = {"nTrials" : 3, 11 | "dxStim_um" : 1000, # Stimulus size 12 | "durFr_s" : 1/60.0, # Frame duration 13 | "nFrPerMarker" : 3, 14 | "RGB_green" : (0,255,0), 15 | "RGB_blue" : (0,0,255)} 16 | QDS.LogUserParameters(p) 17 | 18 | # Do some calculations 19 | # 20 | durMarker_s = p["durFr_s"] *p["nFrPerMarker"] 21 | 22 | # Define stimulus objects 23 | # 24 | QDS.DefObj_Ellipse(1, p["dxStim_um"], p["dxStim_um"]) 25 | 26 | # Start of stimulus run 27 | # 28 | QDS.StartScript() 29 | 30 | for iT in range(p["nTrials"]): 31 | QDS.SetObjColor (1, [1], [p["RGB_green"]]) 32 | QDS.Scene_Render(durMarker_s, 1, [1], [(0,0)], 1) 33 | QDS.Scene_Render(3.0 -durMarker_s, 1, [1], [(0,0)], 0) 34 | 35 | QDS.Scene_Clear(3.0, 0) 36 | 37 | QDS.SetObjColor (1, [1], [p["RGB_blue"]]) 38 | QDS.Scene_Render(durMarker_s, 1, [1], [(0,0)], 1) 39 | QDS.Scene_Render(3.0-durMarker_s, 1, [1], [(0,0)], 0) 40 | 41 | QDS.Scene_Clear(3.0, 0) 42 | 43 | # Finalize stimulus 44 | # 45 | QDS.EndScript() 46 | 47 | # -------------------------------------------------------------------------- 48 | -------------------------------------------------------------------------------- /Stimuli/RGC_BWNoise_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # --------------------------------------------------------------------- 4 | import QDS 5 | import os 6 | 7 | # Initialize QDS 8 | # 9 | QDS.Initialize("RGC_Noise", "'noise' in fingerprinting stimulus set") 10 | 11 | # Define global stimulus parameters 12 | # 13 | p = {"durStim_s" : 0.200, 14 | "boxDx_um" : 40, 15 | "boxDy_um" : 40, 16 | "fIntenW" : 255, # intensity factor 17 | # (pixel value(0,1) x fIntenW) 18 | "fNameNoise" : "RGC_BWNoise_official", 19 | "durFr_s" : 1/60.0, # Frame duration 20 | "nFrPerMarker" : 3} 21 | 22 | QDS.LogUserParameters(p) 23 | 24 | # Do some calculations and preparations 25 | # 26 | fPath = QDS.GetStimulusPath() 27 | durMarker_s = p["durFr_s"] *p["nFrPerMarker"] 28 | 29 | ''' 30 | print(os.getcwd(), fPath) 31 | ''' 32 | # Read file with M sequence 33 | # 34 | try: 35 | f = open(fPath +os.path.sep +p["fNameNoise"] +'.txt', 'r') 36 | iLn = 0 37 | Frames = [] 38 | 39 | while 1: 40 | line = f.readline() 41 | if not line: 42 | break 43 | parts = line.split(',') 44 | if iLn == 0: 45 | nX = int(parts[0]) 46 | nY = int(parts[1]) 47 | nFr = int(parts[2]) 48 | nB = nX*nY 49 | else: 50 | Frame = [] 51 | for iB in range(1, nB+1): 52 | r = int(parts[iB-1]) *p["fIntenW"] 53 | Frame.append((r,r,r)) 54 | Frames.append(Frame) 55 | iLn += 1 56 | finally: 57 | f.close() 58 | 59 | # Define objects 60 | # 61 | # Create box objects, one for each field of the checkerboard, such that we 62 | # can later just change their color to let them appear or disappear 63 | # 64 | for iB in range(1, nB+1): 65 | QDS.DefObj_Box(iB, p["boxDx_um"], p["boxDy_um"], 0) 66 | 67 | # Create two lists, one with the indices of the box objects and one with 68 | # their positions in the checkerboard; these lists later facilitate using 69 | # the Scene_Render() command to display the checkerboard 70 | # 71 | BoxIndList = [] 72 | BoxPosList = [] 73 | for iX in range(nX): 74 | for iY in range(nY): 75 | iB = 1 +iX +iY*nX 76 | x = iX*p["boxDx_um"] -(p["boxDx_um"]*nX/2) 77 | y = iY*p["boxDy_um"] -(p["boxDy_um"]*nY/2) 78 | BoxIndList.append(iB) 79 | BoxPosList.append((x,y)) 80 | 81 | # Start of stimulus run 82 | # 83 | QDS.StartScript() 84 | QDS.Scene_Clear(1.0, 0) 85 | 86 | for iF in range(nFr): 87 | QDS.SetObjColor(nB, BoxIndList, Frames[iF]) 88 | QDS.Scene_Render(durMarker_s, nB, BoxIndList, BoxPosList, 1) 89 | QDS.Scene_Render(p["durStim_s"] -durMarker_s, nB, BoxIndList, BoxPosList, 0) 90 | 91 | QDS.Scene_Clear(1.0, 0) 92 | 93 | # Finalize stimulus 94 | # 95 | QDS.EndScript() 96 | 97 | # ----------------------------------------------------------------------------- 98 | -------------------------------------------------------------------------------- /Stimuli/RGC_Chirp_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # --------------------------------------------------------------------- 4 | import QDS 5 | import math 6 | 7 | QDS.Initialize("RGC_Chirp", "'chirp' in fingerprinting stimulus set") 8 | 9 | # Define global stimulus parameters 10 | # 11 | p = {"nTrials" : 3, 12 | "chirpDur_s" : 8.0, # Rising chirp phase 13 | "chirpMaxFreq_Hz" : 8.0, # Peak frequency of chirp (Hz) 14 | "ContrastFreq_Hz" : 2.0, # Freqency at which contrast 15 | # is modulated 16 | "tSteadyOFF_s" : 3.0, # Light OFF at beginning ... 17 | "tSteadyOFF2_s" : 2.0, # ... and end of stimulus 18 | "tSteadyON_s" : 3.0, # Light 100% ON before and 19 | # after chirp 20 | "tSteadyMID_s" : 2.0, # Light at 50% for steps 21 | "IHalf" : 127, 22 | "IFull" : 254, 23 | "dxStim_um" : 1000, # Stimulus size 24 | "StimType" : 2, # 1 = Box, 2 = Circle/ 25 | "durFr_s" : 1/60.0, # Frame duration 26 | "nFrPerMarker" : 3} 27 | QDS.LogUserParameters(p) 28 | 29 | # Some calculations 30 | # 31 | # Chirp formula is f(t) = sin( 2pi*F0 + pi*K*t^2)) 32 | # where: 33 | # F0 is Starting Frequency 34 | # K = Acceleration (Hz / s) 35 | # t = time (s) 36 | # 37 | nPntChirp = int(p["chirpDur_s"] /p["durFr_s"]) 38 | K_HzPerSec = p["chirpMaxFreq_Hz"] /p["chirpDur_s"] # acceleration in Hz/s 39 | durMarker_s = p["durFr_s"]*p["nFrPerMarker"] 40 | 41 | RGB_IHalf = 3 *(p["IHalf"],) 42 | RGB_IFull = 3 *(p["IFull"],) 43 | 44 | # Define stimulus objects 45 | # 46 | QDS.DefObj_Box(1, p["dxStim_um"], p["dxStim_um"], 0) 47 | QDS.DefObj_Ellipse(2, p["dxStim_um"], p["dxStim_um"]) 48 | 49 | # Start of stimulus run 50 | # 51 | QDS.StartScript() 52 | 53 | for iL in range(p["nTrials"]): 54 | # Steady steps 55 | # 56 | QDS.Scene_Clear(durMarker_s, 1) 57 | QDS.Scene_Clear(p["tSteadyOFF2_s"] -durMarker_s, 0) 58 | 59 | QDS.SetObjColor(1, [p["StimType"]], [RGB_IFull]) 60 | QDS.Scene_Render(p["tSteadyON_s"], 1, [p["StimType"]], [(0,0)], 0) 61 | 62 | QDS.Scene_Clear(durMarker_s, 1) 63 | QDS.Scene_Clear(p["tSteadyOFF_s"] -durMarker_s, 0) 64 | 65 | QDS.SetObjColor(1, [p["StimType"]], [RGB_IHalf]) 66 | QDS.Scene_Render(p["tSteadyMID_s"], 1, [p["StimType"]], [(0,0)], 0) 67 | 68 | # Frequency chirp 69 | # 70 | for iT in range(nPntChirp): 71 | t_s = iT*p["durFr_s"] # in ms 72 | Intensity = math.sin(math.pi *K_HzPerSec *t_s**2) *p["IHalf"] +p["IHalf"] 73 | RGB = 3 *(int(Intensity),) # -> RGB tuple 74 | QDS.SetObjColor (1, [p["StimType"]], [RGB]) 75 | QDS.Scene_Render(p["durFr_s"], 1, [p["StimType"]], [(0,0)], 0) 76 | 77 | # Gap between frequency chirp and contrast chirp 78 | # 79 | QDS.SetObjColor (1, [p["StimType"]], [RGB_IHalf]) 80 | QDS.Scene_Render(p["tSteadyMID_s"], 1, [p["StimType"]], [(0,0)], 0) 81 | 82 | # Contrast chirp 83 | # 84 | for iPnt in range(nPntChirp): 85 | t_s = iPnt*p["durFr_s"] 86 | IRamp = int(p["IHalf"] *t_s /p["chirpDur_s"]) 87 | 88 | Intensity = math.sin(2*math.pi *p["ContrastFreq_Hz"] *t_s) *IRamp +p["IHalf"] 89 | RGB = 3 *(int(Intensity),) 90 | QDS.SetObjColor(1, [p["StimType"]], [RGB]) 91 | QDS.Scene_Render(p["durFr_s"], 1, [p["StimType"]], [(0,0)], 0) 92 | 93 | # Gap after contrast chirp 94 | # 95 | QDS.SetObjColor(1, [p["StimType"]], [RGB_IHalf]) 96 | QDS.Scene_Render(p["tSteadyMID_s"], 1, [p["StimType"]], [(0,0)], 0) 97 | 98 | QDS.Scene_Clear(p["tSteadyOFF_s"], 0) 99 | 100 | # Finalize stimulus 101 | # 102 | QDS.EndScript() 103 | 104 | # ----------------------------------------------------------------------------- 105 | -------------------------------------------------------------------------------- /Stimuli/RGC_MovingBar_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # -------------------------------------------------------------------------- 4 | import QDS 5 | import math 6 | 7 | # -------------------------------------------------------------------------- 8 | def MoveBarSeq(): 9 | # A function that presents the moving bar in the given number of 10 | # directions (= moving bar sequence) 11 | # 12 | for rot_deg in p["DirList"]: 13 | # Calculate rotation angle and starting position of bar 14 | # 15 | rot_rad = (rot_deg -90)/360.0 *2 *math.pi 16 | x = math.cos(rot_rad) *( moveDist_um /2.0) 17 | y = math.sin(rot_rad) *(-moveDist_um /2.0) 18 | 19 | # Move the bar stepwise across the screen (as smooth as permitted 20 | # by the refresh frequency) 21 | # 22 | QDS.Scene_Clear(durMarker_s, 1) 23 | for iStep in range(round(nFrToMove)): 24 | QDS.Scene_RenderEx(p["durFr_s"], [1], [(x,y)], [(1.0,1.0)], [rot_deg], 25 | iStep < p["nFrPerMarker"]) 26 | x -= math.cos(rot_rad) *umPerFr 27 | y += math.sin(rot_rad) *umPerFr 28 | 29 | # -------------------------------------------------------------------------- 30 | # Main script 31 | # -------------------------------------------------------------------------- 32 | QDS.Initialize("RGC_MovingBar", "'moving bar' in fingerprinting stimulus set") 33 | 34 | # Define global stimulus parameters 35 | # 36 | p = {"nTrials" : 3, 37 | "DirList" : [0,180, 45,225, 90,270, 135,315], 38 | 39 | "vel_umSec" : 1000.0, # speed of moving bar in um/sec 40 | "tMoveDur_s" : 4.0, # duration of movement (defines distance 41 | # the bar travels, not its speed) 42 | "barDx_um" : 300.0, # bar dimensions in um 43 | "barDy_um" : 1000.0, 44 | "bkgColor" : (10,20,30, 30,20,10), # background color 45 | "barColor" : (255,255,255), # bar color 46 | "durFr_s" : 1/60.0, # Frame duration 47 | "nFrPerMarker" : 3 48 | } 49 | QDS.LogUserParameters(p) 50 | 51 | # Do some calculations 52 | # 53 | durMarker_s = p["durFr_s"]*p["nFrPerMarker"] 54 | freq_Hz = round(1.0 /p["durFr_s"]) 55 | umPerFr = float(p["vel_umSec"]) /freq_Hz 56 | moveDist_um = p["vel_umSec"] *p["tMoveDur_s"] 57 | nFrToMove = float(moveDist_um) /umPerFr 58 | 59 | ''' 60 | print(nFrToMove, int(nFrToMove), int(nFrToMove)*p["durFr_s"], durMarker_s) 61 | ''' 62 | # Define stimulus objects 63 | # 64 | QDS.DefObj_Box(1, p["barDx_um"], p["barDy_um"]) 65 | 66 | # Start of stimulus run 67 | # 68 | QDS.StartScript() 69 | 70 | QDS.SetObjColor(1, [1], [p["barColor"]]) 71 | QDS.SetBkgColor(p["bkgColor"]) 72 | QDS.Scene_Clear(3.0, 0) 73 | 74 | # Loop the moving bar sequence nTrial times 75 | # 76 | QDS.Loop(p["nTrials"], MoveBarSeq) 77 | 78 | QDS.Scene_Clear(1.0, 0) 79 | 80 | # Finalize stimulus 81 | # 82 | QDS.EndScript() 83 | 84 | # -------------------------------------------------------------------------- 85 | -------------------------------------------------------------------------------- /Stimuli/RGC_MovingBar_2_RGBU.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # -------------------------------------------------------------------------- 4 | import QDS 5 | import math 6 | 7 | # -------------------------------------------------------------------------- 8 | def MoveBarSeq(): 9 | # A function that presents the moving bar in the given number of 10 | # directions (= moving bar sequence) 11 | # 12 | for rot_deg in p["DirList"]: 13 | # Calculate rotation angle and starting position of bar 14 | # 15 | rot_rad = (rot_deg -90)/360.0 *2 *math.pi 16 | x = math.cos(rot_rad) *( moveDist_um /2.0) 17 | y = math.sin(rot_rad) *(-moveDist_um /2.0) 18 | 19 | # Move the bar stepwise across the screen (as smooth as permitted 20 | # by the refresh frequency) 21 | # 22 | QDS.Scene_Clear(durMarker_s, 1) 23 | for iStep in range(round(nFrToMove)): 24 | QDS.Scene_RenderEx(p["durFr_s"], [1], [(x,y)], [(1.0,1.0)], [rot_deg], 25 | iStep < p["nFrPerMarker"]) 26 | x -= math.cos(rot_rad) *umPerFr 27 | y += math.sin(rot_rad) *umPerFr 28 | 29 | # -------------------------------------------------------------------------- 30 | # Main script 31 | # -------------------------------------------------------------------------- 32 | QDS.Initialize("RGC_MovingBar", "'moving bar' in fingerprinting stimulus set") 33 | 34 | # Define global stimulus parameters 35 | # 36 | p = {"nTrials" : 3, 37 | "DirList" : [0,180, 45,225, 90,270, 135,315], 38 | 39 | "vel_umSec" : 1000.0, # speed of moving bar in um/sec 40 | "tMoveDur_s" : 4.0, # duration of movement (defines distance 41 | # the bar travels, not its speed) 42 | "barDx_um" : 300.0, # bar dimensions in um 43 | "barDy_um" : 1000.0, 44 | "bkgColor" : (10,20,30, 30,20,10), # background color 45 | "barColor" : (255,255,255,128), # bar color 46 | "durFr_s" : 1/60.0, # Frame duration 47 | "nFrPerMarker" : 3 48 | } 49 | QDS.LogUserParameters(p) 50 | 51 | # Do some calculations 52 | # 53 | durMarker_s = p["durFr_s"]*p["nFrPerMarker"] 54 | freq_Hz = round(1.0 /p["durFr_s"]) 55 | umPerFr = float(p["vel_umSec"]) /freq_Hz 56 | moveDist_um = p["vel_umSec"] *p["tMoveDur_s"] 57 | nFrToMove = float(moveDist_um) /umPerFr 58 | 59 | print(nFrToMove, int(nFrToMove), int(nFrToMove)*p["durFr_s"], durMarker_s) 60 | # Define stimulus objects 61 | # 62 | QDS.DefObj_Box(1, p["barDx_um"], p["barDy_um"]) 63 | 64 | # Start of stimulus run 65 | # 66 | QDS.StartScript() 67 | 68 | QDS.SetObjColor(1, [1], [p["barColor"]]) 69 | QDS.SetBkgColor(p["bkgColor"]) 70 | QDS.Scene_Clear(3.0, 0) 71 | 72 | # Loop the moving bar sequence nTrial times 73 | # 74 | QDS.Loop(p["nTrials"], MoveBarSeq) 75 | 76 | QDS.Scene_Clear(1.0, 0) 77 | 78 | # Finalize stimulus 79 | # 80 | QDS.EndScript() 81 | 82 | # -------------------------------------------------------------------------- 83 | -------------------------------------------------------------------------------- /Stimuli/Test1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --------------------------------------------------------------------- 5 | import random 6 | import QDS 7 | import Devices.lightcrafter_4500 as LCr 8 | 9 | QDS.Initialize("Test1", "Test for Lightcrafter") 10 | 11 | print("---") 12 | dev = LCr.Lightcrafter() 13 | result = dev.connect() 14 | if result[0] == LCr.ERROR.OK: 15 | dev.getHardwareStatus() 16 | dev.getSystemStatus() 17 | dev.getMainStatus() 18 | dev.getVideoSignalDetectStatus() 19 | dev.disconnect() 20 | else: 21 | print("WARNING: This script required a lightcrafter") 22 | print("---") 23 | 24 | QDS.LC_setLEDCurrents(0, [0,50,0]) 25 | 26 | random.seed(1) 27 | p = {} 28 | p['nTrials'] = 20 29 | p['nRows'] = 2 30 | p['nCols'] = 2 31 | p['boxDx'] = 50 32 | p['boxDy'] = 50 33 | 34 | nB = p['nRows']*p['nCols'] 35 | for iB in range(1, nB+1): 36 | QDS.DefObj_Box(iB, p['boxDx'], p['boxDy']) 37 | 38 | BoxIndList = [] 39 | BoxPosList = [] 40 | BoxMagList = [] 41 | BoxRotList = [] 42 | 43 | for iX in range(p['nCols']): 44 | for iY in range(p['nRows']): 45 | iB = 1 +iX +iY*p['nCols'] 46 | x = iX*p['boxDx'] +p['boxDx']/2.0 -p['boxDx']*p['nCols']/2.0 47 | y = iY*p['boxDy'] +p['boxDy']/2.0 -p['boxDy']*p['nRows']/2.0 48 | BoxIndList.append(iB) 49 | BoxPosList.append((x,y)) 50 | BoxMagList.append((1.0, 1.0)) 51 | BoxRotList.append(0) 52 | 53 | 54 | QDS.StartScript() 55 | QDS.SetBkgColor((32,16,64)) 56 | QDS.Scene_Clear(0.1, 0) 57 | 58 | rot = 0.0 59 | phase = 0 60 | for iT in range(p['nTrials']): 61 | BoxColList = [] 62 | BoxAlpList = [] 63 | BoxRotList = [] 64 | for iB in range(1, nB+1): 65 | r = 0 66 | g = 0 67 | b = 0 68 | if phase == 0: 69 | if iB == 1 or iB == 3: 70 | b = 255 71 | elif phase == 1: 72 | if iB == 1 or iB == 4: 73 | g = 255 74 | if iB == 2: 75 | r = 128 76 | g = 128 77 | b = 128 78 | 79 | BoxColList.append((r, g, b)) 80 | BoxAlpList.append(255) 81 | BoxRotList.append(rot) 82 | rot += 0.005 83 | QDS.SetObjColorEx(BoxIndList, BoxColList, BoxAlpList) 84 | QDS.Scene_RenderEx(0.050, BoxIndList, BoxPosList, BoxMagList, 85 | BoxRotList, int((iT % 20) == 0)) 86 | phase += 1 87 | if phase > 1: 88 | phase = 0 89 | 90 | QDS.Scene_Clear(0.1, 0) 91 | QDS.EndScript() 92 | 93 | # --------------------------------------------------------------------- 94 | -------------------------------------------------------------------------------- /Stimuli/Test6.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --------------------------------------------------------------------- 5 | import QDS 6 | import random 7 | 8 | QDS.Initialize("Test6", "Test for Lightcrafter") 9 | #QDS.setColorMode((8,7,7), (0,1,1), 0) 10 | 11 | random.seed(1) 12 | 13 | p = {} 14 | p['nTrials'] = 5 15 | p['dt_s'] = 10.0 #1/60.0 16 | p['nRows'] = 5 17 | p['nCols'] = 5 18 | 19 | boxDx = max(30, 500.0/p['nCols']) 20 | boxDy = max(30, 500.0/p['nRows']) 21 | nObj = p['nRows']*p['nCols'] 22 | 23 | ObjShaList = [] 24 | ObjIndList = [] 25 | ObjPosList = [] 26 | ObjMagList = [] 27 | ObjRotList = [] 28 | 29 | for iObj in range(1, nObj+1): 30 | isShaObj = int(((iObj % 2) == 0)) 31 | if isShaObj: 32 | ObjShaList.append(iObj) 33 | QDS.DefObj_EllipseEx(iObj, boxDx, boxDy, isShaObj) 34 | #QDS.DefObj_BoxEx(iObj, boxDx, boxDy, isShaObj) 35 | r = random.randint(5, 250) 36 | g = random.randint(5, 250) 37 | b = random.randint(5, 250) 38 | QDS.SetObjColorEx([iObj], [(r,g,b)], [255]) 39 | 40 | ObjIndList.append(iObj) 41 | ObjMagList.append((1.0,1.0)) 42 | ObjRotList.append(10.0 *iObj-1) 43 | 44 | border = 1.2 45 | for iX in range(p['nCols']): 46 | for iY in range(p['nRows']): 47 | x = (iX+0.5 -p['nCols']/2.0)*boxDx*border 48 | y = (iY+0.5 -p['nRows']/2.0)*boxDy*border 49 | ObjPosList.append((x,y)) 50 | 51 | QDS.DefShader(1, "SINE_WAVE_GRATING_MIX") 52 | 53 | perLen_um = 30.0 54 | perDur_s = 0.5 55 | """ 56 | minRGBA = (0, 200, 0, 255) 57 | maxRGBA = (200, 0, 0, 255) 58 | """ 59 | minRGBA = ( 20, 20, 20, 255) 60 | maxRGBA = (235,235,235, 255) 61 | QDS.SetShaderParams(1, [perLen_um, perDur_s, minRGBA, maxRGBA]) 62 | 63 | QDS.SetObjShader(ObjShaList, len(ObjShaList)*[1]) 64 | 65 | # --------------------------------------------------------------------- 66 | def myLoop(): 67 | for iT in range(p['nTrials']): 68 | """ 69 | QDS.SetObjShader(ObjShaList, len(ObjShaList)//2*[1,1]) 70 | QDS.Scene_RenderEx(p['dt_s'], ObjIndList, ObjPosList, ObjMagList, ObjRotList, 0) 71 | QDS.SetObjShader(ObjShaList, len(ObjShaList)//2*[-1,-1]) 72 | """ 73 | QDS.Scene_RenderEx(p['dt_s'], ObjIndList, ObjPosList, ObjMagList, ObjRotList, 0) 74 | 75 | # --------------------------------------------------------------------- 76 | QDS.StartScript() 77 | QDS.Scene_Clear(1.0, 0) 78 | QDS.Loop(300, myLoop) 79 | QDS.Scene_Clear(1.0, 0) 80 | QDS.EndScript() 81 | 82 | # --------------------------------------------------------------------- -------------------------------------------------------------------------------- /Stimuli/Test6_rp5.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --------------------------------------------------------------------- 5 | import QDS 6 | import random 7 | 8 | QDS.Initialize("Test6_rp5", "Test for Lightcrafter (RPi 5)") 9 | #QDS.setColorMode((8,7,7), (0,1,1), 0) 10 | 11 | random.seed(1) 12 | 13 | p = {} 14 | p['nTrials'] = 5 15 | p['dt_s'] = 10.0 #1/60.0 16 | p['nRows'] = 5 17 | p['nCols'] = 5 18 | 19 | boxDx = max(30, 500.0/p['nCols']) 20 | boxDy = max(30, 500.0/p['nRows']) 21 | nObj = p['nRows']*p['nCols'] 22 | 23 | ObjShaList = [] 24 | ObjIndList = [] 25 | ObjPosList = [] 26 | ObjMagList = [] 27 | ObjRotList = [] 28 | 29 | for iObj in range(1, nObj+1): 30 | isShaObj = int(((iObj % 2) == 0)) 31 | if isShaObj: 32 | ObjShaList.append(iObj) 33 | QDS.DefObj_EllipseEx(iObj, boxDx, boxDy, isShaObj) 34 | #QDS.DefObj_BoxEx(iObj, boxDx, boxDy, isShaObj) 35 | r = random.randint(5, 250) 36 | g = random.randint(5, 250) 37 | b = random.randint(5, 250) 38 | QDS.SetObjColorEx([iObj], [(r,g,b)], [255]) 39 | 40 | ObjIndList.append(iObj) 41 | ObjMagList.append((1.0,1.0)) 42 | ObjRotList.append(10.0 *iObj-1) 43 | 44 | border = 1.2 45 | for iX in range(p['nCols']): 46 | for iY in range(p['nRows']): 47 | x = (iX+0.5 -p['nCols']/2.0)*boxDx*border 48 | y = (iY+0.5 -p['nRows']/2.0)*boxDy*border 49 | ObjPosList.append((x,y)) 50 | 51 | QDS.DefShader(1, "SQUARE_WAVE_GRATING_MIX_RP5") 52 | 53 | perLen_um = 30.0 54 | perDur_s = 0.5 55 | minRGBA = ( 20, 20, 20, 255) 56 | maxRGBA = (235,235,235, 255) 57 | mixA = 0.5 58 | QDS.SetShaderParams(1, [perLen_um, perDur_s, minRGBA, maxRGBA, mixA]) 59 | 60 | 61 | 62 | QDS.SetObjShader(ObjShaList, len(ObjShaList)*[1]) 63 | 64 | # --------------------------------------------------------------------- 65 | def myLoop(): 66 | for iT in range(p['nTrials']): 67 | """ 68 | QDS.SetObjShader(ObjShaList, len(ObjShaList)//2*[1,1]) 69 | QDS.Scene_RenderEx(p['dt_s'], ObjIndList, ObjPosList, ObjMagList, ObjRotList, 0) 70 | QDS.SetObjShader(ObjShaList, len(ObjShaList)//2*[-1,-1]) 71 | """ 72 | QDS.Scene_RenderEx(p['dt_s'], ObjIndList, ObjPosList, ObjMagList, ObjRotList, 0) 73 | 74 | # --------------------------------------------------------------------- 75 | QDS.StartScript() 76 | QDS.Scene_Clear(1.0, 0) 77 | QDS.Loop(300, myLoop) 78 | QDS.Scene_Clear(1.0, 0) 79 | QDS.EndScript() 80 | 81 | # --------------------------------------------------------------------- 82 | -------------------------------------------------------------------------------- /Stimuli/Test6a.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --------------------------------------------------------------------- 5 | import QDS 6 | import random 7 | 8 | QDS.Initialize("Test6a", "Test for Lightcrafter") 9 | # 10 | random.seed(1) 11 | 12 | p = {} 13 | p['nTrials'] = 150 14 | p['dt_s'] = 1.0 #1/60.0 15 | p['nRows'] = 1 16 | p['nCols'] = 1 17 | 18 | boxDx = max(30, 500.0/p['nCols']) 19 | boxDy = max(30, 500.0/p['nRows']) 20 | nObj = p['nRows']*p['nCols'] 21 | 22 | ObjShaList = [] 23 | ObjIndList = [] 24 | ObjPosList = [] 25 | ObjMagList = [] 26 | ObjRotList = [] 27 | 28 | for iObj in range(1, nObj+1): 29 | isShaObj = int(((iObj % 2) > 0)) 30 | if isShaObj: 31 | ObjShaList.append(iObj) 32 | QDS.DefObj_EllipseEx(iObj, boxDx, boxDy, isShaObj) 33 | #QDS.DefObj_BoxEx(iObj, boxDx, boxDy, isShaObj) 34 | r = random.randint(5, 250) 35 | g = random.randint(5, 250) 36 | b = random.randint(5, 250) 37 | QDS.SetObjColorEx([iObj], [(r,g,b)], [255]) 38 | 39 | ObjIndList.append(iObj) 40 | ObjMagList.append((1.0,1.0)) 41 | ObjRotList.append(10.0 *iObj-1) 42 | 43 | border = 1.2 44 | for iX in range(p['nCols']): 45 | for iY in range(p['nRows']): 46 | x = (iX+0.5 -p['nCols']/2.0)*boxDx*border 47 | y = (iY+0.5 -p['nRows']/2.0)*boxDy*border 48 | ObjPosList.append((x,y)) 49 | 50 | #QDS.DefShader(1, "SINE_WAVE_GRATING_MIX2") 51 | QDS.DefShader(1, "SQUARE_WAVE_GRATING_MIX2") 52 | 53 | perLen_um = 30.0 54 | perDur_s = 0.5 55 | mixA = 0.2 56 | """ 57 | minRGBA = (0, 200, 0, 255) 58 | maxRGBA = (200, 0, 0, 255) 59 | """ 60 | minRGBA = (0, 0, 0, 255) 61 | maxRGBA = (255,255,255, 255) 62 | QDS.SetShaderParams(1, [perLen_um, perDur_s, minRGBA, maxRGBA, mixA]) 63 | 64 | QDS.SetObjShader(ObjShaList, len(ObjShaList)*[1]) 65 | 66 | # --------------------------------------------------------------------- 67 | def myLoop(): 68 | for iT in range(p['nTrials']): 69 | mixA = random.random() 70 | perLen_um = random.randint(10, 500) 71 | QDS.SetShaderParams(1, [perLen_um, perDur_s, minRGBA, maxRGBA, mixA]) 72 | ObjRotList = [random.randint(0,359)] *nObj 73 | QDS.Scene_RenderEx(p['dt_s'], ObjIndList, ObjPosList, ObjMagList, ObjRotList, 0) 74 | 75 | # --------------------------------------------------------------------- 76 | QDS.StartScript() 77 | QDS.Scene_Clear(1.0, 0) 78 | QDS.Loop(300, myLoop) 79 | QDS.Scene_Clear(1.0, 0) 80 | QDS.EndScript() 81 | 82 | # --------------------------------------------------------------------- -------------------------------------------------------------------------------- /Stimuli/Test_grating1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --------------------------------------------------------------------- 5 | import QDS 6 | 7 | QDS.Initialize("Test_grating1", "stimulator test") 8 | 9 | # Define global stimulus parameters 10 | # 11 | boxRGB = (0,0,255) 12 | scalRGB = (0,255,0) 13 | nTrials = 10 14 | dxScr = 640 15 | dyScr = 480 16 | nx = 12 17 | ny = int(nx *dyScr/dxScr) 18 | dxEdge = 8 19 | dyEdge = 8 20 | dxScal = 200 # um 21 | dyScal = 200 # um 22 | 23 | dxBox = (dxScr -dxEdge*2)/nx 24 | dyBox = (dyScr -dyEdge*2)/ny 25 | 26 | # Define objects 27 | # Generate one box object per grid position 28 | # 29 | nB = nx * ny 30 | for iB in range(1, nB+1): 31 | QDS.DefObj_Box(iB, dxBox-2, dyBox-2) 32 | 33 | QDS.DefObj_Box(1000, dxScr -dxEdge*2, dyScr -dyEdge*2) 34 | QDS.SetObjColorEx([1000], [boxRGB], [255]) 35 | 36 | # Define scaling elements 37 | # 38 | QDS.DefObj_Box(1001, dxScal, dxScal) 39 | QDS.SetObjColorEx([1001], [scalRGB], [80]) 40 | QDS.DefObj_Box(1002, dxScal-2, dxScal-2) 41 | QDS.SetObjColorEx([1002], [(0,0,0)], [80]) 42 | 43 | QDS.DefObj_Box(1003, dxScal/2, dxScal/2) 44 | QDS.SetObjColorEx([1003], [scalRGB], [160]) 45 | QDS.DefObj_Box(1004, dxScal/2-2, dxScal/2-2) 46 | QDS.SetObjColorEx([1004], [(0,0,0)], [160]) 47 | 48 | QDS.DefObj_Box(1005, dxScr, 2) 49 | QDS.SetObjColorEx([1005], [scalRGB], [255]) 50 | QDS.DefObj_Box(1006, 2, dyScr) 51 | QDS.SetObjColorEx([1006], [scalRGB], [255]) 52 | 53 | ScalIndList = [1001,1002,1003,1004, 1005,1006] 54 | nScal =len(ScalIndList) 55 | 56 | # Fill list with parameters for every box object 57 | # 58 | BoxIndList = [] 59 | BoxPosList = [] 60 | BoxMagList = [] 61 | BoxRotList = [] 62 | BoxColList = [] 63 | BoxAlpList = [] 64 | 65 | for iX in range(nx): 66 | for iY in range(ny): 67 | iB = 1 +iX +iY*nx 68 | x = iX*dxBox +dxBox/2 -dxBox *nx/2.0 69 | y = iY*dyBox +dyBox/2 -dyBox *ny/2.0 70 | BoxIndList.append(iB) 71 | BoxPosList.append((x,y)) 72 | BoxMagList.append((1.0, 1.0)) 73 | BoxRotList.append(0) 74 | BoxColList.append((0,0,0)) 75 | BoxAlpList.append(255) 76 | 77 | # Start of stimulus run 78 | # 79 | QDS.StartScript() 80 | QDS.SetBkgColor((0,0,0)) 81 | QDS.Scene_Clear(1.0, 0) 82 | 83 | QDS.SetObjColorEx(BoxIndList, BoxColList, BoxAlpList) 84 | 85 | # Present grid 86 | # 87 | for iT in range(nTrials): 88 | 89 | QDS.Scene_RenderEx(60.0, [1000] +BoxIndList +ScalIndList, 90 | [(0,0)] +BoxPosList +[(0,0)]*nScal, 91 | [(1.0,1.0)] +BoxMagList +[(1.0,1.0)]*nScal, 92 | [0] +BoxRotList +[0]*nScal, 0) 93 | 94 | QDS.Scene_Clear(1.0, 0) 95 | 96 | # Finalize stimulus 97 | # 98 | QDS.EndScript() 99 | 100 | # --------------------------------------------------------------------- 101 | -------------------------------------------------------------------------------- /Stimuli/__autorun.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --------------------------------------------------------------------- 5 | import QDS 6 | ''' 7 | import Devices.lightcrafter as LCr 8 | ''' 9 | QDS.Initialize("__autorun", "") 10 | 11 | """ 12 | print("---") 13 | dev = LCr.Lightcrafter() 14 | result = dev.connect() 15 | if result[0] == LCr.ERROR.OK: 16 | dev.getHardwareStatus() 17 | dev.getSystemStatus() 18 | dev.getMainStatus() 19 | dev.getVideoSignalDetectStatus() 20 | dev.disconnect() 21 | else: 22 | print("WARNING: This script required a lightcrafter") 23 | print("---") 24 | """ 25 | 26 | # Requires at least one (dummy) object 27 | # 28 | QDS.DefObj_Box(1, 10,10) 29 | 30 | # Just clear screen 31 | # 32 | QDS.StartScript() 33 | 34 | #QDS.LC_setLEDCurrents(0, [0,5,5]) 35 | 36 | QDS.SetBkgColor((0,0,0)) 37 | QDS.Scene_Clear(0.1, 0) 38 | 39 | QDS.EndScript() 40 | 41 | # --------------------------------------------------------------------- 42 | -------------------------------------------------------------------------------- /Stimuli/__toGB_8bit_patternMode.py_template: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import Devices.lightcrafter_4500 as lcr 4 | import time 5 | 6 | # Generate lightcrafter object and try to connect to it 7 | dev = lcr.Lightcrafter(_isCheckOnly=False, _logLevel=3) 8 | res = dev.connect() 9 | if res[0] is not lcr.ERROR.OK: 10 | # No connection 11 | exit() 12 | 13 | # Print report and video signal status 14 | dev.getFirmwareVersion() 15 | dev.getHardwareStatus() 16 | dev.getMainStatus() 17 | dev.getSystemStatus() 18 | dev.getVideoSignalDetectStatus() 19 | 20 | dev.stopPatternSequence() 21 | 22 | # Select pattern sequence mode 23 | dev.setDisplayMode(lcr.DispMode.Pattern) 24 | 25 | # Select the 24bit RGB stream as input 26 | dev.setPatternDisplayDataInputSource(lcr.SourcePat.Parallel) 27 | 28 | # Setup LUT: 29 | # 2 LUT entries, repeat sequence, 2 patterns/sequence, n/a 30 | dev.setPatternDispLUTControl(2, True, 2, 1) 31 | 32 | # Set trigger mode to VSYNC 33 | dev.setPatternTriggerMode(lcr.PatTrigMode.Vsync_fixedExposure) 34 | 35 | # Set exposure time, frame rate (in usec) 36 | dev.setPatternExpTimeFrPer(16666, 16666) 37 | 38 | # Open LUT mailbox for pattern sequence mode (external input) 39 | dev.setPatternDispLUTAccessControl(lcr.MailboxCmd.OpenPat) 40 | 41 | # LUT entry #0 ... 42 | dev.setPatternDispLUTOffsetPointer(0) 43 | # Pattern G0-G7, internal trigger, 8 bit, red LED 44 | dev.setPatternDispLUTData(lcr.MailboxPat.G76543210, 45 | lcr.MailboxTrig.ExternalPos, 46 | 8, lcr.MailboxLED.Red) 47 | # LUT entry #1 ... 48 | dev.setPatternDispLUTOffsetPointer(1) 49 | # Pattern B0-B7, internal trigger, 8 bit, blue LED, trigger out 1 stays high 50 | # to share time with previous pattern 51 | dev.setPatternDispLUTData(lcr.MailboxPat.B76543210, 52 | lcr.MailboxTrig.None_, 53 | 8, lcr.MailboxLED.Blue, 54 | _trigOut1High=True) 55 | 56 | # Close LUT mailbox 57 | dev.setPatternDispLUTAccessControl(lcr.MailboxCmd.Close) 58 | 59 | # Validate pattern sequence 60 | res = dev.validateDataCommandResponse() 61 | 62 | if res[0] == lcr.ERROR.OK: # and res[1]: 63 | dev.getHardwareStatus() 64 | dev.getMainStatus() 65 | time.sleep(1) 66 | dev.startPatternSequence() 67 | time.sleep(1) 68 | 69 | dev.getMainStatus() 70 | dev.getSystemStatus() 71 | 72 | dev.disconnect() 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /Stimuli/__toVideoMode.py_template: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import Devices.lightcrafter_4500 as lcr 4 | import time 5 | 6 | # Generate lightcrafter object and try to connect to it 7 | dev = lcr.Lightcrafter(_isCheckOnly=False, _logLevel=3) 8 | res = dev.connect() 9 | if res[0] is not lcr.ERROR.OK: 10 | # No connection 11 | exit() 12 | 13 | # Print report and video signal status 14 | dev.getFirmwareVersion() 15 | dev.getHardwareStatus() 16 | dev.getMainStatus() 17 | dev.getSystemStatus() 18 | dev.getVideoSignalDetectStatus() 19 | 20 | dev.stopPatternSequence() 21 | 22 | # Go back to normal video mode 23 | dev.setInputSource(lcr.SourceSel.HDMI, lcr.SourcePar.Bit24) 24 | dev.setDisplayMode(lcr.DispMode.Video) 25 | 26 | dev.getMainStatus() 27 | dev.getSystemStatus() 28 | 29 | dev.disconnect() 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Stimuli/dome_grid.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --------------------------------------------------------------------- 5 | import QDS 6 | 7 | QDS.Initialize("dome_grid", "Grid to visualize stimulus in dome") 8 | 9 | # fmt: off 10 | # Define global stimulus parameters 11 | gridRGB = (255, 255, 255) 12 | bkgRGB = (0, 0, 0) 13 | dxScr = 1920 //2 14 | dyScr = 1080 //2 15 | nx = 12 16 | ny = int(nx * dyScr / dxScr) 17 | nTrials = 200 18 | dtTrial = 1.0 19 | 20 | dxBox = dxScr / nx 21 | dyBox = dxBox 22 | x0 = 0 23 | y0 = 0 24 | # fmt: on 25 | 26 | # Define objects 27 | # Generate one box object per grid position 28 | nB = nx * ny 29 | for iB in range(1, nB + 1): 30 | QDS.DefObj_Box(iB, dxBox - 2, dyBox - 2) 31 | 32 | QDS.DefObj_EllipseEx(1000, dxBox, dyBox) 33 | QDS.DefObj_EllipseEx(1001, dxBox +10, dyBox +10) 34 | QDS.DefObj_EllipseEx(1002, dxBox /5, dyBox /5) 35 | 36 | # Fill list with parameters for every box object 37 | BoxIndList = [] 38 | BoxPosList = [] 39 | BoxMagList = [] 40 | BoxRotList = [] 41 | BoxColList = [] 42 | BoxAlpList = [] 43 | 44 | for iX in range(nx): 45 | for iY in range(ny): 46 | iB = 1 + iX + iY * nx 47 | x = iX * dxBox + dxBox / 2 - dxBox * nx / 2.0 48 | y = iY * dyBox + dyBox / 2 - dyBox * ny / 2.0 49 | BoxIndList.append(iB) 50 | BoxPosList.append((x, y)) 51 | BoxMagList.append((1.0, 1.0)) 52 | BoxRotList.append(0) 53 | BoxColList.append(gridRGB if ((iB +iY) % 2) > 0 else bkgRGB) 54 | BoxAlpList.append(255) 55 | 56 | # Start of stimulus run 57 | QDS.StartScript() 58 | QDS.SetBkgColor(bkgRGB) 59 | QDS.Scene_Clear(1.0, 0) 60 | 61 | QDS.SetObjColorEx(BoxIndList, BoxColList, BoxAlpList) 62 | QDS.SetObjColorEx([1001], [(128,128,128)], [255]) 63 | 64 | # Present grid 65 | for iT in range(nTrials): 66 | QDS.SetObjColorEx( 67 | [1000, 1002], 68 | [gridRGB, bkgRGB] if (iT % 2) > 0 else [bkgRGB, gridRGB], 69 | [255] *2 70 | ) 71 | QDS.Scene_RenderEx( 72 | dtTrial, 73 | BoxIndList +[1001, 1000, 1002], 74 | BoxPosList +[(x0, y0)] *3, 75 | BoxMagList +[(1, 1)] *3, 76 | BoxRotList +[0] *3, 77 | 0, 78 | ) 79 | 80 | QDS.Scene_Clear(1.0, 0) 81 | 82 | # Finalize stimulus 83 | QDS.EndScript() 84 | 85 | # --------------------------------------------------------------------- 86 | -------------------------------------------------------------------------------- /Stimuli/dome_grid_sphere.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --------------------------------------------------------------------- 5 | import QDS 6 | 7 | QDS.Initialize("dome_grid_sphere", "Grid to visualize stimulus in dome") 8 | 9 | # fmt: off 10 | # Define global stimulus parameters 11 | gridRGB = (255, 255, 255) 12 | bkgRGB = (0, 0, 0) 13 | dxScr = 1920 //2 14 | dyScr = 1080 //2 15 | nx = 12 16 | ny = int(nx * dyScr / dxScr) 17 | nTrials = 200 18 | dtTrial = 1.0 19 | 20 | dxBox = dxScr / nx 21 | dyBox = dxBox 22 | x0 = 0 23 | y0 = 0 24 | # fmt: on 25 | 26 | # Define objects 27 | # Generate one box object per grid position 28 | nB = nx * ny 29 | for iB in range(1, nB + 1): 30 | QDS.DefObj_Box(iB, dxBox - 2, dyBox - 2) 31 | 32 | QDS.DefObj_EllipseEx(1000, dxBox, dyBox) 33 | QDS.DefObj_EllipseEx(1001, dxBox +10, dyBox +10) 34 | QDS.DefObj_EllipseEx(1002, dxBox /5, dyBox /5) 35 | 36 | # Fill list with parameters for every box object 37 | BoxIndList = [] 38 | BoxPosList = [] 39 | BoxMagList = [] 40 | BoxRotList = [] 41 | BoxColList = [] 42 | BoxAlpList = [] 43 | 44 | for iX in range(nx): 45 | for iY in range(ny): 46 | iB = 1 + iX + iY * nx 47 | x = iX * dxBox + dxBox / 2 - dxBox * nx / 2.0 48 | y = iY * dyBox + dyBox / 2 - dyBox * ny / 2.0 49 | BoxIndList.append(iB) 50 | BoxPosList.append((x, y)) 51 | BoxMagList.append((1.0, 1.0)) 52 | BoxRotList.append(0) 53 | BoxColList.append(gridRGB if ((iB +iY) % 2) > 0 else bkgRGB) 54 | BoxAlpList.append(255) 55 | 56 | 57 | QDS.DefObj_Box(2000, 500, 500, True) 58 | QDS.DefShader(2000, "SPHERE") 59 | 60 | # Start of stimulus run 61 | QDS.StartScript() 62 | QDS.SetBkgColor(bkgRGB) 63 | QDS.Scene_Clear(1.0, 0) 64 | 65 | QDS.SetObjColorEx(BoxIndList, BoxColList, BoxAlpList) 66 | QDS.SetObjColorEx([1001], [(128,128,128)], [255]) 67 | 68 | # Present grid 69 | for iT in range(nTrials): 70 | QDS.SetObjColorEx( 71 | [1000, 1002], 72 | [gridRGB, bkgRGB] if (iT % 2) > 0 else [bkgRGB, gridRGB], 73 | [255] *2 74 | ) 75 | QDS.Scene_RenderEx( 76 | dtTrial, 77 | BoxIndList +[1001, 1000, 1002, 2000], 78 | BoxPosList +[(x0, y0)] *4, 79 | BoxMagList +[(1, 1)] *4, 80 | BoxRotList +[0] *4, 81 | 0, 82 | ) 83 | 84 | QDS.Scene_Clear(1.0, 0) 85 | 86 | # Finalize stimulus 87 | QDS.EndScript() 88 | 89 | # --------------------------------------------------------------------- 90 | -------------------------------------------------------------------------------- /Stimuli/dome_optokinetic_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --------------------------------------------------------------------- 5 | import QDS 6 | import random 7 | 8 | # Initialize QDS (allways needs to be called first) 9 | QDS.Initialize("Optokinetic1", "Moving vertical bar gratings") 10 | 11 | # --------------------------------------------------------------------- 12 | # Define stimulus parameters 13 | # (as a dictionary, to be able to write it easily to the log) 14 | p = {} 15 | p["nTrials"] = 1 16 | p["dxy"] = (600, 1200) # Stimulus size in um 17 | p["pxy"] = (0, 0) # Stimulus centre position in um 18 | p["mxy"] = (1.0, 1.0) # Magnification factor 19 | p["rot_deg"] = 90 # Rotation angle of grating in degrees 20 | 21 | # Define grating parameters 22 | p["dtTrial_s"] = 3.0 # Duration of one trial in s 23 | p["nPer"] = [4, 8, 16] # Periods per stimulus area 24 | p["rContr"] = [0.9, 0.5, 0.1] # Contrast ratios 25 | p["speed"] = 25 # Speed factor (TODO) 26 | p["rSeed"] = 1 # Random seed for trial generation 27 | 28 | # Define grating bar colors at max. contrast 29 | p["minRGBA"] = (0, 0, 0, 255) 30 | p["maxRGBA"] = (255, 255, 255, 255) 31 | 32 | # Calculate background color (mean) 33 | bkgCol = tuple( 34 | [int((p["maxRGBA"][i] -p["minRGBA"][i]) /2 +p["minRGBA"][i]) 35 | for i in range(3)] 36 | ) 37 | 38 | # --------------------------------------------------------------------- 39 | # Define trials (as pairs of condition indices) 40 | # First, generate a list of all possible conditions 41 | allConds = [(iP, iC) for iP in range(len(p["nPer"])) for iC in range(len(p["rContr"]))] 42 | 43 | # Then, generate a list of `nTrials` shuffeled copies of this list 44 | random.seed(p["rSeed"] ) 45 | p["trials"] = [] 46 | for iT in range(p["nTrials"]): 47 | random.shuffle(allConds) 48 | p["trials"] += allConds 49 | 50 | # Log parameters 51 | QDS.LogUserParameters(p) 52 | 53 | # --------------------------------------------------------------------- 54 | # Define stimulus box object on which the shader will be applied 55 | GRA_ID = 1 56 | QDS.DefObj_BoxEx(GRA_ID, p["dxy"][0], p["dxy"][1], _enShader = 1) 57 | col = [int((p["maxRGBA"][i] -p["minRGBA"][i]) /2 +p["minRGBA"][i]) for i in range(2)] 58 | QDS.SetObjColorEx([GRA_ID], [bkgCol], [255]) 59 | 60 | BKG_ID = 2 61 | QDS.DefObj_BoxEx(BKG_ID, p["dxy"][0], p["dxy"][1], _enShader = 0) 62 | col = [int((p["maxRGBA"][i] -p["minRGBA"][i]) /2 +p["minRGBA"][i]) for i in range(2)] 63 | QDS.SetObjColorEx([BKG_ID], [bkgCol], [255]) 64 | 65 | # Define shader 66 | SHA_ID = 1 67 | QDS.DefShader(SHA_ID, "SQUARE_WAVE_GRATING_MIX4") 68 | 69 | # Link shader to object 70 | QDS.SetObjShader([GRA_ID], [SHA_ID]) 71 | 72 | # --------------------------------------------------------------------- 73 | # Start protocol and clear screen 74 | QDS.StartScript() 75 | QDS.Scene_Clear(1.0, 0) 76 | 77 | # Wait for trigger 78 | #QDS.AwaitTTL() 79 | 80 | # Repeat stimulus `nTrials` times 81 | for iT in range(p["nTrials"]): 82 | for iP, iC in p["trials"]: 83 | # Get condition parameters 84 | perLen_um = p["dxy"][0] /p["nPer"][iP] 85 | perDur_s = 1 /p["speed"] *perLen_um /p["dtTrial_s"] 86 | mixA = 1 -p["rContr"][iC] 87 | 88 | # Present gray for 0.05 s with trigger 89 | QDS.SetShaderParams( 90 | SHA_ID, [perLen_um, perDur_s, 91 | p["maxRGBA"] , p["maxRGBA"] , 0.5] 92 | ) 93 | QDS.Scene_RenderEx( 94 | 0.05, 95 | [GRA_ID], [p["pxy"]], [p["mxy"]], [p["rot_deg"]], 1 96 | ) 97 | 98 | # Present stimulus 99 | QDS.SetShaderParams( 100 | SHA_ID, 101 | [perLen_um, perDur_s, 102 | p["minRGBA"] , p["maxRGBA"] , mixA] 103 | ) 104 | QDS.Scene_RenderEx( 105 | p["dtTrial_s"] -0.05, 106 | [GRA_ID], [p["pxy"]], [p["mxy"]], [p["rot_deg"]], 0 107 | ) 108 | 109 | # Clear screen and end protocol 110 | QDS.Scene_Clear(1.0, 0) 111 | QDS.EndScript() 112 | 113 | # --------------------------------------------------------------------- 114 | -------------------------------------------------------------------------------- /Stimuli/movie.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --------------------------------------------------------------------- 5 | import QDS 6 | 7 | QDS.Initialize("movie", "Example for playing a movie") 8 | 9 | # Define global stimulus parameters 10 | # 11 | p = {} 12 | p['nTrials'] = 3 # number of stimulus presentations 13 | p['mScal'] = (2.0, 2.0) # movie scaling (x, y) 14 | p['mOrient'] = [0, 45, 90, 135] # movie orientations 15 | p['mAlpha'] = 250 # transparency of movie 16 | 17 | FrRefr_Hz = QDS.GetDefaultRefreshRate() 18 | 19 | # Define objects 20 | # 21 | QDS.DefObj_Movie(1, "rabbit.jpg") 22 | 23 | # Update parameter dictionary with the movie parameters 24 | # -> "dxFr", "dyFr", "nFr" 25 | # 26 | p.update(QDS.GetMovieParameters(1)) 27 | print(p) 28 | 29 | # Start of stimulus run 30 | # 31 | QDS.StartScript() 32 | QDS.Scene_Clear(1.00, 0) 33 | 34 | for iT in range(p['nTrials']): 35 | for iOrient in p['mOrient']: 36 | QDS.Start_Movie(1, (0,0), [0, 0, FrRefr_Hz, 1], 37 | p['mScal'], p['mAlpha'], iOrient) 38 | QDS.Scene_Clear(1.00, 0) 39 | 40 | QDS.Start_Movie(1, (0,0), [0,p['nFr']-1, 3, 1], 41 | p['mScal'], p['mAlpha'], iOrient) 42 | QDS.Scene_Clear(0.05, 1) 43 | QDS.Scene_Clear(1.95, 0) 44 | 45 | QDS.Start_Movie(1, (0,0), [p['nFr']-1, p['nFr']-1, 2*FrRefr_Hz, 1], 46 | p['mScal'], p['mAlpha'], iOrient) 47 | QDS.Scene_Clear(2.00, 0) 48 | 49 | QDS.Start_Movie(1, (0,0), [p['nFr']-1, p['nFr']-1, FrRefr_Hz, 1], 50 | p['mScal'], p['mAlpha'], iOrient) 51 | QDS.Scene_Clear(1.00, 0) 52 | 53 | QDS.Start_Movie(1, (0,0), [p['nFr']-1, 0, 3, 1], 54 | p['mScal'], p['mAlpha'], iOrient) 55 | QDS.Scene_Clear(0.05, 1) 56 | QDS.Scene_Clear(1.95, 0) 57 | 58 | QDS.Start_Movie(1, (0,0), [0, 0, 2*FrRefr_Hz, 1], 59 | p['mScal'], p['mAlpha'], iOrient) 60 | QDS.Scene_Clear(2.00, 0) 61 | 62 | QDS.Scene_Clear(1.00, 0) 63 | 64 | # Finalize stimulus 65 | # 66 | QDS.EndScript() 67 | 68 | # ----------------------------------------------------------------------------- 69 | 70 | -------------------------------------------------------------------------------- /Stimuli/movie_water.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --------------------------------------------------------------------- 5 | import QDS 6 | 7 | QDS.Initialize("movie_water1", "Water movie test") 8 | 9 | # Define global stimulus parameters 10 | # 11 | FrRefr_Hz = QDS.GetDefaultRefreshRate() 12 | 13 | p = {"nTrials" : 1, # number of stimulus presentations 14 | "movScale" : (4.0, 4.0), # movie scaling (x, y) 15 | "movOrient" : 0, # movie orientation 16 | "movAlpha" : 255, # transparency of movie 17 | "MarkPer_s" : 1.0, # number of markers per second 18 | "durFr_s" : 1/FrRefr_Hz, # frame duration 19 | "nFrPerMarker" : 3, 20 | 21 | "movName" : "water1_flipped.jpg", 22 | "rng_seed" : 1, # Random seed 23 | "tex_ydim" : 90, # Output resolution, Y dimension 24 | "tex_xdim" : 128, # Output resolution, X dimension 25 | "duration" : 180, # Stimulus duration (seconds) 26 | "yNodes" : 6, # Generator image, Y dimension 27 | "xNodes" : 6, # Generator image, X dimension 28 | "upFactor" : 48, # Upscaling factor 29 | "tempFreq" : 2.5, # Temporal frequency 30 | "tempKernelLength": 61, # Length of temporal filter kernel 31 | "spatialFreq" : 0.06, # Spatial frequency [?] 32 | "fps" : 60, # Frame rate per second 33 | "screenSize" : [64, 45], # Screen resolution 34 | "compensator" : 8} # keep the spatial frequencies 35 | # unchanged as the upscale factor 36 | # changes 37 | QDS.LogUserParameters(p) 38 | 39 | # Define objects 40 | # 41 | QDS.DefObj_Movie(1, p["movName"]) 42 | movparams = QDS.GetMovieParameters(1) 43 | print(movparams) 44 | p["movparams"] = movparams 45 | dFr = 1 /FrRefr_Hz 46 | nMark = int(movparams["nFr"] /FrRefr_Hz /p["MarkPer_s"]) 47 | dMark_s = p["nFrPerMarker"] *dFr 48 | 49 | # Start of stimulus run 50 | # 51 | QDS.StartScript() 52 | QDS.Scene_Clear(1.00, 0) 53 | 54 | for iT in range(p["nTrials"]): 55 | QDS.Start_Movie(1, (0,0), [0, 0, FrRefr_Hz, 1], 56 | p["movScale"], p["movAlpha"], p["movOrient"]) 57 | QDS.Scene_Clear(1.00, 0) 58 | QDS.Start_Movie(1, (0,0), [0, movparams["nFr"]-1, 1, 1], 59 | p["movScale"], p["movAlpha"], p["movOrient"]) 60 | 61 | for iM in range(nMark): 62 | QDS.Scene_Clear(dMark_s, 1) 63 | QDS.Scene_Clear(p["MarkPer_s"] -dMark_s, 0) 64 | 65 | QDS.Scene_Clear(1.00, 0) 66 | 67 | # Finalize stimulus 68 | # 69 | QDS.EndScript() 70 | 71 | # ----------------------------------------------------------------------------- 72 | 73 | -------------------------------------------------------------------------------- /Stimuli/noise_Colored1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --------------------------------------------------------------------- 5 | import random 6 | import QDS 7 | 8 | QDS.Initialize("noise_Colored1", "Example for random-colored boxes flickering") 9 | 10 | # Set random generator seed 11 | # 12 | random.seed(1) 13 | 14 | # Define global stimulus parameters 15 | # 16 | p = {} 17 | p['dtFr_s'] = 3/60.0 # presentation time per pattern 18 | p['nTrials'] = 50 # # of repeats 19 | p['nPrMark'] = 20 # present marker every p['nPrMark']*p['dtFr_s'] 20 | p['nRows'] = 30 # dimensions of pattern grid 21 | p['nCols'] = 30 22 | p['boxDx'] = 15 # box size in um 23 | p['boxDy'] = 15 24 | p['dRot_step'] = 0 # angle by which boxes are rotated 25 | 26 | # Define objects 27 | # Generate one box object per grid position 28 | # 29 | nB = p['nRows']*p['nCols'] 30 | for iB in range(1, nB+1): 31 | QDS.DefObj_Box(iB, p['boxDx'], p['boxDy']) 32 | 33 | # Fill list with parameters for every box object 34 | # 35 | BoxIndList = [] 36 | BoxPosList = [] 37 | BoxMagList = [] 38 | BoxRotList = [] 39 | 40 | for iX in range(p['nCols']): 41 | for iY in range(p['nRows']): 42 | iB = 1 +iX +iY*p['nCols'] 43 | x = iX*p['boxDx'] +p['boxDx']/2.0 -p['boxDx']*p['nCols']/2.0 44 | y = iY*p['boxDy'] +p['boxDy']/2.0 -p['boxDy']*p['nRows']/2.0 45 | BoxIndList.append(iB) 46 | BoxPosList.append((x,y)) 47 | BoxMagList.append((1.0, 1.0)) 48 | BoxRotList.append(0) 49 | 50 | # Start of stimulus run 51 | # 52 | QDS.StartScript() 53 | QDS.Scene_Clear(1.0, 0) 54 | 55 | # Present grid 56 | # 57 | rot = 0.0 58 | for iT in range(p['nTrials']): 59 | BoxColList = [] 60 | BoxAlpList = [] 61 | BoxRotList = [] 62 | for iB in range(1, nB+1): 63 | r = random.randint(5, 250) 64 | g = random.randint(5, 250) 65 | b = random.randint(5, 250) 66 | BoxColList.append((r, g, b)) 67 | BoxAlpList.append(255) 68 | BoxRotList.append(rot) 69 | rot += p['dRot_step'] 70 | QDS.SetObjColorEx(BoxIndList, BoxColList, BoxAlpList) 71 | QDS.Scene_RenderEx(p['dtFr_s'], BoxIndList, BoxPosList, BoxMagList, 72 | BoxRotList, int((iT % p['nPrMark']) == 0)) 73 | 74 | QDS.Scene_Clear(1.0, 0) 75 | 76 | # Finalize stimulus 77 | # 78 | QDS.EndScript() 79 | 80 | # --------------------------------------------------------------------- 81 | -------------------------------------------------------------------------------- /Stimuli/noise_Colored1_RGBU.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --------------------------------------------------------------------- 5 | import random 6 | import QDS 7 | 8 | QDS.Initialize("noise_Colored1_RGBU", "Example for random-colored boxes flickering") 9 | 10 | # Set random generator seed 11 | # 12 | random.seed(1) 13 | 14 | # Define global stimulus parameters 15 | # 16 | p = {} 17 | p['dtFr_s'] = 3/60.0 # presentation time per pattern 18 | p['nTrials'] = 50 # 0 # # of repeats 19 | p['nPrMark'] = 20 # present marker every p['nPrMark']*p['dtFr_s'] 20 | p['nRows'] = 30 # dimensions of pattern grid 21 | p['nCols'] = 30 22 | p['boxDx'] = 15 # box size in um 23 | p['boxDy'] = 15 24 | p['dRot_step'] = 0 # angle by which boxes are rotated 25 | 26 | # Define objects 27 | # Generate one box object per grid position 28 | # 29 | nB = p['nRows']*p['nCols'] 30 | for iB in range(1, nB+1): 31 | QDS.DefObj_Box(iB, p['boxDx'], p['boxDy']) 32 | 33 | # Fill list with parameters for every box object 34 | # 35 | BoxIndList = [] 36 | BoxPosList = [] 37 | BoxMagList = [] 38 | BoxRotList = [] 39 | 40 | for iX in range(p['nCols']): 41 | for iY in range(p['nRows']): 42 | iB = 1 +iX +iY*p['nCols'] 43 | x = iX*p['boxDx'] +p['boxDx']/2.0 -p['boxDx']*p['nCols']/2.0 44 | y = iY*p['boxDy'] +p['boxDy']/2.0 -p['boxDy']*p['nRows']/2.0 45 | BoxIndList.append(iB) 46 | BoxPosList.append((x,y)) 47 | BoxMagList.append((1.0, 1.0)) 48 | BoxRotList.append(0) 49 | 50 | # Start of stimulus run 51 | # 52 | QDS.StartScript() 53 | QDS.SetBkgColor((100,0,50, 0,50,50)) 54 | QDS.Scene_Clear(1.0, 0) 55 | QDS.SetBkgColor((0,100,50, 50,0,50)) 56 | QDS.Scene_Clear(1.0, 0) 57 | 58 | # Present grid 59 | # 60 | rot = 0.0 61 | for iT in range(p['nTrials']): 62 | BoxColList = [] 63 | BoxAlpList = [] 64 | BoxRotList = [] 65 | for iB in range(1, nB+1): 66 | r = random.randint(5, 250) 67 | g = random.randint(5, 250) 68 | b = random.randint(5, 250) 69 | u = random.randint(5, 250) 70 | BoxColList.append((r, g, b, 0, 0, u)) 71 | BoxAlpList.append(255) 72 | BoxRotList.append(rot) 73 | rot += p['dRot_step'] 74 | QDS.SetObjColorEx(BoxIndList, BoxColList, BoxAlpList) 75 | QDS.Scene_RenderEx(p['dtFr_s'], BoxIndList, BoxPosList, BoxMagList, 76 | BoxRotList, int((iT % p['nPrMark']) == 0)) 77 | 78 | QDS.Scene_Clear(1.0, 0) 79 | QDS.SetBkgColor((50,0,100, 50,50,0)) 80 | QDS.Scene_Clear(1.0, 0) 81 | QDS.SetBkgColor((0,0,0, 0,0,0)) 82 | QDS.Scene_Clear(1.0, 0) 83 | 84 | # Finalize stimulus 85 | # 86 | QDS.EndScript() 87 | 88 | # --------------------------------------------------------------------- 89 | -------------------------------------------------------------------------------- /Stimuli/noise_Colored_Wait.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --------------------------------------------------------------------- 5 | import random 6 | import QDS 7 | 8 | # --------------------------------------------------------------------- 9 | def presentGrid(): 10 | # Present grid 11 | # 12 | QDS.AwaitTTL() 13 | 14 | rot = 0.0 15 | for iT in range(p['nFr']): 16 | BoxColList = [] 17 | BoxAlpList = [] 18 | BoxRotList = [] 19 | for iB in range(1, nB+1): 20 | r = random.randint(5, 250) 21 | g = random.randint(5, 250) 22 | b = random.randint(5, 250) 23 | BoxColList.append((r, g, b)) 24 | BoxAlpList.append(127) 25 | BoxRotList.append(rot) 26 | rot += p['dRot_step'] 27 | QDS.SetObjColorEx(BoxIndList, BoxColList, BoxAlpList) 28 | QDS.Scene_RenderEx(p['dtFr_s'], BoxIndList, BoxPosList, BoxMagList, 29 | BoxRotList, int((iT % p['nPrMark']) == 0)) 30 | 31 | # --------------------------------------------------------------------- 32 | QDS.Initialize("noise_Colored_wait", "Example for await trigger") 33 | 34 | # Set random generator seed 35 | # 36 | random.seed(1) 37 | 38 | # Define global stimulus parameters 39 | # 40 | p = {} 41 | p['dtFr_s'] = 3/60.0 # presentation time per pattern 42 | p['nFr'] = 50 # # of frames per trial 43 | p['nTrials'] = 5 # # of trials 44 | p['nPrMark'] = 20 # present marker every p['nPrMark']*p['dtFr_s'] 45 | p['nRows'] = 20 # dimensions of pattern grid 46 | p['nCols'] = 20 47 | p['boxDx'] = 25 # box size in um 48 | p['boxDy'] = 25 49 | p['dRot_step'] = 0 # angle by which boxes are rotated 50 | 51 | # Define objects 52 | # Generate one box object per grid position 53 | # 54 | nB = p['nRows']*p['nCols'] 55 | for iB in range(1, nB+1): 56 | QDS.DefObj_Box(iB, p['boxDx'], p['boxDy']) 57 | 58 | # Fill list with parameters for every box object 59 | # 60 | BoxIndList = [] 61 | BoxPosList = [] 62 | BoxMagList = [] 63 | BoxRotList = [] 64 | 65 | for iX in range(p['nCols']): 66 | for iY in range(p['nRows']): 67 | iB = 1 +iX +iY*p['nCols'] 68 | x = iX*p['boxDx'] +p['boxDx']/2.0 -p['boxDx']*p['nCols']/2.0 69 | y = iY*p['boxDy'] +p['boxDy']/2.0 -p['boxDy']*p['nRows']/2.0 70 | BoxIndList.append(iB) 71 | BoxPosList.append((x,y)) 72 | BoxMagList.append((2.0, 1.0)) 73 | BoxRotList.append(0) 74 | 75 | # Start of stimulus run 76 | # 77 | QDS.StartScript() 78 | QDS.Scene_Clear(1.0, 0) 79 | 80 | QDS.Loop(p["nTrials"], presentGrid) 81 | 82 | QDS.Scene_Clear(1.0, 0) 83 | 84 | # Finalize stimulus 85 | # 86 | QDS.EndScript() 87 | 88 | # --------------------------------------------------------------------- 89 | -------------------------------------------------------------------------------- /Stimuli/rabbit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Stimuli/rabbit.jpg -------------------------------------------------------------------------------- /Stimuli/rabbit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Stimuli/rabbit.png -------------------------------------------------------------------------------- /Stimuli/rabbit.txt: -------------------------------------------------------------------------------- 1 | [QDSMovie2Description] 2 | QDSVersionID=xQDS 3 | FrWidth=227 4 | FrHeight=192 5 | FrCount=40 6 | isFirstFrBottomLeft=True 7 | Comment=a rabbit movie -------------------------------------------------------------------------------- /Stimuli/video_perlin16.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --------------------------------------------------------------------- 5 | import QDS 6 | 7 | QDS.Initialize("video_perlin16", "Perlin noise test") 8 | 9 | # Define global stimulus parameters 10 | # 11 | FrRefr_Hz = QDS.GetDefaultRefreshRate() 12 | 13 | p = {"nTrials" : 1, # number of stimulus presentations 14 | "vidScale" : (3.0, 3.0), # movie scaling (x, y) 15 | "vidOrient" : 0, # movie orientation 16 | "vidAlpha" : 255, # transparency of movie 17 | "MarkPer_s" : 1.0, # number of markers per second 18 | "durFr_s" : 1/FrRefr_Hz, # frame duration 19 | "nFrPerMarker" : 3, 20 | "vidName" : "Perlin16.avi"} 21 | 22 | QDS.LogUserParameters(p) 23 | 24 | # Define objects 25 | # 26 | QDS.DefObj_Video(1, p["vidName"]) 27 | vidparams = QDS.GetVideoParameters(1) 28 | p["vidparams"] = vidparams 29 | dFr = 1 /FrRefr_Hz 30 | nMark = int(vidparams["nFr"] /FrRefr_Hz /p["MarkPer_s"]) 31 | dMark_s = p["nFrPerMarker"] *dFr 32 | dInterval_s = 1.0/p["MarkPer_s"] 33 | 34 | # Start of stimulus run 35 | # 36 | QDS.StartScript() 37 | QDS.Scene_Clear(1.00, 0) 38 | 39 | for iT in range(p["nTrials"]): 40 | QDS.Start_Video(1, (0,0), p["vidScale"], p["vidAlpha"], p["vidOrient"]) 41 | for iM in range(nMark): 42 | QDS.Scene_Clear(dMark_s, 1) 43 | QDS.Scene_Clear(dInterval_s -dMark_s, 0) 44 | 45 | QDS.Scene_Clear(1.00, 0) 46 | 47 | # Finalize stimulus 48 | # 49 | QDS.EndScript() 50 | 51 | # ----------------------------------------------------------------------------- 52 | -------------------------------------------------------------------------------- /Stimuli/video_perlin32.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --------------------------------------------------------------------- 5 | import QDS 6 | 7 | QDS.Initialize("video_perlin32", "Perlin noise test") 8 | 9 | # Define global stimulus parameters 10 | # 11 | FrRefr_Hz = QDS.GetDefaultRefreshRate() 12 | 13 | p = {"nTrials" : 1, # number of stimulus presentations 14 | "vidScale" : (3.0, 3.0), # movie scaling (x, y) 15 | "vidOrient" : 0, # movie orientation 16 | "vidAlpha" : 255, # transparency of movie 17 | "MarkPer_s" : 1.0, # number of markers per second 18 | "durFr_s" : 1/FrRefr_Hz, # frame duration 19 | "nFrPerMarker" : 3, 20 | "vidName" : "Perlin32.avi"} 21 | 22 | QDS.LogUserParameters(p) 23 | 24 | # Define objects 25 | # 26 | QDS.DefObj_Video(1, p["vidName"]) 27 | vidparams = QDS.GetVideoParameters(1) 28 | p["vidparams"] = vidparams 29 | dFr = 1 /FrRefr_Hz 30 | nMark = int(vidparams["nFr"] /FrRefr_Hz /p["MarkPer_s"]) 31 | dMark_s = p["nFrPerMarker"] *dFr 32 | dInterval_s = 1.0/p["MarkPer_s"] 33 | 34 | # Start of stimulus run 35 | # 36 | QDS.StartScript() 37 | QDS.Scene_Clear(1.00, 0) 38 | 39 | for iT in range(p["nTrials"]): 40 | QDS.Start_Video(1, (0,0), p["vidScale"], p["vidAlpha"], p["vidOrient"]) 41 | for iM in range(nMark): 42 | QDS.Scene_Clear(dMark_s, 1) 43 | QDS.Scene_Clear(dInterval_s -dMark_s, 0) 44 | 45 | QDS.Scene_Clear(1.00, 0) 46 | 47 | # Finalize stimulus 48 | # 49 | QDS.EndScript() 50 | 51 | # ----------------------------------------------------------------------------- 52 | -------------------------------------------------------------------------------- /Stimuli/video_perlin32_RGBU.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --------------------------------------------------------------------- 5 | import QDS 6 | 7 | QDS.Initialize("video_perlin32_ovl", "Perlin noise test") 8 | 9 | # Define global stimulus parameters 10 | # 11 | FrRefr_Hz = QDS.GetDefaultRefreshRate() 12 | 13 | p = {"nTrials" : 1, # number of stimulus presentations 14 | "vidScale" : (3.0, 3.0), # movie scaling (x, y) 15 | "vidOrient" : 0, # movie orientation 16 | "vidAlpha" : 255, # transparency of movie 17 | "MarkPer_s" : 1.0, # number of markers per second 18 | "durFr_s" : 1/FrRefr_Hz, # frame duration 19 | "nFrPerMarker" : 3, 20 | "vidName" : "Perlin32.avi"} 21 | 22 | QDS.LogUserParameters(p) 23 | 24 | # Define objects 25 | # 26 | QDS.DefObj_Video(1, p["vidName"]) 27 | QDS.DefObj_Video(2, p["vidName"]) 28 | vidparams = QDS.GetVideoParameters(1) 29 | p["vidparams"] = vidparams 30 | dFr = 1 /FrRefr_Hz 31 | nMark = int(vidparams["nFr"] /FrRefr_Hz /p["MarkPer_s"]) 32 | dMark_s = p["nFrPerMarker"] *dFr 33 | dInterval_s = 1.0/p["MarkPer_s"] 34 | 35 | nMark = 10 36 | 37 | # Start of stimulus run 38 | # 39 | QDS.StartScript() 40 | QDS.Scene_Clear(1.00, 0) 41 | 42 | for iT in range(p["nTrials"]): 43 | QDS.Start_Video(1, (0,0), p["vidScale"], p["vidAlpha"], p["vidOrient"], 44 | _screen=0) 45 | QDS.Start_Video(2, (0,0), p["vidScale"], p["vidAlpha"], p["vidOrient"], 46 | _screen=1) 47 | 48 | for iM in range(nMark): 49 | QDS.Scene_Clear(dMark_s, 1) 50 | QDS.Scene_Clear(dInterval_s -dMark_s, 0) 51 | 52 | QDS.Scene_Clear(1.00, 0) 53 | 54 | # Finalize stimulus 55 | # 56 | QDS.EndScript() 57 | 58 | # ----------------------------------------------------------------------------- 59 | -------------------------------------------------------------------------------- /Stimuli/video_perlin48.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --------------------------------------------------------------------- 5 | import QDS 6 | 7 | QDS.Initialize("video_perlin48", "Perlin noise test") 8 | 9 | # Define global stimulus parameters 10 | # 11 | FrRefr_Hz = QDS.GetDefaultRefreshRate() 12 | 13 | p = {"nTrials" : 1, # number of stimulus presentations 14 | "vidScale" : (3.0, 3.0), # movie scaling (x, y) 15 | "vidOrient" : 0, # movie orientation 16 | "vidAlpha" : 255, # transparency of movie 17 | "MarkPer_s" : 1.0, # number of markers per second 18 | "durFr_s" : 1/FrRefr_Hz, # frame duration 19 | "nFrPerMarker" : 3, 20 | "vidName" : "Perlin48.avi"} 21 | 22 | QDS.LogUserParameters(p) 23 | 24 | # Define objects 25 | # 26 | QDS.DefObj_Video(1, p["vidName"]) 27 | vidparams = QDS.GetVideoParameters(1) 28 | p["vidparams"] = vidparams 29 | dFr = 1 /FrRefr_Hz 30 | nMark = int(vidparams["nFr"] /FrRefr_Hz /p["MarkPer_s"]) 31 | dMark_s = p["nFrPerMarker"] *dFr 32 | dInterval_s = 1.0/p["MarkPer_s"] 33 | 34 | # Start of stimulus run 35 | # 36 | QDS.StartScript() 37 | QDS.Scene_Clear(1.00, 0) 38 | 39 | for iT in range(p["nTrials"]): 40 | QDS.Start_Video(1, (0,0), p["vidScale"], p["vidAlpha"], p["vidOrient"]) 41 | for iM in range(nMark): 42 | QDS.Scene_Clear(dMark_s, 1) 43 | QDS.Scene_Clear(dInterval_s -dMark_s, 0) 44 | 45 | QDS.Scene_Clear(1.00, 0) 46 | 47 | # Finalize stimulus 48 | # 49 | QDS.EndScript() 50 | 51 | # ----------------------------------------------------------------------------- 52 | -------------------------------------------------------------------------------- /Stimuli/video_water.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --------------------------------------------------------------------- 5 | import QDS 6 | 7 | QDS.Initialize("video_water", "Water noise test") 8 | 9 | # Define global stimulus parameters 10 | # 11 | FrRefr_Hz = QDS.GetDefaultRefreshRate() 12 | 13 | p = {"nTrials" : 1, # number of stimulus presentations 14 | "vidScale" : (3.0, 3.0), # movie scaling (x, y) 15 | "vidOrient" : 0, # movie orientation 16 | "vidAlpha" : 255, # transparency of movie 17 | "MarkPer_s" : 1.0, # number of markers per second 18 | "durFr_s" : 1/FrRefr_Hz, # frame duration 19 | "nFrPerMarker" : 3, 20 | "vidName" : "water.avi"} 21 | 22 | QDS.LogUserParameters(p) 23 | 24 | # Define objects 25 | # 26 | QDS.DefObj_Video(1, p["vidName"]) 27 | vidparams = QDS.GetVideoParameters(1) 28 | p["vidparams"] = vidparams 29 | dFr = 1 /FrRefr_Hz 30 | nMark = int(vidparams["nFr"] /FrRefr_Hz /p["MarkPer_s"]) 31 | dMark_s = p["nFrPerMarker"] *dFr 32 | dInterval_s = 1.0/p["MarkPer_s"] 33 | 34 | # Start of stimulus run 35 | # 36 | QDS.StartScript() 37 | QDS.Scene_Clear(1.00, 0) 38 | 39 | for iT in range(p["nTrials"]): 40 | QDS.Start_Video(1, (0,0), p["vidScale"], p["vidAlpha"], p["vidOrient"]) 41 | for iM in range(nMark): 42 | QDS.Scene_Clear(dMark_s, 1) 43 | QDS.Scene_Clear(dInterval_s -dMark_s, 0) 44 | 45 | QDS.Scene_Clear(1.00, 0) 46 | 47 | # Finalize stimulus 48 | # 49 | QDS.EndScript() 50 | 51 | # ----------------------------------------------------------------------------- 52 | -------------------------------------------------------------------------------- /Stimuli/water.avi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Stimuli/water.avi -------------------------------------------------------------------------------- /Stimuli/water1_flipped.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/Stimuli/water1_flipped.jpg -------------------------------------------------------------------------------- /Stimuli/water1_flipped.txt: -------------------------------------------------------------------------------- 1 | [QDSMovie2Description] 2 | QDSVersionID=xQDS 3 | FrWidth=128 4 | FrHeight=90 5 | FrCount=10800 6 | isFirstFrBottomLeft=True 7 | Comment=a water movie -------------------------------------------------------------------------------- /__autorun_default_DO_NOT_DELETE.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/__autorun_default_DO_NOT_DELETE.pickle -------------------------------------------------------------------------------- /__autorun_default_DO_NOT_DELETE.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --------------------------------------------------------------------- 5 | import QDS 6 | 7 | QDS.Initialize("__autorun_default_DO_NOT_DELETE", "") 8 | QDS.DefObj_Box(1, 10,10) 9 | QDS.StartScript() 10 | QDS.SetBkgColor((0,0,0, 0,0,0)) 11 | QDS.Scene_Clear(0.1, 0) 12 | QDS.EndScript() 13 | 14 | # --------------------------------------------------------------------- 15 | -------------------------------------------------------------------------------- /grating.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eulerlab/QDSpy/28feaa31a88f91a6fde77803a7420918ffc192b4/grating.jpg -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyqt6 2 | numpy 3 | pywin32 4 | pyglet==1.5.5 5 | moviepy<2 6 | psutil 7 | pyserial 8 | hidapi 9 | hid 10 | pygame 11 | -------------------------------------------------------------------------------- /test_mqtt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Test program for MQTT version of QDSpy 5 | 6 | Copyright (c) 2024-2025 Thomas Euler 7 | All rights reserved. 8 | 9 | 2024-08-03 - Initial version 10 | 2025-03-30 - Use `QDSpy.ini` 11 | """ 12 | # --------------------------------------------------------------------- 13 | __author__ = "code@eulerlab.de" 14 | 15 | import platform 16 | import time 17 | import sys 18 | import Libraries.mqtt_client as mqtt 19 | import Libraries.mqtt_globals as mgl 20 | import QDSpy_config as cfg 21 | 22 | PLATFORM_WINDOWS = platform.system() == "Windows" 23 | if not PLATFORM_WINDOWS: 24 | WindowsError = FileNotFoundError 25 | 26 | # --------------------------------------------------------------------- 27 | def handleMsg(_msg) -> None: 28 | pass 29 | 30 | # --------------------------------------------------------------------- 31 | if __name__ == "__main__": 32 | if len(sys.argv) == 1: 33 | # If no command line argument, exit 34 | print("No arguments") 35 | sys.exit() 36 | 37 | # Load configuration, if any 38 | UUID = mgl.UUID 39 | broker = mgl.broker_address 40 | Conf = cfg.Config() 41 | if Conf.isLoaded: 42 | UUID = Conf.UUID 43 | broker = Conf.broker_address 44 | 45 | # Initialize 46 | iMsg = 0 47 | mqtt.Client.handler = handleMsg 48 | mqtt.Client.connect(_broker=broker, _ID=UUID, _isServ=False) 49 | mqtt.Client.start() 50 | 51 | # Convert command line arguments 52 | arg = sys.argv[1].split(",") 53 | msg = f"{arg[0]},{iMsg}" 54 | if len(arg[1:]) > 0: 55 | msg = msg +"," +",".join(arg[1:]) 56 | mqtt.Client.publish(msg, _doWait=True) 57 | 58 | tDone = time.time() +1.0 59 | while tDone > time.time(): 60 | time.sleep(0.02) 61 | 62 | mqtt.Client.stop() 63 | 64 | # --------------------------------------------------------------------- 65 | --------------------------------------------------------------------------------