From a8b7969135ebabd5d9408ff95c4e35be04c71b46 Mon Sep 17 00:00:00 2001 From: Olle Hansson <olle.hansson@liu.se> Date: Fri, 24 Feb 2023 16:03:24 +0000 Subject: [PATCH] Add FromFile signal generator. --- b_asic/GUI/signal_generator_input.py | 36 +++++++++++++++++++- b_asic/GUI/simulate_sfg_window.py | 51 +++------------------------- b_asic/signal_generator.py | 26 ++++++++++++++ test/test_gui.py | 24 +++++++++---- test/test_signal_generator.py | 16 +++++++++ test/test_signal_generator/bad.csv | 2 ++ test/test_signal_generator/input.csv | 5 +++ 7 files changed, 107 insertions(+), 53 deletions(-) create mode 100644 test/test_signal_generator/bad.csv create mode 100644 test/test_signal_generator/input.csv diff --git a/b_asic/GUI/signal_generator_input.py b/b_asic/GUI/signal_generator_input.py index 8fba385a..6c20dfa0 100644 --- a/b_asic/GUI/signal_generator_input.py +++ b/b_asic/GUI/signal_generator_input.py @@ -1,8 +1,16 @@ # -*- coding: utf-8 -*- -from qtpy.QtWidgets import QGridLayout, QLabel, QLineEdit, QSpinBox +from qtpy.QtWidgets import ( + QFileDialog, + QGridLayout, + QLabel, + QLineEdit, + QPushButton, + QSpinBox, +) from b_asic.signal_generator import ( Constant, + FromFile, Gaussian, Impulse, SignalGenerator, @@ -94,6 +102,31 @@ class ZeroPadInput(SignalGeneratorInput): return ZeroPad(input_values) +class FromFileInput(SignalGeneratorInput): + """ + Class for graphically configuring and generating a + :class:`~b_asic.signal_generators.FromFile` signal generator. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.file_label = QLabel("Browse") + self.addWidget(self.file_label, 0, 0) + self.file_browser = QPushButton("No file selected") + self.file_browser.clicked.connect( + lambda i: self.get_input_file(i, self.file_browser) + ) + self.addWidget(self.file_browser, 0, 1) + + def get_generator(self) -> SignalGenerator: + return FromFile(self.file_browser.text()) + + def get_input_file(self, i, file_browser): + module, accepted = QFileDialog().getOpenFileName() + file_browser.setText(module) + return + + class SinusoidInput(SignalGeneratorInput): """ Class for graphically configuring and generating a @@ -272,4 +305,5 @@ _GENERATOR_MAPPING = { "Step": StepInput, "Uniform": UniformInput, "ZeroPad": ZeroPadInput, + "FromFile": FromFileInput, } diff --git a/b_asic/GUI/simulate_sfg_window.py b/b_asic/GUI/simulate_sfg_window.py index 13fbf3c8..b0513006 100644 --- a/b_asic/GUI/simulate_sfg_window.py +++ b/b_asic/GUI/simulate_sfg_window.py @@ -16,6 +16,7 @@ from qtpy.QtWidgets import ( QGridLayout, QHBoxLayout, QLabel, + QLayout, QLineEdit, QPushButton, QShortcut, @@ -25,6 +26,7 @@ from qtpy.QtWidgets import ( ) from b_asic.GUI.signal_generator_input import _GENERATOR_MAPPING +from b_asic.signal_generator import FromFile class SimulateSFGWindow(QDialog): @@ -40,6 +42,7 @@ class SimulateSFGWindow(QDialog): self.setWindowTitle("Simulate SFG") self.dialog_layout = QVBoxLayout() + self.dialog_layout.setSizeConstraint(QLayout.SetFixedSize) self.simulate_btn = QPushButton("Simulate") self.simulate_btn.clicked.connect(self.save_properties) self.dialog_layout.addWidget(self.simulate_btn) @@ -89,15 +92,13 @@ class SimulateSFGWindow(QDialog): self.input_grid.addWidget(input_label, i, 0) input_dropdown = QComboBox() - input_dropdown.insertItems( - 0, list(_GENERATOR_MAPPING.keys()) + ["File"] - ) + input_dropdown.insertItems(0, list(_GENERATOR_MAPPING.keys())) input_dropdown.currentTextChanged.connect( lambda text, i=i: self.change_input_format(i, text) ) self.input_grid.addWidget(input_dropdown, i, 1, alignment=Qt.AlignLeft) - self.change_input_format(i, "Impulse") + self.change_input_format(i, "Constant") y += 1 @@ -125,14 +126,6 @@ class SimulateSFGWindow(QDialog): if text in _GENERATOR_MAPPING: param_grid = _GENERATOR_MAPPING[text](self._window.logger) - elif text == "File": - file_label = QLabel("Browse") - param_grid.addWidget(file_label, 0, 0) - file_browser = QPushButton("No file selected") - file_browser.clicked.connect( - lambda i=i: self.get_input_file(i, file_browser) - ) - param_grid.addWidget(file_browser, 0, 1) else: raise Exception("Input selection is not implemented") @@ -140,30 +133,6 @@ class SimulateSFGWindow(QDialog): return - def get_input_file(self, i, file_browser): - module, accepted = QFileDialog().getOpenFileName() - file_browser.setText(module) - return - - def parse_input_values(self, input_values): - _input_values = [] - for _list in input_values: - _list_values = [] - for val in _list: - val = val.strip() - try: - if not val: - val = 0 - - _list_values.append(complex(val)) - except ValueError: - self._window.logger.warning(f"Skipping value: {val}, not a digit.") - continue - - _input_values.append(_list_values) - - return _input_values - def save_properties(self): for sfg, _properties in self.input_fields.items(): ic_value = self.input_fields[sfg]["iteration_count"].value() @@ -178,16 +147,6 @@ class SimulateSFGWindow(QDialog): if in_format in _GENERATOR_MAPPING: tmp2 = in_param.get_generator() - elif in_format == "File": - widget = in_param.itemAtPosition(0, 1).widget() - path = widget.text() - try: - tmp2 = self.parse_input_values( - np.loadtxt(path, dtype=str).tolist() - ) - except FileNotFoundError: - self._window.logger.error(f"Selected input file not found.") - continue else: raise Exception("Input selection is not implemented") diff --git a/b_asic/signal_generator.py b/b_asic/signal_generator.py index 27b12518..69f7acbc 100644 --- a/b_asic/signal_generator.py +++ b/b_asic/signal_generator.py @@ -13,6 +13,7 @@ if you want more information. from math import pi, sin from numbers import Number +from pathlib import Path from typing import Optional, Sequence import numpy as np @@ -161,6 +162,31 @@ class ZeroPad(SignalGenerator): return f"ZeroPad({self._data})" +class FromFile(SignalGenerator): + """ + Signal generator that reads from file and pads a sequence with zeros. + File should be of type .txt or .csv and contain a single column vector + Parameters + ---------- + path : string + Path to input file. + """ + + def __init__(self, path) -> None: + self._path = path + data = np.loadtxt(path, dtype=complex).tolist() + self._data = data + self._len = len(data) + + def __call__(self, time: int) -> complex: + if 0 <= time < self._len: + return self._data[time] + return 0.0 + + def __repr__(self) -> str: + return f"FromFile({self._path!r})" + + class Sinusoid(SignalGenerator): """ Signal generator that generates a sinusoid. diff --git a/test/test_gui.py b/test/test_gui.py index e8d16e3f..33639f09 100644 --- a/test/test_gui.py +++ b/test/test_gui.py @@ -148,6 +148,22 @@ def test_help_dialogs(qtbot): widget.exit_app() +def test_simulate(qtbot, datadir): + # Smoke test to open up the "Simulate SFG" and run default simulation + # Should really test all different tests + widget = GUI.MainWindow() + qtbot.addWidget(widget) + widget._load_from_file(datadir.join('twotapfir.py')) + assert 'twotapfir' in widget.sfg_dict + widget.simulate_sfg() + qtbot.wait(100) + # widget.dialog.save_properties() + # qtbot.wait(100) + widget.dialog.close() + + widget.exit_app() + + def test_properties_window_smoke_test(qtbot, datadir): # Smoke test to open up the properties window # Should really check that the contents are correct and changes works etc @@ -159,9 +175,7 @@ def test_properties_window_smoke_test(qtbot, datadir): dragbutton = widget.operationDragDict[op] dragbutton.show_properties_window() assert dragbutton._properties_window.operation == dragbutton - qtbot.mouseClick( - dragbutton._properties_window.ok, QtCore.Qt.MouseButton.LeftButton - ) + qtbot.mouseClick(dragbutton._properties_window.ok, QtCore.Qt.MouseButton.LeftButton) widget.exit_app() @@ -179,9 +193,7 @@ def test_properties_window_change_name(qtbot, datadir): dragbutton.show_properties_window() assert dragbutton._properties_window.edit_name.text() == "cmul2" dragbutton._properties_window.edit_name.setText("cmul73") - qtbot.mouseClick( - dragbutton._properties_window.ok, QtCore.Qt.MouseButton.LeftButton - ) + qtbot.mouseClick(dragbutton._properties_window.ok, QtCore.Qt.MouseButton.LeftButton) dragbutton._properties_window.save_properties() assert dragbutton.name == "cmul73" assert dragbutton.operation.name == "cmul73" diff --git a/test/test_signal_generator.py b/test/test_signal_generator.py index b982e6cc..b086ff06 100644 --- a/test/test_signal_generator.py +++ b/test/test_signal_generator.py @@ -1,3 +1,4 @@ +import os from math import sqrt import pytest @@ -5,6 +6,7 @@ import pytest from b_asic.signal_generator import ( Constant, Delay, + FromFile, Gaussian, Impulse, Sinusoid, @@ -269,3 +271,17 @@ def test_division(): assert str(g) == "Sinusoid(0.5, 0.25) / (0.5 * Step())" assert isinstance(g, _DivGenerator) + + +def test_fromfile(datadir): + g = FromFile(datadir.join('input.csv')) + assert g(-1) == 0.0 + assert g(0) == 0 + assert g(1) == 1 + assert g(2) == 0 + + with pytest.raises(FileNotFoundError, match="tset.py not found"): + g = FromFile(datadir.join('tset.py')) + + with pytest.raises(ValueError, match="could not convert string"): + g = FromFile(datadir.join('bad.csv')) diff --git a/test/test_signal_generator/bad.csv b/test/test_signal_generator/bad.csv new file mode 100644 index 00000000..94f8f3c9 --- /dev/null +++ b/test/test_signal_generator/bad.csv @@ -0,0 +1,2 @@ +0 +a diff --git a/test/test_signal_generator/input.csv b/test/test_signal_generator/input.csv new file mode 100644 index 00000000..b0917c8e --- /dev/null +++ b/test/test_signal_generator/input.csv @@ -0,0 +1,5 @@ +0 +1 +0 +0 +0 -- GitLab