This commit is contained in:
jupfi 2024-04-30 16:02:46 +02:00
parent f926398596
commit 2c63fa4028
5 changed files with 409 additions and 178 deletions

View file

@ -0,0 +1 @@
"""Init file for the nqrduck_spectrometer_limenqr package."""

View file

@ -1,3 +1,5 @@
"""Controller module for the Lime NQR spectrometer."""
import logging import logging
import tempfile import tempfile
from pathlib import Path from pathlib import Path
@ -10,24 +12,33 @@ from nqrduck_spectrometer.base_spectrometer_controller import BaseSpectrometerCo
from nqrduck_spectrometer.measurement import Measurement from nqrduck_spectrometer.measurement import Measurement
from nqrduck_spectrometer.pulseparameters import TXPulse, RXReadout from nqrduck_spectrometer.pulseparameters import TXPulse, RXReadout
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class LimeNQRController(BaseSpectrometerController): class LimeNQRController(BaseSpectrometerController):
"""Controller class for the Lime NQR spectrometer."""
def __init__(self, module): def __init__(self, module):
"""Initializes the LimeNQRController."""
super().__init__(module) super().__init__(module)
def start_measurement(self): def start_measurement(self):
"""Starts the measurement procedure."""
self.log_start_message() self.log_start_message()
lime = self.initialize_lime() lime = self.initialize_lime()
if lime is None: if lime is None:
# Emit error message # Emit error message
self.emit_measurement_error("Error with Lime driver. Is the Lime driver installed?") self.emit_measurement_error(
"Error with Lime driver. Is the Lime driver installed?"
)
return -1 return -1
elif lime.Npulses == 0: elif lime.Npulses == 0:
# Emit error message # Emit error message
self.emit_measurement_error("Error with pulse sequence. Is the pulse sequence empty?") self.emit_measurement_error(
"Error with pulse sequence. Is the pulse sequence empty?"
)
return -1 return -1
self.setup_lime_parameters(lime) self.setup_lime_parameters(lime)
@ -37,7 +48,9 @@ class LimeNQRController(BaseSpectrometerController):
if not self.perform_measurement(lime): if not self.perform_measurement(lime):
self.emit_status_message("Measurement failed") self.emit_status_message("Measurement failed")
self.emit_measurement_error("Error with measurement data. Did you set an RX event?") self.emit_measurement_error(
"Error with measurement data. Did you set an RX event?"
)
return -1 return -1
measurement_data = self.process_measurement_results(lime) measurement_data = self.process_measurement_results(lime)
@ -48,12 +61,14 @@ class LimeNQRController(BaseSpectrometerController):
else: else:
self.emit_measurement_error("Measurement failed. Unable to retrieve data.") self.emit_measurement_error("Measurement failed. Unable to retrieve data.")
def log_start_message(self): def log_start_message(self) -> None:
"""This method logs a message when the measurement is started.""" """Logs a message when the measurement is started."""
logger.debug("Starting measurement with spectrometer: %s", self.module.model.name) logger.debug(
"Starting measurement with spectrometer: %s", self.module.model.name
)
def initialize_lime(self): def initialize_lime(self) -> PyLimeConfig:
"""This method initializes the limr object that is used to communicate with the pulseN driver. """Initializes the limr object that is used to communicate with the pulseN driver.
Returns: Returns:
PyLimeConfig: The PyLimeConfig object that is used to communicate with the pulseN driver PyLimeConfig: The PyLimeConfig object that is used to communicate with the pulseN driver
@ -67,17 +82,18 @@ class LimeNQRController(BaseSpectrometerController):
except Exception as e: except Exception as e:
logger.error("Error while initializing Lime driver: %s", e) logger.error("Error while initializing Lime driver: %s", e)
import traceback import traceback
traceback.print_exc() traceback.print_exc()
return None return None
def setup_lime_parameters(self, lime): def setup_lime_parameters(self, lime: PyLimeConfig) -> None:
"""This method sets the parameters of the lime config according to the settings set in the spectrometer module. """Sets the parameters of the lime config according to the settings set in the spectrometer module.
Args: Args:
lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver
""" """
#lime.noi = -1 # lime.noi = -1
lime.override_init = -1 lime.override_init = -1
# #
# lime.nrp = 1 # lime.nrp = 1
@ -87,19 +103,19 @@ class LimeNQRController(BaseSpectrometerController):
lime.averages = self.module.model.averages lime.averages = self.module.model.averages
self.log_lime_parameters(lime) self.log_lime_parameters(lime)
def setup_temporary_storage(self, lime): def setup_temporary_storage(self, lime: PyLimeConfig) -> None:
"""This method sets up the temporary storage for the measurement data. """Sets up the temporary storage for the measurement data.
Args: Args:
lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver
""" """
temp_dir = tempfile.TemporaryDirectory() temp_dir = tempfile.TemporaryDirectory()
logger.debug("Created temporary directory at: %s", temp_dir.name) logger.debug("Created temporary directory at: %s", temp_dir.name)
lime.save_path = str(Path(temp_dir.name)) + "/" # Temporary storage path lime.save_path = str(Path(temp_dir.name)) + "/" # Temporary storage path
lime.file_pattern = "temp" # Temporary filename prefix or related config lime.file_pattern = "temp" # Temporary filename prefix or related config
def perform_measurement(self, lime): def perform_measurement(self, lime: PyLimeConfig) -> bool:
"""This method executes the measurement procedure. """Executes the measurement procedure.
Args: Args:
lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver
@ -115,8 +131,8 @@ class LimeNQRController(BaseSpectrometerController):
logger.error("Failed to execute the measurement: %s", e) logger.error("Failed to execute the measurement: %s", e)
return False return False
def process_measurement_results(self, lime): def process_measurement_results(self, lime: PyLimeConfig) -> Measurement:
"""This method processes the measurement results and returns a Measurement object. """Processes the measurement results and returns a Measurement object.
Args: Args:
lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver
@ -133,8 +149,10 @@ class LimeNQRController(BaseSpectrometerController):
logger.debug("RX event begins at: %sµs and ends at: %sµs", rx_begin, rx_stop) logger.debug("RX event begins at: %sµs and ends at: %sµs", rx_begin, rx_stop)
return self.calculate_measurement_data(lime, rx_begin, rx_stop) return self.calculate_measurement_data(lime, rx_begin, rx_stop)
def calculate_measurement_data(self, lime, rx_begin, rx_stop): def calculate_measurement_data(
"""This method calculates the measurement data from the limr object. self, lime: PyLimeConfig, rx_begin: float, rx_stop: float
) -> Measurement:
"""Calculates the measurement data from the limr object.
Args: Args:
lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver
@ -146,34 +164,44 @@ class LimeNQRController(BaseSpectrometerController):
""" """
try: try:
path = lime.get_path() path = lime.get_path()
hdf = HDF(path) hdf = HDF(path)
evidx = self.find_evaluation_range_indices(hdf, rx_begin, rx_stop) evidx = self.find_evaluation_range_indices(hdf, rx_begin, rx_stop)
tdx, tdy = self.extract_measurement_data(lime, hdf, evidx) tdx, tdy = self.extract_measurement_data(lime, hdf, evidx)
fft_shift = self.get_fft_shift() fft_shift = self.get_fft_shift()
return Measurement(tdx, tdy, self.module.model.target_frequency, frequency_shift=fft_shift, IF_frequency=self.module.model.if_frequency) return Measurement(
tdx,
tdy,
self.module.model.target_frequency,
frequency_shift=fft_shift,
IF_frequency=self.module.model.if_frequency,
)
except Exception as e: except Exception as e:
logger.error("Error processing measurement result: %s", e) logger.error("Error processing measurement result: %s", e)
return None return None
def find_evaluation_range_indices(self, hdf, rx_begin, rx_stop): def find_evaluation_range_indices(
"""This method finds the indices of the evaluation range in the measurement data. self, hdf: HDF, rx_begin: float, rx_stop: float
) -> list:
"""Finds the indices of the evaluation range in the measurement data.
Args: Args:
HDF (HDF): The HDF object that is used to read the measurement data hdf (HDF): The HDF object that is used to read the measurement data
rx_begin (float): The start time of the RX event in µs rx_begin (float): The start time of the RX event in µs
rx_stop (float): The stop time of the RX event in µs rx_stop (float): The stop time of the RX event in µs
Returns: Returns:
list: The indices of the evaluation range in the measurement data list: The indices of the evaluation range in the measurement data
""" """
return np.where((hdf.tdx > rx_begin) & (hdf.tdx < rx_stop))[0] return np.where((hdf.tdx > rx_begin) & (hdf.tdx < rx_stop))[0]
def extract_measurement_data(self, lime, hdf, indices): def extract_measurement_data(
"""This method extracts the measurement data from the limr object. self, lime: PyLimeConfig, hdf: HDF, indices: list
) -> tuple:
"""Extracts the measurement data from the PyLimeConfig object.
Args: Args:
lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver
HDF (HDF): The HDF object that is used to read the measurement data hdf (HDF): The HDF object that is used to read the measurement data
indices (list): The indices of the evaluation range in the measurement data indices (list): The indices of the evaluation range in the measurement data
Returns: Returns:
@ -185,17 +213,19 @@ class LimeNQRController(BaseSpectrometerController):
tdy = tdy.flatten() tdy = tdy.flatten()
return tdx, tdy return tdx, tdy
def get_fft_shift(self): def get_fft_shift(self) -> int:
"""This method returns the FFT shift value from the settings. """Rreturns the FFT shift value from the settings.
Returns: Returns:
int: The FFT shift value int: The FFT shift value
""" """
fft_shift_enabled = self.module.model.get_setting_by_name(self.module.model.FFT_SHIFT).value fft_shift_enabled = self.module.model.get_setting_by_name(
self.module.model.FFT_SHIFT
).value
return self.module.model.if_frequency if fft_shift_enabled else 0 return self.module.model.if_frequency if fft_shift_enabled else 0
def emit_measurement_data(self, measurement_data): def emit_measurement_data(self, measurement_data: Measurement) -> None:
"""This method emits the measurement data to the GUI. """Emits the measurement data to the GUI.
Args: Args:
measurement_data (Measurement): The measurement data measurement_data (Measurement): The measurement data
@ -203,16 +233,16 @@ class LimeNQRController(BaseSpectrometerController):
logger.debug("Emitting measurement data") logger.debug("Emitting measurement data")
self.module.nqrduck_signal.emit("measurement_data", measurement_data) self.module.nqrduck_signal.emit("measurement_data", measurement_data)
def emit_status_message(self, message): def emit_status_message(self, message: str) -> None:
"""This method emits a status message to the GUI. """Emits a status message to the GUI.
Args: Args:
message (str): The status message message (str): The status message
""" """
self.module.nqrduck_signal.emit("statusbar_message", message) self.module.nqrduck_signal.emit("statusbar_message", message)
def emit_measurement_error(self, error_message): def emit_measurement_error(self, error_message: str) -> None:
"""This method emits a measurement error to the GUI. """Emits a measurement error to the GUI.
Args: Args:
error_message (str): The error message error_message (str): The error message
@ -220,24 +250,24 @@ class LimeNQRController(BaseSpectrometerController):
logger.error(error_message) logger.error(error_message)
self.module.nqrduck_signal.emit("measurement_error", error_message) self.module.nqrduck_signal.emit("measurement_error", error_message)
def log_lime_parameters(self, lime): def log_lime_parameters(self, lime: PyLimeConfig) -> None:
"""This method logs the parameters of the limr object. """Logs the parameters of the limr object.
Args: Args:
lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver
""" """
# for key, value in lime.__dict__.items(): # for key, value in lime.__dict__.items():
# logger.debug("Lime parameter %s has value %s", key, value) # logger.debug("Lime parameter %s has value %s", key, value)
logger.debug("Lime parameter %s has value %s", "srate", lime.srate) logger.debug("Lime parameter %s has value %s", "srate", lime.srate)
def update_settings(self, lime): def update_settings(self, lime: PyLimeConfig) -> PyLimeConfig:
"""This method sets the parameters of the limr object according to the settings set in the spectrometer module. """Sets the parameters of the limr object according to the settings set in the spectrometer module.
Args: Args:
lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver
Returns: Returns:
lime: The updated limr object lime: The updated limr object
""" """
logger.debug( logger.debug(
"Updating settings for spectrometer: %s for measurement", "Updating settings for spectrometer: %s for measurement",
@ -259,7 +289,9 @@ class LimeNQRController(BaseSpectrometerController):
lime.RX_matching = setting.get_setting() lime.RX_matching = setting.get_setting()
# Careful this doesn't only set the IF frequency but the local oscillator frequency # Careful this doesn't only set the IF frequency but the local oscillator frequency
elif setting.name == self.module.model.IF_FREQUENCY: elif setting.name == self.module.model.IF_FREQUENCY:
lime.frq = self.module.model.target_frequency - setting.get_setting() lime.frq = (
self.module.model.target_frequency - setting.get_setting()
)
self.module.model.if_frequency = setting.get_setting() self.module.model.if_frequency = setting.get_setting()
elif setting.name == self.module.model.ACQUISITION_TIME: elif setting.name == self.module.model.ACQUISITION_TIME:
lime.rectime_secs = setting.get_setting() lime.rectime_secs = setting.get_setting()
@ -309,8 +341,8 @@ class LimeNQRController(BaseSpectrometerController):
lime.c3_tim = c3_tim lime.c3_tim = c3_tim
return lime return lime
def translate_pulse_sequence(self, lime): def translate_pulse_sequence(self, lime: PyLimeConfig) -> PyLimeConfig:
"""This method translates the pulse sequence to the limr object. """Ttranslates the pulse sequence to the limr object.
Args: Args:
lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver
@ -325,15 +357,25 @@ class LimeNQRController(BaseSpectrometerController):
self.log_parameter_details(parameter) self.log_parameter_details(parameter)
if self.is_translatable_tx_parameter(parameter): if self.is_translatable_tx_parameter(parameter):
pulse_shape, pulse_amplitude = self.prepare_pulse_amplitude(event, parameter) pulse_shape, pulse_amplitude = self.prepare_pulse_amplitude(
pulse_amplitude, modulated_phase = self.modulate_pulse_amplitude(pulse_amplitude, event, lime) event, parameter
)
pulse_amplitude, modulated_phase = self.modulate_pulse_amplitude(
pulse_amplitude, event, lime
)
if first_pulse: # If the pulse frequency list is empty if first_pulse: # If the pulse frequency list is empty
pfr, pdr, pam, pof, pph = self.initialize_pulse_lists(lime, pulse_amplitude, pulse_shape, modulated_phase) pfr, pdr, pam, pof, pph = self.initialize_pulse_lists(
lime, pulse_amplitude, pulse_shape, modulated_phase
)
first_pulse = False first_pulse = False
else: else:
pfr_ext, pdr_ext, pam_ext, pph_ext = self.extend_pulse_lists(lime, pulse_amplitude, pulse_shape, modulated_phase) pfr_ext, pdr_ext, pam_ext, pph_ext = self.extend_pulse_lists(
pof_ext = self.calculate_and_set_offsets(lime, pulse_shape, events, event, pulse_amplitude) pulse_amplitude, pulse_shape, modulated_phase
)
pof_ext = self.calculate_and_set_offsets(
lime, pulse_shape, events, event, pulse_amplitude
)
pfr.extend(pfr_ext) pfr.extend(pfr_ext)
pdr.extend(pdr_ext) pdr.extend(pdr_ext)
@ -351,8 +393,9 @@ class LimeNQRController(BaseSpectrometerController):
lime.Npulses = len(lime.p_frq) lime.Npulses = len(lime.p_frq)
return lime return lime
def get_number_of_pulses(self): def get_number_of_pulses(self) -> int:
"""This method calculates the number of pulses in the pulse sequence before the LimeDriverBinding is initialized. """Calculates the number of pulses in the pulse sequence before the LimeDriverBinding is initialized.
This makes sure it"s initialized with the correct size of the pulse lists. This makes sure it"s initialized with the correct size of the pulse lists.
Returns: Returns:
@ -371,31 +414,35 @@ class LimeNQRController(BaseSpectrometerController):
# Helper functions below: # Helper functions below:
def fetch_pulse_sequence_events(self): def fetch_pulse_sequence_events(self) -> list:
"""This method fetches the pulse sequence events from the pulse programmer module. """Fetches the pulse sequence events from the pulse programmer module.
Returns: Returns:
list: The pulse sequence events list: The pulse sequence events
""" """
return self.module.model.pulse_programmer.model.pulse_sequence.events return self.module.model.pulse_programmer.model.pulse_sequence.events
def log_event_details(self, event): def log_event_details(self, event) -> None:
"""Logs the details of an event."""
logger.debug("Event %s has parameters: %s", event.name, event.parameters) logger.debug("Event %s has parameters: %s", event.name, event.parameters)
def log_parameter_details(self, parameter): def log_parameter_details(self, parameter) -> None:
"""Logs the details of a parameter."""
logger.debug("Parameter %s has options: %s", parameter.name, parameter.options) logger.debug("Parameter %s has options: %s", parameter.name, parameter.options)
def is_translatable_tx_parameter(self, parameter): def is_translatable_tx_parameter(self, parameter):
"""This method checks if a parameter a pulse with a transmit pulse shape (amplitude nonzero) """Checks if a parameter a pulse with a transmit pulse shape (amplitude nonzero).
Args: Args:
parameter (Parameter): The parameter to check parameter (Parameter): The parameter to check
""" """
return (parameter.name == self.module.model.TX and return (
parameter.get_option_by_name(TXPulse.RELATIVE_AMPLITUDE).value > 0) parameter.name == self.module.model.TX
and parameter.get_option_by_name(TXPulse.RELATIVE_AMPLITUDE).value > 0
)
def prepare_pulse_amplitude(self, event, parameter): def prepare_pulse_amplitude(self, event, parameter):
"""This method prepares the pulse amplitude for the limr object. """Prepares the pulse amplitude for the limr object.
Args: Args:
event (Event): The event that contains the parameter event (Event): The event that contains the parameter
@ -405,13 +452,16 @@ class LimeNQRController(BaseSpectrometerController):
tuple: A tuple containing the pulse shape and the pulse amplitude tuple: A tuple containing the pulse shape and the pulse amplitude
""" """
pulse_shape = parameter.get_option_by_name(TXPulse.TX_PULSE_SHAPE).value pulse_shape = parameter.get_option_by_name(TXPulse.TX_PULSE_SHAPE).value
pulse_amplitude = abs(pulse_shape.get_pulse_amplitude(event.duration)) * \ pulse_amplitude = abs(pulse_shape.get_pulse_amplitude(event.duration)) * (
(parameter.get_option_by_name(TXPulse.RELATIVE_AMPLITUDE).value / 100) parameter.get_option_by_name(TXPulse.RELATIVE_AMPLITUDE).value / 100
)
pulse_amplitude = np.clip(pulse_amplitude, -0.99, 0.99) pulse_amplitude = np.clip(pulse_amplitude, -0.99, 0.99)
return pulse_shape, pulse_amplitude return pulse_shape, pulse_amplitude
def modulate_pulse_amplitude(self, pulse_amplitude, event, lime): def modulate_pulse_amplitude(
"""This method modulates the pulse amplitude for the limr object. We need to do this to have the pulse at IF frequency instead of LO frequency. self, pulse_amplitude: float, event, lime: PyLimeConfig
) -> tuple:
"""Modulates the pulse amplitude for the limr object. We need to do this to have the pulse at IF frequency instead of LO frequency.
Args: Args:
pulse_amplitude (float): The pulse amplitude pulse_amplitude (float): The pulse amplitude
@ -430,58 +480,72 @@ class LimeNQRController(BaseSpectrometerController):
modulated_phase = self.unwrap_phase(np.angle(pulse_complex)) modulated_phase = self.unwrap_phase(np.angle(pulse_complex))
return modulated_amplitude, modulated_phase return modulated_amplitude, modulated_phase
def unwrap_phase(self, phase): def unwrap_phase(self, phase: float) -> float:
"""This method unwraps the phase of the pulse. """This method unwraps the phase of the pulse.
Args: Args:
phase (float): The phase of the pulse phase (float): The phase of the pulse
""" """
return (np.unwrap(phase) + 2 * np.pi) % (2 * np.pi) return (np.unwrap(phase) + 2 * np.pi) % (2 * np.pi)
def initialize_pulse_lists(self, lime, pulse_amplitude, pulse_shape, modulated_phase): def initialize_pulse_lists(
self,
lime: PyLimeConfig,
pulse_amplitude: np.array,
pulse_shape,
modulated_phase: np.array,
) -> tuple:
"""This method initializes the pulse lists of the limr object. """This method initializes the pulse lists of the limr object.
Args: Args:
lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver
pulse_amplitude (float): The pulse amplitude pulse_amplitude (np.array): The pulse amplitude
pulse_shape (PulseShape): The pulse shape pulse_shape (Function): The pulse shape
modulated_phase (float): The modulated phase modulated_phase (np.array): The modulated phase
""" """
pfr = [float(self.module.model.if_frequency)] * len(pulse_amplitude) pfr = [float(self.module.model.if_frequency)] * len(pulse_amplitude)
# We set the first len(pulse_amplitude) of the p_dur # We set the first len(pulse_amplitude) of the p_dur
pdr = [float(pulse_shape.resolution)] * len(pulse_amplitude) pdr = [float(pulse_shape.resolution)] * len(pulse_amplitude)
pam = list(pulse_amplitude) pam = list(pulse_amplitude)
pof = ([self.module.model.OFFSET_FIRST_PULSE] + pof = [self.module.model.OFFSET_FIRST_PULSE] + [
[int(pulse_shape.resolution * lime.srate)] * (len(pulse_amplitude) - 1)) int(pulse_shape.resolution * lime.srate)
] * (len(pulse_amplitude) - 1)
pph = list(modulated_phase) pph = list(modulated_phase)
return pfr, pdr, pam, pof, pph return pfr, pdr, pam, pof, pph
def extend_pulse_lists(self, lime, pulse_amplitude, pulse_shape, modulated_phase): def extend_pulse_lists(self, pulse_amplitude, pulse_shape, modulated_phase):
"""This method extends the pulse lists of the limr object. """This method extends the pulse lists of the limr object.
Args: Args:
lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver
pulse_amplitude (float): The pulse amplitude pulse_amplitude (float): The pulse amplitude
pulse_shape (PulseShape): The pulse shape pulse_shape (PulseShape): The pulse shape
modulated_phase (float): The modulated phase modulated_phase (float): The modulated phase
Returns:
tuple: A tuple containing the extended pulse lists (frequency, duration, amplitude, phase)
""" """
pfr = ([float(self.module.model.if_frequency)] * len(pulse_amplitude)) pfr = [float(self.module.model.if_frequency)] * len(pulse_amplitude)
pdr = ([float(pulse_shape.resolution)] * len(pulse_amplitude)) pdr = [float(pulse_shape.resolution)] * len(pulse_amplitude)
pam = (list(pulse_amplitude)) pam = list(pulse_amplitude)
pph = (list(modulated_phase)) pph = list(modulated_phase)
return pfr, pdr, pam, pph return pfr, pdr, pam, pph
def calculate_and_set_offsets(self, lime, pulse_shape, events, current_event, pulse_amplitude): def calculate_and_set_offsets(
self, lime: PyLimeConfig, pulse_shape, events, current_event, pulse_amplitude
) -> list:
"""This method calculates and sets the offsets for the limr object. """This method calculates and sets the offsets for the limr object.
Args: Args:
lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver
pulse_shape (PulseShape): The pulse shape pulse_shape (Function): The pulse shape
events (list): The pulse sequence events events (list): The pulse sequence events
current_event (Event): The current event current_event (Event): The current event
pulse_amplitude (float): The pulse amplitude pulse_amplitude (float): The pulse amplitude
Returns:
list: The offsets for the limr object
""" """
blank_durations = self.get_blank_durations_before_event(events, current_event) blank_durations = self.get_blank_durations_before_event(events, current_event)
@ -500,7 +564,8 @@ class LimeNQRController(BaseSpectrometerController):
pof.extend([offset_per_sample] * (len(pulse_amplitude) - 1)) pof.extend([offset_per_sample] * (len(pulse_amplitude) - 1))
return pof return pof
def get_blank_durations_before_event(self, events, current_event): # This method could be refactored in a potential pulse sequence module
def get_blank_durations_before_event(self, events, current_event) -> list:
"""This method returns the blank durations before the current event. """This method returns the blank durations before the current event.
Args: Args:
@ -511,12 +576,15 @@ class LimeNQRController(BaseSpectrometerController):
list: The blank durations before the current event list: The blank durations before the current event
""" """
blank_durations = [] blank_durations = []
previous_events_without_tx_pulse = self.get_previous_events_without_tx_pulse(events, current_event) previous_events_without_tx_pulse = self.get_previous_events_without_tx_pulse(
events, current_event
)
for event in previous_events_without_tx_pulse: for event in previous_events_without_tx_pulse:
blank_durations.append(float(event.duration)) blank_durations.append(float(event.duration))
return blank_durations return blank_durations
def get_previous_events_without_tx_pulse(self, events, current_event): # This method could be refactored in a potential pulse sequence module
def get_previous_events_without_tx_pulse(self, events, current_event) -> list:
"""This method returns the previous events without a transmit pulse. """This method returns the previous events without a transmit pulse.
Args: Args:
@ -530,15 +598,19 @@ class LimeNQRController(BaseSpectrometerController):
previous_events = events[:index] previous_events = events[:index]
result = [] result = []
for event in reversed(previous_events): for event in reversed(previous_events):
translatable = any(self.is_translatable_tx_parameter(param) for param in event.parameters.values()) translatable = any(
self.is_translatable_tx_parameter(param)
for param in event.parameters.values()
)
if not translatable: if not translatable:
result.append(event) result.append(event)
else: else:
break break
return reversed(result) # Reversed to maintain the original order if needed elsewhere return reversed(
result
) # Reversed to maintain the original order if needed elsewhere
def translate_rx_event(self, lime : PyLimeConfig) -> tuple:
def translate_rx_event(self, lime):
"""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.
Args: Args:
@ -547,19 +619,25 @@ class LimeNQRController(BaseSpectrometerController):
Returns: Returns:
tuple: A tuple containing the start and stop time of the RX event in µs tuple: A tuple containing the start and stop time of the RX event in µs
""" """
CORRECTION_FACTOR = self.module.model.get_setting_by_name(self.module.model.RX_OFFSET).value CORRECTION_FACTOR = self.module.model.get_setting_by_name(
self.module.model.RX_OFFSET
).value
events = self.module.model.pulse_programmer.model.pulse_sequence.events events = self.module.model.pulse_programmer.model.pulse_sequence.events
rx_event = self.find_rx_event(events) rx_event = self.find_rx_event(events)
if not rx_event: if not rx_event:
return None, None return None, None
previous_events_duration = self.calculate_previous_events_duration(events, rx_event) previous_events_duration = self.calculate_previous_events_duration(
events, rx_event
)
rx_duration = float(rx_event.duration) rx_duration = float(rx_event.duration)
offset = self.calculate_offset(lime) offset = self.calculate_offset(lime)
rx_begin = float(previous_events_duration) + float(offset) + float(CORRECTION_FACTOR) rx_begin = (
float(previous_events_duration) + float(offset) + float(CORRECTION_FACTOR)
)
rx_stop = rx_begin + rx_duration rx_stop = rx_begin + rx_duration
return rx_begin * 1e6, rx_stop * 1e6 return rx_begin * 1e6, rx_stop * 1e6
@ -593,7 +671,7 @@ class LimeNQRController(BaseSpectrometerController):
previous_events = events[: events.index(rx_event)] previous_events = events[: events.index(rx_event)]
return sum(event.duration for event in previous_events) return sum(event.duration for event in previous_events)
def calculate_offset(self, lime): def calculate_offset(self, lime :PyLimeConfig) -> float:
"""This method calculates the offset for the RX event. """This method calculates the offset for the RX event.
Args: Args:
@ -604,8 +682,7 @@ class LimeNQRController(BaseSpectrometerController):
""" """
return self.module.model.OFFSET_FIRST_PULSE * (1 / lime.srate) return self.module.model.OFFSET_FIRST_PULSE * (1 / lime.srate)
def set_frequency(self, value: float) -> None:
def set_frequency(self, value: float):
"""This method sets the target frequency of the spectrometer. """This method sets the target frequency of the spectrometer.
Args: Args:
@ -622,7 +699,7 @@ class LimeNQRController(BaseSpectrometerController):
) )
self.module.nqrduck_signal.emit("failure_set_frequency", value) self.module.nqrduck_signal.emit("failure_set_frequency", value)
def set_averages(self, value: int): def set_averages(self, value: int) -> None:
"""This method sets the number of averages for the spectrometer. """This method sets the number of averages for the spectrometer.
Args: Args:

View file

@ -1,3 +1,5 @@
"""BaseSpectrometer for Lime NQR spectrometer."""
from nqrduck_spectrometer.base_spectrometer import BaseSpectrometer from nqrduck_spectrometer.base_spectrometer import BaseSpectrometer
from .model import LimeNQRModel from .model import LimeNQRModel
from .view import LimeNQRView from .view import LimeNQRView

View file

@ -1,12 +1,20 @@
"""Model for the Lime NQR spectrometer."""
import logging import logging
from nqrduck_spectrometer.base_spectrometer_model import BaseSpectrometerModel from nqrduck_spectrometer.base_spectrometer_model import BaseSpectrometerModel
from nqrduck_spectrometer.pulseparameters import TXPulse, RXReadout from nqrduck_spectrometer.pulseparameters import TXPulse, RXReadout
from nqrduck_spectrometer.settings import FloatSetting, IntSetting, BooleanSetting, SelectionSetting from nqrduck_spectrometer.settings import (
FloatSetting,
IntSetting,
BooleanSetting,
SelectionSetting,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class LimeNQRModel(BaseSpectrometerModel): class LimeNQRModel(BaseSpectrometerModel):
"""Model for the Lime NQR spectrometer."""
# Setting constants for the names of the spectrometer settings # Setting constants for the names of the spectrometer settings
CHANNEL = "TX/RX Channel" CHANNEL = "TX/RX Channel"
TX_MATCHING = "TX Matching" TX_MATCHING = "TX Matching"
@ -49,91 +57,228 @@ class LimeNQRModel(BaseSpectrometerModel):
# Settings that are not changed by the user # Settings that are not changed by the user
OFFSET_FIRST_PULSE = 300 OFFSET_FIRST_PULSE = 300
def __init__(self, module) -> None: def __init__(self, module) -> None:
"""Initializes the Lime NQR model."""
super().__init__(module) super().__init__(module)
# Acquisition settings # Acquisition settings
channel_options = ["0", "1"] channel_options = ["0", "1"]
channel_setting = SelectionSetting(self.CHANNEL, channel_options, "0", "TX/RX Channel") channel_setting = SelectionSetting(
self.CHANNEL, channel_options, "0", "TX/RX Channel"
)
self.add_setting(channel_setting, self.ACQUISITION) self.add_setting(channel_setting, self.ACQUISITION)
tx_matching_options = ["0", "1"] tx_matching_options = ["0", "1"]
tx_matching_setting = SelectionSetting(self.TX_MATCHING, tx_matching_options, "0", "TX Matching") tx_matching_setting = SelectionSetting(
self.TX_MATCHING, tx_matching_options, "0", "TX Matching"
)
self.add_setting(tx_matching_setting, self.ACQUISITION) self.add_setting(tx_matching_setting, self.ACQUISITION)
rx_matching_options = ["0", "1"] rx_matching_options = ["0", "1"]
rx_matching_setting = SelectionSetting(self.RX_MATCHING, rx_matching_options, "0", "RX Matching") rx_matching_setting = SelectionSetting(
self.RX_MATCHING, rx_matching_options, "0", "RX Matching"
)
self.add_setting(rx_matching_setting, self.ACQUISITION) self.add_setting(rx_matching_setting, self.ACQUISITION)
sampling_frequency_setting = FloatSetting(self.SAMPLING_FREQUENCY, 30.72e6, "The rate at which the spectrometer samples the input signal.", min_value=0, max_value=30.72e6) sampling_frequency_setting = FloatSetting(
self.SAMPLING_FREQUENCY,
30.72e6,
"The rate at which the spectrometer samples the input signal.",
min_value=0,
max_value=30.72e6,
)
self.add_setting(sampling_frequency_setting, self.ACQUISITION) self.add_setting(sampling_frequency_setting, self.ACQUISITION)
if_frequency_setting = FloatSetting(self.IF_FREQUENCY, 5e6, "The intermediate frequency to which the input signal is down converted during analog-to-digital conversion.", min_value=0) if_frequency_setting = FloatSetting(
self.IF_FREQUENCY,
5e6,
"The intermediate frequency to which the input signal is down converted during analog-to-digital conversion.",
min_value=0,
)
self.add_setting(if_frequency_setting, self.ACQUISITION) self.add_setting(if_frequency_setting, self.ACQUISITION)
self.if_frequency = 5e6 self.if_frequency = 5e6
acquisition_time_setting = FloatSetting(self.ACQUISITION_TIME, 82e-6, "Acquisition time - this is from the beginning of the pulse sequence", min_value=0) acquisition_time_setting = FloatSetting(
self.ACQUISITION_TIME,
82e-6,
"Acquisition time - this is from the beginning of the pulse sequence",
min_value=0,
)
self.add_setting(acquisition_time_setting, self.ACQUISITION) self.add_setting(acquisition_time_setting, self.ACQUISITION)
# Gate Settings # Gate Settings
gate_enable_setting = BooleanSetting(self.GATE_ENABLE, True, "Setting that controls whether gate is on during transmitting.") gate_enable_setting = BooleanSetting(
self.GATE_ENABLE,
True,
"Setting that controls whether gate is on during transmitting.",
)
self.add_setting(gate_enable_setting, self.GATE_SETTINGS) self.add_setting(gate_enable_setting, self.GATE_SETTINGS)
gate_padding_left_setting = IntSetting(self.GATE_PADDING_LEFT, 10, "The number of samples by which to extend the gate window to the left.", min_value=0) gate_padding_left_setting = IntSetting(
self.GATE_PADDING_LEFT,
10,
"The number of samples by which to extend the gate window to the left.",
min_value=0,
)
self.add_setting(gate_padding_left_setting, self.GATE_SETTINGS) self.add_setting(gate_padding_left_setting, self.GATE_SETTINGS)
gate_padding_right_setting = IntSetting(self.GATE_PADDING_RIGHT, 10, "The number of samples by which to extend the gate window to the right.", min_value=0) gate_padding_right_setting = IntSetting(
self.GATE_PADDING_RIGHT,
10,
"The number of samples by which to extend the gate window to the right.",
min_value=0,
)
self.add_setting(gate_padding_right_setting, self.GATE_SETTINGS) self.add_setting(gate_padding_right_setting, self.GATE_SETTINGS)
gate_shift_setting = IntSetting(self.GATE_SHIFT, 53, "The delay, in number of samples, by which the gate window is shifted.", min_value=0) gate_shift_setting = IntSetting(
self.GATE_SHIFT,
53,
"The delay, in number of samples, by which the gate window is shifted.",
min_value=0,
)
self.add_setting(gate_shift_setting, self.GATE_SETTINGS) self.add_setting(gate_shift_setting, self.GATE_SETTINGS)
# RX/TX settings # RX/TX settings
rx_gain_setting = IntSetting(self.RX_GAIN, 55, "The gain level of the receivers amplifier.", min_value=0, max_value=55, spin_box=(True, True)) rx_gain_setting = IntSetting(
self.RX_GAIN,
55,
"The gain level of the receivers amplifier.",
min_value=0,
max_value=55,
spin_box=(True, True),
)
self.add_setting(rx_gain_setting, self.RX_TX_SETTINGS) self.add_setting(rx_gain_setting, self.RX_TX_SETTINGS)
tx_gain_setting = IntSetting(self.TX_GAIN, 30, "The gain level of the transmitters amplifier.", min_value=0, max_value=55, spin_box=(True, True)) tx_gain_setting = IntSetting(
self.TX_GAIN,
30,
"The gain level of the transmitters amplifier.",
min_value=0,
max_value=55,
spin_box=(True, True),
)
self.add_setting(tx_gain_setting, self.RX_TX_SETTINGS) self.add_setting(tx_gain_setting, self.RX_TX_SETTINGS)
rx_lpf_bw_setting = FloatSetting(self.RX_LPF_BW, 30.72e6/2, "The bandwidth of the receivers low-pass filter which attenuates frequencies below a certain threshold.") rx_lpf_bw_setting = FloatSetting(
self.RX_LPF_BW,
30.72e6 / 2,
"The bandwidth of the receivers low-pass filter which attenuates frequencies below a certain threshold.",
)
self.add_setting(rx_lpf_bw_setting, self.RX_TX_SETTINGS) self.add_setting(rx_lpf_bw_setting, self.RX_TX_SETTINGS)
tx_lpf_bw_setting = FloatSetting(self.TX_LPF_BW, 130.0e6, "The bandwidth of the transmitters low-pass filter which limits the frequency range of the transmitted signa") tx_lpf_bw_setting = FloatSetting(
self.TX_LPF_BW,
130.0e6,
"The bandwidth of the transmitters low-pass filter which limits the frequency range of the transmitted signa",
)
self.add_setting(tx_lpf_bw_setting, self.RX_TX_SETTINGS) self.add_setting(tx_lpf_bw_setting, self.RX_TX_SETTINGS)
# Calibration settings # Calibration settings
tx_i_dc_correction_setting = IntSetting(self.TX_I_DC_CORRECTION, -45, "Adjusts the direct current offset errors in the in-phase (I) component of the transmit (TX) path.", min_value=-128, max_value=127, spin_box=(True, True)) tx_i_dc_correction_setting = IntSetting(
self.TX_I_DC_CORRECTION,
-45,
"Adjusts the direct current offset errors in the in-phase (I) component of the transmit (TX) path.",
min_value=-128,
max_value=127,
spin_box=(True, True),
)
self.add_setting(tx_i_dc_correction_setting, self.CALIBRATION) self.add_setting(tx_i_dc_correction_setting, self.CALIBRATION)
tx_q_dc_correction_setting = IntSetting(self.TX_Q_DC_CORRECTION, 0, "Adjusts the direct current offset errors in the quadrature (Q) component of the transmit (TX) path.", min_value=-128, max_value=127, spin_box=(True, True)) tx_q_dc_correction_setting = IntSetting(
self.TX_Q_DC_CORRECTION,
0,
"Adjusts the direct current offset errors in the quadrature (Q) component of the transmit (TX) path.",
min_value=-128,
max_value=127,
spin_box=(True, True),
)
self.add_setting(tx_q_dc_correction_setting, self.CALIBRATION) self.add_setting(tx_q_dc_correction_setting, self.CALIBRATION)
tx_i_gain_correction_setting = IntSetting(self.TX_I_GAIN_CORRECTION, 2047, "Modifies the gain settings for the I channel of the TX path, adjusting for imbalances.", min_value=0, max_value=2047, spin_box=(True, True)) tx_i_gain_correction_setting = IntSetting(
self.TX_I_GAIN_CORRECTION,
2047,
"Modifies the gain settings for the I channel of the TX path, adjusting for imbalances.",
min_value=0,
max_value=2047,
spin_box=(True, True),
)
self.add_setting(tx_i_gain_correction_setting, self.CALIBRATION) self.add_setting(tx_i_gain_correction_setting, self.CALIBRATION)
tx_q_gain_correction_setting = IntSetting(self.TX_Q_GAIN_CORRECTION, 2039, "Modifies the gain settings for the Q channel of the TX path, adjusting for imbalances.", min_value=0, max_value=2047, spin_box=(True, True)) tx_q_gain_correction_setting = IntSetting(
self.TX_Q_GAIN_CORRECTION,
2039,
"Modifies the gain settings for the Q channel of the TX path, adjusting for imbalances.",
min_value=0,
max_value=2047,
spin_box=(True, True),
)
self.add_setting(tx_q_gain_correction_setting, self.CALIBRATION) self.add_setting(tx_q_gain_correction_setting, self.CALIBRATION)
tx_phase_adjustment_setting = IntSetting(self.TX_PHASE_ADJUSTMENT, 3, "Corrects the Phase of I Q signals in the TX path.", min_value=-2048, max_value=2047, spin_box=(True, True)) tx_phase_adjustment_setting = IntSetting(
self.TX_PHASE_ADJUSTMENT,
3,
"Corrects the Phase of I Q signals in the TX path.",
min_value=-2048,
max_value=2047,
spin_box=(True, True),
)
self.add_setting(tx_phase_adjustment_setting, self.CALIBRATION) self.add_setting(tx_phase_adjustment_setting, self.CALIBRATION)
rx_i_dc_correction_setting = IntSetting(self.RX_I_DC_CORRECTION, 0, "Adjusts the direct current offset errors in the in-phase (I) component of the receive (RX) path.", min_value=-63, max_value=63, spin_box=(True, True)) rx_i_dc_correction_setting = IntSetting(
self.RX_I_DC_CORRECTION,
0,
"Adjusts the direct current offset errors in the in-phase (I) component of the receive (RX) path.",
min_value=-63,
max_value=63,
spin_box=(True, True),
)
self.add_setting(rx_i_dc_correction_setting, self.CALIBRATION) self.add_setting(rx_i_dc_correction_setting, self.CALIBRATION)
rx_q_dc_correction_setting = IntSetting(self.RX_Q_DC_CORRECTION, 0, "Adjusts the direct current offset errors in the quadrature (Q) component of the receive (RX) path.", min_value=-63, max_value=63, spin_box=(True, True)) rx_q_dc_correction_setting = IntSetting(
self.RX_Q_DC_CORRECTION,
0,
"Adjusts the direct current offset errors in the quadrature (Q) component of the receive (RX) path.",
min_value=-63,
max_value=63,
spin_box=(True, True),
)
self.add_setting(rx_q_dc_correction_setting, self.CALIBRATION) self.add_setting(rx_q_dc_correction_setting, self.CALIBRATION)
rx_i_gain_correction_setting = IntSetting(self.RX_I_GAIN_CORRECTION, 2047, "Modifies the gain settings for the I channel of the RX path, adjusting for imbalances.", min_value=0, max_value=2047, spin_box=(True, True)) rx_i_gain_correction_setting = IntSetting(
self.RX_I_GAIN_CORRECTION,
2047,
"Modifies the gain settings for the I channel of the RX path, adjusting for imbalances.",
min_value=0,
max_value=2047,
spin_box=(True, True),
)
self.add_setting(rx_i_gain_correction_setting, self.CALIBRATION) self.add_setting(rx_i_gain_correction_setting, self.CALIBRATION)
rx_q_gain_correction_setting = IntSetting(self.RX_Q_GAIN_CORRECTION, 2047, "Modifies the gain settings for the Q channel of the RX path, adjusting for imbalances.", min_value=0, max_value=2047, spin_box=(True, True)) rx_q_gain_correction_setting = IntSetting(
self.RX_Q_GAIN_CORRECTION,
2047,
"Modifies the gain settings for the Q channel of the RX path, adjusting for imbalances.",
min_value=0,
max_value=2047,
spin_box=(True, True),
)
self.add_setting(rx_q_gain_correction_setting, self.CALIBRATION) self.add_setting(rx_q_gain_correction_setting, self.CALIBRATION)
rx_phase_adjustment_setting = IntSetting(self.RX_PHASE_ADJUSTMENT, 0, "Corrects the Phase of I Q signals in the RX path.", min_value=-2048, max_value=2047, spin_box=(True, True)) rx_phase_adjustment_setting = IntSetting(
self.RX_PHASE_ADJUSTMENT,
0,
"Corrects the Phase of I Q signals in the RX path.",
min_value=-2048,
max_value=2047,
spin_box=(True, True),
)
self.add_setting(rx_phase_adjustment_setting, self.CALIBRATION) self.add_setting(rx_phase_adjustment_setting, self.CALIBRATION)
# Signal Processing settings # Signal Processing settings
rx_offset_setting = FloatSetting(self.RX_OFFSET, 2.4e-6, "The offset of the RX event, this changes all the time") rx_offset_setting = FloatSetting(
self.RX_OFFSET,
2.4e-6,
"The offset of the RX event, this changes all the time",
)
self.add_setting(rx_offset_setting, self.SIGNAL_PROCESSING) self.add_setting(rx_offset_setting, self.SIGNAL_PROCESSING)
fft_shift_setting = BooleanSetting(self.FFT_SHIFT, False, "FFT shift") fft_shift_setting = BooleanSetting(self.FFT_SHIFT, False, "FFT shift")
@ -147,6 +292,7 @@ class LimeNQRModel(BaseSpectrometerModel):
# Try to load the pulse programmer module # Try to load the pulse programmer module
try: try:
from nqrduck_pulseprogrammer.pulseprogrammer import pulse_programmer from nqrduck_pulseprogrammer.pulseprogrammer import pulse_programmer
self.pulse_programmer = pulse_programmer self.pulse_programmer = pulse_programmer
logger.debug("Pulse programmer found.") logger.debug("Pulse programmer found.")
self.pulse_programmer.controller.on_loading(self.pulse_parameter_options) self.pulse_programmer.controller.on_loading(self.pulse_parameter_options)
@ -157,6 +303,7 @@ class LimeNQRModel(BaseSpectrometerModel):
@property @property
def target_frequency(self): def target_frequency(self):
"""The target frequency of the spectrometer."""
return self._target_frequency return self._target_frequency
@target_frequency.setter @target_frequency.setter
@ -165,6 +312,7 @@ class LimeNQRModel(BaseSpectrometerModel):
@property @property
def averages(self): def averages(self):
"""The number of averages to be taken."""
return self._averages return self._averages
@averages.setter @averages.setter
@ -173,10 +321,9 @@ class LimeNQRModel(BaseSpectrometerModel):
@property @property
def if_frequency(self): def if_frequency(self):
"""The intermediate frequency to which the input signal is down converted during analog-to-digital conversion."""
return self._if_frequency return self._if_frequency
@if_frequency.setter @if_frequency.setter
def if_frequency(self, value): def if_frequency(self, value):
self._if_frequency = value self._if_frequency = value

View file

@ -1,9 +1,13 @@
"""View for LimeNQR spectrometer."""
from nqrduck_spectrometer.base_spectrometer_view import BaseSpectrometerView from nqrduck_spectrometer.base_spectrometer_view import BaseSpectrometerView
class LimeNQRView(BaseSpectrometerView): class LimeNQRView(BaseSpectrometerView):
"""View class for LimeNQR spectrometer."""
def __init__(self, module): def __init__(self, module):
"""Initialize the LimeNQRView object."""
super().__init__(module) super().__init__(module)
# Setting UI is automatically generated based on the settings specified in the model
self.widget = self.load_settings_ui() self.widget = self.load_settings_ui()