From f3e02080d8e95c332198ace518d98c8d9e9d7009 Mon Sep 17 00:00:00 2001 From: Sepehr Madani <ssepehrmadani@gmail.com> Date: Mon, 3 Aug 2020 00:07:19 -0400 Subject: [PATCH] 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. --- utils/visualizerUI.py | 257 ++++++++++++++++++++++-------------------- 1 file changed, 132 insertions(+), 125 deletions(-) diff --git a/utils/visualizerUI.py b/utils/visualizerUI.py index e7c4340..2b87695 100644 --- a/utils/visualizerUI.py +++ b/utils/visualizerUI.py @@ -1,68 +1,70 @@ -from cmath import exp -from math import log10, pi +from cmath import phase +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.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 import style as ChartStyle from utils.pattern import compute_pattern, range_in_deg -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) -> 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) +# Define Size Policies +prefSizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) +fixedSizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) +expandingSizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) class NullingSlider(QSlider): - def __init__(self, orientation, parent, default_value): + def __init__(self, orientation, parent, value): super().__init__(orientation=orientation, parent=parent) self.parent = parent - self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)) + self.setSizePolicy(expandingSizePolicy) self.setMinimumSize(QSize(100, 0)) - self.setRange(0, 100) + self.setRange(0, 2**self.parent.options.bit_count - 1) self.setTickPosition(QSlider.TicksBelow) - self.setTickInterval(5) - self.connect_signal() - self.update_value(default_value) - - def connect_signal(self): + self.setTickInterval(1) + self.update_value(value) self.valueChanged.connect(lambda: self.parent.update_parameters(source=QSlider)) - def update_value(self, new_value): - self.valueChanged.disconnect() + def normal_value(self): + 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.connect_signal() + self.blockSignals(False) class NullingLineEdit(QLineEdit): - def __init__(self, parent, default_value): + def __init__(self, parent, value): super().__init__(parent=parent) self.parent = parent self.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)) - self.setFixedWidth(64) - self.connect_signal() - self.update_value(default_value) - - def connect_signal(self): - self.textChanged.connect( + self.setFixedWidth(96) + self.update_value(value) + self.editingFinished.connect( lambda: self.parent.update_parameters(source=QLineEdit) ) - def update_value(self, new_value): - self.textChanged.disconnect() - self.setText(str(new_value)) - self.connect_signal() + def normal_value(self): + angle = float(self.text()) + weight = complex(cos(angle), sin(angle)) + return weight + + def update_value(self, value): + self.setText(str(phase(value))) + self.home(False) class NullingLabel(QLabel): @@ -70,20 +72,24 @@ class NullingLabel(QLabel): super().__init__(parent=parent) self.setText(str(label)) self.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)) - self.setFixedWidth(32) + self.setFixedWidth(24) class MainWindow(QMainWindow): - def __init__(self, param_builder): + def __init__(self, algorithm, options): 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.setWindowTitle("Chart Display") + self.setWindowTitle('Chart Display') self.resize(1280, 720) - self.paramBuilder = param_builder - self.parameterSet = self.paramBuilder.build_params() self.create_actions() self.create_menus() self.create_status_bar() @@ -94,42 +100,42 @@ class MainWindow(QMainWindow): def create_actions(self): self.refreshAct = QAction( - "&Refresh chart", - self, + parent=self, + text='&Refresh chart', shortcut=QKeySequence.Refresh, - statusTip="Updates the plot on the right", + 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", + parent=self, + text='Use &Log10 for Chart', + 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, + parent=self, + text='&About', shortcut=QKeySequence.HelpContents, - statusTip="Displays info about this software", + 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", + parent=self, + text='About &Qt', + 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 = self.menuBar().addMenu('&Chart') + self.helpMenu = self.menuBar().addMenu('&Help') self.fileMenu.addAction(self.refreshAct) self.fileMenu.addAction(self.useLog10ForChartAct) @@ -137,45 +143,53 @@ class MainWindow(QMainWindow): self.helpMenu.addAction(self.aboutQtAct) def create_status_bar(self): - self.statusBar().showMessage("Ready") + self.statusBar().showMessage('Ready') def create_control_box(self): - self.parameterControlList = [] - self.labelList = [] - self.sliderList = [] - self.lineEditList = [] - self.toolboxLayout = QVBoxLayout() - - for parameter in self.parameterSet: - # Create widgets for each parameter - self.labelList.append(NullingLabel(parent=self, label=parameter.name)) - self.lineEditList.append(NullingLineEdit(self, parameter.value)) - self.sliderList.append(NullingSlider(Qt.Horizontal, self, parameter.scaleTo100Ratio())) - - # Bundle layout for label, line edit, and slider - self.parameterControlList.append(QHBoxLayout()) - self.parameterControlList[-1].addWidget(self.labelList[-1]) - self.parameterControlList[-1].addWidget(self.lineEditList[-1]) - self.parameterControlList[-1].addWidget(self.sliderList[-1]) - - self.toolboxLayout.addLayout(self.parameterControlList[-1]) - - self.rerunButton = QPushButton("Re-run") + self.toolbox_layout = QVBoxLayout() + + # Create widgets for each parameter + self.weight_control_list = [ + QHBoxLayout() + for _ in range(len(self.final_weights)) + ] + self.weight_label_list = [ + NullingLabel(parent=self, label=f'a{n + 1}') + for n in range(len(self.final_weights)) + ] + self.weight_editor_list = [ + NullingLineEdit(parent=self, value=z) + for z in self.final_weights + ] + self.weight_slider_list = [ + NullingSlider(orientation=Qt.Horizontal, parent=self, value=z) + for z in self.final_weights + ] + + 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.toolboxLayout.addWidget(self.rerunButton) + self.toolbox_layout.addWidget(self.rerunButton) self.toolboxSpacer = QSpacerItem( 20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding ) - self.toolboxLayout.addItem(self.toolboxSpacer) + self.toolbox_layout.addItem(self.toolboxSpacer) def create_plot_box(self): self.plotWidget = QWidget(self.centralWidget) - self.plotWidget.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)) + self.plotWidget.setSizePolicy(expandingSizePolicy) self.plotWidget.setMinimumSize(QSize(720, 480)) 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.chart = self.canvas.figure.subplots() self.canvas.figure.set_tight_layout(True) @@ -183,86 +197,79 @@ class MainWindow(QMainWindow): def set_up_central_widget(self): self.centralLayout = QHBoxLayout(self.centralWidget) - self.centralLayout.addLayout(self.toolboxLayout) + self.centralLayout.addLayout(self.toolbox_layout) 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=None): - for slider, lineEdit, parameter in zip( - self.sliderList, self.lineEditList, self.parameterSet + for slider, editor, ii in zip( + self.weight_slider_list, self.weight_editor_list, range(len(self.final_weights)) ): if source == QSlider: - newValue = parameter.scaleFrom100Ratio(slider.value()) - parameter.value = newValue - lineEdit.update_value(newValue) + newValue = slider.normal_value() if source == QLineEdit: try: - parameter.value = float(lineEdit.text()) - slider.update_value(parameter.scaleTo100Ratio()) + newValue = editor.normal_value() except ValueError: - pass + newValue = self.final_weights[ii] if source == None: - slider.update_value(parameter.scaleTo100Ratio()) - lineEdit.update_value(round(parameter.value, 4)) + newValue = self.final_weights[ii] + + self.final_weights[ii] = newValue + slider.update_value(newValue) + editor.update_value(newValue) self.update_chart() def update_chart(self): - self.statusBar().showMessage("Updating the chart...") + self.statusBar().showMessage('Updating the chart...') self.chart.clear() self.chart.set_xticks([10 * x for x in range(19)]) - self.chart.set_xlabel("Degrees (°)") - self.chart.grid(True, linestyle="--") + self.chart.set_yticks([10 * y for y in range(-7, 3)]) + 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) + data_x = compute_pattern(weights=self.final_weights) for elem in data_x: if elem < 0: - raise ValueError("negative values in list 'data_x'") + 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)") + data_x = list(map(lambda x: 20 * log10(x), data_x)) + self.chart.set_ylim(-70, 20 * log10(16) + 1) + self.chart.set_ylabel('dB (scaled with log10)') else: self.chart.set_ylim(0, 18) - self.chart.set_ylabel("dB") + 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") + raise ValueError('resolution doesn\'t match with data_x\'s length') self.chart.plot(data_y, data_x) self.chart.figure.canvas.draw() - self.statusBar().showMessage("Ready") + self.statusBar().showMessage('Ready') def call_algorithm(self): - A = [x.value for x in self.parameterSet] - self.parameterSet = self.paramBuilder.build_params() - 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() + self.final_weights = self.algorithm.solve()[0] + self.update_parameters() 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>.", + '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") + QMessageBox.aboutQt(self, 'About Qt') -- GitLab