mirror of
https://github.com/nqrduck/nqrduck-spectrometer.git
synced 2024-12-22 08:17:49 +00:00
Merge pull request #20 from nqrduck/formbuilder-and-function-optimization
Formbuilder and function optimization
This commit is contained in:
commit
17d5e79a70
8 changed files with 225 additions and 443 deletions
|
@ -1,5 +1,8 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
### Version 0.0.9 (26-04-2024)
|
||||||
|
- Switched to new formbuilder, moved Function to core
|
||||||
|
|
||||||
### Version 0.0.8 (18-04-2024)
|
### Version 0.0.8 (18-04-2024)
|
||||||
- Automatic deployment to PyPI
|
- Automatic deployment to PyPI
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ allow-direct-references = true
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "nqrduck-spectrometer"
|
name = "nqrduck-spectrometer"
|
||||||
version = "0.0.8"
|
version = "0.0.9"
|
||||||
authors = [
|
authors = [
|
||||||
{ name="jupfi", email="support@nqruck.cool" },
|
{ name="jupfi", email="support@nqruck.cool" },
|
||||||
]
|
]
|
||||||
|
|
|
@ -88,7 +88,7 @@ class BaseSpectrometerModel(ModuleModel):
|
||||||
for option in self.options:
|
for option in self.options:
|
||||||
if option.name == name:
|
if option.name == name:
|
||||||
return option
|
return option
|
||||||
raise ValueError("Option with name %s not found" % name)
|
raise ValueError(f"Option with name {name} not found")
|
||||||
|
|
||||||
def __init__(self, module):
|
def __init__(self, module):
|
||||||
"""Initializes the spectrometer model.
|
"""Initializes the spectrometer model.
|
||||||
|
@ -127,7 +127,7 @@ class BaseSpectrometerModel(ModuleModel):
|
||||||
for setting in self.settings[category]:
|
for setting in self.settings[category]:
|
||||||
if setting.name == name:
|
if setting.name == name:
|
||||||
return setting
|
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(
|
def add_pulse_parameter_option(
|
||||||
self, name: str, pulse_parameter_class: PulseParameter
|
self, name: str, pulse_parameter_class: PulseParameter
|
||||||
|
|
|
@ -38,7 +38,7 @@ class BaseSpectrometerView(ModuleView):
|
||||||
grid = self._ui_form.gridLayout
|
grid = self._ui_form.gridLayout
|
||||||
self._ui_form.verticalLayout.removeItem(self._ui_form.gridLayout)
|
self._ui_form.verticalLayout.removeItem(self._ui_form.gridLayout)
|
||||||
# Add name of the spectrometer to the view
|
# 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;")
|
label.setStyleSheet("font-weight: bold;")
|
||||||
self._ui_form.verticalLayout.setSpacing(5)
|
self._ui_form.verticalLayout.setSpacing(5)
|
||||||
self._ui_form.verticalLayout.addWidget(label)
|
self._ui_form.verticalLayout.addWidget(label)
|
||||||
|
@ -47,7 +47,7 @@ class BaseSpectrometerView(ModuleView):
|
||||||
for category_count, category in enumerate(self.module.model.settings.keys()):
|
for category_count, category in enumerate(self.module.model.settings.keys()):
|
||||||
logger.debug("Adding settings for category: %s", category)
|
logger.debug("Adding settings for category: %s", category)
|
||||||
category_layout = QVBoxLayout()
|
category_layout = QVBoxLayout()
|
||||||
category_label = QLabel("%s:" % category)
|
category_label = QLabel(f"{category}:")
|
||||||
category_label.setStyleSheet("font-weight: bold;")
|
category_label.setStyleSheet("font-weight: bold;")
|
||||||
row = category_count // 2
|
row = category_count // 2
|
||||||
column = category_count % 2
|
column = category_count % 2
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import logging
|
import logging
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from nqrduck.helpers.signalprocessing import SignalProcessing as sp
|
from nqrduck.helpers.signalprocessing import SignalProcessing as sp
|
||||||
|
from nqrduck.helpers.functions import Function
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -39,12 +40,43 @@ class Measurement:
|
||||||
IF_frequency: float = 0,
|
IF_frequency: float = 0,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initializes the measurement."""
|
"""Initializes the measurement."""
|
||||||
|
# Convert to decimal
|
||||||
self.tdx = tdx
|
self.tdx = tdx
|
||||||
self.tdy = tdy
|
self.tdy = tdy
|
||||||
self.target_frequency = target_frequency
|
self.target_frequency = target_frequency
|
||||||
self.fdx, self.fdy = sp.fft(tdx, tdy, frequency_shift)
|
self.fdx, self.fdy = sp.fft(tdx, tdy, frequency_shift)
|
||||||
self.IF_frequency = IF_frequency
|
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
|
# Data saving and loading
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
|
|
|
@ -6,425 +6,22 @@ Todo:
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import logging
|
import logging
|
||||||
import numpy as np
|
|
||||||
import sympy
|
from numpy.core.multiarray import array as array
|
||||||
from decimal import Decimal
|
|
||||||
from PyQt6.QtGui import QPixmap
|
|
||||||
from nqrduck.contrib.mplwidget import MplWidget
|
|
||||||
from nqrduck.helpers.signalprocessing import SignalProcessing as sp
|
|
||||||
from nqrduck.assets.icons import PulseParamters
|
from nqrduck.assets.icons import PulseParamters
|
||||||
|
from nqrduck.helpers.functions import (
|
||||||
|
Function,
|
||||||
|
RectFunction,
|
||||||
|
SincFunction,
|
||||||
|
GaussianFunction,
|
||||||
|
CustomFunction,
|
||||||
|
)
|
||||||
from .base_spectrometer_model import BaseSpectrometerModel
|
from .base_spectrometer_model import BaseSpectrometerModel
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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:
|
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.
|
||||||
|
|
||||||
|
@ -439,6 +36,13 @@ class Option:
|
||||||
value: The value of the option.
|
value: The value of the option.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
subclasses = []
|
||||||
|
|
||||||
|
def __init_subclass__(cls, **kwargs):
|
||||||
|
"""Adds the subclass to the list of subclasses."""
|
||||||
|
super().__init_subclass__(**kwargs)
|
||||||
|
cls.subclasses.append(cls)
|
||||||
|
|
||||||
def __init__(self, name: str, value) -> None:
|
def __init__(self, name: str, value) -> None:
|
||||||
"""Initializes the option."""
|
"""Initializes the option."""
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -457,7 +61,11 @@ class Option:
|
||||||
Returns:
|
Returns:
|
||||||
dict: The json representation of the option.
|
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
|
@classmethod
|
||||||
def from_json(cls, data) -> Option:
|
def from_json(cls, data) -> Option:
|
||||||
|
@ -469,8 +77,9 @@ class Option:
|
||||||
Returns:
|
Returns:
|
||||||
Option: The option.
|
Option: The option.
|
||||||
"""
|
"""
|
||||||
for subclass in cls.__subclasses__():
|
for subclass in cls.subclasses:
|
||||||
if subclass.TYPE == data["type"]:
|
logger.debug(f"Keys data: {data.keys()}")
|
||||||
|
if subclass.__name__ == data["class"]:
|
||||||
cls = subclass
|
cls = subclass
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -486,8 +95,6 @@ class Option:
|
||||||
class BooleanOption(Option):
|
class BooleanOption(Option):
|
||||||
"""Defines a boolean option for a pulse parameter option."""
|
"""Defines a boolean option for a pulse parameter option."""
|
||||||
|
|
||||||
TYPE = "Boolean"
|
|
||||||
|
|
||||||
def set_value(self, value):
|
def set_value(self, value):
|
||||||
"""Sets the value of the option."""
|
"""Sets the value of the option."""
|
||||||
self.value = value
|
self.value = value
|
||||||
|
@ -496,11 +103,67 @@ class BooleanOption(Option):
|
||||||
class NumericOption(Option):
|
class NumericOption(Option):
|
||||||
"""Defines a numeric option for a pulse parameter 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:
|
||||||
|
"""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
|
||||||
|
self.max_value = max_value
|
||||||
|
|
||||||
def set_value(self, value):
|
def set_value(self, value):
|
||||||
"""Sets the value of the option."""
|
"""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."
|
||||||
|
)
|
||||||
|
|
||||||
|
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):
|
class FunctionOption(Option):
|
||||||
|
@ -517,8 +180,6 @@ class FunctionOption(Option):
|
||||||
functions (list): The functions that can be selected.
|
functions (list): The functions that can be selected.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
TYPE = "Function"
|
|
||||||
|
|
||||||
def __init__(self, name, functions) -> None:
|
def __init__(self, name, functions) -> None:
|
||||||
"""Initializes the FunctionOption."""
|
"""Initializes the FunctionOption."""
|
||||||
super().__init__(name, functions[0])
|
super().__init__(name, functions[0])
|
||||||
|
@ -544,7 +205,7 @@ class FunctionOption(Option):
|
||||||
for function in self.functions:
|
for function in self.functions:
|
||||||
if function.name == name:
|
if function.name == name:
|
||||||
return function
|
return function
|
||||||
raise ValueError("Function with name %s not found" % name)
|
raise ValueError(f"Function with name {name} not found")
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
"""Returns a json representation of the option.
|
"""Returns a json representation of the option.
|
||||||
|
@ -552,7 +213,12 @@ class FunctionOption(Option):
|
||||||
Returns:
|
Returns:
|
||||||
dict: The json representation of the option.
|
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
|
@classmethod
|
||||||
def from_json(cls, data):
|
def from_json(cls, data):
|
||||||
|
@ -564,7 +230,9 @@ class FunctionOption(Option):
|
||||||
Returns:
|
Returns:
|
||||||
FunctionOption: The FunctionOption.
|
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 = cls(data["name"], functions)
|
||||||
obj.value = Function.from_json(data["value"])
|
obj.value = Function.from_json(data["value"])
|
||||||
return obj
|
return obj
|
||||||
|
@ -574,34 +242,102 @@ class FunctionOption(Option):
|
||||||
return self.value.get_pixmap()
|
return self.value.get_pixmap()
|
||||||
|
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
def get_pixmap(self):
|
||||||
|
"""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__()
|
||||||
|
self.name = "Sinc"
|
||||||
|
|
||||||
|
def get_pixmap(self):
|
||||||
|
"""Returns the pixmaps of the function."""
|
||||||
|
return PulseParamters.TXSinc()
|
||||||
|
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
def get_pixmap(self):
|
||||||
|
"""Returns the pixmaps of the function."""
|
||||||
|
return PulseParamters.TXGauss()
|
||||||
|
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
def get_pixmap(self):
|
||||||
|
"""Returns the pixmaps of the function."""
|
||||||
|
return PulseParamters.TXCustom()
|
||||||
|
|
||||||
|
|
||||||
class TXPulse(BaseSpectrometerModel.PulseParameter):
|
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:
|
Args:
|
||||||
name (str): The name of the pulse parameter.
|
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_PHASE = "TX Phase"
|
||||||
TX_PULSE_SHAPE = "TX Pulse Shape"
|
TX_PULSE_SHAPE = "TX Pulse Shape"
|
||||||
|
|
||||||
def __init__(self, name) -> None:
|
def __init__(self, name: str) -> None:
|
||||||
"""Initializes the TX Pulse Parameter.
|
"""Initializes the TX Pulse Parameter.
|
||||||
|
|
||||||
It adds the options for the relative amplitude, the phase and the pulse shape.
|
It adds the options for the relative amplitude, the phase and the pulse shape.
|
||||||
"""
|
"""
|
||||||
super().__init__(name)
|
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(NumericOption(self.TX_PHASE, 0))
|
||||||
self.add_option(
|
self.add_option(
|
||||||
FunctionOption(
|
FunctionOption(
|
||||||
self.TX_PULSE_SHAPE,
|
self.TX_PULSE_SHAPE,
|
||||||
[RectFunction(), SincFunction(), GaussianFunction(), CustomFunction()],
|
[
|
||||||
|
TXRectFunction(),
|
||||||
|
TXSincFunction(),
|
||||||
|
TXGaussianFunction(),
|
||||||
|
TXCustomFunction(),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
"""Contains the PulseSequence class that is used to store a pulse sequence and its events."""
|
"""Contains the PulseSequence class that is used to store a pulse sequence and its events."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import importlib.metadata
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from nqrduck.helpers.unitconverter import UnitConverter
|
from nqrduck.helpers.unitconverter import UnitConverter
|
||||||
from nqrduck_spectrometer.pulseparameters import Option
|
from nqrduck_spectrometer.pulseparameters import Option
|
||||||
|
@ -19,9 +20,14 @@ class PulseSequence:
|
||||||
events (list): The events of the pulse sequence
|
events (list): The events of the pulse sequence
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name) -> None:
|
def __init__(self, name, version = None) -> None:
|
||||||
"""Initializes the pulse sequence."""
|
"""Initializes the pulse sequence."""
|
||||||
self.name = name
|
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()
|
self.events = list()
|
||||||
|
|
||||||
def get_event_names(self) -> list:
|
def get_event_names(self) -> list:
|
||||||
|
@ -111,7 +117,7 @@ class PulseSequence:
|
||||||
def duration(self, duration: str):
|
def duration(self, duration: str):
|
||||||
# Duration needs to be a positive number
|
# Duration needs to be a positive number
|
||||||
try:
|
try:
|
||||||
duration = UnitConverter.to_decimal(duration)
|
duration = UnitConverter.to_float(duration)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ValueError("Duration needs to be a number")
|
raise ValueError("Duration needs to be a number")
|
||||||
if duration < 0:
|
if duration < 0:
|
||||||
|
@ -125,7 +131,8 @@ class PulseSequence:
|
||||||
Returns:
|
Returns:
|
||||||
dict: The dict with the sequence data
|
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:
|
for event in self.events:
|
||||||
event_data = {
|
event_data = {
|
||||||
"name": event.name,
|
"name": event.name,
|
||||||
|
@ -156,7 +163,7 @@ class PulseSequence:
|
||||||
Raises:
|
Raises:
|
||||||
KeyError: If the pulse parameter options are not the same as the ones in the pulse sequence
|
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"]:
|
for event_data in sequence["events"]:
|
||||||
obj.events.append(cls.Event.load_event(event_data, pulse_parameter_options))
|
obj.events.append(cls.Event.load_event(event_data, pulse_parameter_options))
|
||||||
|
|
||||||
|
|
|
@ -156,6 +156,7 @@ class FloatSetting(NumericalSetting):
|
||||||
"""
|
"""
|
||||||
if state:
|
if state:
|
||||||
self.value = text
|
self.value = text
|
||||||
|
self.settings_changed.emit()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self):
|
def value(self):
|
||||||
|
@ -164,6 +165,7 @@ class FloatSetting(NumericalSetting):
|
||||||
|
|
||||||
@value.setter
|
@value.setter
|
||||||
def value(self, value):
|
def value(self, value):
|
||||||
|
logger.debug(f"Setting {self.name} to {value}")
|
||||||
self._value = float(value)
|
self._value = float(value)
|
||||||
self.settings_changed.emit()
|
self.settings_changed.emit()
|
||||||
|
|
||||||
|
@ -210,6 +212,7 @@ class IntSetting(NumericalSetting):
|
||||||
"""
|
"""
|
||||||
if state:
|
if state:
|
||||||
self.value = text
|
self.value = text
|
||||||
|
self.settings_changed.emit()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self):
|
def value(self):
|
||||||
|
@ -218,6 +221,7 @@ class IntSetting(NumericalSetting):
|
||||||
|
|
||||||
@value.setter
|
@value.setter
|
||||||
def value(self, value):
|
def value(self, value):
|
||||||
|
logger.debug(f"Setting {self.name} to {value}")
|
||||||
value = int(float(value))
|
value = int(float(value))
|
||||||
self._value = value
|
self._value = value
|
||||||
self.settings_changed.emit()
|
self.settings_changed.emit()
|
||||||
|
|
Loading…
Reference in a new issue