Brendan Haines 5c4f7e5c97
All checks were successful
Publish Python 🐍 distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Successful in -41s
Publish Python 🐍 distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been skipped
fix entry point
2024-12-18 23:55:06 -07:00

216 lines
6.5 KiB
Python

import sys
from pathlib import Path
from typing import Callable, List, Tuple
import matplotlib as mpl
import numpy as np
import xarray as xr
from matplotlib import pyplot as plt
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg
from matplotlib.ticker import EngFormatter
from numpy import typing as npt
from PySide6.QtCore import QSize, Qt
from PySide6.QtGui import QAction, QKeySequence, QShortcut
from PySide6.QtWidgets import (
QApplication,
QMainWindow,
QMenu,
QProgressBar,
QPushButton,
QToolBar,
QVBoxLayout,
QWidget,
)
from skrf import plotting as rf_plt
from charon_vna.gui_helpers import FlowLayout
from charon_vna.util import db20, s2vswr
# from vna import Charon
DEFAULT_CONFIG = dict(
frequency=np.arange(1e9, 2e9, 11), # Hz
power=-5, # dB
)
class PlotWidget(QWidget):
enabled_ports: List[Tuple[int | str]]
def __init__(self):
super().__init__()
self.enabled_ports = [(1, 1)]
layout = QVBoxLayout()
self.setLayout(layout)
self.fig = plt.Figure(figsize=(5, 4), dpi=100, tight_layout=True)
self.ax = self.fig.add_subplot(111)
self.setup_logmag()
# self.setup_smith()
self.ax.legend(loc="upper right")
canvas = FigureCanvasQTAgg(self.fig)
layout.addWidget(canvas)
# toolbar = QToolBar("Toolbar")
# toolbar.addAction("blah")
# self.addToolBar(toolbar)
def setup_rect(self) -> None:
self.ax.grid(True)
self.ax.xaxis.set_major_formatter(EngFormatter())
self.ax.set_xlabel("Frequency [Hz]")
def update_rect(self, data: xr.DataArray, func: Callable[[npt.ArrayLike], npt.ArrayLike]) -> None:
self.ax.set_xlim(data["frequency"].min().data, data["frequency"].max().data)
# remove old lines
for line in self.ax.lines:
line.remove()
for m, n in self.enabled_ports:
self.ax.plot(data["frequency"], func(data.sel(m=m, n=n)))
def setup_logmag(self, ylim: List[float] = [-30, 30]) -> None:
self.setup_rect()
self.ax.set_ylim(ylim)
self.ax.set_ylabel("Amplitude [dB]")
def update_logmag(self, data: xr.DataArray) -> None:
self.update_rect(data, db20)
def setup_phase(self) -> None:
self.setup_rect()
self.ax.set_ylim(-200, 200)
self.ax.set_ylabel("Phase [deg]")
def update_phase(self, data: xr.DataArray):
self.update_rect(data, lambda s: np.angle(s, deg=True))
def setup_vswr(self) -> None:
self.setup_rect()
self.ax.set_yticks(np.arange(1, 11))
self.ax.set_ylim(1, 10)
self.ax.set_ylabel("VSWR")
def update_vswr(self, data: xr.DataArray) -> None:
self.update_rect(data, s2vswr)
def setup_smith(self) -> None:
self.ax.grid(False)
self.ax.set_xlim(-1, 1)
self.ax.set_ylim(-1, 1)
self.ax.set_aspect("equal")
rf_plt.smith(ax=self.ax, smithR=1, chart_type="z", draw_vswr=None)
def update_smith(self, data: xr.DataArray) -> None:
# remove old lines
for line in self.ax.lines:
line.remove()
for m, n in self.enabled_ports:
sel = data.sel(m=m, n=n)
self.ax.plot(sel.real, sel.imag)
# Subclass QMainWindow to customize your application's main window
class MainWindow(QMainWindow):
config_path: Path | None
# device: Charon
def __init__(self):
super().__init__()
self.config_path = None
# self.device = Charon("ip:192.168.3.1", frequency=DEFAULT_CONFIG["frequency"])
mpl.use("QtAgg")
self.setWindowTitle("Charon VNA")
# Menu
menubar = self.menuBar()
menu_file = QMenu("&File")
menubar.addMenu(menu_file)
action_load_config = QAction("&Open Configuration", self)
menu_file.addAction(action_load_config)
action_load_config.triggered.connect(self.load_config)
QShortcut(QKeySequence("Ctrl+O"), self).activated.connect(self.load_config)
action_save_config = QAction("&Save Configuration", self)
menu_file.addAction(action_save_config)
action_save_config.triggered.connect(self.save_config)
QShortcut(QKeySequence("Ctrl+S"), self).activated.connect(self.save_config)
action_saveas_config = QAction("Save Configuration &As", self)
menu_file.addAction(action_saveas_config)
action_saveas_config.triggered.connect(self.saveas_config)
QShortcut(QKeySequence("Ctrl+Shift+S"), self).activated.connect(self.saveas_config)
menu_stimulus = QMenu("&Stimulus")
menubar.addMenu(menu_stimulus)
action_set_frequency = QAction("&Frequency", self)
menu_stimulus.addAction(action_set_frequency)
# action_set_frequency.triggered.connect(self.set_frequency)
action_set_power = QAction("&Power", self)
menu_stimulus.addAction(action_set_power)
# action_set_power.triggered.connect(self.set_power)
menu_calibration = QMenu("&Calibration")
menubar.addMenu(menu_calibration)
# Content
window_layout = QVBoxLayout()
prog_sweep = QProgressBar()
prog_sweep.setMinimum(0)
prog_sweep.setMaximum(100)
prog_sweep.setTextVisible(False)
prog_sweep.setValue(50)
window_layout.addWidget(prog_sweep)
# window_widget.se
plot_layout = QVBoxLayout()
# TODO: handle plots properly
for i in range(2):
plot0 = PlotWidget()
plot_layout.addWidget(plot0)
plot_widget = QWidget()
plot_widget.setLayout(plot_layout)
window_layout.addWidget(plot_widget)
# Set the central widget of the Window.
widget = QWidget()
widget.setLayout(window_layout)
self.setCentralWidget(widget)
def saveas_config(self) -> None:
print("Prompting for save path...")
# TODO: prompt for config path
self.config_path = Path(__file__).parent / "config.json"
print(f"Config path is now {self.config_path.resolve()}")
self.save_config()
def save_config(self) -> None:
if self.config_path is None:
self.saveas_config()
else:
print(f"saving config to {self.config_path.resolve()}")
# TODO: save config
def load_config(self) -> None:
print("loading config")
# TODO: load config
def main() -> None:
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
if __name__ == "__main__":
main()