Skip to content
Snippets Groups Projects
Commit f3e02080 authored by Sepehr Madani's avatar Sepehr Madani
Browse files

Fix QSlider and QLineEdit functionallities

Now, the QWidgets in each control layout will behave as expected.
QSliders now move on bit_count ticks, as per genes in GeneticAlgorithm.
Algorithm re-run button now also functions properly.
parent b51992a5
No related branches found
No related tags found
No related merge requests found
from cmath import exp from cmath import phase
from math import log10, pi from math import log10, pi, sin, cos
from PySide2.QtCore import QCoreApplication, QMetaObject, QRect, QSize, Qt from PySide2.QtCore import QSize, Qt
from PySide2.QtWidgets import * from PySide2.QtWidgets import *
from PySide2.QtGui import QKeySequence from PySide2.QtGui import QKeySequence
from matplotlib.backends.backend_qt5agg import FigureCanvas, NavigationToolbar2QT from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT
from matplotlib.figure import Figure from matplotlib.figure import Figure
from matplotlib import style as ChartStyle
from utils.pattern import compute_pattern, range_in_deg from utils.pattern import compute_pattern, range_in_deg
class Parameter: # Define Size Policies
def __init__(self, name, value, min=0.0, max=1.0): prefSizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
self.name = name fixedSizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)
self.value = value expandingSizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
self.min = min
self.max = max
def scaleTo100Ratio(self) -> int:
return int(100 * ((self.value - self.min) / (self.max - self.min)))
def scaleFrom100Ratio(self, ratio_value) -> float:
return self.min + (ratio_value / 100) * (self.max - self.min)
class NullingSlider(QSlider): class NullingSlider(QSlider):
def __init__(self, orientation, parent, default_value): def __init__(self, orientation, parent, value):
super().__init__(orientation=orientation, parent=parent) super().__init__(orientation=orientation, parent=parent)
self.parent = parent self.parent = parent
self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)) self.setSizePolicy(expandingSizePolicy)
self.setMinimumSize(QSize(100, 0)) self.setMinimumSize(QSize(100, 0))
self.setRange(0, 100) self.setRange(0, 2**self.parent.options.bit_count - 1)
self.setTickPosition(QSlider.TicksBelow) self.setTickPosition(QSlider.TicksBelow)
self.setTickInterval(5) self.setTickInterval(1)
self.connect_signal() self.update_value(value)
self.update_value(default_value)
def connect_signal(self):
self.valueChanged.connect(lambda: self.parent.update_parameters(source=QSlider)) self.valueChanged.connect(lambda: self.parent.update_parameters(source=QSlider))
def update_value(self, new_value): def normal_value(self):
self.valueChanged.disconnect() bit_c = self.parent.options.bit_count
bit_res = self.parent.options.bit_resolution
angle = (self.value() - (2**bit_c-1)/2) * (2*pi) / (2**bit_res)
weight = complex(cos(angle), sin(angle))
return weight
def update_value(self, weight):
bit_c = self.parent.options.bit_count
bit_res = self.parent.options.bit_resolution
angle = phase(weight) # (2*pi * value) - pi
new_value = int((angle * 2**bit_res / (2*pi)) + 0.5*(2**bit_c-1))
self.blockSignals(True)
self.setValue(new_value) self.setValue(new_value)
self.connect_signal() self.blockSignals(False)
class NullingLineEdit(QLineEdit): class NullingLineEdit(QLineEdit):
def __init__(self, parent, default_value): def __init__(self, parent, value):
super().__init__(parent=parent) super().__init__(parent=parent)
self.parent = parent self.parent = parent
self.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)) self.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred))
self.setFixedWidth(64) self.setFixedWidth(96)
self.connect_signal() self.update_value(value)
self.update_value(default_value) self.editingFinished.connect(
def connect_signal(self):
self.textChanged.connect(
lambda: self.parent.update_parameters(source=QLineEdit) lambda: self.parent.update_parameters(source=QLineEdit)
) )
def update_value(self, new_value): def normal_value(self):
self.textChanged.disconnect() angle = float(self.text())
self.setText(str(new_value)) weight = complex(cos(angle), sin(angle))
self.connect_signal() return weight
def update_value(self, value):
self.setText(str(phase(value)))
self.home(False)
class NullingLabel(QLabel): class NullingLabel(QLabel):
...@@ -70,20 +72,24 @@ class NullingLabel(QLabel): ...@@ -70,20 +72,24 @@ class NullingLabel(QLabel):
super().__init__(parent=parent) super().__init__(parent=parent)
self.setText(str(label)) self.setText(str(label))
self.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)) self.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred))
self.setFixedWidth(32) self.setFixedWidth(24)
class MainWindow(QMainWindow): class MainWindow(QMainWindow):
def __init__(self, param_builder): def __init__(self, algorithm, options):
super().__init__() super().__init__()
self.centralWidget = QWidget() self.algorithm = algorithm
self.options = options
self.final_weights = self.algorithm.solve()[0]
# self.theta_weights = [phase(x) for x in self.final_weights]
# self.normal_weights = [(angle + pi) / (2*pi) for angle in self.theta_weights]
self.centralWidget = QWidget(self)
self.setCentralWidget(self.centralWidget) self.setCentralWidget(self.centralWidget)
self.setWindowTitle("Chart Display") self.setWindowTitle('Chart Display')
self.resize(1280, 720) self.resize(1280, 720)
self.paramBuilder = param_builder
self.parameterSet = self.paramBuilder.build_params()
self.create_actions() self.create_actions()
self.create_menus() self.create_menus()
self.create_status_bar() self.create_status_bar()
...@@ -94,42 +100,42 @@ class MainWindow(QMainWindow): ...@@ -94,42 +100,42 @@ class MainWindow(QMainWindow):
def create_actions(self): def create_actions(self):
self.refreshAct = QAction( self.refreshAct = QAction(
"&Refresh chart", parent=self,
self, text='&Refresh chart',
shortcut=QKeySequence.Refresh, shortcut=QKeySequence.Refresh,
statusTip="Updates the plot on the right", statusTip='Updates the plot on the right',
triggered=self.update_chart, triggered=self.update_chart,
) )
self.useLog10ForChartAct = QAction( self.useLog10ForChartAct = QAction(
"Use &Log10 for Chart", parent=self,
self, text='Use &Log10 for Chart',
shortcut="Ctrl+L", shortcut='Ctrl+L',
statusTip="If checked, scales the y-axis of chart using log10", statusTip='If checked, scales the y-axis of chart using log10',
triggered=self.update_chart, triggered=self.update_chart,
checkable=True, checkable=True,
) )
self.useLog10ForChartAct.setChecked(True) self.useLog10ForChartAct.setChecked(True)
self.aboutAct = QAction( self.aboutAct = QAction(
"&About", parent=self,
self, text='&About',
shortcut=QKeySequence.HelpContents, shortcut=QKeySequence.HelpContents,
statusTip="Displays info about this software", statusTip='Displays info about this software',
triggered=self.about, triggered=self.about,
) )
self.aboutQtAct = QAction( self.aboutQtAct = QAction(
"About &Qt", parent=self,
self, text='About &Qt',
shortcut="Ctrl+F1", shortcut='Ctrl+F1',
statusTip="Show the Qt library's About box", statusTip='Show the Qt library\'s About box',
triggered=self.aboutQt, triggered=self.aboutQt,
) )
def create_menus(self): def create_menus(self):
self.fileMenu = self.menuBar().addMenu("&Chart") self.fileMenu = self.menuBar().addMenu('&Chart')
self.helpMenu = self.menuBar().addMenu("&Help") self.helpMenu = self.menuBar().addMenu('&Help')
self.fileMenu.addAction(self.refreshAct) self.fileMenu.addAction(self.refreshAct)
self.fileMenu.addAction(self.useLog10ForChartAct) self.fileMenu.addAction(self.useLog10ForChartAct)
...@@ -137,45 +143,53 @@ class MainWindow(QMainWindow): ...@@ -137,45 +143,53 @@ class MainWindow(QMainWindow):
self.helpMenu.addAction(self.aboutQtAct) self.helpMenu.addAction(self.aboutQtAct)
def create_status_bar(self): def create_status_bar(self):
self.statusBar().showMessage("Ready") self.statusBar().showMessage('Ready')
def create_control_box(self): def create_control_box(self):
self.parameterControlList = [] self.toolbox_layout = QVBoxLayout()
self.labelList = []
self.sliderList = [] # Create widgets for each parameter
self.lineEditList = [] self.weight_control_list = [
self.toolboxLayout = QVBoxLayout() QHBoxLayout()
for _ in range(len(self.final_weights))
for parameter in self.parameterSet: ]
# Create widgets for each parameter self.weight_label_list = [
self.labelList.append(NullingLabel(parent=self, label=parameter.name)) NullingLabel(parent=self, label=f'a{n + 1}')
self.lineEditList.append(NullingLineEdit(self, parameter.value)) for n in range(len(self.final_weights))
self.sliderList.append(NullingSlider(Qt.Horizontal, self, parameter.scaleTo100Ratio())) ]
self.weight_editor_list = [
# Bundle layout for label, line edit, and slider NullingLineEdit(parent=self, value=z)
self.parameterControlList.append(QHBoxLayout()) for z in self.final_weights
self.parameterControlList[-1].addWidget(self.labelList[-1]) ]
self.parameterControlList[-1].addWidget(self.lineEditList[-1]) self.weight_slider_list = [
self.parameterControlList[-1].addWidget(self.sliderList[-1]) NullingSlider(orientation=Qt.Horizontal, parent=self, value=z)
for z in self.final_weights
self.toolboxLayout.addLayout(self.parameterControlList[-1]) ]
self.rerunButton = QPushButton("Re-run") for ii in range(len(self.final_weights)):
self.weight_control_list[ii].addWidget(self.weight_label_list[ii])
self.weight_control_list[ii].addWidget(self.weight_editor_list[ii])
self.weight_control_list[ii].addWidget(self.weight_slider_list[ii])
self.toolbox_layout.addLayout(self.weight_control_list[ii])
self.rerunButton = QPushButton('Re-run')
self.rerunButton.clicked.connect(self.call_algorithm) self.rerunButton.clicked.connect(self.call_algorithm)
self.toolboxLayout.addWidget(self.rerunButton) self.toolbox_layout.addWidget(self.rerunButton)
self.toolboxSpacer = QSpacerItem( self.toolboxSpacer = QSpacerItem(
20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding 20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding
) )
self.toolboxLayout.addItem(self.toolboxSpacer) self.toolbox_layout.addItem(self.toolboxSpacer)
def create_plot_box(self): def create_plot_box(self):
self.plotWidget = QWidget(self.centralWidget) self.plotWidget = QWidget(self.centralWidget)
self.plotWidget.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)) self.plotWidget.setSizePolicy(expandingSizePolicy)
self.plotWidget.setMinimumSize(QSize(720, 480)) self.plotWidget.setMinimumSize(QSize(720, 480))
self.plotLayout = QVBoxLayout(self.plotWidget) self.plotLayout = QVBoxLayout(self.plotWidget)
self.canvas = FigureCanvas(Figure(figsize=(5, 3))) ChartStyle.use("ggplot")
self.canvas = FigureCanvasQTAgg(Figure(figsize=(5, 3)))
self.addToolBar(NavigationToolbar2QT(self.canvas, self)) self.addToolBar(NavigationToolbar2QT(self.canvas, self))
self.chart = self.canvas.figure.subplots() self.chart = self.canvas.figure.subplots()
self.canvas.figure.set_tight_layout(True) self.canvas.figure.set_tight_layout(True)
...@@ -183,86 +197,79 @@ class MainWindow(QMainWindow): ...@@ -183,86 +197,79 @@ class MainWindow(QMainWindow):
def set_up_central_widget(self): def set_up_central_widget(self):
self.centralLayout = QHBoxLayout(self.centralWidget) self.centralLayout = QHBoxLayout(self.centralWidget)
self.centralLayout.addLayout(self.toolboxLayout) self.centralLayout.addLayout(self.toolbox_layout)
self.centralLayout.addWidget(self.plotWidget) self.centralLayout.addWidget(self.plotWidget)
self.centralLayout.setStretch(0, 2) self.centralLayout.setStretch(0, 2)
self.centralLayout.setStretch(1, 5) self.centralLayout.setStretch(1, 5)
self.centralWidget.setLayout(self.centralLayout) self.centralWidget.setLayout(self.centralLayout)
def update_parameters(self, source=None): def update_parameters(self, source=None):
for slider, lineEdit, parameter in zip( for slider, editor, ii in zip(
self.sliderList, self.lineEditList, self.parameterSet self.weight_slider_list, self.weight_editor_list, range(len(self.final_weights))
): ):
if source == QSlider: if source == QSlider:
newValue = parameter.scaleFrom100Ratio(slider.value()) newValue = slider.normal_value()
parameter.value = newValue
lineEdit.update_value(newValue)
if source == QLineEdit: if source == QLineEdit:
try: try:
parameter.value = float(lineEdit.text()) newValue = editor.normal_value()
slider.update_value(parameter.scaleTo100Ratio())
except ValueError: except ValueError:
pass newValue = self.final_weights[ii]
if source == None: if source == None:
slider.update_value(parameter.scaleTo100Ratio()) newValue = self.final_weights[ii]
lineEdit.update_value(round(parameter.value, 4))
self.final_weights[ii] = newValue
slider.update_value(newValue)
editor.update_value(newValue)
self.update_chart() self.update_chart()
def update_chart(self): def update_chart(self):
self.statusBar().showMessage("Updating the chart...") self.statusBar().showMessage('Updating the chart...')
self.chart.clear() self.chart.clear()
self.chart.set_xticks([10 * x for x in range(19)]) self.chart.set_xticks([10 * x for x in range(19)])
self.chart.set_xlabel("Degrees (°)") self.chart.set_yticks([10 * y for y in range(-7, 3)])
self.chart.grid(True, linestyle="--") self.chart.set_xlabel('Degrees (°)')
self.chart.grid(True, linestyle='--')
customWeights = [exp(2 * pi * 1j * x.value) for x in self.parameterSet] + ( data_x = compute_pattern(weights=self.final_weights)
[1] * (16 - len(self.parameterSet))
)
data_x = compute_pattern(weights=customWeights)
for elem in data_x: for elem in data_x:
if elem < 0: if elem < 0:
raise ValueError("negative values in list 'data_x'") raise ValueError('negative values in list "data_x"')
if self.useLog10ForChartAct.isChecked(): if self.useLog10ForChartAct.isChecked():
data_x = list(map(lambda x: 10 * log10(x), data_x)) data_x = list(map(lambda x: 20 * log10(x), data_x))
self.chart.set_ylim(-30, 10 * log10(16) + 1) self.chart.set_ylim(-70, 20 * log10(16) + 1)
self.chart.set_ylabel("dB (scaled with log10)") self.chart.set_ylabel('dB (scaled with log10)')
else: else:
self.chart.set_ylim(0, 18) self.chart.set_ylim(0, 18)
self.chart.set_ylabel("dB") self.chart.set_ylabel('dB')
data_y = range_in_deg(0.1) data_y = range_in_deg(0.1)
if len(data_x) != len(data_y): if len(data_x) != len(data_y):
raise ValueError("resolution doesn't match with data_x's length") raise ValueError('resolution doesn\'t match with data_x\'s length')
self.chart.plot(data_y, data_x) self.chart.plot(data_y, data_x)
self.chart.figure.canvas.draw() self.chart.figure.canvas.draw()
self.statusBar().showMessage("Ready") self.statusBar().showMessage('Ready')
def call_algorithm(self): def call_algorithm(self):
A = [x.value for x in self.parameterSet] self.final_weights = self.algorithm.solve()[0]
self.parameterSet = self.paramBuilder.build_params() self.update_parameters()
B = [x.value for x in self.parameterSet]
diff = [int(100 * (a - b)) for a, b in zip(A, B)]
if sum(list(map(abs, diff))) > 0:
print(diff)
self.update_parameters()
def about(self): def about(self):
QMessageBox.about( QMessageBox.about(
self, self,
"About Nulling-Python", 'About Nulling-Python',
"<b>Nulling-Python</b> is a small tool for analyzing and testing" '<b>Nulling-Python</b> is a small tool for analyzing and testing'
" algorithms for nulling systems of mmWave WLANs. <br/>" ' algorithms for nulling systems of mmWave WLANs. <br/>'
" It is developed by Sepehr and Sohrab Madani and available on" ' It is developed by Sepehr and Sohrab Madani and available on'
"<a href='https://gitlab.engr.illinois.edu/smadani2/nulling-python'>" '<a href="https://gitlab.engr.illinois.edu/smadani2/nulling-python">'
" UIUC Engineering Department Gitlab</a>.", ' UIUC Engineering Department Gitlab</a>.',
) )
def aboutQt(self): def aboutQt(self):
QMessageBox.aboutQt(self, "About Qt") QMessageBox.aboutQt(self, 'About Qt')
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment