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

View file

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

View file

@ -2,6 +2,10 @@
import logging import logging
import importlib.metadata import importlib.metadata
from collections import OrderedDict
from quackseq.pulseparameters import PulseParameter, TXPulse, RXReadout
from quackseq.functions import Function
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -11,21 +15,36 @@ class PulseSequence:
Args: Args:
name (str): The name of the pulse sequence name (str): The name of the pulse sequence
version (str): The version of the pulse sequence
Attributes: Attributes:
name (str): The name of the pulse sequence name (str): The name of the pulse sequence
events (list): The events 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.""" """Initializes the pulse sequence."""
self.name = name 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: if version is not None:
self.version = version self.version = version
else: else:
self.version = importlib.metadata.version("nqrduck_spectrometer") self.version = importlib.metadata.version("quackseq")
self.events = list() 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: def get_event_names(self) -> list:
"""Returns a list of the names of the events in the pulse sequence. """Returns a list of the names of the events in the pulse sequence.
@ -34,16 +53,28 @@ class PulseSequence:
list: The names of the events list: The names of the events
""" """
return [event.name for event in self.events] 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. """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: Args:
event_name (str): The name of the event event_name (str): The name of the event
duration (float): The duration of the event duration (float): The duration of the event
Returns:
Event: The created event
""" """
self.events.append(self.Event(event_name, f"{float(duration):.16g}u")) event = self.Event(event_name, f"{float(duration):.16g}u")
self.events.append(event)
return event
def delete_event(self, event_name: str) -> None: def delete_event(self, event_name: str) -> None:
"""Deletes an event from the pulse sequence. """Deletes an event from the pulse sequence.
@ -56,6 +87,8 @@ class PulseSequence:
self.events.remove(event) self.events.remove(event)
break break
# Loading and saving of pulse sequences
def to_json(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.
@ -63,7 +96,7 @@ class PulseSequence:
dict: The dict with the sequence data dict: The dict with the sequence data
""" """
# Get the versions of this package # 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: for event in self.events:
event_data = { event_data = {
"name": event.name, "name": event.name,
@ -95,16 +128,17 @@ class PulseSequence:
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
""" """
try: try:
obj = cls(sequence["name"], version = sequence["version"]) obj = cls(sequence["name"], version=sequence["version"])
except KeyError: except KeyError:
logger.error("Pulse sequence version not found") logger.error("Pulse sequence version not found")
raise KeyError("Pulse sequence version not found") raise KeyError("Pulse sequence version not found")
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))
return obj return obj
# Automation of pulse sequences
class Variable: class Variable:
"""A variable is a parameter that can be used within a pulsesequence as a placeholder. """A variable is a parameter that can be used within a pulsesequence as a placeholder.
@ -163,3 +197,68 @@ class PulseSequence:
if not isinstance(variables, list): if not isinstance(variables, list):
raise TypeError("Variables needs to be a list") raise TypeError("Variables needs to be a list")
self._variables = variables 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: Attributes:
settings (OrderedDict) : The settings of the spectrometer settings (OrderedDict) : The settings of the spectrometer
pulse_parameter_options (OrderedDict) : The pulse parameter options of the spectrometer
""" """
settings: OrderedDict settings: OrderedDict
pulse_parameter_options: OrderedDict
def __init__(self, module): def __init__(self, module):
"""Initializes the spectrometer model. """Initializes the spectrometer model.
@ -31,7 +29,6 @@ class SpectrometerModel():
""" """
super().__init__(module) super().__init__(module)
self.settings = OrderedDict() self.settings = OrderedDict()
self.pulse_parameter_options = OrderedDict()
def add_setting(self, setting: Setting, category: str) -> None: def add_setting(self, setting: Setting, category: str) -> None:
"""Adds a setting to the spectrometer. """Adds a setting to the spectrometer.
@ -62,17 +59,6 @@ class SpectrometerModel():
return setting return setting
raise ValueError(f"Setting with name {name} not found") 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 @property
def target_frequency(self): def target_frequency(self):
"""The target frequency of the spectrometer in Hz. This is the frequency where the magnetic resonance experiment is performed.""" """The target frequency of the spectrometer in Hz. This is the frequency where the magnetic resonance experiment is performed."""

View file

@ -1,32 +1,40 @@
# Dummy test to communicate the structure # Dummy test to communicate the structure
from quackseq.pulsesequence import PulseSequence from quackseq.pulsesequence import QuackSequence
from quackseq.event import Event from quackseq.event import Event
from quackseq.functions import RectFunction 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) seq.add_event(tx)
#tx.set_tx_phase(0)
#tx.set_shape(RectFunction())
#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_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() #sim = Simulator()