Skip to content

Commit

Permalink
feat: enhanced visualize.py with NiceGUI (#406)
Browse files Browse the repository at this point in the history
* Create visualize1.py

* changes

* fix actions + add dark mode + remove unused code

* Update visualize1.py

* Update visualize1.py

* bump ^ nicegui == 1.2.24

* splitter

* add plot

* updated

* add search button

* isort

* formatting

* make plot dynamic

* Update visualize1.py

* make logo dynamic

* comments

* missed one

* fixed plot bug

* + max table children, deprecate old visualize

* Update visualize.py

* Update config.py

* Update config.py

* Update config.py

* refactor: show -> view_file

* Update visualize.py

* Update visualize.py

* downgrade fastapi

* update config

* linting

* remove debug print

* disabled on_select

* Update visualize.py

* fix visualization with tray

* merge privacy api

* Update visualize.py

* pre-commit

* Update poetry.lock
  • Loading branch information
0dm authored Aug 28, 2023
1 parent 6040fae commit 8031a9d
Show file tree
Hide file tree
Showing 10 changed files with 1,054 additions and 559 deletions.
95 changes: 46 additions & 49 deletions openadapt/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
run_app()
"""

from functools import partial
from subprocess import Popen
import base64
import os

from nicegui import app, ui

from openadapt import config, replay, visualize
from openadapt import config, replay
from openadapt.app.cards import recording_prompt, select_import, settings
from openadapt.app.objects.console import Console
from openadapt.app.util import clear_db, on_export, on_import
Expand All @@ -31,62 +33,57 @@

logger = None

# Add logo
# right align icon
with ui.row().classes("w-full justify-right"):
# settings

# alignment trick
with ui.avatar(color="white" if dark else "black", size=128):
logo_base64 = base64.b64encode(
open(os.path.join(FPATH, "assets/logo.png"), "rb").read()
def start(fullscreen: bool = False) -> None:
"""Start the OpenAdapt application."""
with ui.row().classes("w-full justify-right"):
with ui.avatar(color="white" if dark else "black", size=128):
logo_base64 = base64.b64encode(
open(os.path.join(FPATH, "assets/logo.png"), "rb").read()
)
img = bytes(
f"data:image/png;base64,{(logo_base64.decode('utf-8'))}",
encoding="utf-8",
)
ui.image(img.decode("utf-8"))
ui.icon("settings").tooltip("Settings").on("click", lambda: settings(dark))
ui.icon("delete").on("click", lambda: clear_db(log=logger)).tooltip(
"Clear all recorded data"
)
img = bytes(
f"data:image/png;base64,{(logo_base64.decode('utf-8'))}",
encoding="utf-8",
ui.icon("upload").tooltip("Export Data").on("click", lambda: on_export(SERVER))
ui.icon("download").tooltip("Import Data").on(
"click", lambda: select_import(on_import)
)
ui.icon("share").tooltip("Share").on(
"click", lambda: (_ for _ in ()).throw(Exception(NotImplementedError))
)
ui.image(img.decode("utf-8"))
ui.icon("settings").tooltip("Settings").on("click", lambda: settings(dark))
ui.icon("delete").on("click", lambda: clear_db(log=logger)).tooltip(
"Clear all recorded data"
)
ui.icon("upload").tooltip("Export Data").on("click", lambda: on_export(SERVER))
ui.icon("download").tooltip("Import Data").on(
"click", lambda: select_import(on_import)
)
ui.icon("share").tooltip("Share").on(
"click", lambda: (_ for _ in ()).throw(Exception(NotImplementedError))
)

with ui.splitter(value=20) as splitter:
splitter.classes("w-full h-full")
with splitter.before:
with ui.column().classes("w-full h-full"):
record_button = (
ui.icon("radio_button_checked", size="64px")
.on(
"click",
lambda: recording_prompt(OPTIONS, record_button),
with ui.splitter(value=20) as splitter:
splitter.classes("w-full h-full")
with splitter.before:
with ui.column().classes("w-full h-full"):
record_button = (
ui.icon("radio_button_checked", size="64px")
.on(
"click",
lambda: recording_prompt(OPTIONS, record_button),
)
.tooltip("Record a new replay / Stop recording")
)
.tooltip("Record a new replay / Stop recording")
)
ui.icon("visibility", size="64px").on("click", visualize.main).tooltip(
"Visualize the latest replay"
)

ui.icon("play_arrow", size="64px").on(
"click",
lambda: replay.replay("NaiveReplayStrategy"),
).tooltip("Play the latest replay")
with splitter.after:
logger = Console()
logger.log.style("height: 250px;, width: 300px;")
ui.icon("visibility", size="64px").on(
"click", partial(Popen, ["python", "-m", "openadapt.visualize"])
).tooltip("Visualize the latest replay")

splitter.enabled = False
ui.icon("play_arrow", size="64px").on(
"click",
lambda: replay.replay("NaiveReplayStrategy"),
).tooltip("Play the latest replay")
with splitter.after:
logger = Console()
logger.log.style("height: 250px;, width: 300px;")

splitter.enabled = False

def start(fullscreen: bool = False) -> None:
"""Start the OpenAdapt application."""
ui.run(
title="OpenAdapt Client",
native=True,
Expand Down
24 changes: 16 additions & 8 deletions openadapt/app/tray.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
usage: `python -m openadapt.app.tray` or `poetry run app`
"""

from functools import partial
from threading import Thread
from subprocess import Popen
import os
import sys

from loguru import logger
from notifypy import Notify
from PySide6.QtCore import QTimer
from PySide6.QtGui import QAction, QIcon
Expand All @@ -18,7 +20,6 @@
from openadapt.extensions.thread import Thread as oaThread
from openadapt.models import Recording
from openadapt.replay import replay
from openadapt.visualize import main as visualize

# hide dock icon on macos
if sys.platform == "darwin":
Expand Down Expand Up @@ -78,6 +79,8 @@ def __init__(self) -> None:
self.timer.timeout.connect(self.update_tray_icon)
self.timer.start()

self.visualize_proc = None

Notify("Status", "OpenAdapt is running in the background.", "OpenAdapt").send()

def update_tray_icon(self) -> None:
Expand Down Expand Up @@ -117,11 +120,16 @@ def _visualize(self, recording: Recording) -> None:
recording (Recording): The recording to visualize.
"""
Notify("Status", "Starting visualization...", "OpenAdapt").send()
vthread = oaThread(target=visualize, args=(recording,))
vthread.run()
if vthread.join():
Notify("Status", "Visualization finished", "OpenAdapt").send()
else:
try:
if self.visualize_proc is not None:
self.visualize_proc.kill()
self.visualize_proc = Popen(
f"python -m openadapt.visualize --timestamp {recording.timestamp}",
shell=True,
)

except Exception as e:
logger.error(e)
Notify("Status", "Visualization failed", "OpenAdapt").send()

def _replay(self, recording: Recording) -> None:
Expand Down Expand Up @@ -163,7 +171,7 @@ def populate_menu(self, menu: QMenu, action: QAction, action_type: str) -> None:
def show_app(self) -> None:
"""Show the main application window."""
if self.app_thread is None or not self.app_thread.is_alive():
self.app_thread = Thread(target=start, daemon=True, args=(True,))
self.app_thread = oaThread(target=start, daemon=True, args=(True,))
self.app_thread.start()

def run(self) -> None:
Expand Down
9 changes: 9 additions & 0 deletions openadapt/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,13 @@
"children",
],
"PLOT_PERFORMANCE": True,
# VISUALIZATION CONFIGURATIONS
"VISUALIZE_DARK_MODE": False,
"VISUALIZE_RUN_NATIVELY": True,
"VISUALIZE_DENSE_TREES": True,
"VISUALIZE_ANIMATIONS": True,
"VISUALIZE_EXPAND_ALL": False, # not recommended for large trees
"VISUALIZE_MAX_TABLE_CHILDREN": 10,
# Calculate and save the difference between 2 neighboring screenshots
"SAVE_SCREENSHOT_DIFF": False,
"SPACY_MODEL_NAME": "en_core_web_trf",
Expand Down Expand Up @@ -132,6 +139,8 @@ def getenv_fallback(var_name: str) -> str:
"0",
):
rval = rval.lower() == "true" or rval == "1"
if type(rval) is str and rval.isnumeric():
rval = int(rval)
if rval is None:
raise ValueError(f"{var_name=} not defined")
return rval
Expand Down
4 changes: 4 additions & 0 deletions openadapt/deprecated/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"""Deprecated modules.
Module: __init__.py
"""
Loading

0 comments on commit 8031a9d

Please sign in to comment.