Made sequence parameters editable.

This commit is contained in:
jupfi 2024-05-29 06:48:01 +02:00
parent e13ea949bc
commit 70266387e2
5 changed files with 154 additions and 47 deletions

View file

@ -1,6 +1,7 @@
import logging
from collections import OrderedDict
from quackseq.pulsesequence import PulseSequence
from quackseq.pulseparameters import Option
from quackseq.helpers import UnitConverter
@ -13,26 +14,37 @@ class Event:
Args:
name (str): The name of the event
duration (str): The duration of the event
pulse_sequence (PulseSequence): The pulse sequence the event belongs to
Attributes:
name (str): The name of the event
duration (str): The duration of the event
parameters (OrderedDict): The parameters of the event
pulse_sequence (PulseSequence): The pulse sequence the event belongs to
"""
def __init__(self, name: str, duration: str) -> None:
def __init__(self, name: str, duration: str, pulse_sequence : PulseSequence) -> None:
"""Initializes the event."""
self.parameters = OrderedDict()
self.name = name
self.duration = duration
self.pulse_sequence = pulse_sequence
self.parameters = OrderedDict()
self.init_pulse_parameters()
def add_parameter(self, parameter) -> None:
"""Adds a parameter to the event.
Args:
parameter: The parameter to add
"""
self.parameters.append(parameter)
def init_pulse_parameters(self) -> None:
"""Initializes the pulse parameters of the event."""
# Create a default instance of the pulse parameter options and add it to the event
pulse_parameters = self.pulse_sequence.pulse_parameter_options
for name, pulse_parameter_class in pulse_parameters.items():
logger.debug("Adding pulse parameter %s to event %s", name, self.name)
self.parameters[name] = pulse_parameter_class(
name
)
logger.debug(
"Created pulse parameter %s with object id %s",
name,
id(self.parameters[name]),
)
def on_duration_changed(self, duration: str) -> None:
"""This method is called when the duration of the event is changed.

View file

@ -32,6 +32,8 @@ class UnitConverter:
return decimal.Decimal(value)
except TypeError:
return decimal.Decimal(value)
except decimal.InvalidOperation:
raise ValueError(f"Invalid value: {value}, suffixes are n, u, m")
@classmethod
def to_float(cls, value: str) -> float:

View file

@ -2,6 +2,10 @@
import logging
import importlib.metadata
from collections import OrderedDict
from quackseq.pulseparameters import PulseParameter, TXPulse, RXReadout
from quackseq.functions import Function
logger = logging.getLogger(__name__)
@ -11,21 +15,36 @@ class PulseSequence:
Args:
name (str): The name of the pulse sequence
version (str): The version of the pulse sequence
Attributes:
name (str): The name of the pulse sequence
events (list): The events of the pulse sequence
pulse_parameter_options (dict): The pulse parameter options
"""
def __init__(self, name, version = None) -> None:
def __init__(self, name: str, version: str = None) -> None:
"""Initializes the pulse sequence."""
self.name = name
# Saving version to check for compatability of saved sequence
# Saving version to check for compatibility of saved sequence
if version is not None:
self.version = version
else:
self.version = importlib.metadata.version("nqrduck_spectrometer")
self.version = importlib.metadata.version("quackseq")
self.events = list()
self.pulse_parameter_options = OrderedDict()
def add_pulse_parameter_option(
self, name: str, pulse_parameter_class: "PulseParameter"
) -> None:
"""Adds a pulse parameter option to the spectrometer.
Args:
name (str) : The name of the pulse parameter
pulse_parameter_class (PulseParameter) : The pulse parameter class
"""
self.pulse_parameter_options[name] = pulse_parameter_class
def get_event_names(self) -> list:
"""Returns a list of the names of the events in the pulse sequence.
@ -35,15 +54,27 @@ class PulseSequence:
"""
return [event.name for event in self.events]
def add_event(self, event_name: str, duration: float) -> None:
def add_event(self, event: "Event") -> None:
"""Add a new event to the pulse sequence.
Args:
event (Event): The event to add
"""
self.events.append(event)
def create_event(self, event_name: str, duration: float) -> "Event":
"""Create a new event and return it.
Args:
event_name (str): The name of the event
duration (float): The duration of the event
"""
self.events.append(self.Event(event_name, f"{float(duration):.16g}u"))
Returns:
Event: The created event
"""
event = self.Event(event_name, f"{float(duration):.16g}u")
self.events.append(event)
return event
def delete_event(self, event_name: str) -> None:
"""Deletes an event from the pulse sequence.
@ -56,6 +87,8 @@ class PulseSequence:
self.events.remove(event)
break
# Loading and saving of pulse sequences
def to_json(self):
"""Returns a dict with all the data in the pulse sequence.
@ -63,7 +96,7 @@ class PulseSequence:
dict: The dict with the sequence data
"""
# Get the versions of this package
data = {"name": self.name, "version" : self.version, "events": []}
data = {"name": self.name, "version": self.version, "events": []}
for event in self.events:
event_data = {
"name": event.name,
@ -95,7 +128,7 @@ class PulseSequence:
KeyError: If the pulse parameter options are not the same as the ones in the pulse sequence
"""
try:
obj = cls(sequence["name"], version = sequence["version"])
obj = cls(sequence["name"], version=sequence["version"])
except KeyError:
logger.error("Pulse sequence version not found")
raise KeyError("Pulse sequence version not found")
@ -105,6 +138,7 @@ class PulseSequence:
return obj
# Automation of pulse sequences
class Variable:
"""A variable is a parameter that can be used within a pulsesequence as a placeholder.
@ -163,3 +197,68 @@ class PulseSequence:
if not isinstance(variables, list):
raise TypeError("Variables needs to be a list")
self._variables = variables
class QuackSequence(PulseSequence):
"""This is the Pulse Sequence that is compatible with all types of spectrometers.
If you want to implement your own spectrometer specific pulse sequence, you can inherit from the PulseSequence class.
"""
TX_PULSE = "TXPulse"
RX_READOUT = "RXParameters"
def __init__(self, name: str, version: str = None) -> None:
"""Initializes the pulse sequence."""
super().__init__(name, version)
self.add_pulse_parameter_option(self.TX_PULSE, TXPulse)
self.add_pulse_parameter_option(self.RX_READOUT, RXReadout)
# TX Specific functions
def set_tx_amplitude(self, event, amplitude: float) -> None:
"""Sets the amplitude of the transmitter.
Args:
event (Event): The event to set the amplitude for
amplitude (float): The amplitude of the transmitter
"""
event.parameters[self.TX_PULSE].get_option_by_name(
TXPulse.RELATIVE_AMPLITUDE
).value = amplitude
def set_tx_phase(self, event, phase: float) -> None:
"""Sets the phase of the transmitter.
Args:
event (Event): The event to set the phase for
phase (float): The phase of the transmitter
"""
event.parameters[self.TX_PULSE].get_option_by_name(
TXPulse.TX_PHASE
).value = phase
def set_tx_shape(self, event, shape: Function) -> None:
"""Sets the shape of the transmitter.
Args:
event (Event): The event to set the shape for
shape (Any): The shape of the transmitter
"""
event.parameters[self.TX_PULSE].get_option_by_name(
TXPulse.TX_PULSE_SHAPE
).value = shape
# RX Specific functions
def set_rx(self, event, rx: bool) -> None:
"""Sets the receiver on or off.
Args:
event (Event): The event to set the receiver for
rx (bool): The receiver state
"""
event.parameters[self.RX_READOUT].get_option_by_name(
RXReadout.RX
).value = rx

View file

@ -17,11 +17,9 @@ class SpectrometerModel():
Attributes:
settings (OrderedDict) : The settings of the spectrometer
pulse_parameter_options (OrderedDict) : The pulse parameter options of the spectrometer
"""
settings: OrderedDict
pulse_parameter_options: OrderedDict
def __init__(self, module):
"""Initializes the spectrometer model.
@ -31,7 +29,6 @@ class SpectrometerModel():
"""
super().__init__(module)
self.settings = OrderedDict()
self.pulse_parameter_options = OrderedDict()
def add_setting(self, setting: Setting, category: str) -> None:
"""Adds a setting to the spectrometer.
@ -62,17 +59,6 @@ class SpectrometerModel():
return setting
raise ValueError(f"Setting with name {name} not found")
def add_pulse_parameter_option(
self, name: str, pulse_parameter_class: "PulseParameter"
) -> None:
"""Adds a pulse parameter option to the spectrometer.
Args:
name (str) : The name of the pulse parameter
pulse_parameter_class (PulseParameter) : The pulse parameter class
"""
self.pulse_parameter_options[name] = pulse_parameter_class
@property
def target_frequency(self):
"""The target frequency of the spectrometer in Hz. This is the frequency where the magnetic resonance experiment is performed."""

View file

@ -1,32 +1,40 @@
# Dummy test to communicate the structure
from quackseq.pulsesequence import PulseSequence
from quackseq.pulsesequence import QuackSequence
from quackseq.event import Event
from quackseq.functions import RectFunction
seq = PulseSequence("test")
seq = QuackSequence("test")
tx = Event("tx", "10u")
tx = Event("tx", "10u", seq)
# tx.set_tx_amplitude(1)
#tx.set_tx_phase(0)
#tx.set_shape(RectFunction())
seq.add_event(tx)
#seq.add_event(tx)
seq.set_tx_amplitude(tx, 1)
seq.set_tx_phase(tx, 0)
#blank = Event("blank", "10u")
rect = RectFunction()
#seq.add_event(blank)
seq.set_tx_shape(tx, rect)
#rx = Event("rx", "10u")
blank = Event("blank", "10u", seq)
seq.add_event(blank)
rx = Event("rx", "10u", seq)
#rx.set_rx_phase(0)
#rx.set_rx(True)
#seq.add_event(rx)
seq.set_rx(rx, True)
#TR = Event("TR", "1ms")
seq.add_event(rx)
#seq.add_event(TR)
TR = Event("TR", "1m", seq)
seq.add_event(TR)
json = seq.to_json()
print(json)
#sim = Simulator()