Skip to content
Snippets Groups Projects
Commit 47a0cefd authored by Oscar Gustafsson's avatar Oscar Gustafsson :bicyclist:
Browse files

Refactor signal generation GUI

parent 27ba1525
No related branches found
No related tags found
1 merge request!200Refactor signal generation GUI
Pipeline #90215 passed
# -*- coding: utf-8 -*-
from qtpy.QtWidgets import QGridLayout, QLabel, QLineEdit, QSpinBox
from b_asic.signal_generator import (
Constant,
Gaussian,
Impulse,
SignalGenerator,
Sinusoid,
Step,
Uniform,
ZeroPad,
)
class SignalGeneratorInput(QGridLayout):
"""Abstract class for graphically configuring and generating signal generators."""
def __init__(self, logger, *args, **kwargs):
super().__init__(*args, **kwargs)
self._logger = logger
def get_generator(self) -> SignalGenerator:
"""Return the SignalGenerator based on the graphical input."""
raise NotImplementedError
class DelayInput(SignalGeneratorInput):
"""
Abstract class for graphically configuring and generating signal generators that
have a single delay parameter.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.delay_label = QLabel("Delay")
self.addWidget(self.delay_label, 0, 0)
self.delay_spin_box = QSpinBox()
self.delay_spin_box.setRange(0, 2147483647)
self.addWidget(self.delay_spin_box, 0, 1)
def get_generator(self) -> SignalGenerator:
raise NotImplementedError
class ImpulseInput(DelayInput):
"""
Class for graphically configuring and generating a
:class:`~b_asic.signal_generators.Impulse` signal generator.
"""
def get_generator(self) -> SignalGenerator:
return Impulse(self.delay_spin_box.value())
class StepInput(DelayInput):
"""
Class for graphically configuring and generating a
:class:`~b_asic.signal_generators.Step` signal generator.
"""
def get_generator(self) -> SignalGenerator:
return Step(self.delay_spin_box.value())
class ZeroPadInput(SignalGeneratorInput):
"""
Class for graphically configuring and generating a
:class:`~b_asic.signal_generators.ZeroPad` signal generator.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.input_label = QLabel("Input")
self.addWidget(self.input_label, 0, 0)
self.input_sequence = QLineEdit()
self.addWidget(self.input_sequence, 0, 1)
def get_generator(self) -> SignalGenerator:
input_values = []
for val in self.input_sequence.text().split(","):
val = val.strip()
try:
if not val:
val = 0
val = complex(val)
except ValueError:
self._logger.warning(f"Skipping value: {val}, not a digit.")
continue
input_values.append(val)
return ZeroPad(input_values)
class SinusoidInput(SignalGeneratorInput):
"""
Class for graphically configuring and generating a
:class:`~b_asic.signal_generators.Sinusoid` signal generator.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.frequency_label = QLabel("Frequency")
self.addWidget(self.frequency_label, 0, 0)
self.frequency_input = QLineEdit()
self.addWidget(self.frequency_input, 0, 1)
self.phase_label = QLabel("Phase")
self.addWidget(self.phase_label, 1, 0)
self.phase_input = QLineEdit()
self.addWidget(self.phase_input, 1, 1)
def get_generator(self) -> SignalGenerator:
frequency = self.frequency_input.text().strip()
try:
if not frequency:
frequency = 0.1
frequency = float(frequency)
except ValueError:
self._logger.warning(f"Cannot parse frequency: {frequency} not a number.")
frequency = 0.1
phase = self.phase_input.text().strip()
try:
if not phase:
phase = 0
phase = float(phase)
except ValueError:
self._logger.warning(f"Cannot parse phase: {phase} not a number.")
phase = 0
return Sinusoid(frequency, phase)
class GaussianInput(SignalGeneratorInput):
"""
Class for graphically configuring and generating a
:class:`~b_asic.signal_generators.Gaussian` signal generator.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.scale_label = QLabel("Standard deviation")
self.addWidget(self.scale_label, 0, 0)
self.scale_input = QLineEdit()
self.scale_input.setText("1.0")
self.addWidget(self.scale_input, 0, 1)
self.loc_label = QLabel("Average value")
self.addWidget(self.loc_label, 1, 0)
self.loc_input = QLineEdit()
self.loc_input.setText("0.0")
self.addWidget(self.loc_input, 1, 1)
self.seed_label = QLabel("Seed")
self.addWidget(self.seed_label, 2, 0)
self.seed_spin_box = QSpinBox()
self.seed_spin_box.setRange(0, 2147483647)
self.addWidget(self.seed_spin_box, 2, 1)
def get_generator(self) -> SignalGenerator:
scale = self.scale_input.text().strip()
try:
if not scale:
scale = 1
scale = float(scale)
except ValueError:
self._logger.warning(f"Cannot parse scale: {scale} not a number.")
scale = 1
loc = self.loc_input.text().strip()
try:
if not loc:
loc = 0
loc = float(loc)
except ValueError:
self._logger.warning(f"Cannot parse loc: {loc} not a number.")
loc = 0
return Gaussian(self.seed_spin_box.value(), loc, scale)
class UniformInput(SignalGeneratorInput):
"""
Class for graphically configuring and generating a
:class:`~b_asic.signal_generators.Uniform` signal generator.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.low_label = QLabel("Lower bound")
self.addWidget(self.low_label, 0, 0)
self.low_input = QLineEdit()
self.low_input.setText("-1.0")
self.addWidget(self.low_input, 0, 1)
self.high_label = QLabel("Upper bound")
self.addWidget(self.high_label, 1, 0)
self.high_input = QLineEdit()
self.high_input.setText("1.0")
self.addWidget(self.high_input, 1, 1)
self.seed_label = QLabel("Seed")
self.addWidget(self.seed_label, 2, 0)
self.seed_spin_box = QSpinBox()
self.seed_spin_box.setRange(0, 2147483647)
self.addWidget(self.seed_spin_box, 2, 1)
def get_generator(self) -> SignalGenerator:
low = self.low_input.text().strip()
try:
if not low:
low = -1.0
low = float(low)
except ValueError:
self._logger.warning(f"Cannot parse low: {low} not a number.")
low = -1.0
high = self.high_input.text().strip()
try:
if not high:
high = 1.0
high = float(high)
except ValueError:
self._logger.warning(f"Cannot parse high: {high} not a number.")
high = 1.0
return Uniform(self.seed_spin_box.value(), low, high)
class ConstantInput(SignalGeneratorInput):
"""
Class for graphically configuring and generating a
:class:`~b_asic.signal_generators.Constant` signal generator.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.constant_label = QLabel("Constant")
self.addWidget(self.constant_label, 0, 0)
self.constant_input = QLineEdit()
self.constant_input.setText("1.0")
self.addWidget(self.constant_input, 0, 1)
def get_generator(self) -> SignalGenerator:
constant = self.constant_input.text().strip()
try:
if not constant:
constant = 1.0
constant = complex(constant)
except ValueError:
self._logger.warning(f"Cannot parse constant: {constant} not a number.")
constant = 0.0
return Constant(constant)
_GENERATOR_MAPPING = {
"Constant": ConstantInput,
"Gaussian": GaussianInput,
"Impulse": ImpulseInput,
"Sinusoid": SinusoidInput,
"Step": StepInput,
"Uniform": UniformInput,
"ZeroPad": ZeroPadInput,
}
...@@ -2,9 +2,7 @@ ...@@ -2,9 +2,7 @@
B-ASIC window to simulate an SFG. B-ASIC window to simulate an SFG.
""" """
import numpy as np import numpy as np
from matplotlib.backends.backend_qt5agg import ( from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
FigureCanvasQTAgg as FigureCanvas,
)
from matplotlib.figure import Figure from matplotlib.figure import Figure
from qtpy.QtCore import Qt, Signal from qtpy.QtCore import Qt, Signal
from qtpy.QtGui import QKeySequence from qtpy.QtGui import QKeySequence
...@@ -26,7 +24,7 @@ from qtpy.QtWidgets import ( ...@@ -26,7 +24,7 @@ from qtpy.QtWidgets import (
QVBoxLayout, QVBoxLayout,
) )
from b_asic.signal_generator import Impulse, Step, ZeroPad from b_asic.GUI.signal_generator_input import _GENERATOR_MAPPING
class SimulateSFGWindow(QDialog): class SimulateSFGWindow(QDialog):
...@@ -58,12 +56,15 @@ class SimulateSFGWindow(QDialog): ...@@ -58,12 +56,15 @@ class SimulateSFGWindow(QDialog):
spin_box = QSpinBox() spin_box = QSpinBox()
spin_box.setRange(0, 2147483647) spin_box.setRange(0, 2147483647)
spin_box.setValue(100)
options_layout.addRow("Iteration count: ", spin_box) options_layout.addRow("Iteration count: ", spin_box)
check_box_plot = QCheckBox() check_box_plot = QCheckBox()
check_box_plot.setCheckState(Qt.CheckState.Checked)
options_layout.addRow("Plot results: ", check_box_plot) options_layout.addRow("Plot results: ", check_box_plot)
check_box_all = QCheckBox() check_box_all = QCheckBox()
check_box_all.setCheckState(Qt.CheckState.Checked)
options_layout.addRow("Get all results: ", check_box_all) options_layout.addRow("Get all results: ", check_box_all)
sfg_layout.addLayout(options_layout) sfg_layout.addLayout(options_layout)
...@@ -89,14 +90,12 @@ class SimulateSFGWindow(QDialog): ...@@ -89,14 +90,12 @@ class SimulateSFGWindow(QDialog):
input_dropdown = QComboBox() input_dropdown = QComboBox()
input_dropdown.insertItems( input_dropdown.insertItems(
0, ["Impulse", "Step", "Input", "File"] 0, list(_GENERATOR_MAPPING.keys()) + ["File"]
) )
input_dropdown.currentTextChanged.connect( input_dropdown.currentTextChanged.connect(
lambda text, i=i: self.change_input_format(i, text) lambda text, i=i: self.change_input_format(i, text)
) )
self.input_grid.addWidget( self.input_grid.addWidget(input_dropdown, i, 1, alignment=Qt.AlignLeft)
input_dropdown, i, 1, alignment=Qt.AlignLeft
)
self.change_input_format(i, "Impulse") self.change_input_format(i, "Impulse")
...@@ -124,27 +123,8 @@ class SimulateSFGWindow(QDialog): ...@@ -124,27 +123,8 @@ class SimulateSFGWindow(QDialog):
param_grid = QGridLayout() param_grid = QGridLayout()
if text == "Impulse": if text in _GENERATOR_MAPPING:
delay_label = QLabel("Delay") param_grid = _GENERATOR_MAPPING[text](self._window.logger)
param_grid.addWidget(delay_label, 0, 0)
delay_spin_box = QSpinBox()
delay_spin_box.setRange(0, 2147483647)
param_grid.addWidget(delay_spin_box, 0, 1)
elif text == "Step":
delay_label = QLabel("Delay")
param_grid.addWidget(delay_label, 0, 0)
delay_spin_box = QSpinBox()
delay_spin_box.setRange(0, 2147483647)
param_grid.addWidget(delay_spin_box, 0, 1)
elif text == "Input":
input_label = QLabel("Input")
param_grid.addWidget(input_label, 0, 0)
input_sequence = QLineEdit()
param_grid.addWidget(input_sequence, 0, 1)
zpad_label = QLabel("Zpad")
param_grid.addWidget(zpad_label, 1, 0)
zpad_button = QCheckBox()
param_grid.addWidget(zpad_button, 1, 1)
elif text == "File": elif text == "File":
file_label = QLabel("Browse") file_label = QLabel("Browse")
param_grid.addWidget(file_label, 0, 0) param_grid.addWidget(file_label, 0, 0)
...@@ -177,9 +157,7 @@ class SimulateSFGWindow(QDialog): ...@@ -177,9 +157,7 @@ class SimulateSFGWindow(QDialog):
_list_values.append(complex(val)) _list_values.append(complex(val))
except ValueError: except ValueError:
self._window.logger.warning( self._window.logger.warning(f"Skipping value: {val}, not a digit.")
f"Skipping value: {val}, not a digit."
)
continue continue
_input_values.append(_list_values) _input_values.append(_list_values)
...@@ -192,89 +170,40 @@ class SimulateSFGWindow(QDialog): ...@@ -192,89 +170,40 @@ class SimulateSFGWindow(QDialog):
if ic_value == 0: if ic_value == 0:
self._window.logger.error("Iteration count is set to zero.") self._window.logger.error("Iteration count is set to zero.")
tmp = [] input_values = []
for i in range(self.input_grid.rowCount()): for i in range(self.input_grid.rowCount()):
in_format = ( in_format = self.input_grid.itemAtPosition(i, 1).widget().currentText()
self.input_grid.itemAtPosition(i, 1).widget().currentText()
)
in_param = self.input_grid.itemAtPosition(i, 2) in_param = self.input_grid.itemAtPosition(i, 2)
tmp2 = [] if in_format in _GENERATOR_MAPPING:
tmp2 = in_param.get_generator()
if in_format == "Impulse":
g = Impulse(in_param.itemAtPosition(0, 1).widget().value())
for j in range(ic_value):
tmp2.append(str(g(j)))
elif in_format == "Step":
g = Step(in_param.itemAtPosition(0, 1).widget().value())
for j in range(ic_value):
tmp2.append(str(g(j)))
elif in_format == "Input":
widget = in_param.itemAtPosition(0, 1).widget()
tmp3 = widget.text().split(",")
if in_param.itemAtPosition(1, 1).widget().isChecked():
g = ZeroPad(tmp3)
for j in range(ic_value):
tmp2.append(str(g(j)))
else:
tmp2 = tmp3
elif in_format == "File": elif in_format == "File":
widget = in_param.itemAtPosition(0, 1).widget() widget = in_param.itemAtPosition(0, 1).widget()
path = widget.text() path = widget.text()
try: try:
tmp2 = np.loadtxt(path, dtype=str).tolist() tmp2 = self.parse_input_values(
except FileNotFoundError: np.loadtxt(path, dtype=str).tolist()
self._window.logger.error(
f"Selected input file not found."
) )
except FileNotFoundError:
self._window.logger.error(f"Selected input file not found.")
continue continue
else: else:
raise Exception("Input selection is not implemented") raise Exception("Input selection is not implemented")
tmp.append(tmp2) input_values.append(tmp2)
input_values = self.parse_input_values(tmp) self.properties[sfg] = {
"iteration_count": ic_value,
"show_plot": self.input_fields[sfg]["show_plot"].isChecked(),
"all_results": self.input_fields[sfg]["all_results"].isChecked(),
"input_values": input_values,
}
max_len = max(len(list_) for list_ in input_values) # If we plot we should also print the entire data,
min_len = min(len(list_) for list_ in input_values) # since you cannot really interact with the graph.
if self.properties[sfg]["show_plot"]:
if max_len != min_len: self.properties[sfg]["all_results"] = True
self._window.logger.error(
"Minimum length of input lists are not equal to maximum "
f"length of input lists: {max_len} != {min_len}."
)
elif ic_value > min_len:
self._window.logger.error(
"Minimum length of input lists are less than the "
f"iteration count: {ic_value} > {min_len}."
)
else:
self.properties[sfg] = {
"iteration_count": ic_value,
"show_plot": self.input_fields[sfg][
"show_plot"
].isChecked(),
"all_results": self.input_fields[sfg][
"all_results"
].isChecked(),
"input_values": input_values,
}
# If we plot we should also print the entire data,
# since you cannot really interact with the graph.
if self.properties[sfg]["show_plot"]:
self.properties[sfg]["all_results"] = True
continue
self._window.logger.info(
f"Skipping simulation of SFG with name: {sfg.name}, "
"due to previous errors."
)
self.accept() self.accept()
self.simulate.emit() self.simulate.emit()
...@@ -296,9 +225,7 @@ class Plot(FigureCanvas): ...@@ -296,9 +225,7 @@ class Plot(FigureCanvas):
FigureCanvas.__init__(self, fig) FigureCanvas.__init__(self, fig)
self.setParent(parent) self.setParent(parent)
FigureCanvas.setSizePolicy( FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
self, QSizePolicy.Expanding, QSizePolicy.Expanding
)
FigureCanvas.updateGeometry(self) FigureCanvas.updateGeometry(self)
self.save_figure = QShortcut(QKeySequence("Ctrl+S"), self) self.save_figure = QShortcut(QKeySequence("Ctrl+S"), self)
self.save_figure.activated.connect(self._save_plot_figure) self.save_figure.activated.connect(self._save_plot_figure)
...@@ -307,18 +234,14 @@ class Plot(FigureCanvas): ...@@ -307,18 +234,14 @@ class Plot(FigureCanvas):
def _save_plot_figure(self): def _save_plot_figure(self):
self._window.logger.info(f"Saving plot of figure: {self.sfg.name}.") self._window.logger.info(f"Saving plot of figure: {self.sfg.name}.")
file_choices = "PNG (*.png)|*.png" file_choices = "PNG (*.png)|*.png"
path, ext = QFileDialog.getSaveFileName( path, ext = QFileDialog.getSaveFileName(self, "Save file", "", file_choices)
self, "Save file", "", file_choices
)
path = path.encode("utf-8") path = path.encode("utf-8")
if not path[-4:] == file_choices[-4:].encode("utf-8"): if not path[-4:] == file_choices[-4:].encode("utf-8"):
path += file_choices[-4:].encode("utf-8") path += file_choices[-4:].encode("utf-8")
if path: if path:
self.print_figure(path.decode(), dpi=self.dpi) self.print_figure(path.decode(), dpi=self.dpi)
self._window.logger.info( self._window.logger.info(f"Saved plot: {self.sfg.name} to path: {path}.")
f"Saved plot: {self.sfg.name} to path: {path}."
)
def _plot_values_sfg(self): def _plot_values_sfg(self):
x_axis = list(range(len(self.simulation.results["0"]))) x_axis = list(range(len(self.simulation.results["0"])))
......
...@@ -76,6 +76,18 @@ GUI.show\_pc\_window module ...@@ -76,6 +76,18 @@ GUI.show\_pc\_window module
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
GUI.signal\_generator\_input module
-----------------------------------
.. inheritance-diagram:: b_asic.GUI.signal_generator_input
:parts: 1
:top-classes: b_asic.GUI.signal_generator_input.SignalGeneratorInput
.. automodule:: b_asic.GUI.signal_generator_input
:members:
:undoc-members:
:show-inheritance:
GUI.simulate\_sfg\_window module GUI.simulate\_sfg\_window module
-------------------------------- --------------------------------
......
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