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 tempfile
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.pulseparameters import TXPulse, RXReadout
logger = logging.getLogger(__name__)
class LimeNQRController(BaseSpectrometerController):
"""Controller class for the Lime NQR spectrometer."""
def __init__(self, module):
"""Initializes the LimeNQRController."""
super().__init__(module)
def start_measurement(self):
"""Starts the measurement procedure."""
self.log_start_message()
lime = self.initialize_lime()
if lime is None:
# 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
elif lime.Npulses == 0:
# 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
self.setup_lime_parameters(lime)
@ -37,7 +48,9 @@ class LimeNQRController(BaseSpectrometerController):
if not self.perform_measurement(lime):
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
measurement_data = self.process_measurement_results(lime)
@ -48,13 +61,15 @@ class LimeNQRController(BaseSpectrometerController):
else:
self.emit_measurement_error("Measurement failed. Unable to retrieve data.")
def log_start_message(self):
"""This method logs a message when the measurement is started."""
logger.debug("Starting measurement with spectrometer: %s", self.module.model.name)
def log_start_message(self) -> None:
"""Logs a message when the measurement is started."""
logger.debug(
"Starting measurement with spectrometer: %s", self.module.model.name
)
def initialize_lime(self) -> PyLimeConfig:
"""Initializes the limr object that is used to communicate with the pulseN driver.
def initialize_lime(self):
"""This method initializes the limr object that is used to communicate with the pulseN driver.
Returns:
PyLimeConfig: The PyLimeConfig object that is used to communicate with the pulseN driver
"""
@ -67,19 +82,20 @@ class LimeNQRController(BaseSpectrometerController):
except Exception as e:
logger.error("Error while initializing Lime driver: %s", e)
import traceback
traceback.print_exc()
return None
def setup_lime_parameters(self, lime):
"""This method sets the parameters of the lime config according to the settings set in the spectrometer module.
def setup_lime_parameters(self, lime: PyLimeConfig) -> None:
"""Sets the parameters of the lime config according to the settings set in the spectrometer module.
Args:
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.nrp = 1
lime.repetitions = 1
lime = self.update_settings(lime)
@ -87,23 +103,23 @@ class LimeNQRController(BaseSpectrometerController):
lime.averages = self.module.model.averages
self.log_lime_parameters(lime)
def setup_temporary_storage(self, lime):
"""This method sets up the temporary storage for the measurement data.
def setup_temporary_storage(self, lime: PyLimeConfig) -> None:
"""Sets up the temporary storage for the measurement data.
Args:
lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver
"""
temp_dir = tempfile.TemporaryDirectory()
logger.debug("Created temporary directory at: %s", temp_dir.name)
lime.save_path = str(Path(temp_dir.name)) + "/" # Temporary storage path
lime.file_pattern = "temp" # Temporary filename prefix or related config
def perform_measurement(self, lime):
"""This method executes the measurement procedure.
lime.save_path = str(Path(temp_dir.name)) + "/" # Temporary storage path
lime.file_pattern = "temp" # Temporary filename prefix or related config
def perform_measurement(self, lime: PyLimeConfig) -> bool:
"""Executes the measurement procedure.
Args:
lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver
Returns:
bool: True if the measurement was successful, False otherwise
"""
@ -115,9 +131,9 @@ class LimeNQRController(BaseSpectrometerController):
logger.error("Failed to execute the measurement: %s", e)
return False
def process_measurement_results(self, lime):
"""This method processes the measurement results and returns a Measurement object.
def process_measurement_results(self, lime: PyLimeConfig) -> Measurement:
"""Processes the measurement results and returns a Measurement object.
Args:
lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver
@ -133,9 +149,11 @@ class LimeNQRController(BaseSpectrometerController):
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)
def calculate_measurement_data(self, lime, rx_begin, rx_stop):
"""This method calculates the measurement data from the limr object.
def calculate_measurement_data(
self, lime: PyLimeConfig, rx_begin: float, rx_stop: float
) -> Measurement:
"""Calculates the measurement data from the limr object.
Args:
lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver
rx_begin (float): The start time of the RX event in µs
@ -146,34 +164,44 @@ class LimeNQRController(BaseSpectrometerController):
"""
try:
path = lime.get_path()
hdf = HDF(path)
hdf = HDF(path)
evidx = self.find_evaluation_range_indices(hdf, rx_begin, rx_stop)
tdx, tdy = self.extract_measurement_data(lime, hdf, evidx)
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:
logger.error("Error processing measurement result: %s", e)
return None
def find_evaluation_range_indices(self, hdf, rx_begin, rx_stop):
"""This method finds the indices of the evaluation range in the measurement data.
def find_evaluation_range_indices(
self, hdf: HDF, rx_begin: float, rx_stop: float
) -> list:
"""Finds the indices of the evaluation range in the measurement data.
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_stop (float): The stop time of the RX event in µs
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]
def extract_measurement_data(self, lime, hdf, indices):
"""This method extracts the measurement data from the limr object.
def extract_measurement_data(
self, lime: PyLimeConfig, hdf: HDF, indices: list
) -> tuple:
"""Extracts the measurement data from the PyLimeConfig object.
Args:
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
Returns:
@ -185,59 +213,61 @@ class LimeNQRController(BaseSpectrometerController):
tdy = tdy.flatten()
return tdx, tdy
def get_fft_shift(self):
"""This method returns the FFT shift value from the settings.
def get_fft_shift(self) -> int:
"""Rreturns the FFT shift value from the settings.
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
def emit_measurement_data(self, measurement_data):
"""This method emits the measurement data to the GUI.
def emit_measurement_data(self, measurement_data: Measurement) -> None:
"""Emits the measurement data to the GUI.
Args:
measurement_data (Measurement): The measurement data
"""
logger.debug("Emitting measurement data")
self.module.nqrduck_signal.emit("measurement_data", measurement_data)
def emit_status_message(self, message):
"""This method emits a status message to the GUI.
def emit_status_message(self, message: str) -> None:
"""Emits a status message to the GUI.
Args:
message (str): The status message
"""
self.module.nqrduck_signal.emit("statusbar_message", message)
def emit_measurement_error(self, error_message):
"""This method emits a measurement error to the GUI.
def emit_measurement_error(self, error_message: str) -> None:
"""Emits a measurement error to the GUI.
Args:
error_message (str): The error message
"""
logger.error(error_message)
self.module.nqrduck_signal.emit("measurement_error", error_message)
def log_lime_parameters(self, lime):
"""This method logs the parameters of the limr object.
def log_lime_parameters(self, lime: PyLimeConfig) -> None:
"""Logs the parameters of the limr object.
Args:
lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver
"""
# 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)
def update_settings(self, lime):
"""This method sets the parameters of the limr object according to the settings set in the spectrometer module.
def update_settings(self, lime: PyLimeConfig) -> PyLimeConfig:
"""Sets the parameters of the limr object according to the settings set in the spectrometer module.
Args:
lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver
Returns:
lime: The updated limr object
lime: The updated limr object
"""
logger.debug(
"Updating settings for spectrometer: %s for measurement",
@ -259,7 +289,9 @@ class LimeNQRController(BaseSpectrometerController):
lime.RX_matching = setting.get_setting()
# Careful this doesn't only set the IF frequency but the local oscillator 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()
elif setting.name == self.module.model.ACQUISITION_TIME:
lime.rectime_secs = setting.get_setting()
@ -309,9 +341,9 @@ class LimeNQRController(BaseSpectrometerController):
lime.c3_tim = c3_tim
return lime
def translate_pulse_sequence(self, lime):
"""This method translates the pulse sequence to the limr object.
def translate_pulse_sequence(self, lime: PyLimeConfig) -> PyLimeConfig:
"""Ttranslates the pulse sequence to the limr object.
Args:
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)
if self.is_translatable_tx_parameter(parameter):
pulse_shape, pulse_amplitude = self.prepare_pulse_amplitude(event, parameter)
pulse_amplitude, modulated_phase = self.modulate_pulse_amplitude(pulse_amplitude, event, lime)
pulse_shape, pulse_amplitude = self.prepare_pulse_amplitude(
event, parameter
)
pulse_amplitude, modulated_phase = self.modulate_pulse_amplitude(
pulse_amplitude, event, lime
)
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
else:
pfr_ext, pdr_ext, pam_ext, pph_ext = self.extend_pulse_lists(lime, pulse_amplitude, pulse_shape, modulated_phase)
pof_ext = self.calculate_and_set_offsets(lime, pulse_shape, events, event, pulse_amplitude)
pfr_ext, pdr_ext, pam_ext, pph_ext = self.extend_pulse_lists(
pulse_amplitude, pulse_shape, modulated_phase
)
pof_ext = self.calculate_and_set_offsets(
lime, pulse_shape, events, event, pulse_amplitude
)
pfr.extend(pfr_ext)
pdr.extend(pdr_ext)
@ -350,11 +392,12 @@ class LimeNQRController(BaseSpectrometerController):
lime.reptime_secs = float(event.duration)
lime.Npulses = len(lime.p_frq)
return lime
def get_number_of_pulses(self):
"""This method calculates the number of pulses in the pulse sequence before the LimeDriverBinding is initialized.
def get_number_of_pulses(self) -> int:
"""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.
Returns:
int: The number of pulses in the pulse sequence
"""
@ -371,53 +414,60 @@ class LimeNQRController(BaseSpectrometerController):
# Helper functions below:
def fetch_pulse_sequence_events(self):
"""This method fetches the pulse sequence events from the pulse programmer module.
def fetch_pulse_sequence_events(self) -> list:
"""Fetches the pulse sequence events from the pulse programmer module.
Returns:
list: The 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)
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)
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:
parameter (Parameter): The parameter to check
parameter (Parameter): The parameter to check
"""
return (parameter.name == self.module.model.TX and
parameter.get_option_by_name(TXPulse.RELATIVE_AMPLITUDE).value > 0)
return (
parameter.name == self.module.model.TX
and parameter.get_option_by_name(TXPulse.RELATIVE_AMPLITUDE).value > 0
)
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:
event (Event): The event that contains the parameter
parameter (Parameter): The parameter that contains the pulse shape and amplitude
Returns:
tuple: A tuple containing the pulse shape and the pulse amplitude
"""
pulse_shape = parameter.get_option_by_name(TXPulse.TX_PULSE_SHAPE).value
pulse_amplitude = abs(pulse_shape.get_pulse_amplitude(event.duration)) * \
(parameter.get_option_by_name(TXPulse.RELATIVE_AMPLITUDE).value / 100)
pulse_amplitude = abs(pulse_shape.get_pulse_amplitude(event.duration)) * (
parameter.get_option_by_name(TXPulse.RELATIVE_AMPLITUDE).value / 100
)
pulse_amplitude = np.clip(pulse_amplitude, -0.99, 0.99)
return pulse_shape, pulse_amplitude
def modulate_pulse_amplitude(self, pulse_amplitude, event, lime):
"""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.
def modulate_pulse_amplitude(
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:
pulse_amplitude (float): The pulse amplitude
event (Event): The event that contains the parameter
lime (PyLimeConfig) : The PyLimeConfig object that is used to communicate with the pulseN driver
Returns:
tuple: A tuple containing the modulated pulse amplitude and the modulated phase
"""
@ -430,58 +480,72 @@ class LimeNQRController(BaseSpectrometerController):
modulated_phase = self.unwrap_phase(np.angle(pulse_complex))
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.
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)
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.
Args:
lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver
pulse_amplitude (float): The pulse amplitude
pulse_shape (PulseShape): The pulse shape
modulated_phase (float): The modulated phase
pulse_amplitude (np.array): The pulse amplitude
pulse_shape (Function): The pulse shape
modulated_phase (np.array): The modulated phase
"""
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)
pam = list(pulse_amplitude)
pof = ([self.module.model.OFFSET_FIRST_PULSE] +
[int(pulse_shape.resolution * lime.srate)] * (len(pulse_amplitude) - 1))
pof = [self.module.model.OFFSET_FIRST_PULSE] + [
int(pulse_shape.resolution * lime.srate)
] * (len(pulse_amplitude) - 1)
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.
Args:
lime (PyLimeConfig): The PyLimeConfig object that is used to communicate with the pulseN driver
pulse_amplitude (float): The pulse amplitude
pulse_shape (PulseShape): The pulse shape
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))
pdr = ([float(pulse_shape.resolution)] * len(pulse_amplitude))
pam = (list(pulse_amplitude))
pph = (list(modulated_phase))
pfr = [float(self.module.model.if_frequency)] * len(pulse_amplitude)
pdr = [float(pulse_shape.resolution)] * len(pulse_amplitude)
pam = list(pulse_amplitude)
pph = list(modulated_phase)
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.
Args:
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
current_event (Event): The current event
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)
@ -500,29 +564,33 @@ class LimeNQRController(BaseSpectrometerController):
pof.extend([offset_per_sample] * (len(pulse_amplitude) - 1))
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.
Args:
events (list): The pulse sequence events
current_event (Event): The current event
Returns:
list: The blank durations before the current event
"""
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:
blank_durations.append(float(event.duration))
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.
Args:
events (list): The pulse sequence events
current_event (Event): The current event
Returns:
list: The previous events without a transmit pulse
"""
@ -530,15 +598,19 @@ class LimeNQRController(BaseSpectrometerController):
previous_events = events[:index]
result = []
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:
result.append(event)
else:
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):
def translate_rx_event(self, lime : PyLimeConfig) -> tuple:
"""This method translates the RX event of the pulse sequence to the limr object.
Args:
@ -547,28 +619,34 @@ class LimeNQRController(BaseSpectrometerController):
Returns:
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
rx_event = self.find_rx_event(events)
if not rx_event:
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)
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
return rx_begin * 1e6, rx_stop * 1e6
def find_rx_event(self, events):
"""This method finds the RX event in the pulse sequence.
Args:
events (list): The pulse sequence events
Returns:
Event: The RX event
"""
@ -582,30 +660,29 @@ class LimeNQRController(BaseSpectrometerController):
def calculate_previous_events_duration(self, events, rx_event):
"""This method calculates the duration of the previous events.
Args:
events (list): The pulse sequence events
rx_event (Event): The RX event
Returns:
float: The duration of the previous events
"""
previous_events = events[: events.index(rx_event)]
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.
Args:
lime (limr): The limr object that is used to communicate with the pulseN driver
lime (limr): The limr object that is used to communicate with the pulseN driver
Returns:
float: The offset for the RX event
"""
return self.module.model.OFFSET_FIRST_PULSE * (1 / lime.srate)
def set_frequency(self, value: float):
def set_frequency(self, value: float) -> None:
"""This method sets the target frequency of the spectrometer.
Args:
@ -622,7 +699,7 @@ class LimeNQRController(BaseSpectrometerController):
)
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.
Args:

View file

@ -1,6 +1,8 @@
"""BaseSpectrometer for Lime NQR spectrometer."""
from nqrduck_spectrometer.base_spectrometer import BaseSpectrometer
from .model import LimeNQRModel
from .view import LimeNQRView
from .controller import LimeNQRController
LimeNQR = BaseSpectrometer(LimeNQRModel, LimeNQRView, LimeNQRController)
LimeNQR = BaseSpectrometer(LimeNQRModel, LimeNQRView, LimeNQRController)

View file

@ -1,12 +1,20 @@
"""Model for the Lime NQR spectrometer."""
import logging
from nqrduck_spectrometer.base_spectrometer_model import BaseSpectrometerModel
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__)
class LimeNQRModel(BaseSpectrometerModel):
class LimeNQRModel(BaseSpectrometerModel):
"""Model for the Lime NQR spectrometer."""
# Setting constants for the names of the spectrometer settings
CHANNEL = "TX/RX Channel"
TX_MATCHING = "TX Matching"
@ -48,92 +56,229 @@ class LimeNQRModel(BaseSpectrometerModel):
# Settings that are not changed by the user
OFFSET_FIRST_PULSE = 300
def __init__(self, module) -> None:
"""Initializes the Lime NQR model."""
super().__init__(module)
# Acquisition settings
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)
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)
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)
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)
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.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)
# 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)
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)
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)
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)
# 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)
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)
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)
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)
# 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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
# 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)
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:
from nqrduck_pulseprogrammer.pulseprogrammer import pulse_programmer
self.pulse_programmer = pulse_programmer
logger.debug("Pulse programmer found.")
self.pulse_programmer.controller.on_loading(self.pulse_parameter_options)
@ -157,26 +303,27 @@ class LimeNQRModel(BaseSpectrometerModel):
@property
def target_frequency(self):
"""The target frequency of the spectrometer."""
return self._target_frequency
@target_frequency.setter
def target_frequency(self, value):
self._target_frequency = value
@property
def averages(self):
"""The number of averages to be taken."""
return self._averages
@averages.setter
def averages(self, value):
self._averages = value
@property
def if_frequency(self):
"""The intermediate frequency to which the input signal is down converted during analog-to-digital conversion."""
return self._if_frequency
@if_frequency.setter
def if_frequency(self, value):
self._if_frequency = value

View file

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