using psychopy ver 1.81.03 on a mac I want to draw a polygon (e.g. a triangle) on top of an image.
So far, my image stays always on top and thus hides the polygon, no matter the order I put them in. This also stays true if I have the polygon start a frame later than the image.
e.g. see inn the code below (created with the Builder before compiling) how both a blue square and a red triangle are supposed to start at frame 0, but when you run it the blue square always covers the red triangle!?
Is there a way to have the polygon on top? Do I somehow need to merge the image and polygon before drawing them?
Thank you so much for your help!!
Sebastian
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
This experiment was created using PsychoPy2 Experiment Builder (v1.81.03), Sun Jan 18 20:44:26 2015
If you publish work using this script please cite the relevant PsychoPy publications
Peirce, JW (2007) PsychoPy - Psychophysics software in Python. Journal of Neuroscience Methods, 162(1-2), 8-13.
Peirce, JW (2009) Generating stimuli for neuroscience using PsychoPy. Frontiers in Neuroinformatics, 2:10. doi: 10.3389/neuro.11.010.2008
"""
from __future__ import division # so that 1/3=0.333 instead of 1/3=0
from psychopy import visual, core, data, event, logging, sound, gui
from psychopy.constants import * # things like STARTED, FINISHED
import numpy as np # whole numpy lib is available, prepend 'np.'
from numpy import sin, cos, tan, log, log10, pi, average, sqrt, std, deg2rad, rad2deg, linspace, asarray
from numpy.random import random, randint, normal, shuffle
import os # handy system and path functions
# Ensure that relative paths start from the same directory as this script
_thisDir = os.path.dirname(os.path.abspath(__file__))
os.chdir(_thisDir)
# Store info about the experiment session
expName = u'test_triangle_over_square' # from the Builder filename that created this script
expInfo = {'participant':'', 'session':'001'}
dlg = gui.DlgFromDict(dictionary=expInfo, title=expName)
if dlg.OK == False: core.quit() # user pressed cancel
expInfo['date'] = data.getDateStr() # add a simple timestamp
expInfo['expName'] = expName
# Data file name stem = absolute path + name; later add .psyexp, .csv, .log, etc
filename = _thisDir + os.sep + 'data/%s_%s_%s' %(expInfo['participant'], expName, expInfo['date'])
# An ExperimentHandler isn't essential but helps with data saving
thisExp = data.ExperimentHandler(name=expName, version='',
extraInfo=expInfo, runtimeInfo=None,
originPath=None,
savePickle=True, saveWideText=True,
dataFileName=filename)
#save a log file for detail verbose info
logFile = logging.LogFile(filename+'.log', level=logging.EXP)
logging.console.setLevel(logging.WARNING) # this outputs to the screen, not a file
endExpNow = False # flag for 'escape' or other condition => quit the exp
# Start Code - component code to be run before the window creation
# Setup the Window
win = visual.Window(size=(1280, 800), fullscr=True, screen=0, allowGUI=False, allowStencil=False,
monitor='testMonitor', color=[0,0,0], colorSpace='rgb',
blendMode='avg', useFBO=True,
)
# store frame rate of monitor if we can measure it successfully
expInfo['frameRate']=win.getActualFrameRate()
if expInfo['frameRate']!=None:
frameDur = 1.0/round(expInfo['frameRate'])
else:
frameDur = 1.0/60.0 # couldn't get a reliable measure so guess
# Initialize components for Routine "trial"
trialClock = core.Clock()
ISI = core.StaticPeriod(win=win, screenHz=expInfo['frameRate'], name='ISI')
square = visual.ImageStim(win=win, name='square',units='pix',
image=None, mask=None,
ori=0, pos=[0, 0], size=[200, 200],
color=u'blue', colorSpace='rgb', opacity=1,
flipHoriz=False, flipVert=False,
texRes=128, interpolate=True, depth=-1.0)
polygon = visual.ShapeStim(win=win, name='polygon',units='pix',
vertices = [[-[200, 300][0]/2.0,-[200, 300][1]/2.0], [+[200, 300][0]/2.0,-[200, 300][1]/2.0], [0,[200, 300][1]/2.0]],
ori=0, pos=[0, 0],
lineWidth=1, lineColor=[1,1,1], lineColorSpace='rgb',
fillColor=u'red', fillColorSpace='rgb',
opacity=1,interpolate=True)
# Create some handy timers
globalClock = core.Clock() # to track the time since experiment started
routineTimer = core.CountdownTimer() # to track time remaining of each (non-slip) routine
#------Prepare to start Routine "trial"-------
t = 0
trialClock.reset() # clock
frameN = -1
# update component parameters for each repeat
# keep track of which components have finished
trialComponents = []
trialComponents.append(ISI)
trialComponents.append(square)
trialComponents.append(polygon)
for thisComponent in trialComponents:
if hasattr(thisComponent, 'status'):
thisComponent.status = NOT_STARTED
#-------Start Routine "trial"-------
continueRoutine = True
while continueRoutine:
# get current time
t = trialClock.getTime()
frameN = frameN + 1 # number of completed frames (so 0 is the first frame)
# update/draw components on each frame
# *square* updates
if frameN >= 0 and square.status == NOT_STARTED:
# keep track of start time/frame for later
square.tStart = t # underestimates by a little under one frame
square.frameNStart = frameN # exact frame index
square.setAutoDraw(True)
# *polygon* updates
if frameN >= 0 and polygon.status == NOT_STARTED:
# keep track of start time/frame for later
polygon.tStart = t # underestimates by a little under one frame
polygon.frameNStart = frameN # exact frame index
polygon.setAutoDraw(True)
# *ISI* period
if t >= 0.0 and ISI.status == NOT_STARTED:
# keep track of start time/frame for later
ISI.tStart = t # underestimates by a little under one frame
ISI.frameNStart = frameN # exact frame index
ISI.start(0.5)
elif ISI.status == STARTED: #one frame should pass before updating params and completing
ISI.complete() #finish the static period
# check if all components have finished
if not continueRoutine: # a component has requested a forced-end of Routine
routineTimer.reset() # if we abort early the non-slip timer needs reset
break
continueRoutine = False # will revert to True if at least one component still running
for thisComponent in trialComponents:
if hasattr(thisComponent, "status") and thisComponent.status != FINISHED:
continueRoutine = True
break # at least one component has not yet finished
# check for quit (the Esc key)
if endExpNow or event.getKeys(keyList=["escape"]):
core.quit()
# refresh the screen
if continueRoutine: # don't flip if this routine is over or we'll get a blank screen
win.flip()
else: # this Routine was not non-slip safe so reset non-slip timer
routineTimer.reset()
#-------Ending Routine "trial"-------
for thisComponent in trialComponents:
if hasattr(thisComponent, "setAutoDraw"):
thisComponent.setAutoDraw(False)
win.close()
core.quit()
As per Jonas' comment above, PsychoPy uses a layering system in which subsequent stimuli are drawn on top of previous stimuli (as in his code examples).
In the graphical Builder environment, drawing order is represented by the vertical order of stimulus components: stimuli at the top are drawn first, and ones lower down are progressively layered upon them.
You can change the order of stimulus components by right-clicking on them and selecting "Move up", "move down", etc as required.
Sebastian, has, however, identified a bug here, in that the intended drawing order is not honoured between ImageStim and ShapeStim components. As a work-around, you might be able to replace your ShapeStim with a bitmap representation, displayed using an ImageStim. Multiple ImageStims should draw correctly (as do multiple ShapeStims). To get it to draw correctly on top of another image, be sure to save it as a .png file, which supports transparency. That way, only the actual shape will be drawn on top, as its background pixels can be set to be transparent and will not mask the the underlying image.
For a long-term solution, I've added your issue as a bug report to the PsychoPy GitHub project here:
https://github.com/psychopy/psychopy/issues/795
It turned out to be a bug in the Polygon component in Builder.
This is fixed in the upcoming release (1.82.00). The changes needed to make the fix can be seen at
https://github.com/psychopy/psychopy/commit/af1af9a7a85cee9b4ec8ad5e2ff1f03140bd1a36
which you can add to your own installation if you like.
cheers,
Jon
Related
I'm working on thermal simulations in Abaqus. I only need to animate the even frame numbers, so instead of the animation going 1,2,3,4,5 I need it to go 2,4,6,8,10.
Is there a way to only show the even frames? If so, how?
Go to Result --> Active Steps/Frames and deselect the frames that you don't want to be displayed and animated.
You can easily do this using Abaqus Python script.
Following is the overview of steps:
# getting current viewport object
vpName = session.currentViewportName
viewport = session.viewports[vpName]
# get odb object from viewport
odb= viewport.displayedObject
# Get all the steps available in the odb
stepNames = odb.steps.keys()
# Create animation object
ani = session.ImageAnimation(fileName='animation' ,format=AVI)
# add required frame to the animation object
for stepName in stepNames:
stpID = odb.steps[stepName].number - 1
nfrm = len(odb.steps[stp].frames)
for frmID in range(0, nfrm, 2): # 2 --> even frames will be added
viewport.odbDisplay.setFrame(step=stpID,frame=frmID)
ani.writeFrame(canvasObjects=(viewport, ))
ani.close()
I've written a tkinter script with animation, that works fine on Xubuntu, but when I run it on Mac, the animation doesn't work. Here's a little script that demonstrates the problem:
import tkinter as tk
from time import sleep
root = tk.Tk()
canvas = tk.Canvas(root, height=200, width = 200)
canvas.pack()
this = canvas.create_rectangle(25,25, 75, 75, fill='blue')
that = canvas.create_rectangle(125, 125, 175, 175, fill = 'red')
def blink(event):
global this, that
for _ in range(9):
canvas.itemconfigure(this, fill='red')
canvas.itemconfigure(that, fill = 'blue')
canvas.update_idletasks()
sleep(.4)
this, that = that, this
canvas.bind('<ButtonRelease-1>', blink)
root.mainloop()
This draws a red square and a blue square on a canvas. When the user clicks the canvas, the squares repeatedly switch colors. On Xubuntu, it works as intended.
On Mac, when I click the canvas, I get spinning beach ball, and after a few seconds, we see that squares have switched colors, because they switch colors an odd number of times in the code.
It seems to me that update_idletasks isn't working. Is there some way to fix this? I am running python 3.9.5 with Tk 8.6 on Big Sur.
I think what you can do is avoid tasks that will block the mainloop, in this case time.sleep(). So your code can be remade by emulating a for loop with after, and I see nothing that stops this general code from running OS independent:
count = 0 # Think of this as the `_` in for _ in range(9)
def blink(event=None):
global this, that, count
if count < 9: # Basically repeats everytime `count` is less than 9, like a for loop
canvas.itemconfigure(this, fill='red')
canvas.itemconfigure(that, fill='blue')
this, that = that, this
count += 1 # Increase count
root.after(400,blink) # Repeat this code block every 400 ms or 0.4 seconds
else:
count = 0 # After it is 9, set it to 0 for the next click to be processed
I found that using update instead of update_idletasks works on both platforms. It's my understanding though, that the latter is much preferred. See the accepted answer to this question for example. This solves my immediate problem, but does anyone know if update_idletasks ever works on the Mac?
I have this simple game i made in Godot Game Engine, and i have implemented some animations in my game's main character.
1. Run
This is a simple run animation i've added, which is played when the character is moving
2. Idle
This animation is more like a single image which is played when the character is not moving
3. Attack
This animation is played when user presses Left Mouse Button.
I am having my issue in attack animation, when i press Left Mouse Button my Animation doesn't play, instead i get first frame of the animation and then character goes back to idle Animation.
This is how it looks like:
This is my Character's Code:
extends KinematicBody2D
var _inputVec = Vector2.ZERO
var VELOCITY = Vector2.ZERO
var LAST_INPUT = Vector2.ZERO
const MAX_SPEED = 70
const ACCELERATION = 500
const FRICTION = 500
onready var animationPlayer = $AnimationPlayer
func _ready():
print("game started!")
func _physics_process(delta):
_inputVec.x = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
_inputVec.y = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up")
_inputVec = _inputVec.normalized()
if _inputVec != Vector2.ZERO:
if _inputVec.x > 0:
animationPlayer.play("playerRunRight")
elif _inputVec.y < 0:
animationPlayer.play("playerRunUp")
elif _inputVec.y > 0:
animationPlayer.play("playerRunDown")
else:
animationPlayer.play("playerRunLeft")
VELOCITY = VELOCITY.move_toward(_inputVec * MAX_SPEED, ACCELERATION * delta)
LAST_INPUT = _inputVec
else:
VELOCITY = VELOCITY.move_toward(Vector2.ZERO, FRICTION * delta)
if Input.is_action_just_pressed("ui_lmb"):
if LAST_INPUT.x > 0:
animationPlayer.play("playerAttackRight")
elif LAST_INPUT.y < 0:
animationPlayer.play("playerAttackUp")
elif LAST_INPUT.y > 0:
animationPlayer.play("playerAttackDown")
else:
animationPlayer.play("playerAttackLeft")
else:
if LAST_INPUT.x > 0:
animationPlayer.play("playerIdleRight")
elif LAST_INPUT.y < 0:
animationPlayer.play("playerIdleUp")
elif LAST_INPUT.y > 0:
animationPlayer.play("playerIdleDown")
else:
animationPlayer.play("playerIdleLeft")
VELOCITY = move_and_slide(VELOCITY)
Full Project is Available at my Github Repo
Remember that _physics_process runs once per (physics) frame.
So, one frame you pressed the left mouse button, and this line got to execute:
animationPlayer.play("playerAttackRight")
But next (physics) frame, you had not just pressed the left mouse button, so this conditional is false:
if Input.is_action_just_pressed("ui_lmb"):
And then this line get to execute:
animationPlayer.play("playerIdleRight")
As a result, you only see about one frame of the "playerAttackRight" animation.
You are going to need to keep track of the current state (running, attacking, idle). The rule is that you can change from running to idle immediately, but you can only change from attacking to idle, when the attack animation ends.
You can keep track of the current state with a variable, of course. You can take input and the value of the state of the variable to decide the new state. Then separately read the state variable and decide which animation to play. You may also want to set the state variable when some animations end.
And to do something when an animation ends, you can either resource to yield:
yield(animationPlayer, "animation_finished")
Which will have your code resume after it receives the "animation_finished" signal.
Or, otherwise you can connect to the "animation_finished" signal.
By the way, you can also queue animations:
animationPlayer.queue("name_of_some_animation")
While using AnimationPlayer like you do is OK. When it gets complex, there is another tool you should consider: AnimationTree.
Create an AnimationTree node, give it your animation player, and set the root to a new AnimationNodeStateMachine. There you can create your state machine, and configure if the transition between them is immediate or at the end.
Then, from code, you can get the state machine playback object, like this:
var state_machine = $AnimationTree.get("parameters/playback")
You can ask it what the current state is with:
var state:String = state_machine.get_current_node()
Which you can use as part of the decision of which state you want to go to. And then tell it you want it to go to a different state, like this:
state_machine.travel("name_of_some_state")
And using that it will respect the transitions you set, so you do not have to worry about it in code.
You can find more information about using AnimationTree at:
Using AnimationTree
Controlling Animation States
I’m trying to animate a Mayavi pipeline volume:
src = mlab.pipeline.volume(mlab.pipeline.scalar_field(data),vmin=.1*np.max(data),vmax=.2*np.max(data))
that is combined in the pipeline by another dataset represented as a cut plane.
However, I can’t get the volume visualization to update - only the first frame shows up. The animation is stepping through the data correctly (I get different values of the np.max(data[t]) below) but nothing in the visualization changes.
My understanding is that mlab_source_set should re-render correctly, and there’s nothing on the web anywhere that describes this (as far as I can tell).
The animation looks like:
#mlab.show
#mlab.animate(delay=250,ui=True)
def anim(src,data,tax,fig):
"""Animate."""
t = 0
nt = len(tax)
while 1:
vmin = .1*np.max(data[t])
vmax = .2*np.max(data[t])
print 'animation t = ',tax[t],', max = ',np.max(data[t])
src.mlab_source.set(scalar = mlab.pipeline.scalar_field(data[t]), vmin=vmin,vmax=vmax)
t = mod(t+1,nt)
yield
Any thoughts?
Long ago, I got a quick glance at some code (PERL... urp) one of my managers was writing and was stunned by the banner/heading system he used in his code. I didn't get a chance to delve into his code in detail, but just from the banner comments on the screen I could easily tell what the code was meant to do, even from far away.
Unfortunately that was long ago and our conversation at the time did not lend itself to me saying, "Yeah, well forget about our dot-com startup going down the toilet, can I take notes on your coding style?"
Years later, I've not yet achieved a high-level comment style that has the clarity as the (probably now mythical) code I saw that day.
When I say, "banners", I'm referring to the high-level block divisions/headers many coders use to create higher-level devisions in their code. They're usually made up of simple ASCII dashes, slashes, equal signs, etc. In my current daily-use language, one code banner/header hierarchy might be:
# ========================================================
# = Header 1
# ========================================================
# --------------------------------------------------------
# - Header 2
#---------------------------------------------------------
# == Header 3 ============================================
# -- Header 4 --------------------------------------------
# Header 5
and all the usual variations.
Although my searches have turned up nothing noteworthy, surely somewhere on the web someone's attempted to collect examples of these and present them in a systematic manner?
Can someone point to banner comment style "systems" they've found useful? I'm not thinking of "Oh, I like the ones using asterisks", but more of an overall strategy of styles that makes high-level code construction quick and easy to understand as well as easy on the eyes? Picking a preferred system from examples would be easier than comparing descriptions, obviously.
Note: I'm not interested in the contents of the comments per se, but the "flair" used on the comments to provide a clear indications of overall code content and organization.
One of the best ways I have found of verifying the style of commenting is to use a code documentation tool such as doxygen, there is a list of others here, and then see what the output was like - the clearer the output the better the comments.
I would say that the single biggest points are consistency and a clear indication of ranking, followed by completeness and conciseness, i.e. Once you have looked at one you should know what the others will look like and how significant the one you are looking at is. This forces you to have a good design as without it you don't know how significant things are.
Next all the information you need should be present but it should be short enough to take in at a glance - however satisfying both these forces you to change your coding style so that objects/code is not too large, is well named, doesn't have too many parameters, etc., all the things that tools like lint try to teach us.
For python code the style summary at PEP-257 provides a lot of useful guidelines and some examples.
A quick look for some "Good" code on my machine turned up Andrea Gavana's Aquabutton.py I have included a section of the code below but you can see the full code here - I have to say that this was the first module of Andrea's that I opened, being early in the alphabet but I am sure any would have done.
# --------------------------------------------------------------------------------- #
# AQUABUTTON wxPython IMPLEMENTATION
#
# Andrea Gavana, # 07 October 2008
# Latest Revision: 24 Nov 2011, 22.00 GMT
#
#
# TODO List
#
# 1) Anything to do?
#
#
# For all kind of problems, requests of enhancements and bug reports, please
# write to me at:
#
# andrea.gavana#gmail.com
# andrea.gavana#maerskoil.com
#
# Or, obviously, to the wxPython mailing list!!!
#
#
# End Of Comments
# --------------------------------------------------------------------------------- #
"""
:class:`AquaButton` is another custom-drawn button class which *approximatively* mimics
the behaviour of Aqua buttons on the Mac.
Description
===========
:class:`AquaButton` is another custom-drawn button class which *approximatively* mimics
the behaviour of Aqua buttons on the Mac. At the moment this class supports:
* Bubble and shadow effects;
* Customizable background, foreground and hover colours;
* Rounded-corners buttons;
* Text-only or image+text buttons;
* Pulse effect on gaining focus.
And a lot more. Check the demo for an almost complete review of the functionalities.
Usage
=====
Sample usage::
import wx
import wx.lib.agw.aquabutton as AB
app = wx.App(0)
frame = wx.Frame(None, -1, "AquaButton Test")
mainPanel = wx.Panel(frame)
mainPanel.SetBackgroundColour(wx.WHITE)
# Initialize AquaButton 1 (with image)
bitmap = wx.Bitmap("my_button_bitmap.png", wx.BITMAP_TYPE_PNG)
btn1 = AB.AquaButton(mainPanel, -1, bitmap, "AquaButton")
# Initialize AquaButton 2 (no image)
btn2 = AB.AquaButton(mainPanel, -1, None, "Hello World!")
frame.Show()
app.MainLoop()
Supported Platforms
===================
AquaButton has been tested on the following platforms:
* Windows (Windows XP);
* Linux Ubuntu (10.10).
Window Styles
=============
`No particular window styles are available for this class.`
Events Processing
=================
This class processes the following events:
================= ==================================================
Event Name Description
================= ==================================================
``wx.EVT_BUTTON`` Process a `wxEVT_COMMAND_BUTTON_CLICKED` event, when the button is clicked.
================= ==================================================
License And Version
===================
:class:`AquaButton` control is distributed under the wxPython license.
Latest Revision: Andrea Gavana # 22 Nov 2011, 22.00 GMT
Version 0.4
"""
import wx
# Constants for the hovering and clicking effects
HOVER = 1
""" Indicates that the mouse is hovering over :class:`AquaButton` """
CLICK = 2
""" Indicates that :class:`AquaButton` has been clicked """
class AquaButtonEvent(wx.PyCommandEvent):
""" Event sent from the :class:`AquaButton` buttons when the button is activated. """
def __init__(self, eventType, eventId):
"""
Default class constructor.
:param integer `eventType`: the event type;
:param integer `eventId`: the event identifier.
"""
wx.PyCommandEvent.__init__(self, eventType, eventId)
self.isDown = False
self.theButton = None
def SetButtonObj(self, btn):
"""
Sets the event object for the event.
:param `btn`: the button object, an instance of :class:`AquaButton`.
"""
self.theButton = btn
snip
class AquaButton(wx.PyControl):
""" This is the main class implementation of :class:`AquaButton`. """
def __init__(self, parent, id=wx.ID_ANY, bitmap=None, label="", pos=wx.DefaultPosition,
size=wx.DefaultSize, style=wx.NO_BORDER, validator=wx.DefaultValidator,
name="aquabutton"):
"""
Default class constructor.
:param Window `parent`: parent window. Must not be ``None``;
:param integer `id`: window identifier. A value of -1 indicates a default value;
:param Bitmap `bitmap`: the button bitmap (if any);
:param string `label`: the button text label;
:param `pos`: the control position. A value of (-1, -1) indicates a default position,
chosen by either the windowing system or wxPython, depending on platform;
:type `pos`: tuple or :class:`Point`
:param `size`: the control size. A value of (-1, -1) indicates a default size,
chosen by either the windowing system or wxPython, depending on platform;
:type `size`: tuple or :class:`Size`
:param integer `style`: the button style (unused);
:param Validator `validator`: the validator associated to the button;
:param string `name`: the button name.
"""
wx.PyControl.__init__(self, parent, id, pos, size, style, validator, name)
self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_ERASE_BACKGROUND, lambda event: None)
self.Bind(wx.EVT_SIZE, self.OnSize)
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave)
self.Bind(wx.EVT_ENTER_WINDOW, self.OnMouseEnter)
self.Bind(wx.EVT_SET_FOCUS, self.OnGainFocus)
self.Bind(wx.EVT_KILL_FOCUS, self.OnLoseFocus)
self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
self.Bind(wx.EVT_TIMER, self.OnPulseTimer)
if "__WXMSW__" in wx.PlatformInfo:
self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown)
self._mouseAction = None
self.SetBitmapLabel(bitmap)
self._hasFocus = False
self._saveBitmap = True
self._storedBitmap = wx.NullBitmap
self._pulseOnFocus = False
self._gammaFactor = 1.0
self._gammaIncrement = 0.1
self._timer = wx.Timer(self, wx.ID_ANY)
self.SetLabel(label)
self.InheritAttributes()
self.SetInitialSize(size)
# The following defaults are better suited to draw the text outline
if "__WXMAC__" in wx.PlatformInfo:
self._backColour = wx.Colour(147, 202, 255)
self._hoverColour = self.LightColour(self._backColour, 30)
self._disableColour = self.LightColour(self._backColour, 70)
self._textColour = wx.BLACK
else:
self._backColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION)
self._hoverColour = self.LightColour(self._backColour, 30)
self._disableColour = self.LightColour(self._backColour, 70)
self._textColour = wx.WHITE
def SetBitmapLabel(self, bitmap):
"""
Sets the bitmap label for the button.
:param `bitmap`: the bitmap label to set, an instance of :class:`Bitmap`.
"""
self._bitmap = bitmap
self.Refresh()