diff --git a/src/nqrduck_measurement/controller.py b/src/nqrduck_measurement/controller.py index 0164da9..9736926 100644 --- a/src/nqrduck_measurement/controller.py +++ b/src/nqrduck_measurement/controller.py @@ -1,8 +1,9 @@ """Controller for the measurement module.""" + import logging import json import numpy as np -from decimal import Decimal +from decimal import Decimal from PyQt6.QtCore import pyqtSlot, pyqtSignal from PyQt6.QtGui import QValidator from PyQt6.QtWidgets import QApplication @@ -17,16 +18,17 @@ logger = logging.getLogger(__name__) class MeasurementController(ModuleController): """Controller for the measurement module. - + This class is responsible for handling the signals from the view and the module and updating the model. - + Args: module (Module): The module instance. - + Attributes: set_frequency_failure (pyqtSignal): Signal emitted when setting the frequency fails. set_averages_failure (pyqtSignal): Signal emitted when setting the averages fails. """ + set_frequency_failure = pyqtSignal() set_averages_failure = pyqtSignal() @@ -45,9 +47,14 @@ class MeasurementController(ModuleController): ValueError: If value cannot be converted to float. """ # Use validator - if self.module.model.validator_measurement_frequency.validate(value, 0) == QValidator.State.Acceptable: + if ( + self.module.model.validator_measurement_frequency.validate(value, 0) + == QValidator.State.Acceptable + ): self.module.model.measurement_frequency = float(value) * 1e6 - self.module.nqrduck_signal.emit("set_frequency", str(self.module.model.measurement_frequency)) + self.module.nqrduck_signal.emit( + "set_frequency", str(self.module.model.measurement_frequency) + ) self.toggle_start_button() @@ -59,11 +66,16 @@ class MeasurementController(ModuleController): value (str): Number of averages. """ logger.debug("Setting averages to: " + value) - #self.module.nqrduck_signal.emit("set_averages", value) - if self.module.model.validator_averages.validate(value, 0) == QValidator.State.Acceptable: + # self.module.nqrduck_signal.emit("set_averages", value) + if ( + self.module.model.validator_averages.validate(value, 0) + == QValidator.State.Acceptable + ): self.module.model.averages = int(value) - self.module.nqrduck_signal.emit("set_averages", str(self.module.model.averages)) - + self.module.nqrduck_signal.emit( + "set_averages", str(self.module.model.averages) + ) + self.toggle_start_button() @pyqtSlot() @@ -83,7 +95,9 @@ class MeasurementController(ModuleController): self.module.view.measurement_dialog.show() # Set the measurement parameters again in case the user switches spectrometer - self.module.nqrduck_signal.emit("set_frequency", str(self.module.model.measurement_frequency)) + self.module.nqrduck_signal.emit( + "set_frequency", str(self.module.model.measurement_frequency) + ) self.module.nqrduck_signal.emit("set_averages", str(self.module.model.averages)) QApplication.processEvents() @@ -107,7 +121,7 @@ class MeasurementController(ModuleController): def process_signals(self, key: str, value: object) -> None: """Process incoming signal from the nqrduck module. - + Args: key (str): The key of the signal. value (object): The value of the signal. @@ -132,9 +146,7 @@ class MeasurementController(ModuleController): ): logger.debug("Received measurement error.") self.module.view.measurement_dialog.hide() - self.module.nqrduck_signal.emit( - "notification", ["Error", value] - ) + self.module.nqrduck_signal.emit("notification", ["Error", value]) elif ( key == "failure_set_frequency" @@ -150,10 +162,11 @@ class MeasurementController(ModuleController): logger.debug("Received set averages failure.") self.set_averages_failure.emit() elif key == "active_spectrometer_changed": - self.module.view._ui_form.spectrometerLabel.setText("Spectrometer: %s" % value) + self.module.view._ui_form.spectrometerLabel.setText( + "Spectrometer: %s" % value + ) - - def save_measurement(self, file_name : str) -> None: + def save_measurement(self, file_name: str) -> None: """Save measurement to file. Args: @@ -163,13 +176,13 @@ class MeasurementController(ModuleController): if not self.module.model.measurements: logger.debug("No measurement to save.") return - + measurement = self.module.model.measurements[-1].to_json() with open(file_name, "w") as f: json.dump(measurement, f) - def load_measurement(self, file_name: str) -> None: + def load_measurement(self, file_name: str) -> None: """Load measurement from file. Args: @@ -203,20 +216,26 @@ class MeasurementController(ModuleController): "notification", ["Error", "No measurement to apodize."] ) return - - # We need to create a event which corresponds to the measurement. + + # We need to create a event which corresponds to the measurement. event_duration = self.module.model.displayed_measurement.tdx[-1] * 1e-6 event = PulseSequence.Event(name="Apodization", duration=str(event_duration)) parameter = Apodization() - parameter.start_x = 0 + parameter.start_x = 0 parameter.end_x = event_duration dialog = OptionsDialog(event, parameter, self.module.view) result = dialog.exec() if result: for option, function in dialog.return_functions.items(): - logger.debug("Setting option %s of parameter %s in event %s to %s", option, parameter, event, function()) + logger.debug( + "Setting option %s of parameter %s in event %s to %s", + option, + parameter, + event, + function(), + ) option.set_value(function()) # Get the function from the Apodization function @@ -224,9 +243,12 @@ class MeasurementController(ModuleController): logger.debug("Apodization function: %s", function) # Get the y data weights from the function - resolution = (self.module.model.displayed_measurement.tdx[1] - self.module.model.displayed_measurement.tdx[0]) * 1e-6 + resolution = ( + self.module.model.displayed_measurement.tdx[1] + - self.module.model.displayed_measurement.tdx[0] + ) * 1e-6 y_weight = function.get_pulse_amplitude(event.duration, Decimal(resolution)) - #Append the last point to the end of the array + # Append the last point to the end of the array y_weight = np.append(y_weight, y_weight[-1]) tdy_measurement = self.module.model.displayed_measurement.tdy * y_weight @@ -239,4 +261,4 @@ class MeasurementController(ModuleController): ) self.module.model.displayed_measurement = measurement - self.module.model.add_measurement(measurement) \ No newline at end of file + self.module.model.add_measurement(measurement) diff --git a/src/nqrduck_measurement/measurement.py b/src/nqrduck_measurement/measurement.py index 4e30cc6..9597b23 100644 --- a/src/nqrduck_measurement/measurement.py +++ b/src/nqrduck_measurement/measurement.py @@ -1,7 +1,8 @@ """Module initialization file for the nqrduck-measurement module.""" + from nqrduck.module.module import Module from .model import MeasurementModel from .view import MeasurementView from .controller import MeasurementController -Measurement = Module(MeasurementModel, MeasurementView, MeasurementController) \ No newline at end of file +Measurement = Module(MeasurementModel, MeasurementView, MeasurementController) diff --git a/src/nqrduck_measurement/model.py b/src/nqrduck_measurement/model.py index f312945..b5d5d1d 100644 --- a/src/nqrduck_measurement/model.py +++ b/src/nqrduck_measurement/model.py @@ -1,4 +1,5 @@ """Model for the measurement module.""" + import logging from PyQt6.QtCore import pyqtSignal from nqrduck_spectrometer.measurement import Measurement @@ -7,29 +8,30 @@ from nqrduck.helpers.validators import DuckFloatValidator, DuckIntValidator logger = logging.getLogger(__name__) + class MeasurementModel(ModuleModel): """Model for the measurement module. - + This class is responsible for storing the data of the measurement module. - + Attributes: FILE_EXTENSION (str): The file extension of the measurement files. FFT_VIEW (str): The view mode for the FFT view. TIME_VIEW (str): The view mode for the time view. - + displayed_measurement_changed (pyqtSignal): Signal emitted when the displayed measurement changes. measurements_changed (pyqtSignal): Signal emitted when the list of measurements changes. view_mode_changed (pyqtSignal): Signal emitted when the view mode changes. - + measurement_frequency_changed (pyqtSignal): Signal emitted when the measurement frequency changes. averages_changed (pyqtSignal): Signal emitted when the number of averages changes. - + view_mode (str): The view mode of the measurement view. measurements (list): List of measurements. displayed_measurement (Measurement): The displayed measurement data. measurement_frequency (float): The measurement frequency. averages (int): The number of averages. - + validator_measurement_frequency (DuckFloatValidator): Validator for the measurement frequency. validator_averages (DuckIntValidator): Validator for the number of averages. """ @@ -53,10 +55,12 @@ class MeasurementModel(ModuleModel): self.measurements = [] self._displayed_measurement = None - self.validator_measurement_frequency = DuckFloatValidator(self, min_value=20.0, max_value=1000.0) + self.validator_measurement_frequency = DuckFloatValidator( + self, min_value=20.0, max_value=1000.0 + ) self.validator_averages = DuckIntValidator(self, min_value=1, max_value=1e6) - self.measurement_frequency = 100.0 # MHz + self.measurement_frequency = 100.0 # MHz self.averages = 1 @property @@ -66,9 +70,9 @@ class MeasurementModel(ModuleModel): Can be either "time" or "fft". """ return self._view_mode - + @view_mode.setter - def view_mode(self, value : str): + def view_mode(self, value: str): self._view_mode = value self.view_mode_changed.emit(value) @@ -76,13 +80,13 @@ class MeasurementModel(ModuleModel): def measurements(self): """List of measurements.""" return self._measurements - + @measurements.setter - def measurements(self, value : list[Measurement]): + def measurements(self, value: list[Measurement]): self._measurements = value self.measurements_changed.emit(value) - - def add_measurement(self, measurement : Measurement): + + def add_measurement(self, measurement: Measurement): """Add a measurement to the list of measurements.""" self.measurements.append(measurement) self.measurements_changed.emit(self.measurements) @@ -95,9 +99,9 @@ class MeasurementModel(ModuleModel): It can be data in time domain or frequency domain. """ return self._displayed_measurement - + @displayed_measurement.setter - def displayed_measurement(self, value : Measurement): + def displayed_measurement(self, value: Measurement): self._displayed_measurement = value self.displayed_measurement_changed.emit(value) @@ -105,19 +109,19 @@ class MeasurementModel(ModuleModel): def measurement_frequency(self): """Measurement frequency.""" return self._measurement_frequency - + @measurement_frequency.setter - def measurement_frequency(self, value : float): + def measurement_frequency(self, value: float): # Validator is used to check if the value is in the correct range. self._measurement_frequency = value self.measurement_frequency_changed.emit(value) - + @property def averages(self): """Number of averages.""" return self._averages - + @averages.setter - def averages(self, value : int): + def averages(self, value: int): self._averages = value self.averages_changed.emit(value) diff --git a/src/nqrduck_measurement/signalprocessing_options.py b/src/nqrduck_measurement/signalprocessing_options.py index 8fa81ca..e1c9b95 100644 --- a/src/nqrduck_measurement/signalprocessing_options.py +++ b/src/nqrduck_measurement/signalprocessing_options.py @@ -1,10 +1,17 @@ """Signal processing options.""" + import sympy from nqrduck_spectrometer.base_spectrometer_model import BaseSpectrometerModel -from nqrduck_spectrometer.pulseparameters import FunctionOption, GaussianFunction, CustomFunction, Function +from nqrduck_spectrometer.pulseparameters import ( + FunctionOption, + GaussianFunction, + CustomFunction, + Function, +) # We implement the signal processing options as PulseParamterOptions because we can then easily use the automatic UI generation + class FIDFunction(Function): """The exponetial FID function.""" @@ -22,13 +29,14 @@ class FIDFunction(Function): class Apodization(BaseSpectrometerModel.PulseParameter): """Apodization parameter. - + This parameter is used to apply apodization functions to the signal. The apodization functions are used to reduce the noise in the signal. - + Attributes: APODIZATION_FUNCTIONS (str): The name of the apodization functions option. """ + APODIZATION_FUNCTIONS = "Apodization functions" def __init__(self): @@ -41,5 +49,3 @@ class Apodization(BaseSpectrometerModel.PulseParameter): [FIDFunction(), GaussianFunction(), CustomFunction()], ), ) - - diff --git a/src/nqrduck_measurement/view.py b/src/nqrduck_measurement/view.py index 909bba7..92998ac 100644 --- a/src/nqrduck_measurement/view.py +++ b/src/nqrduck_measurement/view.py @@ -1,4 +1,5 @@ """View for the measurement module.""" + import logging import numpy as np from PyQt6.QtWidgets import QWidget, QDialog, QLabel, QVBoxLayout @@ -11,19 +12,21 @@ from .widget import Ui_Form logger = logging.getLogger(__name__) + class MeasurementView(ModuleView): """View for the measurement module. - + This class is responsible for displaying the measurement data and handling the user input. - + Args: module (Module): The module instance. - + Attributes: widget (QWidget): The widget of the view. _ui_form (Ui_Form): The form of the widget. measurement_dialog (MeasurementDialog): The dialog shown when the measurement is started. """ + def __init__(self, module): """Initialize the measurement view.""" super().__init__(module) @@ -35,31 +38,60 @@ class MeasurementView(ModuleView): # Initialize plotter self.init_plotter() - logger.debug("Facecolor %s" % str(self._ui_form.plotter.canvas.ax.get_facecolor())) + logger.debug( + "Facecolor %s" % str(self._ui_form.plotter.canvas.ax.get_facecolor()) + ) # Measurement dialog - self.measurement_dialog = self.MeasurementDialog() + self.measurement_dialog = self.MeasurementDialog() # Connect signals - self.module.model.displayed_measurement_changed.connect(self.update_displayed_measurement) + self.module.model.displayed_measurement_changed.connect( + self.update_displayed_measurement + ) self.module.model.view_mode_changed.connect(self.update_displayed_measurement) - self._ui_form.buttonStart.clicked.connect(self.on_measurement_start_button_clicked) + self._ui_form.buttonStart.clicked.connect( + self.on_measurement_start_button_clicked + ) self._ui_form.fftButton.clicked.connect(self.module.controller.change_view_mode) # Measurement settings controller - self._ui_form.frequencyEdit.textChanged.connect(lambda: self.module.controller.set_frequency(self._ui_form.frequencyEdit.text())) - self._ui_form.averagesEdit.textChanged.connect(lambda: self.module.controller.set_averages(self._ui_form.averagesEdit.text())) + self._ui_form.frequencyEdit.textChanged.connect( + lambda: self.module.controller.set_frequency( + self._ui_form.frequencyEdit.text() + ) + ) + self._ui_form.averagesEdit.textChanged.connect( + lambda: self.module.controller.set_averages( + self._ui_form.averagesEdit.text() + ) + ) # Update fields - self._ui_form.frequencyEdit.textChanged.connect(lambda: self.update_input_widgets(self._ui_form.frequencyEdit, self.module.model.validator_measurement_frequency )) - self._ui_form.averagesEdit.textChanged.connect(lambda: self.update_input_widgets(self._ui_form.averagesEdit, self.module.model.validator_averages)) + self._ui_form.frequencyEdit.textChanged.connect( + lambda: self.update_input_widgets( + self._ui_form.frequencyEdit, + self.module.model.validator_measurement_frequency, + ) + ) + self._ui_form.averagesEdit.textChanged.connect( + lambda: self.update_input_widgets( + self._ui_form.averagesEdit, self.module.model.validator_averages + ) + ) - self.module.controller.set_frequency_failure.connect(self.on_set_frequency_failure) - self.module.controller.set_averages_failure.connect(self.on_set_averages_failure) + self.module.controller.set_frequency_failure.connect( + self.on_set_frequency_failure + ) + self.module.controller.set_averages_failure.connect( + self.on_set_averages_failure + ) + + self._ui_form.apodizationButton.clicked.connect( + self.module.controller.show_apodization_dialog + ) - self._ui_form.apodizationButton.clicked.connect(self.module.controller.show_apodization_dialog) - # Add logos self._ui_form.buttonStart.setIcon(Logos.Play_16x16()) self._ui_form.buttonStart.setIconSize(self._ui_form.buttonStart.size()) @@ -72,8 +104,12 @@ class MeasurementView(ModuleView): self._ui_form.importButton.setIconSize(self._ui_form.importButton.size()) # Connect measurement save and load buttons - self._ui_form.exportButton.clicked.connect(self.on_measurement_save_button_clicked) - self._ui_form.importButton.clicked.connect(self.on_measurement_load_button_clicked) + self._ui_form.exportButton.clicked.connect( + self.on_measurement_save_button_clicked + ) + self._ui_form.importButton.clicked.connect( + self.on_measurement_load_button_clicked + ) # Make title label bold self._ui_form.titleLabel.setStyleSheet("font-weight: bold;") @@ -90,7 +126,7 @@ class MeasurementView(ModuleView): plotter.canvas.ax.set_ylabel("Amplitude (a.u.)") plotter.canvas.ax.set_title("Measurement data - Time domain") plotter.canvas.ax.grid() - + def change_to_time_view(self) -> None: """Change plotter to time domain view.""" plotter = self._ui_form.plotter @@ -101,7 +137,7 @@ class MeasurementView(ModuleView): plotter.canvas.ax.set_title("Measurement data - Time domain") plotter.canvas.ax.grid() - def change_to_fft_view(self)-> None: + def change_to_fft_view(self) -> None: """Change plotter to frequency domain view.""" plotter = self._ui_form.plotter self._ui_form.fftButton.setText("iFFT") @@ -127,11 +163,16 @@ class MeasurementView(ModuleView): x = self.module.model.displayed_measurement.tdx y = self.module.model.displayed_measurement.tdy - self._ui_form.plotter.canvas.ax.plot(x, y.real, label="Real", linestyle="-", alpha=0.35, color="red") - self._ui_form.plotter.canvas.ax.plot(x, y.imag, label="Imaginary", linestyle="-", alpha=0.35, color="green") + self._ui_form.plotter.canvas.ax.plot( + x, y.real, label="Real", linestyle="-", alpha=0.35, color="red" + ) + self._ui_form.plotter.canvas.ax.plot( + x, y.imag, label="Imaginary", linestyle="-", alpha=0.35, color="green" + ) # Magnitude - self._ui_form.plotter.canvas.ax.plot(x, np.abs(y), label="Magnitude", color="blue") - + self._ui_form.plotter.canvas.ax.plot( + x, np.abs(y), label="Magnitude", color="blue" + ) # Add legend self._ui_form.plotter.canvas.ax.legend() @@ -163,7 +204,9 @@ class MeasurementView(ModuleView): """Slot for when the measurement save button is clicked.""" logger.debug("Measurement save button clicked.") - file_manager = self.QFileManager(self.module.model.FILE_EXTENSION, parent=self.widget) + file_manager = self.QFileManager( + self.module.model.FILE_EXTENSION, parent=self.widget + ) file_name = file_manager.saveFileDialog() if file_name: self.module.controller.save_measurement(file_name) @@ -173,7 +216,9 @@ class MeasurementView(ModuleView): """Slot for when the measurement load button is clicked.""" logger.debug("Measurement load button clicked.") - file_manager = self.QFileManager(self.module.model.FILE_EXTENSION, parent=self.widget) + file_manager = self.QFileManager( + self.module.model.FILE_EXTENSION, parent=self.widget + ) file_name = file_manager.loadFileDialog() if file_name: self.module.controller.load_measurement(file_name) @@ -181,25 +226,18 @@ class MeasurementView(ModuleView): @pyqtSlot() def update_input_widgets(self, widget, validator) -> None: """Update the style of the QLineEdit widget to indicate if the value is valid. - + Args: widget (QLineEdit): The widget to update. validator (QValidator): The validator to use for the widget. """ - if ( - validator.validate(widget.text(), 0) - == QValidator.State.Acceptable - ): + if validator.validate(widget.text(), 0) == QValidator.State.Acceptable: widget.setStyleSheet("QLineEdit { background-color: white; }") - elif ( - validator.validate(widget.text(), 0) - == QValidator.State.Intermediate - ): + elif validator.validate(widget.text(), 0) == QValidator.State.Intermediate: widget.setStyleSheet("QLineEdit { background-color: yellow; }") else: widget.setStyleSheet("QLineEdit { background-color: red; }") - class MeasurementDialog(QDialog): """This Dialog is shown when the measurement is started and therefore blocks the main window. @@ -208,6 +246,7 @@ class MeasurementView(ModuleView): Attributes: finished (bool): True if the spinner movie is finished. """ + def __init__(self): """Initialize the dialog.""" super().__init__() @@ -216,7 +255,7 @@ class MeasurementView(ModuleView): self.setWindowFlag(Qt.WindowType.FramelessWindowHint) self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) - self.message_label = ("Measuring...") + self.message_label = "Measuring..." self.spinner_movie = DuckAnimations.DuckKick128x128() self.spinner_label = QLabel(self) self.spinner_label.setMovie(self.spinner_movie) @@ -239,4 +278,3 @@ class MeasurementView(ModuleView): continue self.spinner_movie.stop() super().hide() -