-
Sepehr Madani authoredSepehr Madani authored
visualizerUI.py 8.15 KiB
import sys
from cmath import exp
from math import log10, pi
from PySide2.QtCore import QCoreApplication, QMetaObject, QRect, QSize, Qt
from PySide2.QtWidgets import *
from PySide2.QtGui import QKeySequence
from matplotlib.backends.backend_qt5agg import FigureCanvas, NavigationToolbar2QT
from matplotlib.figure import Figure
from utils.pattern import compute_pattern, range_in_deg
# Define Size Policies
prefSizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
fixedSizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)
expandingSizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
class Parameter:
def __init__(self, name, value, min=0.0, max=1.0):
self.name = name
self.value = value
self.min = min
self.max = max
def scaleTo100Ratio(self, value) -> int:
return int(100 * ((value - self.min) / (self.max - self.min)))
def scaleFrom100Ratio(self, ratio_value) -> float:
return self.min + (ratio_value / 100) * (self.max - self.min)
class MainWindow(QMainWindow):
def __init__(self, parameterSet):
super().__init__()
self.centralWidget = QWidget()
self.setCentralWidget(self.centralWidget)
self.setWindowTitle("Chart Display")
self.resize(1280, 720)
self.parameterSet = parameterSet
self.create_actions()
self.create_menus()
self.create_status_bar()
self.create_control_box()
self.create_plot_box()
self.set_up_central_widget()
self.update_chart()
def create_actions(self):
self.refreshAct = QAction(
"&Refresh chart",
self,
shortcut=QKeySequence.Refresh,
statusTip="Updates the plot on the right",
triggered=self.update_chart,
)
self.useLog10ForChartAct = QAction(
"Use &Log10 for Chart",
self,
shortcut="Ctrl+L",
statusTip="If checked, scales the y-axis of chart using log10",
triggered=self.update_chart,
checkable=True,
)
self.useLog10ForChartAct.setChecked(True)
self.aboutAct = QAction(
"&About",
self,
shortcut=QKeySequence.HelpContents,
statusTip="Displays info about this software",
triggered=self.about,
)
self.aboutQtAct = QAction(
"About &Qt",
self,
shortcut="Ctrl+F1",
statusTip="Show the Qt library's About box",
triggered=self.aboutQt,
)
def create_menus(self):
self.fileMenu = self.menuBar().addMenu("&Chart")
self.helpMenu = self.menuBar().addMenu("&Help")
self.fileMenu.addAction(self.refreshAct)
self.fileMenu.addAction(self.useLog10ForChartAct)
self.helpMenu.addAction(self.aboutAct)
self.helpMenu.addAction(self.aboutQtAct)
def create_status_bar(self):
self.statusBar().showMessage("Ready")
def about(self):
QMessageBox.about(
self,
"About Nulling-Python",
"<b>Nulling-Python</b> is a small tool for analyzing and testing"
" algorithms for nulling systems of mmWave WLANs. <br/>"
" It is developed by Sepehr and Sohrab Madani and available on"
"<a href='https://gitlab.engr.illinois.edu/smadani2/nulling-python'>"
" UIUC Engineering Department Gitlab</a>.",
)
def aboutQt(self):
QMessageBox.aboutQt(self, "About Qt")
def create_control_box(self):
self.parameterControlList = []
self.labelList = []
self.sliderList = []
self.lineEditList = []
self.toolboxLayout = QVBoxLayout()
for parameter in self.parameterSet:
# Label
newLabel = QLabel(self.centralWidget)
newLabel.setText("Var {}".format(parameter.name))
newLabel.setSizePolicy(fixedSizePolicy)
newLabel.setFixedWidth(48)
# Line Edit
newLineEdit = QLineEdit(self.centralWidget)
newLineEdit.setSizePolicy(fixedSizePolicy)
newLineEdit.setFixedWidth(36)
newLineEdit.setText(str(parameter.value))
newLineEdit.textChanged.connect(
lambda: self.update_parameters(source=QLineEdit)
)
newLabel.setBuddy(newLineEdit)
# Horizontal Slider
newSlider = QSlider(Qt.Horizontal, self.centralWidget)
newSlider.setSizePolicy(expandingSizePolicy)
newSlider.setMinimumSize(QSize(100, 0))
newSlider.setRange(0, 100)
newSlider.setValue(parameter.scaleTo100Ratio(parameter.value))
newSlider.setTickPosition(QSlider.TicksBelow)
newSlider.setTickInterval(5)
newSlider.valueChanged.connect(
lambda: self.update_parameters(source=QSlider)
)
# Bundle layout for label, line edit, and slider
newControlLayout = QHBoxLayout()
newControlLayout.addWidget(newLabel)
newControlLayout.addWidget(newLineEdit)
newControlLayout.addWidget(newSlider)
self.parameterControlList.append(newControlLayout)
self.labelList.append(newLabel)
self.lineEditList.append(newLineEdit)
self.sliderList.append(newSlider)
self.toolboxLayout.addLayout(newControlLayout)
self.toolboxSpacer = QSpacerItem(
20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding
)
self.toolboxLayout.addItem(self.toolboxSpacer)
def create_plot_box(self):
self.plotWidget = QWidget(self.centralWidget)
self.plotWidget.setSizePolicy(expandingSizePolicy)
self.plotWidget.setMinimumSize(QSize(720, 480))
self.plotLayout = QVBoxLayout(self.plotWidget)
self.canvas = FigureCanvas(Figure(figsize=(5, 3)))
self.addToolBar(NavigationToolbar2QT(self.canvas, self))
self.chart = self.canvas.figure.subplots()
self.canvas.figure.set_tight_layout(True)
self.plotLayout.addWidget(self.canvas)
def set_up_central_widget(self):
self.centralLayout = QHBoxLayout(self.centralWidget)
self.centralLayout.addLayout(self.toolboxLayout)
self.centralLayout.addWidget(self.plotWidget)
self.centralLayout.setStretch(0, 2)
self.centralLayout.setStretch(1, 5)
self.centralWidget.setLayout(self.centralLayout)
def update_parameters(self, source):
for slider, lineEdit, parameter in zip(
self.sliderList, self.lineEditList, self.parameterSet
):
if source == QSlider:
newValue = parameter.scaleFrom100Ratio(slider.value())
parameter.value = newValue
lineEdit.setText(str(newValue))
if source == QLineEdit:
try:
newValue = float(lineEdit.text())
parameter.value = newValue
slider.setValue(parameter.scaleTo100Ratio(newValue))
except ValueError:
pass
self.update_chart()
def update_chart(self):
self.chart.clear()
self.chart.set_xticks([10 * x for x in range(19)])
self.chart.set_xlabel("Degrees (°)")
self.chart.grid(True, linestyle="--")
customWeights = [exp(2 * pi * 1j * x.value) for x in self.parameterSet] + (
[1] * (16 - len(self.parameterSet))
)
data_x = compute_pattern(weights=customWeights)
for elem in data_x:
if elem < 0:
raise ValueError("negative values in list 'data_x'")
if self.useLog10ForChartAct.isChecked():
data_x = list(map(lambda x: 10 * log10(x), data_x))
self.chart.set_ylim(-30, 10 * log10(16) + 1)
self.chart.set_ylabel("dB (scaled with log10)")
else:
self.chart.set_ylim(0, 18)
self.chart.set_ylabel("dB")
data_y = range_in_deg(0.1)
if len(data_x) != len(data_y):
raise ValueError("resolution doesn't match with data_x's length")
self.chart.plot(data_y, data_x)
self.chart.figure.canvas.draw()