Skip to content

Commit

Permalink
Merge pull request #3 from pperanich/dev
Browse files Browse the repository at this point in the history
Enable support for more Qt backends.
  • Loading branch information
pperanich authored Jul 11, 2024
2 parents e3ce668 + 4994017 commit 75b1018
Show file tree
Hide file tree
Showing 20 changed files with 310 additions and 110 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

`ezmsg-vispy` is an extension to the [`ezmsg`](https://github.com/iscoe/ezmsg)
framework, crafted to enhance and expedite the development
of real-time data visualizations. By leveraging the power of PyQt6 and the Vispy plotting
of real-time data visualizations. By leveraging the power of Qt and the Vispy plotting
library, `ezmsg-vispy` offers a fluid interface for high-performance
visualizations within systems developed with `ezmsg`.
It's an ideal tool for researchers and developers who require rapid prototyping
Expand Down
24 changes: 15 additions & 9 deletions examples/frontends/line_vis_frontend.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
import logging
from PyQt6 import QtCore, QtGui, QtWidgets
from PyQt6.uic.load_ui import loadUi
import sys
from dataclasses import dataclass
from ezmsg.vispy.frontends.main_window import (
EzMainWindow,
register_command,
register_response,
)
from pathlib import Path

from qtpy import QtGui
from qtpy import QtWidgets

from ezmsg.vispy.frontends.main_window import EzMainWindow
from ezmsg.vispy.frontends.main_window import register_command
from ezmsg.vispy.frontends.main_window import register_response

sys.path.append(str(Path(__file__).parent))
from ui_line_vis_frontend import Ui_AppWindow

logger = logging.getLogger(__name__)


@dataclass
class WaveformCfgMessage:
waveform_type: str
frequency: float
start: bool


class LineVisFrontend(EzMainWindow):
class LineVisFrontend(EzMainWindow, Ui_AppWindow):
ez_frequency_dial: QtWidgets.QDial
ez_waveform_type: QtWidgets.QComboBox
ez_start_btn: QtWidgets.QPushButton

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
loadUi(__file__.replace(".py", ".ui"), self)
self.setupUi(self)

self.ez_waveform_type.addItem(QtGui.QIcon("resources/sine.png"), "sine")
self.ez_waveform_type.addItem(QtGui.QIcon("resources/triangle.png"), "sawtooth")
Expand Down
148 changes: 148 additions & 0 deletions examples/frontends/ui_line_vis_frontend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
################################################################################
## Form generated from reading UI file 'line_vis_frontend.ui'
##
## Created by: Qt User Interface Compiler version 6.7.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################

from PySide6.QtCore import QCoreApplication
from PySide6.QtCore import QMetaObject
from PySide6.QtCore import QSize
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QComboBox
from PySide6.QtWidgets import QDial
from PySide6.QtWidgets import QHBoxLayout
from PySide6.QtWidgets import QLabel
from PySide6.QtWidgets import QPushButton
from PySide6.QtWidgets import QSizePolicy
from PySide6.QtWidgets import QSpacerItem
from PySide6.QtWidgets import QVBoxLayout
from PySide6.QtWidgets import QWidget


class Ui_AppWindow:
def setupUi(self, AppWindow):
if not AppWindow.objectName():
AppWindow.setObjectName("AppWindow")
AppWindow.resize(400, 300)
sizePolicy = QSizePolicy(
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding
)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(AppWindow.sizePolicy().hasHeightForWidth())
AppWindow.setSizePolicy(sizePolicy)
self.cantral = QWidget(AppWindow)
self.cantral.setObjectName("cantral")
sizePolicy.setHeightForWidth(self.cantral.sizePolicy().hasHeightForWidth())
self.cantral.setSizePolicy(sizePolicy)
self.horizontalLayout = QHBoxLayout(self.cantral)
self.horizontalLayout.setObjectName("horizontalLayout")
self.verticalLayout_2 = QVBoxLayout()
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.verticalLayout_2.setContentsMargins(10, 10, 10, 10)
self.verticalSpacer_2 = QSpacerItem(
20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding
)

self.verticalLayout_2.addItem(self.verticalSpacer_2)

self.frequency_dial_label = QLabel(self.cantral)
self.frequency_dial_label.setObjectName("frequency_dial_label")
self.frequency_dial_label.setAlignment(Qt.AlignCenter)

self.verticalLayout_2.addWidget(self.frequency_dial_label)

self.ez_frequency_dial = QDial(self.cantral)
self.ez_frequency_dial.setObjectName("ez_frequency_dial")
sizePolicy1 = QSizePolicy(
QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed
)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(
self.ez_frequency_dial.sizePolicy().hasHeightForWidth()
)
self.ez_frequency_dial.setSizePolicy(sizePolicy1)
self.ez_frequency_dial.setMaximumSize(QSize(16777215, 50))
self.ez_frequency_dial.setBaseSize(QSize(0, 0))
self.ez_frequency_dial.setValue(0)

self.verticalLayout_2.addWidget(self.ez_frequency_dial)

self.waveform_type_label = QLabel(self.cantral)
self.waveform_type_label.setObjectName("waveform_type_label")
self.waveform_type_label.setAlignment(Qt.AlignCenter)

self.verticalLayout_2.addWidget(self.waveform_type_label)

self.ez_waveform_type = QComboBox(self.cantral)
self.ez_waveform_type.setObjectName("ez_waveform_type")

self.verticalLayout_2.addWidget(self.ez_waveform_type)

self.verticalSpacer_3 = QSpacerItem(
20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding
)

self.verticalLayout_2.addItem(self.verticalSpacer_3)

self.ez_start_btn = QPushButton(self.cantral)
self.ez_start_btn.setObjectName("ez_start_btn")
sizePolicy1.setHeightForWidth(
self.ez_start_btn.sizePolicy().hasHeightForWidth()
)
self.ez_start_btn.setSizePolicy(sizePolicy1)

self.verticalLayout_2.addWidget(self.ez_start_btn)

self.verticalSpacer = QSpacerItem(
20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding
)

self.verticalLayout_2.addItem(self.verticalSpacer)

self.horizontalLayout.addLayout(self.verticalLayout_2)

self.verticalLayout = QVBoxLayout()
self.verticalLayout.setObjectName("verticalLayout")
self.ez_ts_plot = QWidget(self.cantral)
self.ez_ts_plot.setObjectName("ez_ts_plot")
sizePolicy.setHeightForWidth(self.ez_ts_plot.sizePolicy().hasHeightForWidth())
self.ez_ts_plot.setSizePolicy(sizePolicy)

self.verticalLayout.addWidget(self.ez_ts_plot)

self.ez_fs_plot = QWidget(self.cantral)
self.ez_fs_plot.setObjectName("ez_fs_plot")
sizePolicy.setHeightForWidth(self.ez_fs_plot.sizePolicy().hasHeightForWidth())
self.ez_fs_plot.setSizePolicy(sizePolicy)

self.verticalLayout.addWidget(self.ez_fs_plot)

self.horizontalLayout.addLayout(self.verticalLayout)

AppWindow.setCentralWidget(self.cantral)

self.retranslateUi(AppWindow)

QMetaObject.connectSlotsByName(AppWindow)

# setupUi

def retranslateUi(self, AppWindow):
AppWindow.setWindowTitle(
QCoreApplication.translate("AppWindow", "Application Example", None)
)
self.frequency_dial_label.setText(
QCoreApplication.translate("AppWindow", "Frequency Select", None)
)
self.waveform_type_label.setText(
QCoreApplication.translate("AppWindow", "Waveform Type", None)
)
self.ez_start_btn.setText(
QCoreApplication.translate("AppWindow", "Start", None)
)

# retranslateUi
15 changes: 7 additions & 8 deletions examples/image_vis_demo.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import numpy as np
from typing import AsyncGenerator
from collections.abc import AsyncGenerator
from dataclasses import dataclass

import numpy as np

# Module specific imports
import ezmsg.core as ez
from ezmsg.util.rate import Rate

from ezmsg.vispy.units.application import SimpleApplication, SimpleApplicationSettings
from ezmsg.vispy.units.image_vis import ImageVis, ImageVisSettings
from ezmsg.vispy.units.application import SimpleApplication
from ezmsg.vispy.units.application import SimpleApplicationSettings
from ezmsg.vispy.units.image_vis import ImageVis
from ezmsg.vispy.units.image_vis import ImageVisSettings


@dataclass
Expand All @@ -25,7 +27,6 @@ class ImageState(ez.State):


class ImageGenerator(ez.Unit):

SETTINGS: ImageSettings
STATE: ImageState

Expand Down Expand Up @@ -82,13 +83,11 @@ def network(self) -> ez.NetworkDefinition:


class ImageVisDemo(ez.Collection):

IMAGE_GENERATOR = ImageGenerator()

IMAGE_APP = ImageApp()

def configure(self) -> None:

self.IMAGE_GENERATOR.apply_settings(
ImageSettings(
fs=1e2,
Expand Down
11 changes: 5 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
[project]
name = "ezmsg-vispy"
version = "0.2.0"
version = "0.3.0"
license = { file = "LICENSE.txt" }
readme = "README.md"
requires-python = ">=3.9"
dependencies = [
"ezmsg>=3.3.4",
"numpy>=1.26.4",
"qdarkstyle>=3.2.3",
"pyqt6>=6.6.1",
"vispy>=0.14.2",
"pyopengl>=3.1.7", # Can remove once https://github.com/vispy/vispy/pull/2549 closes.
"qtpy>=2.4.1",
]

[tool]
rye = { dev-dependencies = [
"pre-commit>=3.7.0",
] }
[tool.rye]
dev-dependencies = ["pre-commit>=3.7.0"]
universal = true

[tool.ruff]
src = ["src"]
Expand Down
9 changes: 3 additions & 6 deletions requirements-dev.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
# features: []
# all-features: true
# with-sources: false
# generate-hashes: false
# universal: true

-e file:.
cfgv==3.4.0
Expand Down Expand Up @@ -37,17 +39,12 @@ platformdirs==4.2.0
pre-commit==3.7.0
pyopengl==3.1.7
# via ezmsg-vispy
pyqt6==6.6.1
# via ezmsg-vispy
pyqt6-qt6==6.6.2
# via pyqt6
pyqt6-sip==13.6.0
# via pyqt6
pyyaml==6.0.1
# via pre-commit
qdarkstyle==3.2.3
# via ezmsg-vispy
qtpy==2.4.1
# via ezmsg-vispy
# via qdarkstyle
setuptools==69.2.0
# via nodeenv
Expand Down
9 changes: 3 additions & 6 deletions requirements.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
# features: []
# all-features: true
# with-sources: false
# generate-hashes: false
# universal: true

-e file:.
ezmsg==3.3.4
Expand All @@ -24,15 +26,10 @@ packaging==24.0
# via vispy
pyopengl==3.1.7
# via ezmsg-vispy
pyqt6==6.6.1
# via ezmsg-vispy
pyqt6-qt6==6.6.2
# via pyqt6
pyqt6-sip==13.6.0
# via pyqt6
qdarkstyle==3.2.3
# via ezmsg-vispy
qtpy==2.4.1
# via ezmsg-vispy
# via qdarkstyle
typing-extensions==4.10.0
# via ezmsg
Expand Down
22 changes: 13 additions & 9 deletions src/ezmsg/vispy/frontends/main_window.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
import logging
import pickle
import select
from typing import Callable
from typing import Dict
from typing import Union

import qdarkstyle
from contextlib import contextmanager
from PyQt6 import QtCore, QtWidgets
from qdarkstyle.dark.palette import DarkPalette
from qdarkstyle.light.palette import LightPalette
from typing import Dict, Tuple, Union, Callable
from qtpy import QtCore
from qtpy import QtWidgets

from ..helpers.constants import UINT64_SIZE, BYTEORDER
from ..helpers.constants import BYTEORDER
from ..helpers.constants import UINT64_SIZE

# Silence logging for qdarkstyle package.
logging.getLogger("qdarkstyle").setLevel(logging.WARNING)
logger = logging.getLogger(__name__)

PYQT_SLOT = Union[Callable[..., None], QtCore.pyqtBoundSignal]
PYQT_SLOT = Union[Callable[..., None], QtCore.Signal]


def merge(a, b, path=None):
Expand Down Expand Up @@ -46,7 +50,7 @@ def wrapper(self):
def register_response(msg_type):
def _register(func):
registrations = {msg_type: [func]}
setattr(func, "callbacks", registrations)
func.callbacks = registrations
return func

return _register
Expand Down Expand Up @@ -74,7 +78,7 @@ def __call__(self, *args, **kwargs):


class EzMainWindow(QtWidgets.QMainWindow, metaclass=EzWindowMeta):
command_signal = QtCore.pyqtSignal(object)
command_signal = QtCore.Signal(object)

def __init__(self, command_socket, response_socket):
super().__init__()
Expand Down Expand Up @@ -129,7 +133,7 @@ def connect_plot_controls(self, obj):
self.set_command_signal(obj)
self.add_callbacks(obj)

@QtCore.pyqtSlot()
@QtCore.Slot()
def _on_response(self):
self.response_notification.setEnabled(False)
while True:
Expand All @@ -150,7 +154,7 @@ def _on_response(self):
callback(obj, msg)
self.response_notification.setEnabled(True)

@QtCore.pyqtSlot(object)
@QtCore.Slot(object)
def _on_command(self, msg):
raw = pickle.dumps(msg)
raw_size = len(raw).to_bytes(UINT64_SIZE, byteorder=BYTEORDER, signed=False)
Expand Down
Loading

0 comments on commit 75b1018

Please sign in to comment.