How to add decorator that allows to run a progress bar before running render function? - python-decorators

I am making a python code that creates a hydro dynamic simulation of the galaxy using swiftsmio. I want to add a progress bar that is able to show the how much time is left until data is fully plotted into an image. I want to use a decorator function so the interpreter knows I want to first run the progress bar before the plot function.
Here is the plot function that I want to add the progress bar decorator to:
def plot(self):
self.render(DataSetName="masses")
fig, axes = plt.subplots(1)
axes.imshow(self.map, norm=LogNorm())
axes.set_title("Mass Map", fontsize=10)
plt.show()
For the progress bar I am using psutil. However, I am not sure whether the progress bar function works.
Here is progress bar function:
import time
import psutil
def progress_bar(duration):
start_time = time.time()
while True:
elapsed_time = time.time() - start_time
# Calculate the CPU usage
cpu_usage = psutil.cpu_percent()
# Calculate the progress as a percentage of the duration
progress = (elapsed_time / duration) * 100
# Check if the progress bar has reached the end
if elapsed_time > duration:
break
# Sleep for 0.1 seconds to allow the CPU usage to be updated
time.sleep(0.1)
# Run the progress bar for 10 seconds
progress_bar(10)
For further information I am using oop. I have all my different maps in different classes which are inherting the render method form the map superclass.
I tried putting the progress bar function inside the decorator function, but the image would be plotted first and the progress bar did not run.
def progress_bar(duration):
def wrapper(*args, **kwargs):
start_time = time.time()
while True:
elapsed_time = time.time() - start_time
# Calculate the CPU usage
cpu_usage = psutil.cpu_percent()
# Calculate the progress as a percentage of the duration
progress = (elapsed_time / duration) * 100
# Check if the progress bar has reached the end
if elapsed_time > duration:
break
# Sleep for 0.1 seconds to allow the CPU usage to be updated
time.sleep(0.1)
return duration(*args, **kwargs)
return wrapper
I am not sure how to implemet decorators into code, so this is probably very wrong.

Related

Some PyQt5 Checkboxes are unclickable after being moved

I'm creating a to-do list where a checkbox, textbox (QLineEdit), and QComboBox (to set Priority 1, 2, etc.) are added each time the "Add Task" button is clicked. When the checkbox for each corresponding task is clicked, the task would move to the bottom of the list of tasks. Conversely, if a priority is set, then the task is moved to the top of the list of tasks.
The problem is that in certain circumstances, when I click on the checkbox, nothing happens. The signal that identifies that the checkbox state has changed is never sent. It's almost like the checkbox isn't there, even though it (or at least, the image of it) is. This problem happens if none of the priorities are set with unchecking—checking any given task works, but certain tasks cannot be unchecked. Other tasks are able to be unchecked, and sometimes if I go back to the task that couldn't be unchecked after unchecking something else, it'll be checkable again. If I were to set some tasks to have a priority and then go and try to check the checkboxes, some checkboxes won't be able to be checked at all. Again, if I were to check some other task and then go back to the task that wasn't able to be checked, it might be able to be checked this time around.
I think the error might be coming from moving the checkboxes... for some reason the functionality behind some of the checkboxes is lost going through the code. One notable thing is tabbing through the items in the GUI. Once items are moved, hitting tab will go through the items in the same order that it was originally set up in (so the cursor will essentially jump around the textboxes instead of going in order from top to bottom). Not sure if that has anything to do with the error; it's just something I've noticed. I'm don't know if this is an error within PyQt5 itself, or if it's a fixable error within the code.
import sys
from PyQt5.QtWidgets import (QWidget, QComboBox,
QPushButton, QApplication, QLabel, QLineEdit, QTextEdit, QMainWindow, QAction, QShortcut, QCheckBox)
from PyQt5.QtGui import (QKeySequence)
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtCore import pyqtSlot, QObject
class App(QMainWindow):
def __init__(self):
super().__init__()
self.i = 40
self.j = 80
self.counter = 1
self.list_eTasks = []
self.initUI()
def initUI(self):
#sets up window
self.setWindowTitle('To-Do List')
self.setGeometry(300, 300, 800, 855)
#button
self.btn1 = QPushButton("Add Task", self)
self.btn1.clicked.connect(self.add_task_button_clicked)
#close the window via keyboard shortcuts
self.exit = QShortcut(QKeySequence("Ctrl+W" or "Ctrl+Q"), self)
self.exit.activated.connect(self.close)
self.show()
#pyqtSlot()
def add_task_button_clicked(self):
self.label = QLabel(self)
self.label.setText(str(self.counter))
self.label.move(5, self.i)
self.btn1.move(50, self.j)
self.textbox = QLineEdit(self)
self.textbox.resize(280, 40)
self.checkbox = QCheckBox(self)
self.checkbox.stateChanged.connect(self.click_box)
self.combobox = QComboBox(self)
self.combobox.addItem("No Priority")
self.combobox.addItem("Priority 1")
self.combobox.addItem("Priority 2")
self.combobox.addItem("Priority 3")
self.combobox.activated[str].connect(self.combobox_changed)
self.obj = eTask(self.checkbox, self.textbox, self.combobox)
self.list_eTasks.append(self.obj)
self.textbox.show()
self.label.show()
self.checkbox.show()
self.combobox.show()
self.i += 40
self.j += 40
self.counter += 1
self.sort_eTasks()
def click_box(self, state):
self.sort_eTasks()
def move_everything(self, new_list):
count = 0
for item in new_list:
y_value = ((40)*(count + 1))
item.check.move(20, y_value)
item.text.move(50, y_value)
item.combo.move(350, y_value)
count += 1
return
def combobox_changed(self, state):
#make new list to not include any already checked off items
for task in self.list_eTasks:
if task.combo.currentText() == "Priority 1":
task.priority = 1
elif task.combo.currentText() == "Priority 2":
task.priority = 2
elif task.combo.currentText() == "Priority 3":
task.priority = 3
else:
task.priority = 10
self.sort_eTasks()
def sort_eTasks(self):
self.list_eTasks.sort(key=lambda eTask: eTask.getRank())
self.move_everything(self.list_eTasks)
for task in self.list_eTasks:
print(str(task.priority) + " pos: " + str(task.check.pos()))
print("-----------------------------------")
class eTask:
check = ""
text = ""
combo = ""
priority = 10
def __init__(self, c, t, co):
self.check = c
self.text = t
self.combo = co
def setP(self, p):
self.priority = p
def getRank(self):
if self.check.isChecked():
return self.priority + 100
else:
return self.priority
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
There are various problems with your code, I'll try to address all of them.
The sorting mechanism is not good: it only happens when an item changes the priority (while it should also apply whenever the user clicks the checkbox)
Using low values like those results in unexpected behavior: if I have a priority 1 checked its priority is augmented by 10, so why should it go under a priority 2 or even 3?
The sorting mechanism should be centralized, instead of being split into more functions that don't do anything else: "one function to rule them all" ;-) Don't check for the combobox contents and then sort basing on another external function call: just call a single function that checks for the combobox and the checkbox, then sort the contents; remember: while OOP is about modularity, you should avoid unnecessary fragmentation (remember the KISS principle);
You should probably not connect to the activated signal, and you should certainly not use the [str] overload (checking for a string is rarely a good idea for index based widgets); use currentIndex() and its currentIndexChanged() signal instead;
Using fixed geometries for child widgets is something that should be avoided as much as possible; layout managers exist for lots of very good reasons (for example, I couldn't click on the QLineEdit if the mouse cursor was too close to the check box, even if it's over the line edit); note that you should avoid using setGeometry for the top level window also;
You should never set instance attributes that are going to be overwritten by a different object: you're continuously overwriting them (self.label, self.textbox, etc.), and the result is that making them instance attributes is completely useless; since you're already creating them with a parent widget, the garbage collector won't delete them at the end of the function, so you should only use local variables instead (label = QLabel(), etc...);
Using a class like you did is not very useful: you're going to group all those widgets anyway, so it's better to use a container widget which not only will manage its children (see the point above about layouts), but will "centralize" all necessary programming logic;
Avoid similar names for class and variables if they refer to different object types: in your case, app refers to the QApplication instance, but App is a QMainWidget subclass;
Here's a possible reimplementation of your code:
from PyQt5 import QtCore, QtWidgets
class TaskWidget(QtWidgets.QWidget):
priorityChanged = QtCore.pyqtSignal()
lastChanged = QtCore.QDateTime.currentDateTime()
def __init__(self, title='', parent=None):
super().__init__(parent)
layout = QtWidgets.QHBoxLayout(self)
self.checkBox = QtWidgets.QCheckBox()
layout.addWidget(self.checkBox)
self.textBox = QtWidgets.QLineEdit(title)
layout.addWidget(self.textBox)
self.textBox.setMinimumWidth(240)
self.priorityCombo = QtWidgets.QComboBox()
layout.addWidget(self.priorityCombo)
self.priorityCombo.addItems([
'No priority',
'Priority 1',
'Priority 2',
'Priority 3',
])
self.priorityCombo.currentIndexChanged.connect(self.updatePriority)
self.checkBox.toggled.connect(self.updatePriority)
def updatePriority(self):
# update the "lastChanged" variable, ensuring that the sorting puts on
# top the last changed task
self.lastChanged = QtCore.QDateTime.currentDateTime()
self.priorityChanged.emit()
def priority(self):
priority = self.priorityCombo.currentIndex()
# set an arbitrary (but still reasonable) priority that would be fine for
# more items in the same priority
if priority > 0:
priority *= 100
else:
priority = self.priorityCombo.count() * 100
if self.checkBox.isChecked():
priority -= 1
return priority, self.lastChanged.msecsTo(QtCore.QDateTime.currentDateTime())
class TaskApp(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
central = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout(central)
self.setCentralWidget(central)
self.taskLayout = QtWidgets.QGridLayout()
layout.addLayout(self.taskLayout)
self.addTaskButton = QtWidgets.QPushButton('Add task')
layout.addWidget(self.addTaskButton)
self.addTaskButton.clicked.connect(self.addTask)
self.tasks = []
self.addTask()
def addTask(self):
task = TaskWidget('Task no. {}'.format(len(self.tasks) + 1))
if not self.taskLayout.count():
# this is for the first item only; we cannot use rowCount, since it
# always returns at least 1, even if it's empty
row = 0
else:
row = self.taskLayout.rowCount()
self.taskLayout.addWidget(QtWidgets.QLabel(str(row + 1)))
self.taskLayout.addWidget(task, row, 1)
self.tasks.append(task)
task.priorityChanged.connect(self.sortTasks)
def sortTasks(self):
tasks = self.tasks[:]
self.tasks.clear()
# since we're using a QGridLayout, we don't need to relate to
# insertWidget(), which is index based and might result in some
# inconsistencies while "regenerating" the UI; in this case, the
# grid will be the same, we're only going to rearrange the child widgets
for row, taskWidget in enumerate(sorted(tasks, key=lambda w: w.priority())):
self.taskLayout.addWidget(taskWidget, row, 1)
self.tasks.append(taskWidget)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = TaskApp()
w.show()
sys.exit(app.exec_())
I just guessed the priority implementation, but that's not the point.
I also added a datetime check, so that the last changed item with the same priority will always be on top.

Alternative methods of computing real-time event frequency

I'm working in an application where there is a lot of reporting of external events. One of the metrics that is used often is the event rate as a function of time. For example, measuring the sample-rate of some external asynchronous sensor.
Currently the way I'm calculating the frequency of events like this is to keep a queue of event timestamps. When the event occurs we push a current timestamp onto the queue, then pop until the oldest timestamp is less than a predefined age. Then, the event frequency is proportional to the size of the queue. In pseudo-code the method usually looks something like this:
def on_event():
var now = current_time()
time_queue.push(now)
while((now - time_queue.front()) > QUEUE_DEPTH_SECONDS):
time_queue.pop()
frequency = time_queue.size() / QUEUE_DEPTH_SECONDS
Now this approach is obviously not optimal:
Memory requirement and computation time is proportional to event rate.
The queue duration has to be manually adjusted based on the expected data rate in order to tune low-frequency performance vs memory requirements.
The response-time of the frequency measurement is also dependent on the queue duration. Longer durations lower the response time of the calculation.
The frequency is only updated when a new event occurs. If the events stop occurring, then the frequency measurement will remain at the value calculated when the last event was received.
I'm curious if there are any alternative algorithms that can be used to calculate the rate of an event, and what trade-offs they have in relation to computational complexity, space requirements, response-time, etc.
https://en.wikipedia.org/wiki/Exponential_smoothing is very efficient and uses only a small and bounded amount of memory. You could try exponential smoothing of the inter-arrival times. When retrieving the smoothed inter-arrival time you could look at the time to the last event, and mix that in if it is larger than the smoothed inter-arrival time.
This is different enough that I would in fact start by collecting a sample of timestamps in current use, so that I could use it to test the result of this or other schemes off-line.
One alternative is a local timer that fires at a constant rate (e.g. once per second):
When an external event occurs, it increments a counter.
When the local timer fires, the counter value is added to a queue, and the count is reset to zero.
Here's how the method compares to yours:
Memory requirement and computation time is independent of the external event rate. It is determined by the local timer rate, which you control.
The queue size depends on how much averaging you want to do. A queue size of 1 (i.e. no queue) with a timer rate of once per second results in a raw events-per-second reading, with no averaging. The larger the queue, the more averaging you get.
The response time is determined by the amount of averaging desired. More averaging results in slower response times.
The frequency is updated at the rate that the local timer fires, regardless of whether external events occur.
I've implemented something similar to calculate the rate/concentration of particles in an air stream. They come at random events (probably poisson distributed) and I want to know the average rate in particles per time. My approach is as follows (taken from the docstring of my code):
Event timestamps are placed in a buffer of a fixed maximum size.
Whenever a concentration estimate is requested, all timestamps up to
a set maximum age are filtered out. If the remaining number of
recent timestamps is below some threshold, the reported
concentration is zero. Otherwise, the concentration is calculated
as the number of remaining events divided by the time difference
between the oldest timestamp in the buffer and now.
I'll attach the Python implementation below for reference. It is a part of a much larger project, so I had to slightly modify it to get rid of references to external code:
particle_concentration_estimator_params.py
#!/usr/bin/env python3
"""This module implements a definition of parameters for the particle
concentration estimator.
"""
__author__ = "bup"
__email__ = "bup#swisens.ch"
__copyright__ = "Copyright 2021, Swisens AG"
__license__ = "GNU General Public License Version 3 (GPLv3)"
__all__ = ['ParticleConcentrationEstimatorParams']
import dataclasses
#dataclasses.dataclass
class ParticleConcentrationEstimatorParams:
"""This provides storage for the parameters used for the particle
concentration estimator.
"""
timestamp_buffer_max_size: int
"""Maximum size of the buffer that is used to keep track of event
timestamps. The size of this buffer mainly affects the filtering
of the reported data.
Unit: - (count)
"""
timestamp_buffer_max_age: float
"""Maximum age of events in the timestamp buffer which are
considered for the concentration calculation. This value is a
tradeoff between a smooth filtered value and the dynamic response
to a changed concentration.
Unit: s
"""
min_number_of_timestamps: int
"""Minimum number of timestamps to use for the concentration
estimation. If less timestamps are available, the concentration is
reported as zero.
Unit: - (count)
"""
particle_concentration_estimator.py
#!/usr/bin/env python3
"""This module implements the particle concentration estimation.
"""
__author__ = "bup"
__email__ = "bup#swisens.ch"
__copyright__ = "Copyright 2021, Swisens AG"
__license__ = "GNU General Public License Version 3 (GPLv3)"
__all__ = ['ParticleConcentrationEstimator']
import logging
import time
from typing import Optional
import numpy as np
from .particle_concentration_estimator_params import ParticleConcentrationEstimatorParams
logger = logging.getLogger(__name__)
class ParticleConcentrationEstimator:
"""An object of this class implements the Poleno particle
concentration estimator. Particle concentration is basically just
a number of particles per time unit. But since the particle events
arrive irregularly, there are various ways to filter the result, to
avoid too much noise especially when the concentration is low. This
class implements the following approach:
Event timestamps are placed in a buffer of a fixed maximum size.
Whenever a concentration estimate is requested, all timestamps up to
a set maximum age are filtered out. If the remaining number of
recent timestamps is below some threshold, the reported
concentration is zero. Otherwise, the concentration is calculated
as the number of remaining events divided by the time difference
between the oldest timestamp in the buffer and now.
"""
def __init__(self, params: ParticleConcentrationEstimatorParams):
"""Initializes the object with no events.
Args:
est_params: Initialized PolenoParams object which includes
information describing the estimator's behaviour.
"""
self.params = params
"""Stored params for the object."""
n_rows = self.params.timestamp_buffer_max_size
self._rb = np.full((n_rows, 2), -1e12)
self._rb_wp = 0
self._num_timestamps = 0
self._concentration_value = 0.0
self._concentration_value_no_mult = 0.0
def tick(self, now: float) -> float:
"""Recalculates the current concentration value.
Args:
now: Current timestamp to use to filter out old entries
in the buffer.
Returns:
The updated concentration value, which is also returned
using the concentration attribute.
"""
min_ts = now - self.params.timestamp_buffer_max_age
min_num = self.params.min_number_of_timestamps
used_rows = self._rb[:, 0] >= min_ts
filt_ts = self._rb[used_rows]
num_ts = round(np.sum(filt_ts[:, 1]))
self._num_timestamps = num_ts
num_ts_no_mult = round(np.sum(used_rows))
if num_ts < min_num:
self._concentration_value = 0.0
self._concentration_value_no_mult = 0.0
else:
t_diff = now - np.min(filt_ts[:, 0])
if t_diff >= 1e-3:
# Do not change the reported value if all events in the
# buffer have the same timestamp.
self._concentration_value = num_ts / t_diff
self._concentration_value_no_mult = num_ts_no_mult / t_diff
return self._concentration_value
def got_timestamp(self,
ts: Optional[float] = None,
multiplier: float = 1.0) -> None:
"""Passes in the most recent event timestamp. Timestamps need
not be ordered.
Calling this method does not immediately update the
concentration value, this is deferred to the tick() method.
Args:
ts: Event timestamp to use. If None, the current time is
used.
multiplier: Optional multiplier, which makes ts to be
counted that many times.
"""
if ts is None:
ts = time.time()
self._rb[self._rb_wp] = (ts, float(multiplier))
self._rb_wp = (self._rb_wp + 1) % self._rb.shape[0]
self._num_timestamps += round(multiplier)
#property
def concentration(self) -> float:
"""The calculated concentration value.
Unit: 1/s
"""
return self._concentration_value
#property
def concentration_no_mult(self) -> float:
"""The calculated concentration value without taking into
account the timestamp multipliers, i.e. as if all timestamps
were given with the default multiplier of 1.
Unit: 1/s
"""
return self._concentration_value_no_mult
#property
def num_timestamps(self) -> int:
"""Returns the number of timestamps which currently are in
the internal buffer.
"""
return self._num_timestamps

What is a reasonable widget count in a GTK3 application?

We have a Python GTK application with a large number of widgets 3-4k.
When porting from GTK2 to GTK3 we noticed a rather big performance hit when:
Adding widgets.
Simply showing (not creating) windows.
My question is: Is this a bug, or is it simply unreasonable to use many thousands of widgets in a GTK3 application?
The following test program perf.py shows both problems:
import sys
import gi
import time
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class DialogExample(Gtk.Dialog):
def __init__(self, parent):
Gtk.Dialog.__init__(self, "My Dialog", parent, 0,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OK, Gtk.ResponseType.OK))
label = Gtk.Label("The snappiness of this dialog depends on the number of \nwidgets in the main program.")
self.get_content_area().add(label)
self.show_all()
class DialogWindow(Gtk.Window):
def __init__(self, count):
Gtk.Window.__init__(self, title="Dialog Example")
box = Gtk.Box()
self.add(box)
button = Gtk.Button("Open dialog")
button.connect("clicked", self.on_button_clicked)
box.pack_start(button, expand=False, fill=True, padding=0)
# Add dummy widgets to show effect
for n in xrange(count):
button = Gtk.Button(str(n))
button.set_no_show_all(True)
t = time.time()
box.pack_start(button, expand=False, fill=True, padding=0)
print('{}\t{:.3f}'.format(n, 1000*(time.time()-t)))
def on_button_clicked(self, widget):
dialog = DialogExample(self)
dialog.run()
dialog.destroy()
count = int(sys.argv[1])
win = DialogWindow(count)
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()
The time to add a widget increases for each widget.
The time it takes to display a dialog is affected by the total widget count in the application.
The Python script takes a command line parameter which is the number of (hidden) widgets to create and prints the existing widget count and the time to add it.
$ python perf.py 20000
0 0.026
1 0.021
...
20000 1.700
At 20000 widgets, point 1 is pretty clear even on fast hardware. Point 2 might require slower hardware or double the widget count to be clearly visible.
The image below displays the widget creation time that goes from around 20 microseconds to 6 ms.
Number one is only somewhat unexpected, but the second point was totally unexpected to me.
I have tested this in Gtk 3.18 on three different machines.
Part of the main application window:

World of Warcraft UI - custom frame without addon

In World Of Warcraft I have created a little coords script that outputs current coords:
local function ou(self,elapsed)
px,py=GetPlayerMapPosition("player")
DEFAULT_CHAT_FRAME:AddMessage(format("( %s ) [%f , %f]",GetZoneText(), px *100, py *100))
end
local f = CreateFrame("frame")
f:SetScript("OnUpdate", ou)
This however spams default chat frame...
How would I create custom frame and how would I access it?
(I can't use custom channel with SendChatMessage)
...I would like to do this WITHOUT making an addon, thanks :)
I found a solution in storing frame in global variable, as I don't intend to create a plugin, the whole program requires a few macros (macro's maximum number of characters is 255).
First macro - prepare function that will set frame attributes later
f = input frame that will be set
x = x coordinate of position
y = y coordinate of position
function setMyFrame(f,x,y)
f:SetSize(288,100)
f:SetPoint("TOPLEFT",UIParent,"TOPLEFT",x,y)
f.text = f.text or f:CreateFontString(nil,"ARTWORK","QuestFont_Shadow_Huge")
f.text:SetAllPoints(true)
end
Second macro - prepare coords function that will set current coords as frame's text
ctotel = time elapsed since last update of the frame
creft = how often should the frame be updated in SECONDS - good one is 0.1 - 10 times a second is performance friendly and fast enaugh to coords update
f = input frame that will be updated
i = "how long it's been since the last update call cycle" (you don't set that - it is inherited from the WoW system)
ctotel = 0
creft = 0.1
function myCoords(f,i)
ctotel = ctotel + i
if ctotel >= creft then
px,py=GetPlayerMapPosition("player")
f.text:SetText(format("( %s ) [%f , %f]",GetZoneText(), px *100, py *100))
ctotel = 0
end
end
Third macro - store frame in global variable and set it and run update script with myCoords as callback
myCoordsFrame = CreateFrame("Frame","MyCoordsFrame",UIParent)
setMyFrame(myCoordsFrame, 500, 0)
myCoordsFrame:SetScript("OnUpdate", myCoords)
Of course in game all macros have to be preceeded with /run and have to be inlined - no line breaks - instead of linebreak just make space...
Also you have to run macros in THIS ^^^ order (first=>second=>third)
Advantage in setting frame and creft as globals:
Frames can't be destroyed while in the world (you have to relog to destroy them) so when it's global you can later move it with
/run setMyFrame(myCoordsFrame, NEW_X_COORDINATE, NEW_Y_COORDINATE)
If you would like the coords to update slower/faster you can do it by resetting the creft - e.g. to almost realtime refresh every 0.05 or even 0.01 seconds:
/run creft = 0.05 ... or even /run creft = 0.01
Make Coords movable - draggable by left mouse (credit to Wanderingfox from WoWhead):
myCoordsFrame:SetMovable(true)
myCoordsFrame:EnableMouse(true)
myCoordsFrame:SetScript("OnMouseDown",function() myCoordsFrame:StartMoving() end)
myCoordsFrame:SetScript("OnMouseUp",function() myCoordsFrame:StopMovingOrSizing() end)
...and as copy-paste ingame macro:
/run myCoordsFrame:SetMovable(true) myCoordsFrame:EnableMouse(true) myCoordsFrame:SetScript("OnMouseDown",function() myCoordsFrame:StartMoving() end) myCoordsFrame:SetScript("OnMouseUp",function() myCoordsFrame:StopMovingOrSizing() end)

psychopy polygon on top of image

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

Resources