This commit is contained in:
jupfi 2024-06-01 20:39:21 +02:00
parent 6f7afada51
commit a8c5a93ae5
8 changed files with 121 additions and 49 deletions

View file

@ -0,0 +1 @@
"""The base module for the quackseq pulse programming library."""

View file

@ -1,3 +1,5 @@
"""Event class for the pulse sequence. Every pulse sequence consists of events, that are executed subsequently and have different parameters."""
import logging import logging
from collections import OrderedDict from collections import OrderedDict
@ -21,7 +23,9 @@ class Event:
pulse_sequence (PulseSequence): The pulse sequence the event belongs to pulse_sequence (PulseSequence): The pulse sequence the event belongs to
""" """
def __init__(self, name: str, duration: float | str, pulse_sequence : "PulseSequence") -> None: def __init__(
self, name: str, duration: float | str, pulse_sequence: "PulseSequence"
) -> None:
"""Initializes the event.""" """Initializes the event."""
self.parameters = OrderedDict() self.parameters = OrderedDict()
self.name = name self.name = name
@ -36,9 +40,7 @@ class Event:
pulse_parameters = self.pulse_sequence.pulse_parameter_options pulse_parameters = self.pulse_sequence.pulse_parameter_options
for name, pulse_parameter_class in pulse_parameters.items(): for name, pulse_parameter_class in pulse_parameters.items():
logger.debug("Adding pulse parameter %s to event %s", name, self.name) logger.debug("Adding pulse parameter %s to event %s", name, self.name)
self.parameters[name] = pulse_parameter_class( self.parameters[name] = pulse_parameter_class(name)
name
)
logger.debug( logger.debug(
"Created pulse parameter %s with object id %s", "Created pulse parameter %s with object id %s",
name, name,

View file

@ -1,8 +1,11 @@
"""Options for the pulse parameters. Options can be of different types, for example boolean, numeric or function. Generally pulse parameters have different values for the different events in a pulse sequence."""
import logging import logging
from quackseq.functions import Function from quackseq.functions import Function
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
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.
@ -102,6 +105,7 @@ class NumericOption(Option):
is_float (bool): If the value is a float. is_float (bool): If the value is a float.
min_value: The minimum value of the option. min_value: The minimum value of the option.
max_value: The maximum value of the option. max_value: The maximum value of the option.
slider (bool): If the option should be displayed as a slider. This is not used for the pulseq module, but visualizations can use this information.
""" """
super().__init__(name, value) super().__init__(name, value)
self.is_float = is_float self.is_float = is_float
@ -116,7 +120,7 @@ class NumericOption(Option):
self.value_changed.emit() self.value_changed.emit()
elif value >= self.max_value: elif value >= self.max_value:
self.value = self.max_value self.value = self.max_value
self.value_changed.emit() self.value_changed.emit()
else: else:
raise ValueError( raise ValueError(
f"Value {value} is not in the range of {self.min_value} to {self.max_value}. This should have been caught earlier." f"Value {value} is not in the range of {self.min_value} to {self.max_value}. This should have been caught earlier."

View file

@ -62,8 +62,10 @@ class PulseSequence:
event (Event): The event to add event (Event): The event to add
""" """
if event.name in self.get_event_names(): if event.name in self.get_event_names():
raise ValueError(f"Event with name {event.name} already exists in the pulse sequence") raise ValueError(
f"Event with name {event.name} already exists in the pulse sequence"
)
self.events.append(event) self.events.append(event)
def create_event(self, event_name: str, duration: str) -> "Event": def create_event(self, event_name: str, duration: str) -> "Event":
@ -78,8 +80,10 @@ class PulseSequence:
""" """
event = Event(event_name, duration, self) event = Event(event_name, duration, self)
if event.name in self.get_event_names(): if event.name in self.get_event_names():
raise ValueError(f"Event with name {event.name} already exists in the pulse sequence") raise ValueError(
f"Event with name {event.name} already exists in the pulse sequence"
)
self.events.append(event) self.events.append(event)
return event return event
@ -224,7 +228,13 @@ class QuackSequence(PulseSequence):
self.add_pulse_parameter_option(self.RX_READOUT, RXReadout) self.add_pulse_parameter_option(self.RX_READOUT, RXReadout)
def add_blank_event(self, event_name: str, duration: float): def add_blank_event(self, event_name: str, duration: float):
event = self.create_event(event_name, duration) """Adds a blank event to the pulse sequence.
Args:
event_name (str): The name of the event
duration (float): The duration of the event with a unit suffix (n, u, m)
"""
_ = self.create_event(event_name, duration)
def add_pulse_event( def add_pulse_event(
self, self,
@ -234,24 +244,44 @@ class QuackSequence(PulseSequence):
phase: float, phase: float,
shape: Function = RectFunction(), shape: Function = RectFunction(),
): ):
"""Adds a pulse event to the pulse sequence.
Args:
event_name (str): The name of the event
duration (float): The duration of the event with a unit suffix (n, u, m)
amplitude (float): The amplitude of the transmit pulse in percent
phase (float): The phase of the transmit pulse
shape (Function): The shape of the transmit pulse
"""
event = self.create_event(event_name, duration) event = self.create_event(event_name, duration)
self.set_tx_amplitude(event, amplitude) self.set_tx_amplitude(event, amplitude)
self.set_tx_phase(event, phase) self.set_tx_phase(event, phase)
self.set_tx_shape(event, shape) self.set_tx_shape(event, shape)
def add_readout_event(self, event_name: str, duration: float): def add_readout_event(self, event_name: str, duration: float):
"""Adds a readout event to the pulse sequence.
Args:
event_name (str): The name of the event
duration (float): The duration of the event with a unit suffix (n, u, m)
"""
event = self.create_event(event_name, duration) event = self.create_event(event_name, duration)
self.set_rx(event, True) self.set_rx(event, True)
# TX Specific functions # TX Specific functions
def set_tx_amplitude(self, event, amplitude: float) -> None: def set_tx_amplitude(self, event, amplitude: float) -> None:
"""Sets the amplitude of the transmitter. """Sets the relative amplitude of the transmit pulse in percent (larger 0 - max 100).
Args: Args:
event (Event): The event to set the amplitude for event (Event): The event to set the amplitude for
amplitude (float): The amplitude of the transmitter amplitude (float): The amplitude of the transmit pulse in percent
""" """
if amplitude <= 0 or amplitude > 100:
raise ValueError(
"Amplitude needs to be larger than 0 and smaller or equal to 100"
)
event.parameters[self.TX_PULSE].get_option_by_name( event.parameters[self.TX_PULSE].get_option_by_name(
TXPulse.RELATIVE_AMPLITUDE TXPulse.RELATIVE_AMPLITUDE
).value = amplitude ).value = amplitude
@ -268,11 +298,11 @@ class QuackSequence(PulseSequence):
).value = phase ).value = phase
def set_tx_shape(self, event, shape: Function) -> None: def set_tx_shape(self, event, shape: Function) -> None:
"""Sets the shape of the transmitter. """Sets the shape of the transmit pulse.
Args: Args:
event (Event): The event to set the shape for event (Event): The event to set the shape for
shape (Any): The shape of the transmitter shape (Function): The shape of the transmit pulse
""" """
event.parameters[self.TX_PULSE].get_option_by_name( event.parameters[self.TX_PULSE].get_option_by_name(
TXPulse.TX_PULSE_SHAPE TXPulse.TX_PULSE_SHAPE

View file

@ -1,7 +1,7 @@
from typing import Any """The base class for the spectrometer used in quackseq. This class is just a skeleton and should be inherited by all spectrometer implementations."""
class Spectrometer(): class Spectrometer:
"""Base class for spectrometers. """Base class for spectrometers.
This class should be inherited by all spectrometers. This class should be inherited by all spectrometers.
@ -15,11 +15,10 @@ class Spectrometer():
""" """
raise NotImplementedError raise NotImplementedError
def set_frequency(self, value : float): def set_frequency(self, value: float):
"""Sets the frequency of the spectrometer.""" """Sets the frequency of the spectrometer."""
raise NotImplementedError raise NotImplementedError
def set_averages(self, value : int): def set_averages(self, value: int):
"""Sets the number of averages.""" """Sets the number of averages."""
raise NotImplementedError raise NotImplementedError

View file

@ -7,7 +7,8 @@ from quackseq.pulsesequence import QuackSequence
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class SpectrometerController():
class SpectrometerController:
"""The base class for all spectrometer controllers.""" """The base class for all spectrometer controllers."""
def run_sequence(self, sequence): def run_sequence(self, sequence):
@ -17,14 +18,14 @@ class SpectrometerController():
""" """
raise NotImplementedError raise NotImplementedError
def set_frequency(self, value : float): def set_frequency(self, value: float):
"""Sets the frequency of the spectrometer.""" """Sets the frequency of the spectrometer."""
raise NotImplementedError raise NotImplementedError
def set_averages(self, value : int): def set_averages(self, value: int):
"""Sets the number of averages.""" """Sets the number of averages."""
raise NotImplementedError raise NotImplementedError
def translate_rx_event(self, sequence: QuackSequence) -> tuple: def translate_rx_event(self, sequence: QuackSequence) -> tuple:
"""This method translates the RX event of the pulse sequence to the limr object. """This method translates the RX event of the pulse sequence to the limr object.
@ -62,7 +63,7 @@ class SpectrometerController():
else: else:
return None, None return None, None
def calculate_simulation_length(self, sequence: QuackSequence) -> float: def calculate_simulation_length(self, sequence: QuackSequence) -> float:
"""This method calculates the simulation length based on the settings and the pulse sequence. """This method calculates the simulation length based on the settings and the pulse sequence.

View file

@ -2,29 +2,54 @@
import logging import logging
from collections import OrderedDict from collections import OrderedDict
from typing import Any
from quackseq.spectrometer.spectrometer_settings import Setting from quackseq.spectrometer.spectrometer_settings import Setting
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class QuackSettings(OrderedDict): class QuackSettings(OrderedDict):
"""The Quack settings class makes the different settings of the spectrometer accessible as attributes. Additionally, it provides methods to get the settings by category."""
def __getattr__(self, key): def __getattr__(self, key):
"""Gets the value of a setting by its key.
Args:
key (str) : The key of the setting
Returns:
The value of the setting
"""
return self[key].value return self[key].value
def __setattr__(self, key, value): def __setattr__(self, key, value):
"""Sets the value of a setting by its key.
Args:
key (str) : The key of the setting
value : The value to set
"""
self[key].value = value self[key].value = value
@property @property
def categories(self): def categories(self):
"""The categories of the settings."""
categories = [] categories = []
for setting in self.values(): for setting in self.values():
if not setting.category in categories: if setting.category not in categories:
categories.append(setting.category) categories.append(setting.category)
return categories return categories
def get_settings_by_category(self, category): def get_settings_by_category(self, category):
"""Gets the settings by category.
Args:
category (str) : The category of the settings
Returns:
dict : The settings with the specified category
"""
settings = dict() settings = dict()
for key, setting in self.items(): for key, setting in self.items():
@ -33,7 +58,8 @@ class QuackSettings(OrderedDict):
return settings return settings
class SpectrometerModel():
class SpectrometerModel:
"""The base class for all spectrometer models. """The base class for all spectrometer models.
It contains the settings and pulse parameters of the spectrometer. It contains the settings and pulse parameters of the spectrometer.
@ -48,8 +74,7 @@ class SpectrometerModel():
"""Initializes the spectrometer model.""" """Initializes the spectrometer model."""
self.settings = QuackSettings() self.settings = QuackSettings()
def add_setting(self, name: str, setting: Setting) -> None:
def add_setting(self,name: str, setting: Setting) -> None:
"""Adds a setting to the spectrometer. """Adds a setting to the spectrometer.
Args: Args:
@ -72,10 +97,10 @@ class SpectrometerModel():
""" """
if name in self.settings: if name in self.settings:
return self.settings[name] return self.settings[name]
raise ValueError(f"No setting with name {name} found") raise ValueError(f"No setting with name {name} found")
def get_setting_by_display_name(self, display_name : str) -> Setting: def get_setting_by_display_name(self, display_name: str) -> Setting:
"""Gets a setting by its display name. """Gets a setting by its display name.
Args: Args:
@ -92,7 +117,7 @@ class SpectrometerModel():
return setting return setting
raise ValueError(f"No setting with display name {display_name} found") raise ValueError(f"No setting with display name {display_name} found")
@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

@ -5,7 +5,7 @@ import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Setting(): class Setting:
"""A setting for the spectrometer is a value that is the same for all events in a pulse sequence. """A setting for the spectrometer is a value that is the same for all events in a pulse sequence.
E.g. the Transmit gain or the number of points in a spectrum. E.g. the Transmit gain or the number of points in a spectrum.
@ -18,23 +18,27 @@ class Setting():
Attributes: Attributes:
name (str) : The name of the setting name (str) : The name of the setting
category (str) : The category of the setting
description (str) : A description of the setting description (str) : A description of the setting
value : The value of the setting value : The value of the setting
category (str) : The category of the setting category (str) : The category of the setting
""" """
def __init__(self, name: str, category : str, description: str = None, default=None) -> None: def __init__(
self, name: str, category: str, description: str = None, default=None
) -> None:
"""Create a new setting. """Create a new setting.
Args: Args:
name (str): The name of the setting. name (str): The name of the setting.
category (str): The category of the setting.
description (str): A description of the setting. description (str): A description of the setting.
default: The default value of the setting. default: The default value of the setting.
""" """
self.name = name self.name = name
self.category = category self.category = category
self.description = description self.description = description
self.default = default self.default = default
if default is not None: if default is not None:
self.value = default self.value = default
# Update the description with the default value # Update the description with the default value
@ -48,7 +52,13 @@ class NumericalSetting(Setting):
""" """
def __init__( def __init__(
self, name: str, category: str, description: str, default, min_value=None, max_value=None self,
name: str,
category: str,
description: str,
default,
min_value=None,
max_value=None,
) -> None: ) -> None:
"""Create a new numerical setting.""" """Create a new numerical setting."""
super().__init__( super().__init__(
@ -104,7 +114,7 @@ class FloatSetting(NumericalSetting):
description: str, description: str,
min_value: float = None, min_value: float = None,
max_value: float = None, max_value: float = None,
slider = False slider=False,
) -> None: ) -> None:
"""Create a new float setting.""" """Create a new float setting."""
super().__init__(name, category, description, default, min_value, max_value) super().__init__(name, category, description, default, min_value, max_value)
@ -117,7 +127,7 @@ class FloatSetting(NumericalSetting):
@value.setter @value.setter
def value(self, value): def value(self, value):
logger.debug(f"Setting {self.name} to {value}") logger.debug(f"Setting {self.name} to {value}")
self._value = float(value) self._value = float(value)
@ -141,13 +151,12 @@ class IntSetting(NumericalSetting):
description: str, description: str,
min_value=None, min_value=None,
max_value=None, max_value=None,
slider = False slider=False,
) -> None: ) -> None:
"""Create a new int setting.""" """Create a new int setting."""
super().__init__(name, category, description, default, min_value, max_value) super().__init__(name, category, description, default, min_value, max_value)
self.slider = slider self.slider = slider
@property @property
def value(self): def value(self):
"""The value of the setting. In this case, an int.""" """The value of the setting. In this case, an int."""
@ -157,7 +166,7 @@ class IntSetting(NumericalSetting):
def value(self, value): def value(self, value):
logger.debug(f"Setting {self.name} to {value}") logger.debug(f"Setting {self.name} to {value}")
value = int(float(value)) value = int(float(value))
self._value = value self._value = value
@ -171,11 +180,12 @@ class BooleanSetting(Setting):
description (str) : A description of the setting description (str) : A description of the setting
""" """
def __init__(self, name: str, category : str, default: bool, description: str) -> None: def __init__(
self, name: str, category: str, default: bool, description: str
) -> None:
"""Create a new boolean setting.""" """Create a new boolean setting."""
super().__init__(name, category, description, default) super().__init__(name, category, description, default)
@property @property
def value(self): def value(self):
"""The value of the setting. In this case, a bool.""" """The value of the setting. In this case, a bool."""
@ -189,7 +199,6 @@ class BooleanSetting(Setting):
raise ValueError("Value must be a bool") raise ValueError("Value must be a bool")
class SelectionSetting(Setting): class SelectionSetting(Setting):
"""A setting that is a selection from a list of options. """A setting that is a selection from a list of options.
@ -202,7 +211,7 @@ class SelectionSetting(Setting):
""" """
def __init__( def __init__(
self, name: str, category : str, options: list, default: str, description: str self, name: str, category: str, options: list, default: str, description: str
) -> None: ) -> None:
"""Create a new selection setting.""" """Create a new selection setting."""
super().__init__(name, category, description, default) super().__init__(name, category, description, default)
@ -212,7 +221,6 @@ class SelectionSetting(Setting):
self.options = options self.options = options
@property @property
def value(self): def value(self):
"""The value of the setting. In this case, a string.""" """The value of the setting. In this case, a string."""
@ -241,7 +249,9 @@ class StringSetting(Setting):
description (str) : A description of the setting description (str) : A description of the setting
""" """
def __init__(self, name: str, category :str, default: str, description: str) -> None: def __init__(
self, name: str, category: str, default: str, description: str
) -> None:
"""Create a new string setting.""" """Create a new string setting."""
super().__init__(name, category, description, default) super().__init__(name, category, description, default)