mirror of
https://github.com/nqrduck/nqrduck-spectrometer.git
synced 2024-11-09 12:30:01 +00:00
Implemented saving and loading of sequences.
This commit is contained in:
parent
26a2a85828
commit
46c167841b
3 changed files with 154 additions and 59 deletions
|
@ -43,17 +43,23 @@ class BaseSpectrometerModel(ModuleModel):
|
||||||
"""
|
"""
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.options = OrderedDict()
|
self.options = list()
|
||||||
|
|
||||||
def get_pixmap(self):
|
def get_pixmap(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def add_option(self, name, option):
|
def add_option(self, option):
|
||||||
self.options[name] = option
|
self.options.append(option)
|
||||||
|
|
||||||
def get_options(self):
|
def get_options(self):
|
||||||
return self.options
|
return self.options
|
||||||
|
|
||||||
|
def get_option_by_name(self, name : str) -> "Option":
|
||||||
|
for option in self.options:
|
||||||
|
if option.name == name:
|
||||||
|
return option
|
||||||
|
raise ValueError("Option with name %s not found" % name)
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, module):
|
def __init__(self, module):
|
||||||
super().__init__(module)
|
super().__init__(module)
|
||||||
|
|
|
@ -9,9 +9,10 @@ from .base_spectrometer_model import BaseSpectrometerModel
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class Function():
|
|
||||||
|
class Function:
|
||||||
name: str
|
name: str
|
||||||
parameters : list
|
parameters: list
|
||||||
expression: str | sympy.Expr
|
expression: str | sympy.Expr
|
||||||
resolution: float
|
resolution: float
|
||||||
start_x: float
|
start_x: float
|
||||||
|
@ -20,17 +21,17 @@ class Function():
|
||||||
def __init__(self, expr) -> None:
|
def __init__(self, expr) -> None:
|
||||||
self.parameters = []
|
self.parameters = []
|
||||||
self.expr = expr
|
self.expr = expr
|
||||||
self.resolution = 22e-9 * 16# 1e-6
|
self.resolution = 22e-9 * 16 # 1e-6
|
||||||
self.start_x = -1
|
self.start_x = -1
|
||||||
self.end_x = 1
|
self.end_x = 1
|
||||||
|
|
||||||
def get_time_points(self, pulse_length : float) -> np.ndarray:
|
def get_time_points(self, pulse_length: float) -> np.ndarray:
|
||||||
"""Returns the time domain points for the function with the given pulse length."""
|
"""Returns the time domain points for the function with the given pulse length."""
|
||||||
# Get the time domain points
|
# Get the time domain points
|
||||||
n = int(pulse_length / self.resolution)
|
n = int(pulse_length / self.resolution)
|
||||||
t = np.linspace(0, pulse_length, n)
|
t = np.linspace(0, pulse_length, n)
|
||||||
return t
|
return t
|
||||||
|
|
||||||
def evaluate(self, pulse_length: float) -> np.ndarray:
|
def evaluate(self, pulse_length: float) -> np.ndarray:
|
||||||
"""Evaluates the function for the given pulse length."""
|
"""Evaluates the function for the given pulse length."""
|
||||||
n = int(pulse_length / self.resolution)
|
n = int(pulse_length / self.resolution)
|
||||||
|
@ -47,15 +48,15 @@ class Function():
|
||||||
found_variables[parameter.symbol] = parameter.value
|
found_variables[parameter.symbol] = parameter.value
|
||||||
|
|
||||||
final_expr = self.expr.subs(found_variables)
|
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 the expression is a number (does not depend on x), return an array of that number
|
||||||
if final_expr.is_number:
|
if final_expr.is_number:
|
||||||
return np.full(t.shape, float(final_expr))
|
return np.full(t.shape, float(final_expr))
|
||||||
|
|
||||||
f = sympy.lambdify([x], final_expr, "numpy")
|
f = sympy.lambdify([x], final_expr, "numpy")
|
||||||
|
|
||||||
return f(t)
|
return f(t)
|
||||||
|
|
||||||
def frequency_domain_plot(self, pulse_length : float) -> MplWidget:
|
def frequency_domain_plot(self, pulse_length: float) -> MplWidget:
|
||||||
mpl_widget = MplWidget()
|
mpl_widget = MplWidget()
|
||||||
td = self.get_time_points(pulse_length)
|
td = self.get_time_points(pulse_length)
|
||||||
yd = self.evaluate(pulse_length)
|
yd = self.evaluate(pulse_length)
|
||||||
|
@ -65,37 +66,83 @@ class Function():
|
||||||
mpl_widget.canvas.ax.set_ylabel("Magnitude")
|
mpl_widget.canvas.ax.set_ylabel("Magnitude")
|
||||||
return mpl_widget
|
return mpl_widget
|
||||||
|
|
||||||
def time_domain_plot(self, pulse_length : float) -> MplWidget:
|
def time_domain_plot(self, pulse_length: float) -> MplWidget:
|
||||||
mpl_widget = MplWidget()
|
mpl_widget = MplWidget()
|
||||||
td = self.get_time_points(pulse_length)
|
td = self.get_time_points(pulse_length)
|
||||||
mpl_widget.canvas.ax.plot(td, self.evaluate(pulse_length))
|
mpl_widget.canvas.ax.plot(td, self.evaluate(pulse_length))
|
||||||
mpl_widget.canvas.ax.set_xlabel("Time in s")
|
mpl_widget.canvas.ax.set_xlabel("Time in s")
|
||||||
mpl_widget.canvas.ax.set_ylabel("Magnitude")
|
mpl_widget.canvas.ax.set_ylabel("Magnitude")
|
||||||
return mpl_widget
|
return mpl_widget
|
||||||
|
|
||||||
def add_parameter(self, parameter : "Function.Parameter"):
|
def add_parameter(self, parameter: "Function.Parameter"):
|
||||||
self.parameters.append(parameter)
|
self.parameters.append(parameter)
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
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):
|
||||||
|
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
|
||||||
|
|
||||||
class Parameter:
|
class Parameter:
|
||||||
def __init__(self, name : str, symbol : str, value : float) -> None:
|
def __init__(self, name: str, symbol: str, value: float) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.symbol = symbol
|
self.symbol = symbol
|
||||||
self.value = value
|
self.value = value
|
||||||
self.default = value
|
self.default = value
|
||||||
|
|
||||||
def set_value(self, value : float):
|
def set_value(self, value: float):
|
||||||
self.value = value
|
self.value = value
|
||||||
logger.debug("Parameter %s set to %s", self.name, self.value)
|
logger.debug("Parameter %s set to %s", self.name, self.value)
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
return {
|
||||||
|
"name": self.name,
|
||||||
|
"symbol": self.symbol,
|
||||||
|
"value": self.value,
|
||||||
|
"default": self.default,
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_json(cls, data):
|
||||||
|
obj = cls(data["name"], data["symbol"], data["value"])
|
||||||
|
obj.default = data["default"]
|
||||||
|
return obj
|
||||||
|
|
||||||
class RectFunction(Function):
|
class RectFunction(Function):
|
||||||
name = "Rectangular"
|
name = "Rectangular"
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
expr = sympy.sympify("1")
|
expr = sympy.sympify("1")
|
||||||
super().__init__(expr)
|
super().__init__(expr)
|
||||||
|
|
||||||
|
|
||||||
class SincFunction(Function):
|
class SincFunction(Function):
|
||||||
name = "Sinc"
|
name = "Sinc"
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
expr = sympy.sympify("sin(x * l)/ (x * l)")
|
expr = sympy.sympify("sin(x * l)/ (x * l)")
|
||||||
super().__init__(expr)
|
super().__init__(expr)
|
||||||
|
@ -103,36 +150,64 @@ class SincFunction(Function):
|
||||||
self.start_x = -np.pi
|
self.start_x = -np.pi
|
||||||
self.end_x = np.pi
|
self.end_x = np.pi
|
||||||
|
|
||||||
|
|
||||||
class GaussianFunction(Function):
|
class GaussianFunction(Function):
|
||||||
name = "Gaussian"
|
name = "Gaussian"
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
expr = sympy.sympify("exp(-0.5 * ((x - mu) / sigma)**2)")
|
expr = sympy.sympify("exp(-0.5 * ((x - mu) / sigma)**2)")
|
||||||
super().__init__(expr)
|
super().__init__(expr)
|
||||||
self.add_parameter(Function.Parameter("Mean", "mu", 0))
|
self.add_parameter(Function.Parameter("Mean", "mu", 0))
|
||||||
self.add_parameter(Function.Parameter("Standard Deviation", "sigma", 1))
|
self.add_parameter(Function.Parameter("Standard Deviation", "sigma", 1))
|
||||||
|
self.start_x = -np.pi
|
||||||
|
self.end_x = np.pi
|
||||||
|
|
||||||
#class TriangleFunction(Function):
|
|
||||||
|
# class TriangleFunction(Function):
|
||||||
# def __init__(self) -> None:
|
# def __init__(self) -> None:
|
||||||
# expr = sympy.sympify("triang(x)")
|
# expr = sympy.sympify("triang(x)")
|
||||||
# super().__init__(lambda x: triang(x))
|
# super().__init__(lambda x: triang(x))
|
||||||
|
|
||||||
|
|
||||||
class CustomFunction(Function):
|
class CustomFunction(Function):
|
||||||
|
name = "Custom"
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
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."""
|
||||||
|
|
||||||
|
def __init__(self, name: str, value) -> None:
|
||||||
|
self.name = name
|
||||||
|
self.value = value
|
||||||
|
|
||||||
def set_value(self):
|
def set_value(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
return {"name": self.name, "value": self.value, "type": self.TYPE}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_json(cls, data) -> "Option":
|
||||||
|
for subclass in cls.__subclasses__():
|
||||||
|
if subclass.TYPE == data["type"]:
|
||||||
|
cls = subclass
|
||||||
|
break
|
||||||
|
|
||||||
|
# Check if from_json is implemented for the subclass
|
||||||
|
if cls.from_json.__func__ == Option.from_json.__func__:
|
||||||
|
obj = cls(data["name"], data["value"])
|
||||||
|
else:
|
||||||
|
obj = cls.from_json(data)
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
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 __init__(self, value) -> None:
|
|
||||||
super().__init__()
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
def set_value(self, value):
|
def set_value(self, value):
|
||||||
self.value = value
|
self.value = value
|
||||||
|
@ -140,10 +215,7 @@ 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, value) -> None:
|
|
||||||
super().__init__()
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
def set_value(self, value):
|
def set_value(self, value):
|
||||||
self.value = float(value)
|
self.value = float(value)
|
||||||
|
@ -152,63 +224,82 @@ class NumericOption(Option):
|
||||||
class FunctionOption(Option):
|
class FunctionOption(Option):
|
||||||
"""Defines a selection option for a pulse parameter option.
|
"""Defines a selection option for a pulse parameter option.
|
||||||
It takes different function objects."""
|
It takes different function objects."""
|
||||||
|
TYPE = "Function"
|
||||||
def __init__(self, functions) -> None:
|
|
||||||
super().__init__()
|
def __init__(self, name, functions) -> None:
|
||||||
|
super().__init__(name, functions[0])
|
||||||
self.functions = functions
|
self.functions = functions
|
||||||
self.value = functions[0]
|
|
||||||
|
|
||||||
def set_value(self, value):
|
def set_value(self, value):
|
||||||
self.value = value
|
self.value = value
|
||||||
|
|
||||||
|
def get_function_by_name(self, 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):
|
||||||
|
return {"name": self.name, "value": self.value.to_json(), "type": self.TYPE}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_json(cls, data):
|
||||||
|
functions = [function() for function in Function.__subclasses__()]
|
||||||
|
obj = cls(data["name"], functions)
|
||||||
|
obj.value = Function.from_json(data["value"])
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
class TXPulse(BaseSpectrometerModel.PulseParameter):
|
class TXPulse(BaseSpectrometerModel.PulseParameter):
|
||||||
|
RELATIVE_AMPLITUDE = "Relative TX Amplitude"
|
||||||
|
TX_PHASE = "TX Phase"
|
||||||
|
TX_PULSE_SHAPE = "TX Pulse Shape"
|
||||||
|
|
||||||
def __init__(self, name) -> None:
|
def __init__(self, name) -> None:
|
||||||
super().__init__(name)
|
super().__init__(name)
|
||||||
self.add_option("TX Amplitude", NumericOption(0))
|
self.add_option(NumericOption(self.RELATIVE_AMPLITUDE, 0))
|
||||||
self.add_option("TX Phase", NumericOption(0))
|
self.add_option(NumericOption(self.TX_PHASE, 0))
|
||||||
self.add_option("TX Pulse Shape", FunctionOption([RectFunction(), SincFunction(), GaussianFunction()]))
|
self.add_option(
|
||||||
|
FunctionOption(self.TX_PULSE_SHAPE, [RectFunction(), SincFunction(), GaussianFunction()]),
|
||||||
|
)
|
||||||
|
|
||||||
def get_pixmap(self):
|
def get_pixmap(self):
|
||||||
self_path = Path(__file__).parent
|
self_path = Path(__file__).parent
|
||||||
if self.options["TX Amplitude"].value > 0:
|
if self.get_option_by_name(self.RELATIVE_AMPLITUDE).value > 0:
|
||||||
image_path = self_path / "resources/pulseparameter/TXOn.png"
|
image_path = self_path / "resources/pulseparameter/TXOn.png"
|
||||||
else:
|
else:
|
||||||
image_path = self_path / "resources/pulseparameter/TXOff.png"
|
image_path = self_path / "resources/pulseparameter/TXOff.png"
|
||||||
pixmap = QPixmap(str(image_path))
|
pixmap = QPixmap(str(image_path))
|
||||||
return pixmap
|
return pixmap
|
||||||
|
|
||||||
|
|
||||||
class RXReadout(BaseSpectrometerModel.PulseParameter):
|
class RXReadout(BaseSpectrometerModel.PulseParameter):
|
||||||
|
RX = "RX"
|
||||||
def __init__(self, name) -> None:
|
def __init__(self, name) -> None:
|
||||||
super().__init__(name)
|
super().__init__(name)
|
||||||
self.add_option("RX", BooleanOption(False))
|
self.add_option(BooleanOption(self.RX, False))
|
||||||
|
|
||||||
def get_pixmap(self):
|
def get_pixmap(self):
|
||||||
self_path = Path(__file__).parent
|
self_path = Path(__file__).parent
|
||||||
if self.options["RX"].value == False:
|
if self.get_option_by_name(self.RX).value == False:
|
||||||
image_path = self_path / "resources/pulseparameter/RXOff.png"
|
image_path = self_path / "resources/pulseparameter/RXOff.png"
|
||||||
else:
|
else:
|
||||||
image_path = self_path / "resources/pulseparameter/RXOn.png"
|
image_path = self_path / "resources/pulseparameter/RXOn.png"
|
||||||
pixmap = QPixmap(str(image_path))
|
pixmap = QPixmap(str(image_path))
|
||||||
return pixmap
|
return pixmap
|
||||||
|
|
||||||
def set_options(self, options):
|
|
||||||
self.state = options
|
|
||||||
|
|
||||||
|
|
||||||
class Gate(BaseSpectrometerModel.PulseParameter):
|
class Gate(BaseSpectrometerModel.PulseParameter):
|
||||||
|
GATE_STATE = "Gate State"
|
||||||
def __init__(self, name) -> None:
|
def __init__(self, name) -> None:
|
||||||
super().__init__(name)
|
super().__init__(name)
|
||||||
self.add_option("Gate State", BooleanOption(False))
|
self.add_option(BooleanOption(self.GATE_STATE, False))
|
||||||
|
|
||||||
def get_pixmap(self):
|
def get_pixmap(self):
|
||||||
self_path = Path(__file__).parent
|
self_path = Path(__file__).parent
|
||||||
if self.options["Gate State"].state == False:
|
if self.get_option_by_name(self.GATE_STATE).value == False:
|
||||||
image_path = self_path / "resources/pulseparameter/GateOff.png"
|
image_path = self_path / "resources/pulseparameter/GateOff.png"
|
||||||
else:
|
else:
|
||||||
image_path = self_path / "resources/pulseparameter/GateOn.png"
|
image_path = self_path / "resources/pulseparameter/GateOn.png"
|
||||||
pixmap = QPixmap(str(image_path))
|
pixmap = QPixmap(str(image_path))
|
||||||
return pixmap
|
return pixmap
|
||||||
|
|
||||||
def set_options(self, options):
|
|
||||||
self.state = options
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from nqrduck_spectrometer.pulseparameters import Option
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -47,20 +48,22 @@ class PulseSequence:
|
||||||
for pulse_parameter_option in pulse_parameter_options.keys():
|
for pulse_parameter_option in pulse_parameter_options.keys():
|
||||||
# This checks if the pulse paramter options are the same as the ones in the pulse sequence
|
# This checks if the pulse paramter options are the same as the ones in the pulse sequence
|
||||||
if pulse_parameter_option == parameter["name"]:
|
if pulse_parameter_option == parameter["name"]:
|
||||||
pulse_paramter_class = pulse_parameter_options[
|
pulse_parameter_class = pulse_parameter_options[
|
||||||
pulse_parameter_option
|
pulse_parameter_option
|
||||||
]
|
]
|
||||||
obj.parameters[pulse_parameter_option] = pulse_paramter_class(
|
obj.parameters[pulse_parameter_option] = pulse_parameter_class(
|
||||||
parameter["name"]
|
parameter["name"]
|
||||||
)
|
)
|
||||||
|
# Delete the default instances of the pulse parameter options
|
||||||
|
obj.parameters[pulse_parameter_option].options = []
|
||||||
for option in parameter["value"]:
|
for option in parameter["value"]:
|
||||||
obj.parameters[pulse_parameter_option].options[
|
obj.parameters[pulse_parameter_option].options.append(
|
||||||
option["name"]
|
Option.from_json(option)
|
||||||
].value = option["value"]
|
)
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def dump_sequence_data(self):
|
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:
|
Returns:
|
||||||
|
@ -74,13 +77,8 @@ class PulseSequence:
|
||||||
}
|
}
|
||||||
for parameter in event.parameters.keys():
|
for parameter in event.parameters.keys():
|
||||||
event_data["parameters"].append({"name": parameter, "value": []})
|
event_data["parameters"].append({"name": parameter, "value": []})
|
||||||
for option in event.parameters[parameter].options.keys():
|
for option in event.parameters[parameter].options:
|
||||||
event_data["parameters"][-1]["value"].append(
|
event_data["parameters"][-1]["value"].append(option.to_json())
|
||||||
{
|
|
||||||
"name": option,
|
|
||||||
"value": event.parameters[parameter].options[option].value,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
data["events"].append(event_data)
|
data["events"].append(event_data)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue