Why is memory stacking up when using gifs in Ursina Engine? - python

I want to load a gif in ursina engine using the Animation class, it works flawlessly but when I want to reassign the Animation to another gif the memory stacks up like if I opened another gif. I tried to delete the instance and then recreate the gif but the memory keeps stacking up, also I tried to force a garbage collection but it doesn't work either.
What can I do?
The code is something like this:
from ursina import *
from ursina.prefabs.animation import Animation
import gc
app = Ursina()
FC={"FC":80}
monitor = Animation("monitor\gifs\monitor-%d.gif" % (FC["FC"]),loop=False)
monitor.enable()
def recreate_monitor():
global monitor
monitor = Animation("monitor\gifs\monitor-%d.gif" % (FC["FC"]),loop=False)
monitor.enabled = True
def refresh_monitor():
global monitor
monitor.sequence.start()
def update():
global monitor
if not monitor:
create_monitor()
else:
if monitor.sequence.finished:
if FC["FC"] != 80:
monitor.remove_node()
del monitor
print("DATA RECOLLECTED: ", gc.collect())
create_monitor()
else:
refresh_monitor()
def input(key):
if key == "space":
FC["FC"] = 100
app.run()

Related

Passing data asynchronously between functions

I'm writing a program that looks for objects in a video stream using a Google Coral device.
I have a function that continuously detects objects in the video.
I want to 'stream' the data from that function to two other functions/classes:
VideoViewer: This is a class that manages a debug window, showing the bounding boxes, etc.
An API endpoint: This part of the application has not been implemented yet, but I want to expose the data from the object detection function, via a websocket, later on.
The following is what I have so far. It yields the results
from PIL import Image
from cv2 import VideoCapture
def detect_with_video(self, video: VideoCapture):
"""Detect objects in a video"""
streaming = True
frame_id = 0
results = []
detection_delay = 4
while streaming:
_, frame = video.read()
if frame_id % detection_delay == 0:
results = self._detect_image(Image.fromarray(frame))
for result in results:
yield result
In my app.py I would like to be able to do something like this:
class App:
"""The application"""
def __init__(self):
self.video: VideoCapture = cv2.VideoCapture(0)
def run(self):
# Initialize video
VideoViewer().new_window()
generator = Detector().detect_with_video(self.video)
VideoViewer().set_stream(generator)
HttpApi().set_stream(generator)
class VideoViewer:
"""VideoViewer launches video windows"""
def set_stream(self, generator):
for item in generator:
print(item)
My guess is that I have to make the set_stream methods async, but yeah.
How can I make both consumers non blocking?
I'm new to python and aren't sure how to do this. Any help is highly appreciated.

How do I make a menu screen using Ursina Python?

Usually I would make a function or an if statement, like this:
def home_screen():
# code
if condition:
game()
def game():
# code
home_screen()
or something like:
game = 1
if game == 1:
# code for home screen
if condition:
game = 2
if game == 2:
# code for game
if game == 3:
# so on
The latter needs a global variable with a class, which is fine for me. However in Ursina, none of these work, either the update function stops on the former, or the color.red, color.blue, etc. stops working out of nowhere, or the second if statement just doesn't run. Does anyone have an alternative? I'm thinking of just making a home_screen.py file entirely but that won't do much good, and I'm not sure how that can be implemented anyway.
Edit: while loops also don't seem to work
Making a functional game menu is actually not that simple.
You could make a function that loads all the game models from a level, a function that shows a menu, and a final one that shows a loading screen.
Load a level :
def loadLevel():
global loading_screen
ground = Entity(model='quad', scale=10, y=-2, collider='box') # dummy entities
player = FirstPersonController()
player_model = Entity(model='player', parent=player)
building = Entity(model='building', collider='box')
destroy(loading_screen) # delete the loading screen when finished
Show the menu :
def showMenu():
play = Button('Play', on_click=showLoadingScreen) # A play button that show the loading menu when clicked
Show the loading screen :
from direct.stdpy import thread # we need threading to load entities in the background (this is specific to ursina, standard threading wouldn't work)
def showLoadingScreen():
global screen
screen = Entity(model='quad', texture='loading_image')
thread.start_new_thread(function=loadLevel, args='') # load entities in the background
Rest of the file :
from ursina import *
if __name__ == '__main__':
app = Ursina()
screen = None # for global statement
showMenu()
app.run()
Edit : a basic example is available here.

Running and exiting PyQt application multiple times

The context
I am creating my own version of the board game Battleship using PyQt. A PyQt main window contains both own and enemy boards. Boards are made up of clickable tiles that players 'fire at'. The implementation supports HUMANvsAI and AIvsAI games. I would like to test the strength of my AI algorithms through simulation. For example, I would like to run in a loop 1,000 AIvsAI games and get stats on % victory, average accuracy, etc.
The main issue
I am struggling to run the PyQt app multiple times to gather game data, e.g. in a for loop. Specifically, I cannot find a way to run the application, exit it, and re-run it again. Conceptually speaking I am looking for something like this:
# conceptual snippet
for i in range(n):
app = QApplication([])
window = MainWindow(b_size, boat_dict, players)
app.exec_()
With the call to exit the app somewhere else and called each time the game is over:
# conceptual snippet
if is_game_over():
sys.exit(app.exec_())
But this simple solution breaks the for loop. Any feedback would be welcome on how to run and exit a PyQt application multiple times, either sequentially (e.g. for loop approach) or in parallel threads.
You should not use sys.exit() since that instruction serves to terminate the execution of the program, if you want to terminate the Qt application you must use QCoreApplication::quit() (or QCoreApplication::exit(0)). In addition, another improvement would be to use multiprocessing:
import random
from multiprocessing import Pool
from PyQt5 import QtCore, QtWidgets
def create_app(i):
app = QtWidgets.QApplication([])
w = QtWidgets.QMainWindow()
w.setWindowTitle("Game-{}".format(i))
w.show()
# emulate end-game
def end_game():
QtCore.QCoreApplication.quit()
timeout = random.randint(1000, 2000) # 1000-2000 milliseconds
QtCore.QTimer.singleShot(timeout, end_game)
app.exec_()
# emulate results
o = {"victory": random.randint(0, 101), "average": random.randint(0, 101)}
return o
def main():
results = []
pool = Pool(processes=8)
for i in range(1000):
r = pool.apply_async(create_app, args=(i,))
results.append(r)
pool.close()
pool.join()
print([result.get() for result in results])
if __name__ == "__main__":
main()
I've to tell you, your question is dangerously near the level of a "too-broad" flag. The real problem is: how do you want to keep track of the gathered data, and what do you want to do with that data? This question may have a lot of answers.
As you already found out, you can't use sys.exit, but you could gather your data in different ways.
If you're going to run your application within a controlled environment, a possible solution is to serialize your gathered data while controlling the whole "gathering" process from the application
Semi-pseudo-code:
from PyQt5 import QtCore, QtWidgets
import json
class BattleShipWindow(QtWidgets.QMainWindow):
restart = QtCore.pyqtSignal()
# ...
def storeData(self, data):
# get the app data location
appDir = QtWidgets.QStandardPath.standardLocations(
QtCore.QStandardaPaths.AppDataLocation)[0]
# if it doesn't exists, create it
if not QtCore.QFile.exists(appDir):
QtCore.QDir().mkpath(appDir)
now = QtCore.QDateTime.currentDateTime()
fileName = str(now.toMSecsSinceEpoch() // 1000)
# write down the data
with open(os.path.join(appDir, fileName), 'w') as df:
df.write(json.dumps(data))
def getGameData(self):
# gather your own data here, this is just an example
return {'gameData': [1, 2, 3]}
def closeEvent(self, event):
if QtWidgets.QMessageBox.question(
self, 'Play again?',
'Play another game?',
QtWidgets.QMessageBox.Yes|QtWidgets.QMessageBox.No
) == QtWidgets.QMessageBox.Yes:
self.storeData(self.getGameData())
self.restart.emit()
class BattleShipApp(QtWidgets.QApplication):
def restart(self):
self.currentGame = BattleShipWindow()
self.currentGame.restart.connect(self.restart)
self.currentGame.show()
def exec_(self):
self.currentGame = BattleShipWindow()
self.currentGame.restart.connect(self.restart)
self.currentGame.show()
super(BattleShipApp, self).exec_()
if __name__ == '__main__':
import sys
app = BattleShipApp(sys.argv)
sys.exit(app.exec_())
Note: this is a [semi]pseudo code, I obviously didn't test it. Its purpose is to show how it could behave, so don't expect it to work as it is.

pyHook hooks are making inputs too laggy - is there a way to optimize those hooks?

I am trying to get inputs when I am playing the game (Warcraft III to be exact). Due to it being an RTS game it requires a high number of inputs in short periods. I am trying to use pyWinhook to get the inputs, mss to get screenshots and cv for image processing.
I have tried disabling image processing just after getting inputs to decrease the amount of work for the script. Nonetheless, it doesn't make much difference. I have also tried to decrease graphic details in-game - no difference also.
I am suspecting that thread is throttling due to high amount of inputs, and not very good hardware on which it's used (I am working at Lenovo X240 with 4GB of RAM).
import os
import cv2
import mss
import numpy as np
import pyWinhook as pyHook
import pythoncom
def main():
# file names for training data arrays
file_name = 'training_data.npy'
copy_file_name = 'training_data_copy.npy'
# deciding if previous file with data is saved. If yes, it is opened. If not it's created
if os.path.isfile(file_name):
print('File exists, loading previous data!')
print(os.path.realpath(file_name))
training_data = list(np.load(file_name, allow_pickle=True))
np.save(copy_file_name, training_data)
else:
print('File does not exist, starting fresh!')
training_data = []
# getting screenshot and processing it for optimal machine learning processing
def get_screen():
with mss.mss() as sct:
screen = np.array(sct.grab((0, 0, 1366, 768)))
screen = cv2.cvtColor(screen, cv2.COLOR_BGR2GRAY)
screen = cv2.resize(screen, (136, 76))
return screen
# saving data after acquiring 2500 sets of inputs and screenshots
def save_data(screen, output):
training_data.append([screen, output])
if len(training_data) % 2500 == 0:
print(len(training_data))
np.save(file_name, training_data)
print("Frames taken: " + str(len(training_data)))
# getting inputs and screen on mouse event
def OnMouseEvent(event):
screen = get_screen()
action = [event.Position, event.Wheel]
output = action
save_data(screen, output)
return True
# getting inputs and screen on keyboard event
def OnKeyboardEvent(event):
screen = get_screen()
output = event.Key
save_data(screen, output)
return True
# create a hook manager
hm = pyHook.HookManager()
# watch for all mouse events
hm.MouseAll = OnMouseEvent
hm.KeyUp = OnKeyboardEvent
# set the hook
hm.HookMouse()
hm.HookKeyboard()
# wait forever
try:
pythoncom.PumpMessages()
except KeyboardInterrupt:
pass
# looping getting data
while True:
pass
if __name__ == '__main__':
main()
I want to optimize getting inputs as much as possible because right now inputs in-game are lagging and hanging (which is not proper, because optimal inputs are the basics for machine learning modules).
I am fully aware that it can be an unsolvable problem due to slow hardware and too high amount of inputs per minute, but I want to try anyway.

Play Different Video Files Depending On Value of a Variable At Runtime

I have 4 video files (different scenes of a movie).
There's a starting scene that will be played when I run the player.
And before that scene ends, let's say the video player reads an int value (0-100) from external file (all happens at runtime), and depending on that int value, it has to determine which scene to play next.
pseudo example:
if (x > 0 && x < 30)
videoSource = scene2
else if (x >= 30 && x < 60)
videoSource = scene3
else if (x >= 60 && x <= 100)
videoSource = scene 4
How can I make it change video sources at runtime, depending on that variable?
I don't care about the format of the video file, (Avi, mp4...) whatever works will be fine.
I don't know how to approach this problem. I've searched for something that has the potential to accomplish this, like pyglet or GStreamer, but I didn't find a clear solution.
EDIT: I have the basic player and video player with pyglet, and I was able to play the video without depending on a variable using this code:
import pyglet
vidPath="sample.mpg"
window = pyglet.window.Window()
player = pyglet.media.Player()
source = pyglet.media.StreamingSource()
MediaLoad = pyglet.media.load(vidPath)
player.queue(MediaLoad)
player.play()
#window.event
def on_draw():
window.clear()
if player.source and player.source.video_format:
player.get_texture().blit(0,0)
pyglet.app.run()
How would I go about this? Guidance in the right direction and/or some sample code would be highly appreciated.
Thanks in advance.
Answer revised based on comments
If your goal is to constantly read a file that is receiving writes from the output of another process, you have a couple aspects that need to be solved...
You either need to read a file periodically that is constantly being overwritten, or you need to tail the output of a file that is being appended to with new values.
Your script currently blocks when you start the pyglet event loop, so this file check will have to be in a different thread, and then you would have to communicate the update event.
I can't fully comment on step 2 because I have never used pyglet and I am not familiar with how it uses events or signals. But I can at least suggest half of it with a thread.
Here is a super basic example of using a thread to read a file and report when a line is found:
import time
from threading import Thread
class Monitor(object):
def __init__(self):
self._stop = False
def run(self, inputFile, secs=3):
self._stop = False
with open(inputFile) as monitor:
while True:
line = monitor.readline().strip()
if line.isdigit():
# this is where you would notify somehow
print int(line)
time.sleep(secs)
if self._stop:
return
def stop(self):
self._stop = True
if __name__ == "__main__":
inputFile = "write.txt"
monitor = Monitor()
monitorThread = Thread(target=monitor.run, args=(inputFile, 1))
monitorThread.start()
try:
while True:
time.sleep(.25)
except:
monitor.stop()
The sleep loop at the end of the code is just a way to emulate your event loop and block.
Here is a test to show how it would work. First I open a python shell and open a new file:
>>> f = open("write.txt", 'w+', 10)
Then you can start this script. And back in the shell you can start writing lines:
>>> f.write('50\n'); f.flush()
In your script terminal you will see it read and print the lines.
The other way would be if your process that is writing to this file is constantly overwriting it, you would instead just reread the file by setting monitor.seek(0) and calling readline().
Again this is a really simple example to get you started. There are more advanced ways of solving this I am sure. The next step would be to figure out how you can signal the pyglet event loop to call a method that will change your video source.
Update
You should review this section of the pyglet docs on how to create your own event dispatcher: http://pyglet.org/doc/programming_guide/creating_your_own_event_dispatcher.html
Again, without much knowledge of pyglet, here is what it might look like:
class VideoNotifier(pyglet.event.EventDispatcher):
def updateIndex(self, value):
self.dispatch_events('on_update_index', value)
VideoNotifier.register_event('on_update_index')
videoNotifier = VideoNotifier()
#videoNotifier.event
def on_update_index(newIndex):
# thread has notified of an update
# Change the video here
pass
And for your thread class, you would pass in the dispatcher instance, and use the updateIndex() event to notify:
class Monitor(object):
def __init__(self, dispatcher):
self._stop = False
self._dispatcher = dispatcher
def run(self, inputFile, secs=3):
...
...
# should notify video of new value
line = int(line_from_file)
self._dispatcher.updateIndex(line)
...
...
Hope that gets you started!

Categories

Resources