diff --git a/b_asic/GUI/signal_generator_input.py b/b_asic/GUI/signal_generator_input.py index 8fba385aa1aa7d5cfa1708cd7def15622e581d97..6c20dfa0c909ff0c6aef3d31bc3eecd02c189b50 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 13fbf3c8281b2fc1888497c87831abde8b2a0a75..b0513006d5c5a37790ccc0a912ea79aac99d5558 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 27b12518651275b0e4ad8e2357b96d75da9752d0..69f7acbc16978655d24787c3aebd6474e5e5d05c 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 e8d16e3f3b9e1fa2a480da370a2e17f274d511f1..33639f09d2dc65f7ef98e5da18afba6216a362c7 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 b982e6cc6530d4b04d9dc43a1187a1bd5dee2e30..b086ff069f98f863be3e73df0b90818aa2a7740e 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 0000000000000000000000000000000000000000..94f8f3c931ba0182527997adcce7541032023315 --- /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 0000000000000000000000000000000000000000..b0917c8e2523298b91f0612287acd0150219ca53 --- /dev/null +++ b/test/test_signal_generator/input.csv @@ -0,0 +1,5 @@ +0 +1 +0 +0 +0