diff --git a/src/nqrduck_broadband/controller.py b/src/nqrduck_broadband/controller.py index 4b88647..f1460d3 100644 --- a/src/nqrduck_broadband/controller.py +++ b/src/nqrduck_broadband/controller.py @@ -1,17 +1,17 @@ import logging -from PyQt6.QtCore import pyqtSlot, pyqtSignal +import numpy as np +from PyQt6.QtCore import pyqtSlot, pyqtSignal, QThread +from PyQt6.QtWidgets import QApplication from nqrduck_spectrometer.measurement import Measurement from nqrduck.module.module_controller import ModuleController logger = logging.getLogger(__name__) class BroadbandController(ModuleController): - MIN_FREQUENCY = 30.0 - MAX_FREQUENCY = 200.0 - - start_measurement = pyqtSignal() + start_broadband_measurement = pyqtSignal() set_averages_failure = pyqtSignal() + set_frequency_step_failure = pyqtSignal() def __init__(self, module): super().__init__(module) @@ -19,18 +19,23 @@ class BroadbandController(ModuleController): @pyqtSlot(str, object) def process_signals(self, key: str, value: Measurement): - if key == "measurement_data": + if key == "measurement_data" and self.module.model.current_broadcast_measurement is not None: logger.debug("Received single measurement.") - self.module.model.single_measurement = value + self.module.model.current_broadcast_measurement.add_measurement(value) elif key == "failure_set_averages" and value == self.module.view._ui_form.averagesEdit.text(): logger.debug("Received set averages failure.") self.set_averages_failure.emit() + @pyqtSlot(str) def set_frequency(self, value): - logger.debug("Setting frequency to: " + value) - self.module.nqrduck_signal.emit("set_frequency", value) + try: + logger.debug("Setting frequency to: " + float(value)) + self.module.nqrduck_signal.emit("set_frequency", value) + except ValueError: + self.set_averages_failure.emit() + self.set_frequency_step_failure.emit() @pyqtSlot(str) def set_averages(self, value): @@ -40,20 +45,62 @@ class BroadbandController(ModuleController): @pyqtSlot(str) def change_start_frequency(self, value): value = float(value) - if value > self.MIN_FREQUENCY: - self.module._model.start_frequency = value + if value > self.module.model.MIN_FREQUENCY: + self.module.model.start_frequency = value * 1e6 else: - self.module._model.start_frequency = self.MIN_FREQUENCY + self.module.model.start_frequency = self.module.model.MIN_FREQUENCY @pyqtSlot(str) def change_stop_frequency(self, value): value = float(value) - if value < self.MAX_FREQUENCY: - self.module._model.stop_frequency = value + if value < self.module.model.MAX_FREQUENCY: + self.module._model.stop_frequency = value * 1e6 else: - self.module._model.stop_frequency = self.MAX_FREQUENCY + self.module._model.stop_frequency = self.module.model.MAX_FREQUENCY + + @pyqtSlot(str) + def change_frequency_step(self, value): + try: + logger.debug("Changing frequency step to: " + value) + value = float(value) * 1e6 + if value > 0: + self.module.model.frequency_step = value + except ValueError: + logger.debug("Invalid frequency step value") + @pyqtSlot() - def start_measurement(self): + def start_broadband_measurement(self): logger.debug("Start measurement clicked") - self.module.nqrduck_signal.emit("start_measurement", None) \ No newline at end of file + # Create a list of different frequency values that we need for our broadband measurement + start_frequency = self.module.model.start_frequency + stop_frequency = self.module.model.stop_frequency + frequency_step = self.module.model.frequency_step + + frequency_list = np.arange(start_frequency, stop_frequency + frequency_step, frequency_step) + logger.debug("Frequency list: " + str(frequency_list)) + + # Create a new broadband measurement object + self.module.model.current_broadcast_measurement = self.module.model.BroadbandMeasurement(frequency_list) + self.module.model.current_broadcast_measurement.received_measurement.connect(self.module.view.on_broadband_measurement_added) + self.module.model.current_broadcast_measurement.received_measurement.connect(self.on_broadband_measurement_added) + + # Start the first measurement + self.module.nqrduck_signal.emit("set_frequency", str(start_frequency)) + self.module.nqrduck_signal.emit("start_measurement", None) + + @pyqtSlot() + def on_broadband_measurement_added(self): + """This slot is called when a single measurement is added to the broadband measurement. + It then checks if there are more frequencies to measure and if so, starts the next measurement. + Furthermore it updates the plots. + """ + logger.debug("Broadband measurement added.") + # Check if there are more frequencies to measure + if not self.module.model.current_broadcast_measurement.is_complete(): + # Get the next frequency to measure + next_frequency = self.module.model.current_broadcast_measurement.get_next_measurement_frequency() + logger.debug("Next frequency: " + str(next_frequency)) + # Start the next measurement + self.module.nqrduck_signal.emit("set_frequency", str(next_frequency)) + self.module.nqrduck_signal.emit("start_measurement", None) \ No newline at end of file diff --git a/src/nqrduck_broadband/model.py b/src/nqrduck_broadband/model.py index bb5019a..d048260 100644 --- a/src/nqrduck_broadband/model.py +++ b/src/nqrduck_broadband/model.py @@ -1,11 +1,26 @@ -from PyQt6.QtCore import pyqtSignal +import logging +from collections import OrderedDict +from PyQt6.QtWidgets import QApplication +from PyQt6.QtCore import pyqtSignal, QObject from nqrduck.module.module_model import ModuleModel +logger = logging.getLogger(__name__) class BroadbandModel(ModuleModel): + MIN_FREQUENCY = 30.0 + MAX_FREQUENCY = 200.0 + DEFAULT_FREQUENCY_STEP = 0.1 + start_frequency_changed = pyqtSignal(float) stop_frequency_changed = pyqtSignal(float) + def __init__(self, module) -> None: + super().__init__(module) + self.start_frequency = self.MIN_FREQUENCY + self.stop_frequency = self.MAX_FREQUENCY + self.DEFAULT_FREQUENCY_STEP = self.DEFAULT_FREQUENCY_STEP + self.current_broadcast_measurement = None + @property def start_frequency(self): return self._start_frequency @@ -23,3 +38,78 @@ class BroadbandModel(ModuleModel): def stop_frequency(self, value): self._stop_frequency = value self.stop_frequency_changed.emit(value) + + @property + def frequency_step(self): + return self._frequency_step + + @frequency_step.setter + def frequency_step(self, value): + self._frequency_step = value + + @property + def current_broadcast_measurement(self): + return self._current_broadcast_measurement + + @current_broadcast_measurement.setter + def current_broadcast_measurement(self, value): + self._current_broadcast_measurement = value + + class BroadbandMeasurement(QObject): + """This class represents a single broadband measurement.""" + + received_measurement = pyqtSignal() + + def __init__(self, frequencies) -> None: + super().__init__() + self._single_frequency_measurements = OrderedDict() + for frequency in frequencies: + self._single_frequency_measurements[frequency] = None + + def add_measurement(self, measurement): + """This method adds a single measurement to the broadband measurement. + + Args: + measurement (Measurement): The measurement object.""" + logger.debug("Adding measurement to broadband measurement at frequency: %s" % str(measurement.target_frequency)) + self._single_frequency_measurements[measurement.target_frequency] = measurement + self.received_measurement.emit() + QApplication.processEvents() + + def is_complete(self): + """This method checks if all frequencies have been measured. + + Returns: + bool: True if all frequencies have been measured, False otherwise.""" + for measurement in self._single_frequency_measurements.values(): + if measurement is None: + return False + return True + + def get_next_measurement_frequency(self): + """This method returns the next frequency that has to be measured. + + Returns: + float: The next frequency that has to be measured.""" + for frequency, measurement in self._single_frequency_measurements.items(): + if measurement is None: + return frequency + + def get_last_completed_measurement(self): + """ This method returns the last completed measurement. + + Returns: + Measurement: The last completed measurement.""" + + for frequency, measurement in self._single_frequency_measurements.items(): + if measurement is not None: + return measurement + + @property + def single_frequency_measurements(self): + """This property contains the dict of all frequencies that have to be measured.""" + return self._single_frequency_measurements + + @single_frequency_measurements.setter + def single_frequency_measurements(self, value): + self._single_frequency_measurements = value diff --git a/src/nqrduck_broadband/resources/broadband_widget.ui b/src/nqrduck_broadband/resources/broadband_widget.ui index 28f46b4..7e6874f 100644 --- a/src/nqrduck_broadband/resources/broadband_widget.ui +++ b/src/nqrduck_broadband/resources/broadband_widget.ui @@ -76,9 +76,9 @@ - + - 0.1 + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -91,7 +91,7 @@ Qt::LeftToRight - 50.0 + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -108,7 +108,7 @@ - 70.0 + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -132,7 +132,7 @@ - 1000 + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter diff --git a/src/nqrduck_broadband/view.py b/src/nqrduck_broadband/view.py index aa9055b..379ae23 100644 --- a/src/nqrduck_broadband/view.py +++ b/src/nqrduck_broadband/view.py @@ -1,6 +1,6 @@ import logging from PyQt6.QtCore import pyqtSlot, pyqtSignal -from PyQt6.QtWidgets import QWidget, QMessageBox +from PyQt6.QtWidgets import QWidget, QMessageBox, QApplication from nqrduck.module.module_view import ModuleView from .widget import Ui_Form @@ -10,7 +10,7 @@ logger = logging.getLogger(__name__) class BroadbandView(ModuleView): - start_measurement = pyqtSignal() + start_broadband_measurement = pyqtSignal() def __init__(self, module): super().__init__(module) @@ -28,25 +28,32 @@ class BroadbandView(ModuleView): def _connect_signals(self) -> None: self._ui_form.start_frequencyField.editingFinished.connect( - lambda: self.module._controller.change_start_frequency( + lambda: self.module.controller.change_start_frequency( self._ui_form.start_frequencyField.text() ) ) self._ui_form.stop_frequencyField.editingFinished.connect( - lambda: self.module._controller.change_stop_frequency( + lambda: self.module.controller.change_stop_frequency( self._ui_form.stop_frequencyField.text() ) ) - self.module._model.start_frequency_changed.connect(self.on_start_frequency_change) - self.module._model.stop_frequency_changed.connect(self.on_stop_frequency_change) + self._ui_form.frequencystepEdit.editingFinished.connect( + lambda: self.module.controller.change_frequency_step( + self._ui_form.frequencystepEdit.text() + ) + ) + + self.module.model.start_frequency_changed.connect(self.on_start_frequency_change) + self.module.model.stop_frequency_changed.connect(self.on_stop_frequency_change) - self._ui_form.start_measurementButton.clicked.connect(lambda: self._start_measurement_clicked()) - self.start_measurement.connect(lambda: self.module._controller.start_measurement()) + self._ui_form.start_measurementButton.clicked.connect(self._start_measurement_clicked) + self.start_broadband_measurement.connect(self.module._controller.start_broadband_measurement) self._ui_form.averagesEdit.editingFinished.connect(lambda: self.on_editing_finished(self._ui_form.averagesEdit.text())) self.module.controller.set_averages_failure.connect(self.on_set_averages_failure) + self.module.controller.set_frequency_step_failure.connect(self.on_set_frequency_step_failure) def _start_measurement_clicked(self): # Create a QMessageBox object @@ -62,7 +69,7 @@ class BroadbandView(ModuleView): # Process the user's choice if choice == QMessageBox.StandardButton.Yes: - self.start_measurement.emit() + self.start_broadband_measurement.emit() def init_plots(self): @@ -89,19 +96,23 @@ class BroadbandView(ModuleView): @pyqtSlot(float) def on_start_frequency_change(self, start_frequency): + logger.debug("Adjusting view to new start frequency: " + str(start_frequency)) self._ui_form.broadbandPlot.canvas.ax.set_xlim(left=start_frequency) self._ui_form.broadbandPlot.canvas.draw() self._ui_form.broadbandPlot.canvas.flush_events() + self._ui_form.start_frequencyField.setText(str(start_frequency* 1e-6)) @pyqtSlot(float) def on_stop_frequency_change(self, stop_frequency): + logger.debug("Adjusting view to new stop frequency: " + str(stop_frequency)) self._ui_form.broadbandPlot.canvas.ax.set_xlim(right=stop_frequency) self._ui_form.broadbandPlot.canvas.draw() self._ui_form.broadbandPlot.canvas.flush_events() + self._ui_form.stop_frequencyField.setText(str(stop_frequency* 1e-6)) @pyqtSlot() def on_editing_finished(self, value): - logger.debug("Averages editing finished.") + logger.debug("Editing finished by.") self.sender().setStyleSheet("") if self.sender() == self._ui_form.averagesEdit: self.module.controller.set_averages(value) @@ -109,4 +120,30 @@ class BroadbandView(ModuleView): @pyqtSlot() def on_set_averages_failure(self): logger.debug("Set averages failure.") - self._ui_form.averagesEdit.setStyleSheet("border: 1px solid red;") \ No newline at end of file + self._ui_form.averagesEdit.setStyleSheet("border: 1px solid red;") + + @pyqtSlot() + def on_set_frequency_step_failure(self): + logger.debug("Set frequency step failure.") + self._ui_form.frequencystepEdit.setStyleSheet("border: 1px solid red;") + + @pyqtSlot() + def on_broadband_measurement_added(self): + # Get last measurement from the broadband measurement object that is not None + logger.debug("Updating broadband plot.") + measurement = self.module.model.current_broadcast_measurement.get_last_completed_measurement() + + td_plotter = self._ui_form.time_domainPlot.canvas.ax + fd_plotter = self._ui_form.frequency_domainPlot.canvas.ax + + td_plotter.clear() + fd_plotter.clear() + + td_plotter.plot(measurement.tdx, measurement.tdy) + fd_plotter.plot(measurement.fdx, measurement.fdy) + + self._ui_form.time_domainPlot.canvas.draw() + self._ui_form.frequency_domainPlot.canvas.draw() + + QApplication.processEvents() + diff --git a/src/nqrduck_broadband/widget.py b/src/nqrduck_broadband/widget.py index 7d31969..5a89a9b 100644 --- a/src/nqrduck_broadband/widget.py +++ b/src/nqrduck_broadband/widget.py @@ -49,12 +49,14 @@ class Ui_Form(object): self.label_3 = QtWidgets.QLabel(parent=Form) self.label_3.setObjectName("label_3") self.gridLayout_3.addWidget(self.label_3, 1, 0, 1, 1) - self.frequency_stepField = QtWidgets.QLineEdit(parent=Form) - self.frequency_stepField.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter) - self.frequency_stepField.setObjectName("frequency_stepField") - self.gridLayout_3.addWidget(self.frequency_stepField, 2, 2, 1, 1) + self.frequencystepEdit = QtWidgets.QLineEdit(parent=Form) + self.frequencystepEdit.setText("") + self.frequencystepEdit.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter) + self.frequencystepEdit.setObjectName("frequencystepEdit") + self.gridLayout_3.addWidget(self.frequencystepEdit, 2, 2, 1, 1) self.start_frequencyField = QtWidgets.QLineEdit(parent=Form) self.start_frequencyField.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) + self.start_frequencyField.setText("") self.start_frequencyField.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter) self.start_frequencyField.setObjectName("start_frequencyField") self.gridLayout_3.addWidget(self.start_frequencyField, 0, 2, 1, 1) @@ -62,6 +64,7 @@ class Ui_Form(object): self.label.setObjectName("label") self.gridLayout_3.addWidget(self.label, 0, 0, 1, 1) self.stop_frequencyField = QtWidgets.QLineEdit(parent=Form) + self.stop_frequencyField.setText("") self.stop_frequencyField.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter) self.stop_frequencyField.setObjectName("stop_frequencyField") self.gridLayout_3.addWidget(self.stop_frequencyField, 1, 2, 1, 1) @@ -72,6 +75,7 @@ class Ui_Form(object): self.label_7.setObjectName("label_7") self.gridLayout_3.addWidget(self.label_7, 3, 0, 1, 1) self.averagesEdit = QtWidgets.QLineEdit(parent=Form) + self.averagesEdit.setText("") self.averagesEdit.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter) self.averagesEdit.setObjectName("averagesEdit") self.gridLayout_3.addWidget(self.averagesEdit, 3, 2, 1, 1) @@ -177,13 +181,9 @@ class Ui_Form(object): self.label_5.setText(_translate("Form", "Frequency Step:")) self.label_2.setText(_translate("Form", "MHz")) self.label_3.setText(_translate("Form", "Stop Frequency:")) - self.frequency_stepField.setText(_translate("Form", "0.1")) - self.start_frequencyField.setText(_translate("Form", "50.0")) self.label.setText(_translate("Form", "Start Frequency:")) - self.stop_frequencyField.setText(_translate("Form", "70.0")) self.label_4.setText(_translate("Form", "MHz")) self.label_7.setText(_translate("Form", "Averages:")) - self.averagesEdit.setText(_translate("Form", "1000")) self.start_measurementButton.setText(_translate("Form", "Start Measurement")) self.label_10.setText(_translate("Form", "Sequence Settings:")) self.label_9.setText(_translate("Form", "Info Box:"))