From b7de6baa6b56974a85a4d79cefd4b4baaf9f3eb1 Mon Sep 17 00:00:00 2001 From: jupfi Date: Fri, 26 Apr 2024 11:09:04 +0200 Subject: [PATCH 01/11] Update to new external functions. --- src/nqrduck_spectrometer/pulseparameters.py | 474 +++----------------- 1 file changed, 60 insertions(+), 414 deletions(-) diff --git a/src/nqrduck_spectrometer/pulseparameters.py b/src/nqrduck_spectrometer/pulseparameters.py index d2bc5e7..4a3507d 100644 --- a/src/nqrduck_spectrometer/pulseparameters.py +++ b/src/nqrduck_spectrometer/pulseparameters.py @@ -6,425 +6,22 @@ Todo: from __future__ import annotations import logging -import numpy as np -import sympy -from decimal import Decimal -from PyQt6.QtGui import QPixmap -from nqrduck.contrib.mplwidget import MplWidget -from nqrduck.helpers.signalprocessing import SignalProcessing as sp + +from numpy.core.multiarray import array as array + from nqrduck.assets.icons import PulseParamters +from nqrduck.helpers.functions import ( + Function, + RectFunction, + SincFunction, + GaussianFunction, + CustomFunction, +) from .base_spectrometer_model import BaseSpectrometerModel 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 - resolution: Decimal - start_x: float - end_x: float - - def __init__(self, expr) -> None: - """Initializes the function.""" - self.parameters = [] - self.expr = expr - self.resolution = Decimal(1 / 30.72e6) - self.start_x = -1 - self.end_x = 1 - - def get_time_points(self, pulse_length: Decimal) -> np.ndarray: - """Returns the time domain points for the function with the given pulse length. - - Args: - pulse_length (Decimal): The pulse length in seconds. - - Returns: - np.ndarray: The time domain points. - """ - # Get the time domain points - n = int(pulse_length / self.resolution) - t = np.linspace(0, float(pulse_length), n) - return t - - def evaluate(self, pulse_length: Decimal, resolution: Decimal = None) -> np.ndarray: - """Evaluates the function for the given pulse length. - - Args: - pulse_length (Decimal): The pulse length in seconds. - resolution (Decimal, optional): The resolution of the function in seconds. Defaults to None. - - Returns: - np.ndarray: The evaluated function. - """ - if resolution is None: - resolution = self.resolution - n = int(pulse_length / resolution) - t = np.linspace(self.start_x, self.end_x, n) - x = sympy.symbols("x") - - found_variables = dict() - # Create a dictionary of the parameters and their values - for parameter in self.parameters: - found_variables[parameter.symbol] = parameter.value - - final_expr = self.expr.subs(found_variables) - # If the expression is a number (does not depend on x), return an array of that number - if final_expr.is_number: - return np.full(t.shape, float(final_expr)) - - f = sympy.lambdify([x], final_expr, "numpy") - - return f(t) - - def get_tdx(self, pulse_length: float) -> np.ndarray: - """Returns the time domain points and the evaluated function for the given pulse length. - - Args: - pulse_length (float): The pulse length in seconds. - - Returns: - np.ndarray: The time domain points. - """ - n = int(pulse_length / self.resolution) - t = np.linspace(self.start_x, self.end_x, n) - return t - - def frequency_domain_plot(self, pulse_length: Decimal) -> MplWidget: - """Plots the frequency domain of the function for the given pulse length. - - Args: - pulse_length (Decimal): The pulse length in seconds. - - Returns: - MplWidget: The matplotlib widget containing the plot. - """ - mpl_widget = MplWidget() - td = self.get_time_points(pulse_length) - yd = self.evaluate(pulse_length) - xdf, ydf = sp.fft(td, yd) - mpl_widget.canvas.ax.plot(xdf, abs(ydf)) - mpl_widget.canvas.ax.set_xlabel("Frequency in Hz") - mpl_widget.canvas.ax.set_ylabel("Magnitude") - mpl_widget.canvas.ax.grid(True) - return mpl_widget - - def time_domain_plot(self, pulse_length: Decimal) -> MplWidget: - """Plots the time domain of the function for the given pulse length. - - Args: - pulse_length (Decimal): The pulse length in seconds. - - Returns: - MplWidget: The matplotlib widget containing the plot. - """ - mpl_widget = MplWidget() - td = self.get_time_points(pulse_length) - mpl_widget.canvas.ax.plot(td, abs(self.evaluate(pulse_length))) - mpl_widget.canvas.ax.set_xlabel("Time in s") - mpl_widget.canvas.ax.set_ylabel("Magnitude") - mpl_widget.canvas.ax.grid(True) - return mpl_widget - - def get_pulse_amplitude( - self, pulse_length: Decimal, resolution: Decimal = None - ) -> np.array: - """Returns the pulse amplitude in the time domain. - - Args: - pulse_length (Decimal): The pulse length in seconds. - resolution (Decimal, optional): The resolution of the function in seconds. Defaults to None. - - Returns: - np.array: The pulse amplitude. - """ - return self.evaluate(pulse_length, resolution=resolution) - - def add_parameter(self, parameter: Function.Parameter) -> None: - """Adds a parameter to the function. - - Args: - parameter (Function.Parameter): The parameter to add. - """ - self.parameters.append(parameter) - - def to_json(self) -> dict: - """Returns a json representation of the function. - - Returns: - dict: The json representation of the function. - """ - return { - "name": self.name, - "parameters": [parameter.to_json() for parameter in self.parameters], - "expression": str(self.expr), - "resolution": self.resolution, - "start_x": self.start_x, - "end_x": self.end_x, - } - - @classmethod - def from_json(cls, data: dict) -> Function: - """Creates a function from a json representation. - - Args: - data (dict): The json representation of the function. - - Returns: - Function: The function. - """ - for subclass in cls.__subclasses__(): - if subclass.name == data["name"]: - cls = subclass - break - - obj = cls() - obj.expr = data["expression"] - obj.name = data["name"] - obj.resolution = data["resolution"] - obj.start_x = data["start_x"] - obj.end_x = data["end_x"] - - obj.parameters = [] - for parameter in data["parameters"]: - obj.add_parameter(Function.Parameter.from_json(parameter)) - - return obj - - @property - def expr(self): - """The sympy expression of the function.""" - return self._expr - - @expr.setter - def expr(self, expr): - if isinstance(expr, str): - try: - self._expr = sympy.sympify(expr) - 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): - self._expr = expr - - @property - def resolution(self): - """The resolution of the function in seconds.""" - return self._resolution - - @resolution.setter - def resolution(self, resolution): - try: - self._resolution = Decimal(resolution) - except ValueError: - logger.error("Could not convert %s to a decimal", resolution) - raise SyntaxError("Could not convert %s to a decimal" % resolution) - - @property - def start_x(self): - """The x value where the evalution of the function starts.""" - return self._start_x - - @start_x.setter - def start_x(self, start_x): - try: - self._start_x = float(start_x) - except ValueError: - logger.error("Could not convert %s to a float", start_x) - raise SyntaxError("Could not convert %s to a float" % start_x) - - @property - def end_x(self): - """The x value where the evalution of the function ends.""" - return self._end_x - - @end_x.setter - def end_x(self, end_x): - try: - self._end_x = float(end_x) - except ValueError: - logger.error("Could not convert %s to a float", end_x) - raise SyntaxError("Could not convert %s to a float" % end_x) - - def get_pixmap(self): - """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 - """ - 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 - self.default = value - - def set_value(self, value: float) -> None: - """Sets the value of the parameter. - - Args: - value (float): The new value of the parameter. - """ - self.value = value - logger.debug("Parameter %s set to %s", self.name, self.value) - - def to_json(self) -> dict: - """Returns a json representation of the parameter. - - Returns: - dict: The json representation of the parameter. - """ - return { - "name": self.name, - "symbol": self.symbol, - "value": self.value, - "default": self.default, - } - - @classmethod - def from_json(cls, data): - """Creates a parameter from a json representation. - - Args: - data (dict): The json representation of the parameter. - - Returns: - Function.Parameter: The parameter. - """ - obj = cls(data["name"], data["symbol"], data["value"]) - obj.default = data["default"] - return obj - - -class RectFunction(Function): - """The rectangular function.""" - - name = "Rectangular" - - def __init__(self) -> None: - """Initializes the RecFunction.""" - expr = sympy.sympify("1") - super().__init__(expr) - - 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 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)) - self.start_x = -np.pi - 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 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)) - self.add_parameter(Function.Parameter("Standard Deviation", "sigma", 1)) - self.start_x = -np.pi - 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 - - -# class TriangleFunction(Function): -# def __init__(self) -> None: -# expr = sympy.sympify("triang(x)") -# super().__init__(lambda x: triang(x)) - - -class CustomFunction(Function): - """A custom 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. @@ -574,6 +171,50 @@ class FunctionOption(Option): return self.value.get_pixmap() +class TXRectFunction(RectFunction): + + def __init__(self) -> None: + super().__init__() + self.name = "TX Rectangular Pulse" + + def get_pixmap(self): + """Returns the pixmaps of the function.""" + return PulseParamters.TXRect() + + +class TXSincFunction(SincFunction): + + def __init__(self) -> None: + super().__init__() + self.name = "TX Sinc Pulse" + + def get_pixmap(self): + """Returns the pixmaps of the function.""" + return PulseParamters.TXSinc() + + +class TXGaussianFunction(GaussianFunction): + + def __init__(self) -> None: + super().__init__() + self.name = "TX Gaussian Pulse" + + def get_pixmap(self): + """Returns the pixmaps of the function.""" + return PulseParamters.TXGauss() + + +class TXCustomFunction(CustomFunction): + + def __init__(self) -> None: + super().__init__() + self.name = "TX Custom Pulse" + + def get_pixmap(self): + """Returns the pixmaps of the function.""" + return PulseParamters.TXCustom() + + class TXPulse(BaseSpectrometerModel.PulseParameter): """Basic TX Pulse Parameter. It includes options for the relative amplitude, the phase and the pulse shape. @@ -601,7 +242,12 @@ class TXPulse(BaseSpectrometerModel.PulseParameter): self.add_option( FunctionOption( self.TX_PULSE_SHAPE, - [RectFunction(), SincFunction(), GaussianFunction(), CustomFunction()], + [ + TXRectFunction(), + TXSincFunction(), + TXGaussianFunction(), + TXCustomFunction(), + ], ), ) From 833f23675f383d40131c6849e49cdec8089b7719 Mon Sep 17 00:00:00 2001 From: jupfi Date: Fri, 26 Apr 2024 12:22:28 +0200 Subject: [PATCH 02/11] Corrected names. --- src/nqrduck_spectrometer/pulseparameters.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/nqrduck_spectrometer/pulseparameters.py b/src/nqrduck_spectrometer/pulseparameters.py index 4a3507d..47d40a4 100644 --- a/src/nqrduck_spectrometer/pulseparameters.py +++ b/src/nqrduck_spectrometer/pulseparameters.py @@ -175,18 +175,17 @@ class TXRectFunction(RectFunction): def __init__(self) -> None: super().__init__() - self.name = "TX Rectangular Pulse" + self.name = "Rectangular" def get_pixmap(self): """Returns the pixmaps of the function.""" return PulseParamters.TXRect() - class TXSincFunction(SincFunction): def __init__(self) -> None: super().__init__() - self.name = "TX Sinc Pulse" + self.name = "Sinc" def get_pixmap(self): """Returns the pixmaps of the function.""" @@ -197,7 +196,7 @@ class TXGaussianFunction(GaussianFunction): def __init__(self) -> None: super().__init__() - self.name = "TX Gaussian Pulse" + self.name = "Gaussian" def get_pixmap(self): """Returns the pixmaps of the function.""" @@ -208,7 +207,7 @@ class TXCustomFunction(CustomFunction): def __init__(self) -> None: super().__init__() - self.name = "TX Custom Pulse" + self.name = "Custom" def get_pixmap(self): """Returns the pixmaps of the function.""" From a9fee217c3c2495e1cdc9a0b74171606e4501bc0 Mon Sep 17 00:00:00 2001 From: jupfi Date: Fri, 26 Apr 2024 12:28:33 +0200 Subject: [PATCH 03/11] Linting. --- .../base_spectrometer_model.py | 4 +-- .../base_spectrometer_view.py | 4 +-- src/nqrduck_spectrometer/pulseparameters.py | 25 ++++++++++++++++--- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/nqrduck_spectrometer/base_spectrometer_model.py b/src/nqrduck_spectrometer/base_spectrometer_model.py index 11fa178..8e5e1dd 100644 --- a/src/nqrduck_spectrometer/base_spectrometer_model.py +++ b/src/nqrduck_spectrometer/base_spectrometer_model.py @@ -88,7 +88,7 @@ class BaseSpectrometerModel(ModuleModel): for option in self.options: if option.name == name: return option - raise ValueError("Option with name %s not found" % name) + raise ValueError(f"Option with name {name} not found") def __init__(self, module): """Initializes the spectrometer model. @@ -127,7 +127,7 @@ class BaseSpectrometerModel(ModuleModel): for setting in self.settings[category]: if setting.name == name: return setting - raise ValueError("Setting with name %s not found" % name) + raise ValueError(f"Setting with name {name} not found") def add_pulse_parameter_option( self, name: str, pulse_parameter_class: PulseParameter diff --git a/src/nqrduck_spectrometer/base_spectrometer_view.py b/src/nqrduck_spectrometer/base_spectrometer_view.py index 9c88908..52b523c 100644 --- a/src/nqrduck_spectrometer/base_spectrometer_view.py +++ b/src/nqrduck_spectrometer/base_spectrometer_view.py @@ -38,7 +38,7 @@ class BaseSpectrometerView(ModuleView): grid = self._ui_form.gridLayout self._ui_form.verticalLayout.removeItem(self._ui_form.gridLayout) # Add name of the spectrometer to the view - label = QLabel("%s Settings:" % self.module.model.toolbar_name) + label = QLabel(f"{self.module.model.toolbar_name} Settings:") label.setStyleSheet("font-weight: bold;") self._ui_form.verticalLayout.setSpacing(5) self._ui_form.verticalLayout.addWidget(label) @@ -47,7 +47,7 @@ class BaseSpectrometerView(ModuleView): for category_count, category in enumerate(self.module.model.settings.keys()): logger.debug("Adding settings for category: %s", category) category_layout = QVBoxLayout() - category_label = QLabel("%s:" % category) + category_label = QLabel(f"{category}:" % category) category_label.setStyleSheet("font-weight: bold;") row = category_count // 2 column = category_count % 2 diff --git a/src/nqrduck_spectrometer/pulseparameters.py b/src/nqrduck_spectrometer/pulseparameters.py index 47d40a4..d259e3d 100644 --- a/src/nqrduck_spectrometer/pulseparameters.py +++ b/src/nqrduck_spectrometer/pulseparameters.py @@ -141,7 +141,7 @@ class FunctionOption(Option): for function in self.functions: if function.name == name: return function - raise ValueError("Function with name %s not found" % name) + raise ValueError(f"Function with name {name} not found") def to_json(self): """Returns a json representation of the option. @@ -172,8 +172,13 @@ class FunctionOption(Option): class TXRectFunction(RectFunction): + """TX Rectangular function. + + Adds the pixmap of the function to the class. + """ def __init__(self) -> None: + """Initializes the TX Rectangular function.""" super().__init__() self.name = "Rectangular" @@ -182,8 +187,12 @@ class TXRectFunction(RectFunction): return PulseParamters.TXRect() class TXSincFunction(SincFunction): - + """TX Sinc function. + + Adds the pixmap of the function to the class. + """ def __init__(self) -> None: + """Initializes the TX Sinc function.""" super().__init__() self.name = "Sinc" @@ -193,8 +202,12 @@ class TXSincFunction(SincFunction): class TXGaussianFunction(GaussianFunction): - + """TX Gaussian function. + + Adds the pixmap of the function to the class. + """ def __init__(self) -> None: + """Initializes the TX Gaussian function.""" super().__init__() self.name = "Gaussian" @@ -204,8 +217,12 @@ class TXGaussianFunction(GaussianFunction): class TXCustomFunction(CustomFunction): - + """TX Custom function. + + Adds the pixmap of the function to the class. + """ def __init__(self) -> None: + """Initializes the TX Custom function.""" super().__init__() self.name = "Custom" From f593c82ec57ab5218589e21ac6e0284ac3583614 Mon Sep 17 00:00:00 2001 From: jupfi Date: Fri, 26 Apr 2024 12:31:33 +0200 Subject: [PATCH 04/11] Typo. --- src/nqrduck_spectrometer/base_spectrometer_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nqrduck_spectrometer/base_spectrometer_view.py b/src/nqrduck_spectrometer/base_spectrometer_view.py index 52b523c..1b53600 100644 --- a/src/nqrduck_spectrometer/base_spectrometer_view.py +++ b/src/nqrduck_spectrometer/base_spectrometer_view.py @@ -47,7 +47,7 @@ class BaseSpectrometerView(ModuleView): for category_count, category in enumerate(self.module.model.settings.keys()): logger.debug("Adding settings for category: %s", category) category_layout = QVBoxLayout() - category_label = QLabel(f"{category}:" % category) + category_label = QLabel(f"{category}:") category_label.setStyleSheet("font-weight: bold;") row = category_count // 2 column = category_count % 2 From 35e05cb25c21b8d1bc761ee740e5d68e376bcf7a Mon Sep 17 00:00:00 2001 From: jupfi Date: Fri, 26 Apr 2024 13:29:02 +0200 Subject: [PATCH 05/11] Fixed another bug with saving and loading of pulse sequences. Added constraints to numerical parameters. --- src/nqrduck_spectrometer/pulseparameters.py | 63 +++++++++++++++------ 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/src/nqrduck_spectrometer/pulseparameters.py b/src/nqrduck_spectrometer/pulseparameters.py index d259e3d..dfeafc3 100644 --- a/src/nqrduck_spectrometer/pulseparameters.py +++ b/src/nqrduck_spectrometer/pulseparameters.py @@ -36,6 +36,12 @@ class Option: value: The value of the option. """ + subclasses = [] + + def __init_subclass__(cls, **kwargs): + super().__init_subclass__(**kwargs) + cls.subclasses.append(cls) + def __init__(self, name: str, value) -> None: """Initializes the option.""" self.name = name @@ -54,7 +60,7 @@ class Option: Returns: dict: The json representation of the option. """ - return {"name": self.name, "value": self.value, "type": self.TYPE} + return {"name": self.name, "value": self.value, "class": self.__class__.__name__} @classmethod def from_json(cls, data) -> Option: @@ -66,8 +72,9 @@ class Option: Returns: Option: The option. """ - for subclass in cls.__subclasses__(): - if subclass.TYPE == data["type"]: + for subclass in cls.subclasses: + logger.debug(f"Keys data: {data.keys()}") + if subclass.__name__ == data["class"]: cls = subclass break @@ -95,9 +102,24 @@ class NumericOption(Option): TYPE = "Numeric" + def __init__( + self, name: str, value, is_float=True, min_value=None, max_value=None + ) -> None: + super().__init__(name, value) + self.is_float = is_float + self.min_value = min_value + self.max_value = max_value + def set_value(self, value): """Sets the value of the option.""" - self.value = float(value) + if value < self.min_value: + self.value = self.min_value + elif value >= self.max_value: + self.value = self.max_value + else: + raise ValueError( + f"Value {value} is not in the range of {self.min_value} to {self.max_value}. This should have been cought earlier." + ) class FunctionOption(Option): @@ -149,7 +171,7 @@ class FunctionOption(Option): Returns: dict: The json representation of the option. """ - return {"name": self.name, "value": self.value.to_json(), "type": self.TYPE} + return {"name": self.name, "value": self.value.to_json(), "class": self.__class__.__name__, "functions" : [function.to_json() for function in self.functions]} @classmethod def from_json(cls, data): @@ -161,7 +183,9 @@ class FunctionOption(Option): Returns: FunctionOption: The FunctionOption. """ - functions = [function() for function in Function.__subclasses__()] + logger.debug(f"Data: {data}") + # These are all available functions + functions = [Function.from_json(function) for function in data["functions"]] obj = cls(data["name"], functions) obj.value = Function.from_json(data["value"]) return obj @@ -173,7 +197,7 @@ class FunctionOption(Option): class TXRectFunction(RectFunction): """TX Rectangular function. - + Adds the pixmap of the function to the class. """ @@ -186,11 +210,13 @@ class TXRectFunction(RectFunction): """Returns the pixmaps of the function.""" return PulseParamters.TXRect() + class TXSincFunction(SincFunction): """TX Sinc function. - + Adds the pixmap of the function to the class. """ + def __init__(self) -> None: """Initializes the TX Sinc function.""" super().__init__() @@ -203,9 +229,10 @@ class TXSincFunction(SincFunction): class TXGaussianFunction(GaussianFunction): """TX Gaussian function. - + Adds the pixmap of the function to the class. """ + def __init__(self) -> None: """Initializes the TX Gaussian function.""" super().__init__() @@ -218,9 +245,10 @@ class TXGaussianFunction(GaussianFunction): class TXCustomFunction(CustomFunction): """TX Custom function. - + Adds the pixmap of the function to the class. """ + def __init__(self) -> None: """Initializes the TX Custom function.""" super().__init__() @@ -236,24 +264,23 @@ class TXPulse(BaseSpectrometerModel.PulseParameter): 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" + RELATIVE_AMPLITUDE = "Relative TX Amplitude (%)" TX_PHASE = "TX Phase" TX_PULSE_SHAPE = "TX Pulse Shape" - def __init__(self, name) -> None: + def __init__(self, name: str) -> 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.RELATIVE_AMPLITUDE, 0, is_float=False, min_value=0, max_value=100 + ) + ) self.add_option(NumericOption(self.TX_PHASE, 0)) self.add_option( FunctionOption( From 27b43fc4944018503dacc12f86dd4399c47ccfc7 Mon Sep 17 00:00:00 2001 From: jupfi Date: Fri, 26 Apr 2024 13:38:40 +0200 Subject: [PATCH 06/11] Fixed bug with loading of min-max values. --- src/nqrduck_spectrometer/pulseparameters.py | 53 +++++++++++++++++---- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/src/nqrduck_spectrometer/pulseparameters.py b/src/nqrduck_spectrometer/pulseparameters.py index dfeafc3..6a85e3c 100644 --- a/src/nqrduck_spectrometer/pulseparameters.py +++ b/src/nqrduck_spectrometer/pulseparameters.py @@ -60,7 +60,11 @@ class Option: Returns: dict: The json representation of the option. """ - return {"name": self.name, "value": self.value, "class": self.__class__.__name__} + return { + "name": self.name, + "value": self.value, + "class": self.__class__.__name__, + } @classmethod def from_json(cls, data) -> Option: @@ -90,8 +94,6 @@ class Option: class BooleanOption(Option): """Defines a boolean option for a pulse parameter option.""" - TYPE = "Boolean" - def set_value(self, value): """Sets the value of the option.""" self.value = value @@ -100,8 +102,6 @@ class BooleanOption(Option): class NumericOption(Option): """Defines a numeric option for a pulse parameter option.""" - TYPE = "Numeric" - def __init__( self, name: str, value, is_float=True, min_value=None, max_value=None ) -> None: @@ -121,6 +121,40 @@ class NumericOption(Option): f"Value {value} is not in the range of {self.min_value} to {self.max_value}. This should have been cought earlier." ) + 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, + "class": self.__class__.__name__, + "is_float": self.is_float, + "min_value": self.min_value, + "max_value": self.max_value, + } + + @classmethod + def from_json(cls, data): + """Creates a NumericOption from a json representation. + + Args: + data (dict): The json representation of the NumericOption. + + Returns: + NumericOption: The NumericOption. + """ + obj = cls( + data["name"], + data["value"], + is_float=data["is_float"], + min_value=data["min_value"], + max_value=data["max_value"], + ) + return obj + class FunctionOption(Option): """Defines a selection option for a pulse parameter option. @@ -136,8 +170,6 @@ class FunctionOption(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]) @@ -171,7 +203,12 @@ class FunctionOption(Option): Returns: dict: The json representation of the option. """ - return {"name": self.name, "value": self.value.to_json(), "class": self.__class__.__name__, "functions" : [function.to_json() for function in self.functions]} + return { + "name": self.name, + "value": self.value.to_json(), + "class": self.__class__.__name__, + "functions": [function.to_json() for function in self.functions], + } @classmethod def from_json(cls, data): From 9d1b0fb3acda5d3c58a3a027bc7d01c5a1ae8584 Mon Sep 17 00:00:00 2001 From: jupfi Date: Fri, 26 Apr 2024 13:49:10 +0200 Subject: [PATCH 07/11] Added saving of version to pulsesequence. --- src/nqrduck_spectrometer/pulsesequence.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/nqrduck_spectrometer/pulsesequence.py b/src/nqrduck_spectrometer/pulsesequence.py index 1e64844..6c24c43 100644 --- a/src/nqrduck_spectrometer/pulsesequence.py +++ b/src/nqrduck_spectrometer/pulsesequence.py @@ -1,6 +1,7 @@ """Contains the PulseSequence class that is used to store a pulse sequence and its events.""" import logging +import importlib.metadata from collections import OrderedDict from nqrduck.helpers.unitconverter import UnitConverter from nqrduck_spectrometer.pulseparameters import Option @@ -19,9 +20,14 @@ class PulseSequence: events (list): The events of the pulse sequence """ - def __init__(self, name) -> None: + def __init__(self, name, version = None) -> None: """Initializes the pulse sequence.""" self.name = name + # Saving version to check for compatability of saved sequence + if version is not None: + self.version = version + else: + self.version = importlib.metadata.version("nqrduck_spectrometer") self.events = list() def get_event_names(self) -> list: @@ -125,7 +131,8 @@ class PulseSequence: Returns: dict: The dict with the sequence data """ - data = {"name": self.name, "events": []} + # Get the versions of this package + data = {"name": self.name, "version" : self.version, "events": []} for event in self.events: event_data = { "name": event.name, @@ -156,7 +163,7 @@ class PulseSequence: Raises: KeyError: If the pulse parameter options are not the same as the ones in the pulse sequence """ - obj = cls(sequence["name"]) + obj = cls(sequence["name"], version = sequence["version"]) for event_data in sequence["events"]: obj.events.append(cls.Event.load_event(event_data, pulse_parameter_options)) From 06a4cbc6cde3d08cfd72289d4d1935cb7dabb4a5 Mon Sep 17 00:00:00 2001 From: jupfi Date: Fri, 26 Apr 2024 14:42:20 +0200 Subject: [PATCH 08/11] Implemented apodization. --- src/nqrduck_spectrometer/measurement.py | 34 +++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/nqrduck_spectrometer/measurement.py b/src/nqrduck_spectrometer/measurement.py index 2415678..4648706 100644 --- a/src/nqrduck_spectrometer/measurement.py +++ b/src/nqrduck_spectrometer/measurement.py @@ -2,7 +2,9 @@ import logging import numpy as np +from decimal import Decimal from nqrduck.helpers.signalprocessing import SignalProcessing as sp +from nqrduck.helpers.functions import Function logger = logging.getLogger(__name__) @@ -39,12 +41,44 @@ class Measurement: IF_frequency: float = 0, ) -> None: """Initializes the measurement.""" + # Convert to decimal self.tdx = tdx self.tdy = tdy self.target_frequency = target_frequency self.fdx, self.fdy = sp.fft(tdx, tdy, frequency_shift) self.IF_frequency = IF_frequency + def apodization(self, function : Function): + """ + Applies apodization to the measurement data. + + Args: + function (Function): Apodization function. + + returns: + Measurement : The apodized measurement. + """ + # Get the y data weights from the function + duration = (self.tdx[-1] - self.tdx[0]) * 1e-6 + + resolution = duration / len(self.tdx) + + logger.debug("Resolution: %s", resolution) + + y_weight = function.get_pulse_amplitude(duration, resolution) + + tdy_measurement = self.tdy * y_weight + + apodized_measurement = Measurement( + self.tdx, + tdy_measurement, + target_frequency=self.target_frequency, + IF_frequency=self.IF_frequency, + ) + + return apodized_measurement + + # Data saving and loading def to_json(self): From 37796e58001a036e544c4823fc9b2b9aa3392dea Mon Sep 17 00:00:00 2001 From: jupfi Date: Fri, 26 Apr 2024 16:10:27 +0200 Subject: [PATCH 09/11] Linting. --- src/nqrduck_spectrometer/measurement.py | 4 +--- src/nqrduck_spectrometer/pulseparameters.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/nqrduck_spectrometer/measurement.py b/src/nqrduck_spectrometer/measurement.py index 4648706..927f536 100644 --- a/src/nqrduck_spectrometer/measurement.py +++ b/src/nqrduck_spectrometer/measurement.py @@ -2,7 +2,6 @@ import logging import numpy as np -from decimal import Decimal from nqrduck.helpers.signalprocessing import SignalProcessing as sp from nqrduck.helpers.functions import Function @@ -49,8 +48,7 @@ class Measurement: self.IF_frequency = IF_frequency def apodization(self, function : Function): - """ - Applies apodization to the measurement data. + """Applies apodization to the measurement data. Args: function (Function): Apodization function. diff --git a/src/nqrduck_spectrometer/pulseparameters.py b/src/nqrduck_spectrometer/pulseparameters.py index 6a85e3c..ccbc05f 100644 --- a/src/nqrduck_spectrometer/pulseparameters.py +++ b/src/nqrduck_spectrometer/pulseparameters.py @@ -39,6 +39,7 @@ class Option: subclasses = [] def __init_subclass__(cls, **kwargs): + """Adds the subclass to the list of subclasses.""" super().__init_subclass__(**kwargs) cls.subclasses.append(cls) @@ -105,6 +106,15 @@ class NumericOption(Option): def __init__( self, name: str, value, is_float=True, min_value=None, max_value=None ) -> None: + """Initializes the NumericOption. + + Args: + name (str): The name of the option. + value: The value of the option. + is_float (bool): If the value is a float. + min_value: The minimum value of the option. + max_value: The maximum value of the option. + """ super().__init__(name, value) self.is_float = is_float self.min_value = min_value From adc088aae1c3982707a3886af4c3f3917d9c01b8 Mon Sep 17 00:00:00 2001 From: jupfi Date: Fri, 26 Apr 2024 17:31:42 +0200 Subject: [PATCH 10/11] Improved logging. Removed Decimals. --- src/nqrduck_spectrometer/pulsesequence.py | 2 +- src/nqrduck_spectrometer/settings.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/nqrduck_spectrometer/pulsesequence.py b/src/nqrduck_spectrometer/pulsesequence.py index 6c24c43..b0499fd 100644 --- a/src/nqrduck_spectrometer/pulsesequence.py +++ b/src/nqrduck_spectrometer/pulsesequence.py @@ -117,7 +117,7 @@ class PulseSequence: def duration(self, duration: str): # Duration needs to be a positive number try: - duration = UnitConverter.to_decimal(duration) + duration = UnitConverter.to_float(duration) except ValueError: raise ValueError("Duration needs to be a number") if duration < 0: diff --git a/src/nqrduck_spectrometer/settings.py b/src/nqrduck_spectrometer/settings.py index 4528b79..7187f1d 100644 --- a/src/nqrduck_spectrometer/settings.py +++ b/src/nqrduck_spectrometer/settings.py @@ -156,6 +156,7 @@ class FloatSetting(NumericalSetting): """ if state: self.value = text + self.settings_changed.emit() @property def value(self): @@ -164,6 +165,7 @@ class FloatSetting(NumericalSetting): @value.setter def value(self, value): + logger.debug(f"Setting {self.name} to {value}") self._value = float(value) self.settings_changed.emit() @@ -210,6 +212,7 @@ class IntSetting(NumericalSetting): """ if state: self.value = text + self.settings_changed.emit() @property def value(self): @@ -218,6 +221,7 @@ class IntSetting(NumericalSetting): @value.setter def value(self, value): + logger.debug(f"Setting {self.name} to {value}") value = int(float(value)) self._value = value self.settings_changed.emit() From 4802bb0e6e78b51c7e89dc446122b8f1b6d98a91 Mon Sep 17 00:00:00 2001 From: jupfi Date: Fri, 26 Apr 2024 17:49:09 +0200 Subject: [PATCH 11/11] Version bump. --- CHANGELOG.md | 3 +++ pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index babc14d..2763596 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +### Version 0.0.9 (26-04-2024) +- Switched to new formbuilder, moved Function to core + ### Version 0.0.8 (18-04-2024) - Automatic deployment to PyPI diff --git a/pyproject.toml b/pyproject.toml index aab9c24..97835fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ allow-direct-references = true [project] name = "nqrduck-spectrometer" -version = "0.0.8" +version = "0.0.9" authors = [ { name="jupfi", email="support@nqruck.cool" }, ]