Skip to content

Instantly share code, notes, and snippets.

@cortical-iv
Last active February 19, 2020 20:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cortical-iv/3a6d0b89ab52d5de69c4a4e2edba0b37 to your computer and use it in GitHub Desktop.
Save cortical-iv/3a6d0b89ab52d5de69c4a4e2edba0b37 to your computer and use it in GitHub Desktop.
dual card acting funny
import numpy as np
from direct.showbase.ShowBase import ShowBase
from panda3d.core import Texture, CardMaker, TextureStage
from panda3d.core import WindowProperties, ColorBlendAttrib, TransformState
from direct.showbase import ShowBaseGlobal
def sin_byte(X, freq = 1):
"""
Creates unsigned 8 bit representation of sin (T_unsigned_Byte).
"""
sin_float = np.sin(freq*X)
sin_transformed = (sin_float + 1)*127.5; #from 0-255
return np.uint8(sin_transformed)
class SinRgbTex:
"""
RGB sinusoidal grating tex class, used by ShowBase.
"""
def __init__(self, texture_size = 512, texture_name = "sin_rgb",
spatial_frequency = 10, rgb = (255, 0, 0)):
self.frequency = spatial_frequency
self.rgb = rgb
self.texture_size = texture_size
self.texture_name = texture_name
self.texture_array = self.create_texture()
self.texture = Texture(self.texture_name)
self.texture.setup2dTexture(self.texture_size, self.texture_size,
Texture.T_unsigned_byte,
Texture.F_rgb8)
self.texture.setRamImageAs(self.texture_array, "RGB")
def create_texture(self):
"""
Sinusoid that goes from black to the given rgb value.
"""
if not (all([x >= 0 for x in self.rgb]) and all([x <= 255 for x in self.rgb])):
raise ValueError("SinRgbTex.sin_texture_rgb(): rgb values must lie in [0,255]")
x = np.linspace(0, 2*np.pi, self.texture_size+1)
y = np.linspace(0, 2*np.pi, self.texture_size+1)
array, Y = np.meshgrid(x[: self.texture_size],y[: self.texture_size])
R = np.uint8((self.rgb[0]/255)*sin_byte(array, freq = self.frequency))
G = np.uint8((self.rgb[1]/255)*sin_byte(array, freq = self.frequency))
B = np.uint8((self.rgb[2]/255)*sin_byte(array, freq = self.frequency))
rgb_sin = np.zeros((self.texture_size, self.texture_size, 3), dtype = np.uint8)
rgb_sin[...,0] = R
rgb_sin[...,1] = G
rgb_sin[...,2] = B
return rgb_sin
class KeyboardToggleStim(ShowBase):
"""
toggles between different stim based on keyboard inputs (0/1)
Useful for testing things out quickly before pushing to the ClostLoopStim classes.
"""
def __init__(self, stim_classes, stim_params):
super().__init__()
self.stim_classes = stim_classes
self.current_stim_num = 0
self.stim_params = stim_params
self.window_size = 512
self.bgcolor = (0.5, 0.5, 0.5, 1)
self.stimulus_initialized = False #to handle case from -1 (uninitalize) to 0 (first stim)
self.scale = np.sqrt(8)
#Window properties
self.windowProps = WindowProperties()
self.windowProps.setSize(self.window_size, self.window_size)
ShowBaseGlobal.base.win.requestProperties(self.windowProps) #base is a panda3d global
#Set initial texture
self.set_stimulus(str(self.current_stim_num))
# Set up event handlers and task managers
self.accept('0', self.set_stimulus, ['0']) #event handler
self.accept('1', self.set_stimulus, ['1'])
self.taskMgr.add(self.move_texture_task, "move_textures") #task
@property
def current_stim_params(self):
"""
returns actual value of current stimulus
"""
return self.stim_params[self.current_stim_num]
def set_stimulus(self, data):
"""
Called with relevant keyboard events
"""
if not self.stimulus_initialized:
self.stimulus_initialized = True
else:
if self.current_stim_params['type'] == 'single':
self.card.clearTexture(self.texture_stage) #turn off stage
else:
self.left_card.clearTexture(self.left_texture_stage) #turn off stage
self.left_card.clearTexture(self.left_mask_stage)
self.right_card.clearTexture(self.right_texture_stage)
self.right_card.clearTexture(self.right_mask_stage)
if data == '0':
self.current_stim_num = 0
elif data == '1':
self.current_stim_num = 1
print(self.current_stim_num, self.current_stim_params)
self.stim = self.stim_classes[self.current_stim_num]
if self.current_stim_params['type'] == 'single':
#Create scenegraph
cm = CardMaker('card')
cm.setFrameFullscreenQuad()
self.card = self.aspect2d.attachNewNode(cm.generate())
self.card.setScale(np.sqrt(8))
self.texture_stage = TextureStage("texture_stage")
self.card.setColor((1, 1, 1, 1))
self.card.setTexture(self.texture_stage, self.stim.texture)
self.card.setR(self.current_stim_params['angle'])
else:
# Following creates two cards (left/right) and two textures for each
# (stimulus and mask)
self.create_dual_stim()
return
def move_texture_task(self, task):
"""
The stimulus (texture) is set: now move it if needed.
"""
if self.current_stim_params['type'] == 'single':
new_position = -task.time*self.current_stim_params['velocity']
self.card.setTexPos(self.texture_stage, new_position, 0, 0) #u, v, w
else:
left_tex_position = -task.time*self.current_stim_params['velocities'][0] #negative b/c texture stage
right_tex_position = -task.time*self.current_stim_params['velocities'][1]
self.left_card.setTexPos(self.left_texture_stage, left_tex_position, 0, 0)
self.right_card.setTexPos(self.right_texture_stage, right_tex_position, 0, 0)
return task.cont
def create_dual_stim(self):
"""
This creates two textures that each cover half the card, and move at
artibrary velocities.
"""
#CREATE MASK ARRAYS
self.left_mask_array = 255*np.ones((self.stim.texture_size, self.stim.texture_size), dtype=np.uint8)
self.left_mask_array[:, self.stim.texture_size//2 - 3 :] = 0
self.right_mask_array = 255*np.ones((self.stim.texture_size, self.stim.texture_size), dtype=np.uint8)
self.right_mask_array[:, : self.stim.texture_size//2 + 3] = 0
#TEXTURE STAGES FOR LEFT CARD
self.left_texture_stage = TextureStage('left_texture_stage')
#Mask
self.left_mask = Texture("left_mask_texture")
self.left_mask.setup2dTexture(self.stim.texture_size, self.stim.texture_size,
Texture.T_unsigned_byte, Texture.F_luminance)
self.left_mask.setRamImage(self.left_mask_array)
self.left_mask_stage = TextureStage('left_mask_array')
#Multiply the texture stages together
self.left_mask_stage.setCombineRgb(TextureStage.CMModulate,
TextureStage.CSTexture,
TextureStage.COSrcColor,
TextureStage.CSPrevious,
TextureStage.COSrcColor)
#TEXTURE STAGES FOR RIGHT CARD
self.right_texture_stage = TextureStage('right_texture_stage')
#Mask
self.right_mask = Texture("right_mask_texture")
self.right_mask.setup2dTexture(self.stim.texture_size, self.stim.texture_size,
Texture.T_unsigned_byte, Texture.F_luminance)
self.right_mask.setRamImage(self.right_mask_array)
self.right_mask_stage = TextureStage('right_mask_stage')
#Multiply the texture stages together
self.right_mask_stage.setCombineRgb(TextureStage.CMModulate,
TextureStage.CSTexture,
TextureStage.COSrcColor,
TextureStage.CSPrevious,
TextureStage.COSrcColor)
#CREATE CARDS/SCENEGRAPH
cm = CardMaker('stimcard')
cm.setFrameFullscreenQuad()
self.setBackgroundColor((0,0,0,1))
self.left_card = self.aspect2d.attachNewNode(cm.generate())
self.right_card = self.aspect2d.attachNewNode(cm.generate())
self.left_card.setAttrib(ColorBlendAttrib.make(ColorBlendAttrib.M_add))
self.right_card.setAttrib(ColorBlendAttrib.make(ColorBlendAttrib.M_add))
#ADD TEXTURE STAGES TO CARDS
self.left_card.setTexture(self.left_texture_stage, self.stim.texture)
self.left_card.setTexture(self.left_mask_stage, self.left_mask)
self.right_card.setTexture(self.right_texture_stage, self.stim.texture)
self.right_card.setTexture(self.right_mask_stage, self.right_mask)
#TRANSFORMS
#Masks
self.mask_transform = self.trs_transform()
self.left_card.setTexTransform(self.left_mask_stage, self.mask_transform)
self.right_card.setTexTransform(self.right_mask_stage, self.mask_transform)
#Left texture
self.left_card.setTexScale(self.left_texture_stage, 1/self.scale)
self.left_card.setTexRotate(self.left_texture_stage, self.current_stim_params['angle'])
#Right texture
self.right_card.setTexScale(self.right_texture_stage, 1/self.scale)
self.right_card.setTexRotate(self.right_texture_stage, self.current_stim_params['angle'])
def trs_transform(self):
"""
trs = translate rotate scale transform for mask stage
rdb-inspired code
"""
pos = 0.5, 0.5
center_shift = TransformState.make_pos2d((-pos[0], -pos[1]))
scale = TransformState.make_scale2d(1/self.scale)
rotate = TransformState.make_rotate2d(self.current_stim_params['angle'])
translate = TransformState.make_pos2d((0.5, 0.5))
return translate.compose(rotate.compose(scale.compose(center_shift)))
#%
if __name__ == '__main__':
stim1 = SinRgbTex(rgb = (50, 255, 255))
stim2 = SinRgbTex()
stim_classes = [stim1, stim2]
stim_params = [{'type': 'single', 'angle': 45, 'velocity': 0.1},
{'type': 'double', 'angle': -45, 'velocities': (-0.05, 0.05)}]
toggle_show = KeyboardToggleStim(stim_classes, stim_params)
toggle_show.run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment