-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
243f244
commit 60fd936
Showing
17 changed files
with
421 additions
and
0 deletions.
There are no files selected for viewing
3 changes: 3 additions & 0 deletions
3
learning-projects/DearPyGuiWithMultiprocessingAndAsyncIO/.gitignore
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.pytest_cache | ||
__pycache__ | ||
.mypy_cache |
30 changes: 30 additions & 0 deletions
30
learning-projects/DearPyGuiWithMultiprocessingAndAsyncIO/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# Learning Project: Asynchronous Coding and Dear PyGui | ||
|
||
This is a small project to explore some of the built-in python tools around parallel processing, using an example of displaying a real-time signal that is being collected by some sensor. | ||
|
||
Some things I wanted to learn: | ||
- What's it like to use DearPyGui, and does it seem like a useful tool for data collection applications? I've already come to like Streamlit | ||
for web apps, and I'm happy with FastAPI, but somehow Tkinter and PyQt have only been marginal | ||
successes in my book, and Vispy and Kivy never matured enough for me to be totally happy with real-time data applications. DearPyGui has potential. | ||
|
||
- Can I find some simple, reusable patterns for organizing and composing together multiple data streams? | ||
|
||
|
||
After playing around a bit with `threading`, `multiprocessing`, and `asynicio`, I settled on using a combination of multiprocessing and asyncio: | ||
- Multiprocessing's `Manager` to separate each script into its own environment and define a clear interface between the two. (which should be even cleaner and higher-perfoamnce from Python 3.12 onward, now that seperate GILs are being put in place for each process). | ||
|
||
- Separate AsynicIO event loops for each process, with async functions to help increase the performance of IO-bound signals (here, that's mainly graphic rendering and console writing and reading) | ||
|
||
|
||
Some Learnings: | ||
- New Functions/classes: | ||
- `multiprocessing.Manager` | ||
- `asyncio.ensure_future()` | ||
- `sys.stdout.buffer.write()` and `flush()` | ||
- Much higher-performance version of `print()`, useful for simulating the high-speed sensor (I think I got about 10kHz) | ||
- DearPyGui is pretty great! I particularly liked how simple it was to implement an asynicio-based event loop. Also, keeping the Gui responsive was really straightforward, as was debugging problems. Very nice! | ||
- There were some interesting bugs while handling the data stream; in the future, it might be good to automate performance testings to verify these tradeoffs (what "Evolutionary Architecture" calls "fitness functions"), as they were really subtle and time-consuming to track down. | ||
|
||
|
||
Overall, really neat! | ||
|
61 changes: 61 additions & 0 deletions
61
...g-projects/DearPyGuiWithMultiprocessingAndAsyncIO/experiments/can_run_in_other_process.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import dearpygui.dearpygui as dpg | ||
from multiprocessing import Process, Manager | ||
import time | ||
|
||
|
||
|
||
def setup(data): | ||
# Setup | ||
dpg.create_context() | ||
dpg.create_viewport() | ||
dpg.setup_dearpygui() | ||
dpg.show_viewport() | ||
|
||
|
||
with dpg.window(label='win'): | ||
dpg.add_button(label='Hi!', tag='button') | ||
|
||
# Update and Render Loop | ||
while dpg.is_dearpygui_running(): | ||
dpg.set_item_label('button', data['time']) | ||
dpg.render_dearpygui_frame() | ||
|
||
# Cleanup | ||
dpg.destroy_context() | ||
|
||
|
||
def log_time(data): | ||
while True: | ||
time.sleep(.001) | ||
current = time.time() | ||
data['time'] = current | ||
print(current) | ||
|
||
|
||
def manage_app(timer_model, render_model): | ||
while True: | ||
time.sleep(0) | ||
render_model['time'] = str(round(timer_model['time'], 2)) | ||
|
||
|
||
manager = Manager() | ||
timer_model = manager.dict(time=3.14) | ||
render_model = manager.dict(time='3.123424242324324') | ||
|
||
|
||
gui_proc = Process(target=setup, kwargs=dict(data=render_model)) | ||
timer_proc = Process(target=log_time, kwargs=dict(data=timer_model), daemon=True) | ||
main_proc = Process(target=manage_app, kwargs=dict(timer_model=timer_model, render_model=render_model), daemon=True) | ||
|
||
gui_proc.start() | ||
timer_proc.start() | ||
main_proc.start() | ||
gui_proc.join() | ||
gui_proc.close() | ||
timer_proc.terminate() | ||
main_proc.terminate() | ||
while timer_proc.is_alive() or main_proc.is_alive(): # There's a bit of delay before the terminate() call actually takes effect. | ||
pass | ||
timer_proc.close() | ||
main_proc.close() | ||
print('done!') |
27 changes: 27 additions & 0 deletions
27
learning-projects/DearPyGuiWithMultiprocessingAndAsyncIO/experiments/dpg_io_handler_demo.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import dearpygui.dearpygui as dpg | ||
|
||
dpg.create_context() | ||
|
||
def change_text(sender, app_data): | ||
dpg.set_value("text item", f"Mouse Button ID: {app_data}") | ||
|
||
def visible_call(sender, app_data): | ||
print("I'm visible") | ||
|
||
with dpg.item_handler_registry(tag="widget handler") as handler: | ||
dpg.add_item_clicked_handler(callback=change_text) | ||
dpg.add_item_visible_handler(callback=visible_call) | ||
|
||
with dpg.window(width=500, height=300): | ||
dpg.add_text("Click me with any mouse button", tag="text item") | ||
dpg.add_text("Close window with arrow to change visible state printing to console", tag="text item 2") | ||
|
||
# bind item handler registry to item | ||
dpg.bind_item_handler_registry("text item", "widget handler") | ||
dpg.bind_item_handler_registry("text item 2", "widget handler") | ||
|
||
dpg.create_viewport(title='Custom Title', width=800, height=600) | ||
dpg.setup_dearpygui() | ||
dpg.show_viewport() | ||
dpg.start_dearpygui() | ||
dpg.destroy_context() |
46 changes: 46 additions & 0 deletions
46
...ing-projects/DearPyGuiWithMultiprocessingAndAsyncIO/experiments/errors_multiprocessing.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import tkinter as tk | ||
import time | ||
import multiprocessing as mp | ||
from queue import Empty | ||
|
||
|
||
def generate_layout_model(): | ||
return {"text": "Hello, World!"} | ||
|
||
|
||
def gui_process(layout_queue): | ||
def update_label(): | ||
try: | ||
layout = layout_queue.get_nowait() | ||
label.config(text=layout["text"], fg=layout["color"]) | ||
except Empty: | ||
pass | ||
|
||
root.after(1000, update_label) | ||
|
||
root = tk.Tk() | ||
root.title("GUI Framework") | ||
label = tk.Label(root, text="", font=("Arial", 14)) | ||
label.pack(padx=20, pady=20) | ||
update_label() | ||
root.mainloop() | ||
|
||
|
||
def layout_generator_process(layout_queue): | ||
while True: | ||
layout = generate_layout_model() | ||
layout_queue.put(layout) | ||
time.sleep(1) | ||
|
||
|
||
if __name__ == "__main__": | ||
layout_queue = mp.Queue() | ||
|
||
gui = mp.Process(target=gui_process, args=(layout_queue,)) | ||
layout_generator = mp.Process(target=layout_generator_process, args=(layout_queue,)) | ||
|
||
gui.start() | ||
layout_generator.start() | ||
|
||
gui.join() | ||
layout_generator.join() |
27 changes: 27 additions & 0 deletions
27
learning-projects/DearPyGuiWithMultiprocessingAndAsyncIO/experiments/is_dpg_blocking.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import dearpygui.dearpygui as dpg | ||
import time | ||
import matplotlib.pyplot as plt | ||
|
||
dpg.create_context() | ||
|
||
dpg.create_viewport() | ||
dpg.set_viewport_vsync(False) # if vsync is off, it's nonblocking | ||
dpg.setup_dearpygui() | ||
dpg.show_viewport() | ||
|
||
dts = [] | ||
|
||
last_time = time.perf_counter() | ||
for _ in range(200): | ||
dpg.render_dearpygui_frame() | ||
current_time = time.perf_counter() | ||
# time.sleep(.01) | ||
dt = current_time - last_time | ||
last_time = current_time | ||
dts.append(dt) | ||
|
||
dpg.destroy_context() | ||
|
||
|
||
plt.hist(dts, bins=15) | ||
plt.show() |
12 changes: 12 additions & 0 deletions
12
learning-projects/DearPyGuiWithMultiprocessingAndAsyncIO/experiments/run_demo.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import dearpygui.dearpygui as dpg | ||
import dearpygui.demo as demo | ||
|
||
dpg.create_context() | ||
dpg.create_viewport(title='Custom Title', width=600, height=600) | ||
|
||
demo.show_demo() | ||
|
||
dpg.setup_dearpygui() | ||
dpg.show_viewport() | ||
dpg.start_dearpygui() | ||
dpg.destroy_context() |
1 change: 1 addition & 0 deletions
1
learning-projects/DearPyGuiWithMultiprocessingAndAsyncIO/gui/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .main import run |
39 changes: 39 additions & 0 deletions
39
learning-projects/DearPyGuiWithMultiprocessingAndAsyncIO/gui/dpg_utils.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
from __future__ import annotations | ||
|
||
import asyncio | ||
from contextlib import contextmanager | ||
from typing import NamedTuple | ||
|
||
import dearpygui.dearpygui as dpg | ||
|
||
|
||
@contextmanager | ||
def create_context(vsync: bool = True): | ||
dpg.create_context() | ||
dpg.create_viewport() | ||
dpg.set_viewport_vsync(vsync) # if vsync is off, it's nonblocking | ||
dpg.setup_dearpygui() | ||
dpg.show_viewport() | ||
# dpg.render_dearpygui_frame() | ||
yield | ||
dpg.destroy_context() | ||
|
||
|
||
|
||
class FrameData(NamedTuple): | ||
num: int | ||
dt: float = 0 | ||
|
||
def __repr__(self) -> str: | ||
return f"FrameData(num={self.num}, dt={round(self.dt, 4)})" | ||
|
||
|
||
|
||
|
||
async def handle_shutdown(loop): | ||
while dpg.is_dearpygui_running(): | ||
await asyncio.sleep(0.01) | ||
loop.stop() | ||
|
||
|
||
|
34 changes: 34 additions & 0 deletions
34
learning-projects/DearPyGuiWithMultiprocessingAndAsyncIO/gui/gui.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
from typing import List, TypedDict | ||
import dearpygui.dearpygui as dpg | ||
|
||
|
||
class Model(TypedDict): | ||
x_data: List[int] | ||
y_data: List[int] | ||
|
||
|
||
def build_model(app_data) -> Model: | ||
model: Model = {'x_data': app_data['x'], 'y_data': app_data['y']} | ||
return model | ||
|
||
|
||
def update_gui(app_data): | ||
model: Model = build_model(app_data=app_data) | ||
dpg.set_value('line_series', [model['x_data'], model['y_data']]) | ||
dpg.set_axis_limits('x_axis', ymin=min(model['x_data']), ymax=max(model['x_data'])) | ||
# dpg.set_axis_limits('y_axis', ymin=min(model['y_data']), ymax=max(model['y_data'])) | ||
|
||
|
||
def setup_gui(): | ||
with dpg.window(label='Plot'): | ||
with dpg.plot(label='Sine Wave', width=1200, height=600): | ||
x_axis = dpg.add_plot_axis(dpg.mvXAxis, label='X Axis', tag='x_axis') | ||
y_axis = dpg.add_plot_axis(dpg.mvYAxis, label='Y Axis', tag='y_axis') | ||
|
||
dpg.add_line_series( | ||
[2, 3, 4, 5], | ||
[-1, 0, 1, 0], | ||
label='The Data', | ||
parent=y_axis, | ||
tag='line_series', | ||
) |
23 changes: 23 additions & 0 deletions
23
learning-projects/DearPyGuiWithMultiprocessingAndAsyncIO/gui/main.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
from __future__ import annotations | ||
|
||
import asyncio | ||
from typing import TYPE_CHECKING | ||
|
||
import dearpygui.dearpygui as dpg | ||
|
||
from gui import dpg_utils | ||
from gui.gui import setup_gui, update_gui | ||
from utils import repeat | ||
|
||
if TYPE_CHECKING: | ||
from main import AppData | ||
|
||
def run(proc_data: AppData = None): | ||
|
||
if proc_data is None: | ||
proc_data = {'x': [2, 3, 4], 'y': [4, 4.5, 6]} | ||
with dpg_utils.create_context(vsync=False): | ||
setup_gui() | ||
asyncio.ensure_future(repeat(dpg.render_dearpygui_frame, dt=.015, run_cond=dpg.is_dearpygui_running)) | ||
asyncio.ensure_future(repeat(update_gui, data=(proc_data,), dt=.002, run_cond=dpg.is_dearpygui_running)) | ||
asyncio.get_event_loop().run_forever() |
24 changes: 24 additions & 0 deletions
24
learning-projects/DearPyGuiWithMultiprocessingAndAsyncIO/main.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
|
||
import asyncio | ||
from typing import List, TypedDict | ||
import multiprocessing as mp | ||
|
||
import gui | ||
import sensor | ||
|
||
|
||
class AppData(TypedDict): | ||
x: List[int|float] | ||
y: List[int|float] | ||
|
||
|
||
if __name__ == '__main__': | ||
manager = mp.Manager() | ||
data: AppData = manager.dict({'x': [1, 2, 5], 'y': [6, 8, 8.2]}) | ||
|
||
gui_proc = mp.Process(target=gui.run, args=(data,)) | ||
daq_proc = mp.Process(target=lambda: asyncio.run(sensor.simulate_data_update(data)), daemon=True) | ||
gui_proc.start() | ||
daq_proc.start() | ||
gui_proc.join() | ||
daq_proc.join() |
4 changes: 4 additions & 0 deletions
4
learning-projects/DearPyGuiWithMultiprocessingAndAsyncIO/requirements.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
pytest | ||
pytest-asyncio | ||
dearpygui | ||
asyncstdlib |
1 change: 1 addition & 0 deletions
1
learning-projects/DearPyGuiWithMultiprocessingAndAsyncIO/sensor/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .sensor import simulate_data_update |
20 changes: 20 additions & 0 deletions
20
learning-projects/DearPyGuiWithMultiprocessingAndAsyncIO/sensor/run_sensor.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import random | ||
import struct | ||
import sys | ||
import time | ||
from math import sin | ||
|
||
SAMPLING_RATE_HZ = 100_000 | ||
|
||
while True: | ||
# time.sleep(1 / SAMPLING_RATE_HZ) | ||
time.sleep(0.0000000000000000000000000000000001) | ||
t = time.perf_counter() | ||
y = sin(8 * t + .2 * random.random()) | ||
packet = struct.pack('dd', t, y) | ||
try: | ||
sys.stdout.buffer.write(packet) | ||
sys.stdout.buffer.flush() | ||
except BrokenPipeError: | ||
break | ||
|
Oops, something went wrong.