diff --git a/pyproject.toml b/pyproject.toml index f3cca80..c4f32d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,5 +32,31 @@ dependencies = [ "scipy", ] +[project.optional-dependencies] +dev = [ + "black", + "pydocstyle", + "pyupgrade", + "ruff", +] + +[tool.ruff] +exclude = [ + "widget.py", + "base_spectrometer_widget.py", +] + +[tool.ruff.lint] +extend-select = [ + "UP", # pyupgrade + "D", # pydocstyle +] + +[tool.ruff.lint.per-file-ignores] +"__init__.py" = ["F401"] + +[tool.ruff.lint.pydocstyle] +convention = "google" + [project.entry-points."nqrduck"] "nqrduck-spectrometer" = "nqrduck_spectrometer.spectrometer:Spectrometer" \ No newline at end of file diff --git a/src/nqrduck_spectrometer/__init__.py b/src/nqrduck_spectrometer/__init__.py index 78a0537..0f6cec1 100644 --- a/src/nqrduck_spectrometer/__init__.py +++ b/src/nqrduck_spectrometer/__init__.py @@ -1 +1,2 @@ +"""Init for importing the module.""" from .spectrometer import Spectrometer as Module \ No newline at end of file diff --git a/src/nqrduck_spectrometer/base_spectrometer.py b/src/nqrduck_spectrometer/base_spectrometer.py index 494e336..79f943b 100644 --- a/src/nqrduck_spectrometer/base_spectrometer.py +++ b/src/nqrduck_spectrometer/base_spectrometer.py @@ -1,27 +1,39 @@ +"""Base Spectrometer Module.""" + from PyQt6.QtCore import pyqtSignal from nqrduck.module.module import Module + class BaseSpectrometer(Module): - """Base class for all spectrometers. All spectrometers should inherit from this class.""" + """Base class for all spectrometers. All spectrometers should inherit from this class. + + Args: + Model (SpectrometerModel) : The model of the spectrometer + View (SpectrometerView) : The view of the spectrometer + Controller (SpectrometerController) : The controller of the spectrometer + + Signals: + change_spectrometer (str) : Signal emitted when the spectrometer is changed + """ + change_spectrometer = pyqtSignal(str) def __init__(self, model, view, controller): + """Initializes the spectrometer.""" super().__init__(model, None, controller) # This stops the view from being added to the main window. self.view = None self.settings_view = view(self) - + def set_active(self): - """Sets the spectrometer as the active spectrometer. - """ + """Sets the spectrometer as the active spectrometer.""" self.change_spectrometer.emit(self._model.name) @property def settings_view(self): - """The settings view of the spectrometer. - """ + """The settings view of the spectrometer.""" return self._settings_view - + @settings_view.setter def settings_view(self, value): self._settings_view = value diff --git a/src/nqrduck_spectrometer/base_spectrometer_controller.py b/src/nqrduck_spectrometer/base_spectrometer_controller.py index fd53cc0..a96e368 100644 --- a/src/nqrduck_spectrometer/base_spectrometer_controller.py +++ b/src/nqrduck_spectrometer/base_spectrometer_controller.py @@ -1,20 +1,26 @@ +"""Base class for all spectrometer controllers.""" + from nqrduck.module.module_controller import ModuleController + class BaseSpectrometerController(ModuleController): """The base class for all spectrometer controllers.""" def __init__(self, module): + """Initializes the spectrometer controller.""" super().__init__(module) def start_measurement(self): """Starts the measurement. + This method should be called when the measurement is started. """ raise NotImplementedError - + def set_frequency(self, value): """Sets the frequency of the spectrometer.""" raise NotImplementedError - + def set_averages(self, value): - raise NotImplementedError \ No newline at end of file + """Sets the number of averages.""" + raise NotImplementedError diff --git a/src/nqrduck_spectrometer/base_spectrometer_model.py b/src/nqrduck_spectrometer/base_spectrometer_model.py index 2273f89..11fa178 100644 --- a/src/nqrduck_spectrometer/base_spectrometer_model.py +++ b/src/nqrduck_spectrometer/base_spectrometer_model.py @@ -1,110 +1,127 @@ +"""The base class for all spectrometer models.""" + import logging from collections import OrderedDict -from PyQt6.QtCore import pyqtSlot, pyqtSignal, QObject +from PyQt6.QtGui import QPixmap from nqrduck.module.module_model import ModuleModel -from.settings import Setting +from .settings import Setting logger = logging.getLogger(__name__) + class BaseSpectrometerModel(ModuleModel): """The base class for all spectrometer models. + It contains the settings and pulse parameters of the spectrometer. - - Arguments: - module (Module) -- The module that the spectrometer is connected to - + + Args: + module (Module) : The module that the spectrometer is connected to + Attributes: - settings (OrderedDict) -- The settings of the spectrometer - pulse_parameter_options (OrderedDict) -- The pulse parameter options of the spectrometer + settings (OrderedDict) : The settings of the spectrometer + pulse_parameter_options (OrderedDict) : The pulse parameter options of the spectrometer """ - settings : OrderedDict - pulse_parameter_options : OrderedDict - + + settings: OrderedDict + pulse_parameter_options: OrderedDict + class PulseParameter: """A pulse parameter is a value that can be different for each event in a pulse sequence. + E.g. the transmit pulse power or the phase of the transmit pulse. - - Arguments: - name (str) -- The name of the pulse parameter - + + Args: + name (str) : The name of the pulse parameter + Attributes: - name (str) -- The name of the pulse parameter - options (OrderedDict) -- The options of the pulse parameter + name (str) : The name of the pulse parameter + options (OrderedDict) : The options of the pulse parameter """ - def __init__(self, name : str): + + def __init__(self, name: str): """Initializes the pulse parameter. - + Arguments: - name (str) -- The name of the pulse parameter + name (str) : The name of the pulse parameter """ self.name = name self.options = list() - - def get_pixmap(self): + + def get_pixmap(self) -> QPixmap: + """Gets the pixmap of the pulse parameter. + + Implment this method in the derived class. + + Returns: + QPixmap : The pixmap of the pulse parameter + """ raise NotImplementedError - - def add_option(self, option : "Option") -> None: + + def add_option(self, option: "Option") -> None: """Adds an option to the pulse parameter. - - Arguments: - option (Option) -- The option to add + + Args: + option (Option) : The option to add """ self.options.append(option) def get_options(self) -> list: - """ Gets the options of the pulse parameter. - + """Gets the options of the pulse parameter. + Returns: - list -- The options of the pulse parameter + list : The options of the pulse parameter """ return self.options - - def get_option_by_name(self, name : str) -> "Option": + + def get_option_by_name(self, name: str) -> "Option": """Gets an option by its name. - Arguments: - name (str) -- The name of the option + Args: + name (str) : The name of the option Returns: - Option -- The option with the specified name - + Option : The option with the specified name + Raises: - ValueError -- If no option with the specified name is found + ValueError : If no option with the specified name is found """ - for option in self.options: if option.name == name: return option raise ValueError("Option with name %s not found" % name) - def __init__(self, module): """Initializes the spectrometer model. - - Arguments: - module (Module) -- The module that the spectrometer is connected to + + Args: + module (Module) : The module that the spectrometer is connected to """ super().__init__(module) self.settings = OrderedDict() self.pulse_parameter_options = OrderedDict() - def add_setting(self, setting : Setting, category : str) -> None: + def add_setting(self, setting: Setting, category: str) -> None: + """Adds a setting to the spectrometer. + Args: + setting (Setting) : The setting to add + category (str) : The category of the setting + """ if category not in self.settings.keys(): self.settings[category] = [] self.settings[category].append(setting) - def get_setting_by_name(self, name : str) -> Setting: + def get_setting_by_name(self, name: str) -> Setting: """Gets a setting by its name. - - Arguments: - name (str) -- The name of the setting - + + Args: + name (str) : The name of the setting + Returns: - Setting -- The setting with the specified name - + Setting : The setting with the specified name + Raises: - ValueError -- If no setting with the specified name is found + ValueError : If no setting with the specified name is found """ for category in self.settings.keys(): for setting in self.settings[category]: @@ -112,28 +129,31 @@ class BaseSpectrometerModel(ModuleModel): return setting raise ValueError("Setting with name %s not found" % name) - def add_pulse_parameter_option(self, name : str, pulse_parameter_class : PulseParameter) -> None: - """ Adds a pulse parameter option to the spectrometer. - - Arguments: - name (str) -- The name of the pulse parameter - pulse_parameter_class (PulseParameter) -- The pulse parameter class""" + def add_pulse_parameter_option( + self, name: str, pulse_parameter_class: PulseParameter + ) -> None: + """Adds a pulse parameter option to the spectrometer. + + Args: + name (str) : The name of the pulse parameter + pulse_parameter_class (PulseParameter) : The pulse parameter class + """ self.pulse_parameter_options[name] = pulse_parameter_class @property def target_frequency(self): - """ The target frequency of the spectrometer in Hz. This is the frequency where the magnetic resonance experiment is performed. """ + """The target frequency of the spectrometer in Hz. This is the frequency where the magnetic resonance experiment is performed.""" raise NotImplementedError - + @target_frequency.setter def target_frequency(self, value): raise NotImplementedError - + @property def averages(self): - """ The number of averages for the spectrometer.""" + """The number of averages for the spectrometer.""" raise NotImplementedError - + @averages.setter def averages(self, value): raise NotImplementedError diff --git a/src/nqrduck_spectrometer/base_spectrometer_view.py b/src/nqrduck_spectrometer/base_spectrometer_view.py index 3834345..f891346 100644 --- a/src/nqrduck_spectrometer/base_spectrometer_view.py +++ b/src/nqrduck_spectrometer/base_spectrometer_view.py @@ -1,26 +1,34 @@ +"""The Base Class for all Spectrometer Views.""" + import logging -from pathlib import Path -from PyQt6.QtWidgets import QWidget, QLabel, QLineEdit, QHBoxLayout, QSizePolicy, QSpacerItem, QVBoxLayout, QCheckBox, QComboBox -from PyQt6.QtGui import QIcon -from PyQt6.QtCore import Qt, pyqtSlot +from PyQt6.QtWidgets import ( + QWidget, + QLabel, + QHBoxLayout, + QSizePolicy, + QSpacerItem, + QVBoxLayout, +) from nqrduck.module.module_view import ModuleView from nqrduck.assets.icons import Logos -from .settings import FloatSetting, IntSetting, BooleanSetting, SelectionSetting, StringSetting logger = logging.getLogger(__name__) class BaseSpectrometerView(ModuleView): """The View Class for all Spectrometers.""" - + def __init__(self, module): + """Initializes the spectrometer view.""" super().__init__(module) def load_settings_ui(self) -> None: """This method automatically generates a view for the settings of the module. - If there is a widget file that has been generated by Qt Designer, it will be used. Otherwise, a default view will be generated.""" - + + If there is a widget file that has been generated by Qt Designer, it will be used. Otherwise, a default view will be generated. + """ from .base_spectrometer_widget import Ui_Form + widget = QWidget() widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) self._ui_form = Ui_Form() @@ -53,37 +61,20 @@ class BaseSpectrometerView(ModuleView): setting_label = QLabel(setting.name) setting_label.setMinimumWidth(200) - # Depending on the setting type we add different widgets to the view - if isinstance(setting, FloatSetting) or isinstance(setting, IntSetting) or isinstance(setting, StringSetting): - edit_widget = QLineEdit(str(setting.value)) - edit_widget.setMinimumWidth(100) - # Connect the editingFinished signal to the on_value_changed slot of the setting - edit_widget.editingFinished.connect(lambda x=edit_widget, s=setting: s.on_value_changed(x.text())) - - elif isinstance(setting, BooleanSetting): - edit_widget = QCheckBox() - edit_widget.setChecked(setting.value) - edit_widget.stateChanged.connect(lambda x=edit_widget, s=setting: s.on_value_changed(x)) - - elif isinstance(setting, SelectionSetting): - edit_widget = QComboBox() - edit_widget.addItems(setting.options) - edit_widget.setCurrentText(setting.value) - edit_widget.currentTextChanged.connect(lambda x=edit_widget, s=setting: s.on_value_changed(x)) - + edit_widget = setting.widget + logger.debug("Setting widget: %s", edit_widget) # Add a icon that can be used as a tooltip if setting.description is not None: logger.debug("Adding tooltip to setting: %s", setting.name) - self_path = Path(__file__).parent icon = Logos.QuestionMark_16x16() icon_label = QLabel() icon_label.setPixmap(icon.pixmap(icon.availableSizes()[0])) icon_label.setFixedSize(icon.availableSizes()[0]) icon_label.setToolTip(setting.description) - - # Add a horizontal layout for the setting + + # Add a horizontal layout for the setting layout = QHBoxLayout() # Add the label and the line edit to the layout layout.addItem(spacer) @@ -93,12 +84,9 @@ class BaseSpectrometerView(ModuleView): layout.addStretch(1) # Add the layout to the vertical layout of the widget category_layout.addLayout(layout) - + category_layout.addStretch(1) self._ui_form.gridLayout.addLayout(category_layout, row, column) - - + # Push all the settings to the top of the widget self._ui_form.verticalLayout.addStretch(1) - - \ No newline at end of file diff --git a/src/nqrduck_spectrometer/base_spectrometer_widget.py b/src/nqrduck_spectrometer/base_spectrometer_widget.py index 37465dc..02a3651 100644 --- a/src/nqrduck_spectrometer/base_spectrometer_widget.py +++ b/src/nqrduck_spectrometer/base_spectrometer_widget.py @@ -6,10 +6,10 @@ # run again. Do not edit this file unless you know what you are doing. -from PyQt6 import QtCore, QtGui, QtWidgets +from PyQt6 import QtCore, QtWidgets -class Ui_Form(object): +class Ui_Form: def setupUi(self, Form): Form.setObjectName("Form") Form.resize(800, 647) diff --git a/src/nqrduck_spectrometer/controller.py b/src/nqrduck_spectrometer/controller.py index def160f..031913c 100644 --- a/src/nqrduck_spectrometer/controller.py +++ b/src/nqrduck_spectrometer/controller.py @@ -1,5 +1,9 @@ +"""The Controller for the Spectrometer Module. + +Careful - this is not the base class for the spectrometer submodules, but the controller for the spectrometer module itself. +""" + import logging -from PyQt6.QtCore import pyqtSlot from nqrduck.module.module_controller import ModuleController from nqrduck.core.main_controller import MainController from nqrduck_spectrometer.base_spectrometer import BaseSpectrometer @@ -40,6 +44,7 @@ class SpectrometerController(ModuleController): def process_signals(self, key: str, value: object) -> None: """This method processes the signals from the nqrduck module. + It is called by the nqrduck module when a signal is emitted. It then calls the corresponding method of the spectrometer model. @@ -59,6 +64,7 @@ class SpectrometerController(ModuleController): def on_loading(self) -> None: """This method is called when the module is loaded. + It connects the signals from the spectrometer model to the view. """ self._module.model.spectrometer_added.connect( @@ -71,6 +77,7 @@ class SpectrometerController(ModuleController): def on_measurement_start(self) -> None: """This method is called when a measurement is started. + It calls the on_measurement_start method of the active spectrometer. """ logger.debug( diff --git a/src/nqrduck_spectrometer/measurement.py b/src/nqrduck_spectrometer/measurement.py index f4cbe33..2415678 100644 --- a/src/nqrduck_spectrometer/measurement.py +++ b/src/nqrduck_spectrometer/measurement.py @@ -1,14 +1,25 @@ +"""Class for handling measurement data.""" + import logging import numpy as np from nqrduck.helpers.signalprocessing import SignalProcessing as sp logger = logging.getLogger(__name__) -class Measurement(): - """This class defines how measurement data should look. + +class Measurement: + """This class defines how measurement data should look. + It includes pulse parameters necessary for further signal processing. Every spectrometer should adhere to this data structure in order to be compatible with the rest of the nqrduck. + Args: + tdx (np.array): Time axis for the x axis of the measurement data. + tdy (np.array): Time axis for the y axis of the measurement data. + target_frequency (float): Target frequency of the measurement. + frequency_shift (float, optional): Frequency shift of the measurement. Defaults to 0. + IF_frequency (float, optional): Intermediate frequency of the measurement. Defaults to 0. + Attributes: tdx (np.array): Time axis for the x axis of the measurement data. tdy (np.array): Time axis for the y axis of the measurement data. @@ -19,7 +30,15 @@ class Measurement(): yf (np.array): Frequency axis for the y axis of the measurement data. """ - def __init__(self, tdx, tdy, target_frequency, frequency_shift : float = 0, IF_frequency : float = 0) -> None: + def __init__( + self, + tdx, + tdy, + target_frequency, + frequency_shift: float = 0, + IF_frequency: float = 0, + ) -> None: + """Initializes the measurement.""" self.tdx = tdx self.tdy = tdy self.target_frequency = target_frequency @@ -30,33 +49,35 @@ class Measurement(): def to_json(self): """Converts the measurement to a json-compatible format. - + Returns: - dict -- The measurement in json-compatible format. + dict : The measurement in json-compatible format. """ return { "tdx": self.tdx.tolist(), - "tdy": [[x.real, x.imag] for x in self.tdy], # Convert complex numbers to list + "tdy": [ + [x.real, x.imag] for x in self.tdy + ], # Convert complex numbers to list "target_frequency": self.target_frequency, - "IF_frequency": self.IF_frequency + "IF_frequency": self.IF_frequency, } - + @classmethod - def from_json(cls, json): + def from_json(cls, json: dict): """Converts the json format to a measurement. - - Arguments: - json (dict) -- The measurement in json-compatible format. - + + Args: + json (dict) : The measurement in json-compatible format. + Returns: - Measurement -- The measurement. + Measurement : The measurement. """ tdy = np.array([complex(y[0], y[1]) for y in json["tdy"]]) return cls( np.array(json["tdx"]), tdy, - target_frequency = json["target_frequency"], - IF_frequency = json["IF_frequency"] + target_frequency=json["target_frequency"], + IF_frequency=json["IF_frequency"], ) # Measurement data @@ -64,7 +85,7 @@ class Measurement(): def tdx(self): """Time axis for the x axis of the measurement data.""" return self._tdx - + @tdx.setter def tdx(self, value): self._tdx = value @@ -73,7 +94,7 @@ class Measurement(): def tdy(self): """Time axis for the y axis of the measurement data.""" return self._tdy - + @tdy.setter def tdy(self, value): self._tdy = value @@ -82,7 +103,7 @@ class Measurement(): def fdx(self): """Frequency axis for the x axis of the measurement data.""" return self._fdx - + @fdx.setter def fdx(self, value): self._fdx = value @@ -91,7 +112,7 @@ class Measurement(): def fdy(self): """Frequency axis for the y axis of the measurement data.""" return self._fdy - + @fdy.setter def fdy(self, value): self._fdy = value @@ -101,7 +122,7 @@ class Measurement(): def target_frequency(self): """Target frequency of the measurement.""" return self._target_frequency - + @target_frequency.setter def target_frequency(self, value): self._target_frequency = value diff --git a/src/nqrduck_spectrometer/model.py b/src/nqrduck_spectrometer/model.py index 7e7fc31..a2343f8 100644 --- a/src/nqrduck_spectrometer/model.py +++ b/src/nqrduck_spectrometer/model.py @@ -1,3 +1,5 @@ +"""The model for the nqrduck-spectrometer module. This module is responsible for managing the spectrometers.""" + import logging from PyQt6.QtCore import pyqtSignal from nqrduck.module.module_model import ModuleModel @@ -5,39 +7,62 @@ from .base_spectrometer import BaseSpectrometer logger = logging.getLogger(__name__) + class SpectrometerModel(ModuleModel): + """The model for the spectrometer module. + + This class is responsible for managing the spectrometers. + + Args: + module (Module) : The module that the spectrometer is connected to + + Attributes: + active_spectrometer (BaseSpectrometer) : The currently active spectrometer + available_spectrometers (dict) : A dictionary of all available spectrometers + + Signals: + spectrometer_added (BaseSpectrometer) : Signal emitted when a spectrometer is added + active_spectrometer_changed (BaseSpectrometer) : Signal emitted when the active spectrometer is changed + """ + spectrometer_added = pyqtSignal(BaseSpectrometer) active_spectrometer_changed = pyqtSignal(BaseSpectrometer) def __init__(self, module) -> None: + """Initializes the spectrometer model.""" super().__init__(module) self._active_spectrometer = None self._available_spectrometers = dict() @property def active_spectrometer(self): - """The currently active spectrometer. This is the one that is currently being used. - """ + """The currently active spectrometer. This is the one that is currently being used.""" return self._active_spectrometer - + @active_spectrometer.setter def active_spectrometer(self, value): self._active_spectrometer = value self.active_spectrometer_changed.emit(value) spectrometer_module_name = value.model.toolbar_name logger.debug("Active spectrometer changed to %s", spectrometer_module_name) - self.module.nqrduck_signal.emit("active_spectrometer_changed", spectrometer_module_name) - + self.module.nqrduck_signal.emit( + "active_spectrometer_changed", spectrometer_module_name + ) + @property def available_spectrometers(self): - """A dictionary of all available spectrometers. The key is the name of the spectrometer and the value is the module. - """ + """A dictionary of all available spectrometers. The key is the name of the spectrometer and the value is the module.""" return self._available_spectrometers - + def add_spectrometers(self, spectrometer_module_name, module): - self._available_spectrometers [spectrometer_module_name] = module + """Adds a spectrometer to the available spectrometers. + + Args: + spectrometer_module_name (str) : The name of the spectrometer + module (BaseSpectrometer) : The module of the spectrometer + """ + self._available_spectrometers[spectrometer_module_name] = module logger.debug("Added module: %s", spectrometer_module_name) self.spectrometer_added.emit(module) self.active_spectrometer = module - - + self.add_submodule(spectrometer_module_name) diff --git a/src/nqrduck_spectrometer/pulseparameters.py b/src/nqrduck_spectrometer/pulseparameters.py index b87ea7f..d2bc5e7 100644 --- a/src/nqrduck_spectrometer/pulseparameters.py +++ b/src/nqrduck_spectrometer/pulseparameters.py @@ -1,9 +1,14 @@ +"""Contains the classes for the pulse parameters of the spectrometer. It includes the functions and the options for the pulse parameters. + +Todo: + * This shouldn't be in the spectrometer module. It should be in it"s own pulse sequence module. +""" + from __future__ import annotations import logging import numpy as np import sympy from decimal import Decimal -from pathlib import Path from PyQt6.QtGui import QPixmap from nqrduck.contrib.mplwidget import MplWidget from nqrduck.helpers.signalprocessing import SignalProcessing as sp @@ -14,6 +19,22 @@ logger = logging.getLogger(__name__) class Function: + """A function that can be used as a pulse parameter. + + This class is the base class for all functions that can be used as pulse parameters. Functions can be used for pulse shapes, for example. + + Args: + expr (str | sympy.Expr): The expression of the function. + + Attributes: + name (str): The name of the function. + parameters (list): The parameters of the function. + expr (sympy.Expr): The sympy expression of the function. + resolution (Decimal): The resolution of the function in seconds. + start_x (float): The x value where the evalution of the function starts. + end_x (float): The x value where the evalution of the function ends. + """ + name: str parameters: list expression: str | sympy.Expr @@ -22,6 +43,7 @@ class Function: end_x: float def __init__(self, expr) -> None: + """Initializes the function.""" self.parameters = [] self.expr = expr self.resolution = Decimal(1 / 30.72e6) @@ -42,7 +64,7 @@ class Function: t = np.linspace(0, float(pulse_length), n) return t - def evaluate(self, pulse_length: Decimal, resolution : Decimal = None) -> np.ndarray: + def evaluate(self, pulse_length: Decimal, resolution: Decimal = None) -> np.ndarray: """Evaluates the function for the given pulse length. Args: @@ -121,7 +143,9 @@ class Function: mpl_widget.canvas.ax.grid(True) return mpl_widget - def get_pulse_amplitude(self, pulse_length: Decimal, resolution : Decimal = None) -> np.array: + def get_pulse_amplitude( + self, pulse_length: Decimal, resolution: Decimal = None + ) -> np.array: """Returns the pulse amplitude in the time domain. Args: @@ -133,11 +157,12 @@ class Function: """ return self.evaluate(pulse_length, resolution=resolution) - def add_parameter(self, parameter: "Function.Parameter") -> None: + def add_parameter(self, parameter: Function.Parameter) -> None: """Adds a parameter to the function. Args: - parameter (Function.Parameter): The parameter to add.""" + parameter (Function.Parameter): The parameter to add. + """ self.parameters.append(parameter) def to_json(self) -> dict: @@ -156,7 +181,7 @@ class Function: } @classmethod - def from_json(cls, data: dict) -> "Function": + def from_json(cls, data: dict) -> Function: """Creates a function from a json representation. Args: @@ -193,7 +218,7 @@ class Function: if isinstance(expr, str): try: self._expr = sympy.sympify(expr) - except: + except ValueError: logger.error("Could not convert %s to a sympy expression", expr) raise SyntaxError("Could not convert %s to a sympy expression" % expr) elif isinstance(expr, sympy.Expr): @@ -208,7 +233,7 @@ class Function: def resolution(self, resolution): try: self._resolution = Decimal(resolution) - except: + except ValueError: logger.error("Could not convert %s to a decimal", resolution) raise SyntaxError("Could not convert %s to a decimal" % resolution) @@ -221,7 +246,7 @@ class Function: def start_x(self, start_x): try: self._start_x = float(start_x) - except: + except ValueError: logger.error("Could not convert %s to a float", start_x) raise SyntaxError("Could not convert %s to a float" % start_x) @@ -234,7 +259,7 @@ class Function: def end_x(self, end_x): try: self._end_x = float(end_x) - except: + except ValueError: logger.error("Could not convert %s to a float", end_x) raise SyntaxError("Could not convert %s to a float" % end_x) @@ -242,12 +267,30 @@ class Function: """This is the default pixmap for every function. If one wants to have a custom pixmap, this method has to be overwritten. Returns: - QPixmap -- The default pixmap for every function""" + QPixmap : The default pixmap for every function + """ pixmap = PulseParamters.TXCustom() return pixmap class Parameter: + """A parameter of a function. + + This can be for example the standard deviation of a Gaussian function. + + Args: + name (str): The name of the parameter. + symbol (str): The symbol of the parameter. + value (float): The value of the parameter. + + Attributes: + name (str): The name of the parameter. + symbol (str): The symbol of the parameter. + value (float): The value of the parameter. + default (float): The default value of the parameter. + """ + def __init__(self, name: str, symbol: str, value: float) -> None: + """Initializes the parameter.""" self.name = name self.symbol = symbol self.value = value @@ -296,20 +339,31 @@ class RectFunction(Function): name = "Rectangular" def __init__(self) -> None: + """Initializes the RecFunction.""" expr = sympy.sympify("1") super().__init__(expr) - def get_pixmap(self): + def get_pixmap(self) -> QPixmap: + """Returns the pixmap of the rectangular function. + + Returns: + QPixmap: The pixmap of the rectangular function. + """ pixmap = PulseParamters.TXRect() return pixmap class SincFunction(Function): - """The sinc function.""" + """The sinc function. + + The sinc function is defined as sin(x * l) / (x * l). + The parameter is the scale factor l. + """ name = "Sinc" def __init__(self) -> None: + """Initializes the SincFunction.""" expr = sympy.sympify("sin(x * l)/ (x * l)") super().__init__(expr) self.add_parameter(Function.Parameter("Scale Factor", "l", 2)) @@ -317,16 +371,26 @@ class SincFunction(Function): self.end_x = np.pi def get_pixmap(self): + """Returns the pixmap of the sinc function. + + Returns: + QPixmap: The pixmap of the sinc function. + """ pixmap = PulseParamters.TXSinc() return pixmap class GaussianFunction(Function): - """The Gaussian function.""" + """The Gaussian function. + + The Gaussian function is defined as exp(-0.5 * ((x - mu) / sigma)**2). + The parameters are the mean and the standard deviation. + """ name = "Gaussian" def __init__(self) -> None: + """Initializes the GaussianFunction.""" expr = sympy.sympify("exp(-0.5 * ((x - mu) / sigma)**2)") super().__init__(expr) self.add_parameter(Function.Parameter("Mean", "mu", 0)) @@ -335,6 +399,11 @@ class GaussianFunction(Function): self.end_x = np.pi def get_pixmap(self): + """Returns the QPixmap of the Gaussian function. + + Returns: + QPixmap: The QPixmap of the Gaussian function. + """ pixmap = PulseParamters.TXGauss() return pixmap @@ -351,25 +420,55 @@ class CustomFunction(Function): name = "Custom" def __init__(self) -> None: + """Initializes the Custom Function.""" expr = sympy.sympify(" 2 * x**2 + 3 * x + 1") super().__init__(expr) class Option: - """Defines options for the pulse parameters which can then be set accordingly.""" + """Defines options for the pulse parameters which can then be set accordingly. + + Options can be of different types, for example boolean, numeric or function. + + Args: + name (str): The name of the option. + value: The value of the option. + + Attributes: + name (str): The name of the option. + value: The value of the option. + """ def __init__(self, name: str, value) -> None: + """Initializes the option.""" self.name = name self.value = value def set_value(self): + """Sets the value of the option. + + This method has to be implemented in the derived classes. + """ raise NotImplementedError def to_json(self): + """Returns a json representation of the option. + + Returns: + dict: The json representation of the option. + """ return {"name": self.name, "value": self.value, "type": self.TYPE} @classmethod - def from_json(cls, data) -> "Option": + def from_json(cls, data) -> Option: + """Creates an option from a json representation. + + Args: + data (dict): The json representation of the option. + + Returns: + Option: The option. + """ for subclass in cls.__subclasses__(): if subclass.TYPE == data["type"]: cls = subclass @@ -390,6 +489,7 @@ class BooleanOption(Option): TYPE = "Boolean" def set_value(self, value): + """Sets the value of the option.""" self.value = value @@ -399,49 +499,102 @@ class NumericOption(Option): TYPE = "Numeric" def set_value(self, value): + """Sets the value of the option.""" self.value = float(value) class FunctionOption(Option): """Defines a selection option for a pulse parameter option. - It takes different function objects.""" + + It takes different function objects. + + Args: + name (str): The name of the option. + functions (list): The functions that can be selected. + + Attributes: + name (str): The name of the option. + functions (list): The functions that can be selected. + """ TYPE = "Function" def __init__(self, name, functions) -> None: + """Initializes the FunctionOption.""" super().__init__(name, functions[0]) self.functions = functions def set_value(self, value): + """Sets the value of the option. + + Args: + value: The value of the option. + """ self.value = value def get_function_by_name(self, name): + """Returns the function with the given name. + + Args: + name (str): The name of the function. + + Returns: + Function: The function with the given name. + """ for function in self.functions: if function.name == name: return function raise ValueError("Function with name %s not found" % name) def to_json(self): + """Returns a json representation of the option. + + Returns: + dict: The json representation of the option. + """ return {"name": self.name, "value": self.value.to_json(), "type": self.TYPE} @classmethod def from_json(cls, data): + """Creates a FunctionOption from a json representation. + + Args: + data (dict): The json representation of the FunctionOption. + + Returns: + FunctionOption: The FunctionOption. + """ functions = [function() for function in Function.__subclasses__()] obj = cls(data["name"], functions) obj.value = Function.from_json(data["value"]) return obj def get_pixmap(self): + """Returns the pixmap of the function.""" return self.value.get_pixmap() class TXPulse(BaseSpectrometerModel.PulseParameter): - """ Basic TX Pulse Parameter. It includes options for the relative amplitude, the phase and the pulse shape.""" + """Basic TX Pulse Parameter. It includes options for the relative amplitude, the phase and the pulse shape. + + Args: + name (str): The name of the pulse parameter. + + Attributes: + RELATIVE_AMPLITUDE (str): The relative amplitude of the pulse. + TX_PHASE (str): The phase of the pulse. + TX_PULSE_SHAPE (str): The pulse shape. + """ + RELATIVE_AMPLITUDE = "Relative TX Amplitude" TX_PHASE = "TX Phase" TX_PULSE_SHAPE = "TX Pulse Shape" def __init__(self, name) -> None: + """Initializes the TX Pulse Parameter. + + It adds the options for the relative amplitude, the phase and the pulse shape. + """ super().__init__(name) self.add_option(NumericOption(self.RELATIVE_AMPLITUDE, 0)) self.add_option(NumericOption(self.TX_PHASE, 0)) @@ -453,6 +606,11 @@ class TXPulse(BaseSpectrometerModel.PulseParameter): ) def get_pixmap(self): + """Returns the pixmap of the TX Pulse Parameter. + + Returns: + QPixmap: The pixmap of the TX Pulse Parameter depending on the relative amplitude. + """ if self.get_option_by_name(self.RELATIVE_AMPLITUDE).value > 0: return self.get_option_by_name(self.TX_PULSE_SHAPE).get_pixmap() else: @@ -461,15 +619,32 @@ class TXPulse(BaseSpectrometerModel.PulseParameter): class RXReadout(BaseSpectrometerModel.PulseParameter): - """Basic PulseParameter for the RX Readout. It includes an option for the RX Readout state.""" + """Basic PulseParameter for the RX Readout. It includes an option for the RX Readout state. + + Args: + name (str): The name of the pulse parameter. + + Attributes: + RX (str): The RX Readout state. + """ + RX = "RX" def __init__(self, name) -> None: + """Initializes the RX Readout PulseParameter. + + It adds an option for the RX Readout state. + """ super().__init__(name) self.add_option(BooleanOption(self.RX, False)) def get_pixmap(self): - if self.get_option_by_name(self.RX).value == False: + """Returns the pixmap of the RX Readout PulseParameter. + + Returns: + QPixmap: The pixmap of the RX Readout PulseParameter depending on the RX Readout state. + """ + if self.get_option_by_name(self.RX).value is False: pixmap = PulseParamters.RXOff() else: pixmap = PulseParamters.RXOn() @@ -477,14 +652,32 @@ class RXReadout(BaseSpectrometerModel.PulseParameter): class Gate(BaseSpectrometerModel.PulseParameter): + """Basic PulseParameter for the Gate. It includes an option for the Gate state. + + Args: + name (str): The name of the pulse parameter. + + Attributes: + GATE_STATE (str): The Gate state. + """ + GATE_STATE = "Gate State" def __init__(self, name) -> None: + """Initializes the Gate PulseParameter. + + It adds an option for the Gate state. + """ super().__init__(name) self.add_option(BooleanOption(self.GATE_STATE, False)) def get_pixmap(self): - if self.get_option_by_name(self.GATE_STATE).value == False: + """Returns the pixmap of the Gate PulseParameter. + + Returns: + QPixmap: The pixmap of the Gate PulseParameter depending on the Gate state. + """ + if self.get_option_by_name(self.GATE_STATE).value is False: pixmap = PulseParamters.GateOff() else: pixmap = PulseParamters.GateOn() diff --git a/src/nqrduck_spectrometer/pulsesequence.py b/src/nqrduck_spectrometer/pulsesequence.py index 40b74c7..1e64844 100644 --- a/src/nqrduck_spectrometer/pulsesequence.py +++ b/src/nqrduck_spectrometer/pulsesequence.py @@ -1,3 +1,5 @@ +"""Contains the PulseSequence class that is used to store a pulse sequence and its events.""" + import logging from collections import OrderedDict from nqrduck.helpers.unitconverter import UnitConverter @@ -7,34 +9,70 @@ logger = logging.getLogger(__name__) class PulseSequence: - """A pulse sequence is a collection of events that are executed in a certain order.""" + """A pulse sequence is a collection of events that are executed in a certain order. + + Args: + name (str): The name of the pulse sequence + + Attributes: + name (str): The name of the pulse sequence + events (list): The events of the pulse sequence + """ def __init__(self, name) -> None: + """Initializes the pulse sequence.""" self.name = name self.events = list() def get_event_names(self) -> list: + """Returns a list of the names of the events in the pulse sequence. + + Returns: + list: The names of the events + """ return [event.name for event in self.events] class Event: - """An event is a part of a pulse sequence. It has a name and a duration and different parameters that have to be set.""" + """An event is a part of a pulse sequence. It has a name and a duration and different parameters that have to be set. + + Args: + name (str): The name of the event + duration (str): The duration of the event + + Attributes: + name (str): The name of the event + duration (str): The duration of the event + parameters (OrderedDict): The parameters of the event + """ def __init__(self, name: str, duration: str) -> None: + """Initializes the event.""" self.parameters = OrderedDict() self.name = name self.duration = duration def add_parameter(self, parameter) -> None: + """Adds a parameter to the event. + + Args: + parameter: The parameter to add + """ self.parameters.append(parameter) def on_duration_changed(self, duration: str) -> None: + """This method is called when the duration of the event is changed. + + Args: + duration (str): The new duration of the event + """ logger.debug("Duration of event %s changed to %s", self.name, duration) self.duration = duration @classmethod def load_event(cls, event, pulse_parameter_options): - """ - Loads an event from a dict. The pulse paramter options are needed to load the parameters + """Loads an event from a dict. + + The pulse paramter options are needed to load the parameters and determine if the correct spectrometer is active. Args: @@ -63,13 +101,14 @@ class PulseSequence: ) return obj - + @property def duration(self): + """The duration of the event.""" return self._duration - + @duration.setter - def duration(self, duration : str): + def duration(self, duration: str): # Duration needs to be a positive number try: duration = UnitConverter.to_decimal(duration) @@ -77,15 +116,15 @@ class PulseSequence: raise ValueError("Duration needs to be a number") if duration < 0: raise ValueError("Duration needs to be a positive number") - + self._duration = duration - def to_json(self): - """Returns a dict with all the data in the pulse sequence + """Returns a dict with all the data in the pulse sequence. Returns: - dict: The dict with the sequence data""" + dict: The dict with the sequence data + """ data = {"name": self.name, "events": []} for event in self.events: event_data = { @@ -102,7 +141,9 @@ class PulseSequence: @classmethod def load_sequence(cls, sequence, pulse_parameter_options): - """Loads a pulse sequence from a dict. The pulse paramter options are needed to load the parameters + """Loads a pulse sequence from a dict. + + The pulse paramter options are needed to load the parameters and make sure the correct spectrometer is active. Args: @@ -120,54 +161,62 @@ class PulseSequence: obj.events.append(cls.Event.load_event(event_data, pulse_parameter_options)) return obj - + class Variable: - """ A variable is a parameter that can be used within a pulsesequence as a placeholder. + """A variable is a parameter that can be used within a pulsesequence as a placeholder. + For example the event duration a Variable with name a can be set. This variable can then be set to a list of different values. - On execution of the pulse sequence the event duration will be set to the first value in the list. + On execution of the pulse sequence the event duration will be set to the first value in the list. Then the pulse sequence will be executed with the second value of the list. This is repeated until the pulse sequence has - been executed with all values in the list.""" - + been executed with all values in the list. + """ + @property def name(self): + """The name of the variable.""" return self._name - + @name.setter - def name(self, name : str): + def name(self, name: str): if not isinstance(name, str): raise TypeError("Name needs to be a string") self._name = name - + @property def values(self): + """The values of the variable. This is a list of values that the variable can take.""" return self._values - + @values.setter - def values(self, values : list): + def values(self, values: list): if not isinstance(values, list): raise TypeError("Values needs to be a list") self._values = values class VariableGroup: - """ Variables can be grouped together. - If we have groups a and b the pulse sequence will be executed for all combinations of variables in a and b.""" - + """Variables can be grouped together. + + If we have groups a and b the pulse sequence will be executed for all combinations of variables in a and b. + """ + @property def name(self): + """The name of the variable group.""" return self._name - + @name.setter - def name(self, name : str): + def name(self, name: str): if not isinstance(name, str): raise TypeError("Name needs to be a string") self._name = name @property def variables(self): + """The variables in the group. This is a list of variables.""" return self._variables - + @variables.setter - def variables(self, variables : list): + def variables(self, variables: list): if not isinstance(variables, list): raise TypeError("Variables needs to be a list") - self._variables = variables \ No newline at end of file + self._variables = variables diff --git a/src/nqrduck_spectrometer/settings.py b/src/nqrduck_spectrometer/settings.py index 36a3e07..53fd8c0 100644 --- a/src/nqrduck_spectrometer/settings.py +++ b/src/nqrduck_spectrometer/settings.py @@ -1,117 +1,342 @@ +"""Settings for the different spectrometers.""" + import logging import ipaddress from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot +from PyQt6.QtWidgets import QLineEdit, QComboBox, QCheckBox +from PyQt6.QtGui import QValidator +from nqrduck.helpers.validators import DuckIntValidator, DuckFloatValidator logger = logging.getLogger(__name__) + class Setting(QObject): - """A setting for the spectrometer is a value that is the same for all events in a pulse sequence. - E.g. the number of averages or the number of points in a spectrum.""" - settings_changed = pyqtSignal() + """A setting for the spectrometer is a value that is the same for all events in a pulse sequence. - def __init__(self, name, description) -> None: - super().__init__() - self.name = name - self.description = description + E.g. the Transmit gain or the number of points in a spectrum. + + Args: + name (str) : The name of the setting + description (str) : A description of the setting + default : The default value of the setting + + Attributes: + name (str) : The name of the setting + description (str) : A description of the setting + value : The value of the setting + widget : The widget that is used to change the setting + """ + + settings_changed = pyqtSignal() + + def __init__(self, name: str, description: str, default=None) -> None: + """Create a new setting. + + Args: + name (str): The name of the setting. + description (str): A description of the setting. + default: The default value of the setting. + """ + super().__init__() + self.name = name + self.description = description + if default is not None: + self.value = default + + # This can be overridden by subclasses + self.widget = self.get_widget() + + @pyqtSlot(str) + def on_value_changed(self, value): + """This method is called when the value of the setting is changed. + + Args: + value (str): The new value of the setting. + """ + logger.debug("Setting %s changed to %s", self.name, value) + self.value = value + self.settings_changed.emit() + + def get_setting(self): + """Return the value of the setting. + + Returns: + The value of the setting. + """ + return float(self.value) + + def get_widget(self): + """Return a widget for the setting. + + The default widget is simply a QLineEdit. + This method can be overwritten by subclasses to return a different widget. + + Returns: + QLineEdit: A QLineEdit widget that can be used to change the setting. + """ + widget = QLineEdit(str(self.value)) + widget.setMinimumWidth(100) + widget.editingFinished.connect( + lambda x=widget, s=self: s.on_value_changed(x.text()) + ) + return widget + + def update_widget_style(self): + """Update the style of the QLineEdit widget to indicate if the value is valid.""" + if ( + self.validator.validate(self.widget.text(), 0) + == QValidator.State.Acceptable + ): + self.widget.setStyleSheet("QLineEdit { background-color: white; }") + elif ( + self.validator.validate(self.widget.text(), 0) + == QValidator.State.Intermediate + ): + self.widget.setStyleSheet("QLineEdit { background-color: yellow; }") + else: + self.widget.setStyleSheet("QLineEdit { background-color: red; }") - @pyqtSlot(str) - def on_value_changed(self, value): - logger.debug("Setting %s changed to %s", self.name, value) - self.value = value - self.settings_changed.emit() - def get_setting(self): - return float(self.value) - class FloatSetting(Setting): - """ A setting that is a Float. """ - def __init__(self, name : str, default : float, description : str) -> None: - super().__init__(name, description) - self.value = default + """A setting that is a Float. + + Args: + name (str) : The name of the setting + default : The default value of the setting + description (str) : A description of the setting + validator (QValidator) : A validator for the setting + min_value : The minimum value of the setting + max_value : The maximum value of the setting + """ + + DEFAULT_LENGTH = 100 + + def __init__( + self, + name: str, + default: float, + description: str, + validator: QValidator = None, + min_value: float = None, + max_value: float = None, + ) -> None: + """Create a new float setting.""" + super().__init__(name, description, default) + + # If a validator is given, set it for the QLineEdit widget + if validator: + self.validator = validator + else: + self.validator = DuckFloatValidator(self, min_value, max_value) + + self.widget = self.get_widget() + # self.widget.setValidator(self.validator) + # Connect the update_widget_style method to the textChanged signal + self.widget.textChanged.connect(self.update_widget_style) @property def value(self): + """The value of the setting. In this case, a float.""" return self._value - + @value.setter def value(self, value): try: - self._value = float(value) + if self.validator.validate(value, 0) == QValidator.State.Acceptable: + self._value = float(value) + self.settings_changed.emit() + # This should never be reached because the validator should prevent this except ValueError: raise ValueError("Value must be a float") - self.settings_changed.emit() - + # This happens when the validator has not yet been set + except AttributeError: + self._value = float(value) + self.settings_changed.emit() + class IntSetting(Setting): - """ A setting that is an Integer.""" - def __init__(self, name : str, default : int, description : str) -> None: - super().__init__(name, description) - self.value = default + """A setting that is an Integer. + + Args: + name (str) : The name of the setting + default : The default value of the setting + description (str) : A description of the setting + validator (QValidator) : A validator for the setting + min_value : The minimum value of the setting + max_value : The maximum value of the setting + """ + + def __init__( + self, + name: str, + default: int, + description: str, + validator: QValidator = None, + min_value=None, + max_value=None, + ) -> None: + """Create a new int setting.""" + super().__init__(name, description, default) + + # If a validator is given, set it for the QLineEdit widget + if validator: + self.validator = validator + else: + self.validator = DuckIntValidator(self, min_value, max_value) + + self.widget = self.get_widget() + # Connect the update_widget_style method to the textChanged signal + self.widget.textChanged.connect(self.update_widget_style) + + self.min_value = min_value + self.max_value = max_value @property def value(self): + """The value of the setting. In this case, an int.""" return self._value - + @value.setter def value(self, value): try: - self._value = int(value) + if self.validator.validate(value, 0) == QValidator.State.Acceptable: + value = int(float(value)) + + self._value = value + self.settings_changed.emit() except ValueError: raise ValueError("Value must be an int") - self.settings_changed.emit() + # This happens when the validator has not yet been set + except AttributeError as e: + logger.debug(e) + self._value = int(float(value)) + self.settings_changed.emit() + class BooleanSetting(Setting): - """ A setting that is a Boolean.""" - - def __init__(self, name : str, default : bool, description : str) -> None: - super().__init__(name, description) - self.value = default + """A setting that is a Boolean. + + Args: + name (str) : The name of the setting + default : The default value of the setting + description (str) : A description of the setting + """ + + def __init__(self, name: str, default: bool, description: str) -> None: + """Create a new boolean setting.""" + super().__init__(name, description, default) + + # Overrides the default widget + self.widget = self.get_widget() @property def value(self): + """The value of the setting. In this case, a bool.""" return self._value - + @value.setter def value(self, value): try: self._value = bool(value) + self.settings_changed.emit() except ValueError: raise ValueError("Value must be a bool") - self.settings_changed.emit() + + def get_widget(self): + """Return a widget for the setting. + + This returns a QCheckBox widget. + + Returns: + QCheckBox: A QCheckBox widget that can be used to change the setting. + """ + widget = QCheckBox() + widget.setChecked(self.value) + widget.stateChanged.connect( + lambda x=widget, s=self: s.on_value_changed(bool(x)) + ) + return widget + class SelectionSetting(Setting): - """ A setting that is a selection from a list of options.""" - def __init__(self, name : str, options : list, default : str, description : str) -> None: - super().__init__(name, description) + """A setting that is a selection from a list of options. + + Args: + name (str) : The name of the setting + options (list) : A list of options to choose from + default : The default value of the setting + description (str) : A description of the setting + """ + + def __init__( + self, name: str, options: list, default: str, description: str + ) -> None: + """Create a new selection setting.""" + super().__init__(name, description, default) # Check if default is in options if default not in options: raise ValueError("Default value must be one of the options") - + self.options = options - self.value = default + + # Overrides the default widget + self.widget = self.get_widget() @property def value(self): + """The value of the setting. In this case, a string.""" return self._value - + @value.setter def value(self, value): - if value in self.options: + try: + if value in self.options: + self._value = value + self.settings_changed.emit() + else: + raise ValueError("Value must be one of the options") + # This fixes a bug when creating the widget when the options are not yet set + except AttributeError: self._value = value - else: - raise ValueError("Value must be one of the options") - self.settings_changed.emit() + self.options = [value] + self.settings_changed.emit() + + def get_widget(self): + """Return a widget for the setting. + + This returns a QComboBox widget. + + Returns: + QComboBox: A QComboBox widget that can be used to change the setting. + """ + widget = QComboBox() + widget.addItems(self.options) + widget.setCurrentText(self.value) + widget.currentTextChanged.connect( + lambda x=widget, s=self: s.on_value_changed(x) + ) + return widget + class IPSetting(Setting): - """ A setting that is an IP address.""" - def __init__(self, name : str, default : str, description : str) -> None: + """A setting that is an IP address. + + Args: + name (str) : The name of the setting + default : The default value of the setting + description (str) : A description of the setting + """ + + def __init__(self, name: str, default: str, description: str) -> None: + """Create a new IP setting.""" super().__init__(name, description) self.value = default @property def value(self): + """The value of the setting. In this case, an IP address.""" return self._value - + @value.setter def value(self, value): try: @@ -121,21 +346,29 @@ class IPSetting(Setting): raise ValueError("Value must be a valid IP address") self.settings_changed.emit() + class StringSetting(Setting): - """ A setting that is a string.""" - def __init__(self, name : str, default : str, description : str) -> None: - super().__init__(name, description) - self.value = default + """A setting that is a string. + + Args: + name (str) : The name of the setting + default : The default value of the setting + description (str) : A description of the setting + """ + + def __init__(self, name: str, default: str, description: str) -> None: + """Create a new string setting.""" + super().__init__(name, description, default) @property def value(self): + """The value of the setting. In this case, a string.""" return self._value - + @value.setter def value(self, value): try: self._value = str(value) + self.settings_changed.emit() except ValueError: raise ValueError("Value must be a string") - - self.settings_changed.emit() \ No newline at end of file diff --git a/src/nqrduck_spectrometer/spectrometer.py b/src/nqrduck_spectrometer/spectrometer.py index 4c10425..8ad7354 100644 --- a/src/nqrduck_spectrometer/spectrometer.py +++ b/src/nqrduck_spectrometer/spectrometer.py @@ -1,3 +1,4 @@ +"""Module creation for the spectrometer module.""" from nqrduck.module.module import Module from .model import SpectrometerModel from .view import SpectrometerView diff --git a/src/nqrduck_spectrometer/view.py b/src/nqrduck_spectrometer/view.py index bc14499..70a1888 100644 --- a/src/nqrduck_spectrometer/view.py +++ b/src/nqrduck_spectrometer/view.py @@ -1,7 +1,9 @@ +"""View for the Spectrometer Module. Careful - this is not the base class for the spectrometer submodules, but the view for the spectrometer module itself.""" + import logging -from PyQt6.QtWidgets import QWidget, QToolButton, QToolBar, QMenu +from PyQt6.QtWidgets import QWidget, QMenu from PyQt6.QtGui import QAction -from PyQt6.QtCore import pyqtSlot, pyqtSignal +from PyQt6.QtCore import pyqtSlot from nqrduck.module.module_view import ModuleView from .widget import Ui_Form @@ -9,11 +11,16 @@ logger = logging.getLogger(__name__) class SpectrometerView(ModuleView): + """The view for the spectrometer module. This class is responsible for displaying the spectrometer module in the main window. + + It contains the menu buttons for the different spectrometers and the stacked widget that shows the different spectrometer views. + + Args: + module (Module) : The spectrometer module that this view belongs to + """ + def __init__(self, module): - """This class is the view for the spectrometer module. It contains the menu buttons for the different spectrometers. - It also contains the stacked widget that shows the different spectrometer views. - :param module: The spectrometer module that this view belongs to. - """ + """Initializes the spectrometer view. It sets up the UI and the stacked widget for the spectrometers.""" super().__init__(module) widget = QWidget() @@ -25,17 +32,20 @@ class SpectrometerView(ModuleView): self.blank = QWidget() self._ui_form.stackedWidgetSettings.setStyleSheet( - "QStackedWidget { background-color: #fafafa; border: 2px solid #000; }" + "QStackedWidget { border: 2px solid #000; }" ) self._ui_form.stackedWidgetPulseProgrammer.setStyleSheet( - "QStackedWidget { background-color: #fafafa; border: 2px solid #000; }" + "QStackedWidget { border: 2px solid #000; }" ) def on_active_spectrometer_changed(self, module): """This method is called when the active spectrometer is changed. + It changes the active view in the stacked widget to the one that was just activated. - :param module: The BaseSpectrometer module that was just activated. + + Args: + module (BaseSpectrometer) : The spectrometer module that was just activated """ self._ui_form.stackedWidgetSettings.setCurrentWidget(module.settings_view) @@ -52,8 +62,11 @@ class SpectrometerView(ModuleView): def on_spectrometer_widget_changed(self, module): """This method is called when a new spectrometer widget is added to the module. + It adds the widget to the stacked widget and sets it as the current widget. - :param widget: The widget that was added to the module. + + Args: + module (BaseSpectrometer) : The spectrometer module that was just added """ logger.debug( "Adding settings widget to stacked widget: %s", module.settings_view @@ -72,7 +85,7 @@ class SpectrometerView(ModuleView): self._ui_form.stackedWidgetPulseProgrammer.setCurrentWidget( module.model.pulse_programmer.pulse_programmer_view ) - except AttributeError as e: + except AttributeError: logger.debug( "No pulse programmer widget to add for spectrometer %s", module.model.name, @@ -83,7 +96,9 @@ class SpectrometerView(ModuleView): def on_spectrometer_added(self, module): """This method changes the active spectrometer to the one that was just added. - :param module: The BaseSpectrometer module that was just added. + + Args: + module (BaseSpectrometer) : The spectrometer module that was just added """ module.change_spectrometer.connect(self.on_menu_button_clicked) self.on_spectrometer_widget_changed(module) @@ -119,10 +134,14 @@ class SpectrometerView(ModuleView): @pyqtSlot(str) def on_menu_button_clicked(self, spectrometer_name): - """This method is called when a menu button is clicked. It changes the active spectrometer to the one that was clicked. + """This method is called when a menu button is clicked. + + It changes the active spectrometer to the one that was clicked. It also unchecks all other menu buttons. - :param spectrometer_name: The name of the spectrometer that was clicked.""" + Args: + spectrometer_name (str) : The name of the spectrometer that was clicked + """ logger.debug("Active module changed to: %s", spectrometer_name) for action in self._actions.values(): action.setChecked(False)