This commit is contained in:
jupfi 2024-04-29 19:31:41 +02:00
parent 8ff8d85547
commit 8b8093b2d9
5 changed files with 332 additions and 119 deletions

View file

@ -1 +1,2 @@
"""Init file for the nqrduck_broadband package."""
from .broadband import Broadband as Module from .broadband import Broadband as Module

View file

@ -1,3 +1,4 @@
"""Module creation for the Broadband module."""
from nqrduck.module.module import Module from nqrduck.module.module import Module
from .model import BroadbandModel from .model import BroadbandModel
from .view import BroadbandView from .view import BroadbandView

View file

@ -1,3 +1,5 @@
"""This module contains the BroadbandController class."""
import logging import logging
import numpy as np import numpy as np
import json import json
@ -10,11 +12,20 @@ logger = logging.getLogger(__name__)
class BroadbandController(ModuleController): class BroadbandController(ModuleController):
"""Controller class for the Broadband module.
Signals:
start_broadband_measurement: Signal to start a broadband measurement.
set_averages_failure: Signal that the set averages command failed.
set_frequency_step_failure: Signal that the set frequency step command failed.
"""
start_broadband_measurement = pyqtSignal() start_broadband_measurement = pyqtSignal()
set_averages_failure = pyqtSignal() set_averages_failure = pyqtSignal()
set_frequency_step_failure = pyqtSignal() set_frequency_step_failure = pyqtSignal()
def __init__(self, module): def __init__(self, module):
"""Initializes the BroadbandController."""
super().__init__(module) super().__init__(module)
@pyqtSlot(str, object) @pyqtSlot(str, object)
@ -27,7 +38,10 @@ class BroadbandController(ModuleController):
""" """
logger.debug(self.module.model.waiting_for_tune_and_match) logger.debug(self.module.model.waiting_for_tune_and_match)
if key == "measurement_data" and self.module.model.current_broadband_measurement is not None: if (
key == "measurement_data"
and self.module.model.current_broadband_measurement is not None
):
logger.debug("Received single measurement.") logger.debug("Received single measurement.")
self.module.model.current_broadband_measurement.add_measurement(value) self.module.model.current_broadband_measurement.add_measurement(value)
@ -38,22 +52,27 @@ class BroadbandController(ModuleController):
logger.debug("Received set averages failure.") logger.debug("Received set averages failure.")
self.set_averages_failure.emit() self.set_averages_failure.emit()
# receive LUT data # receive LUT data
elif key == "LUT_finished": elif key == "LUT_finished":
self.received_LUT(value) self.received_LUT(value)
elif key == "confirm_tune_and_match" and self.module.model.waiting_for_tune_and_match: elif (
key == "confirm_tune_and_match"
and self.module.model.waiting_for_tune_and_match
):
logger.debug("Confirmed tune and match.") logger.debug("Confirmed tune and match.")
reflection = value reflection = value
logger.debug("Reflection: " + str(reflection)) logger.debug("Reflection: " + str(reflection))
if reflection is not None: if reflection is not None:
self.module.model.current_broadband_measurement.add_tune_and_match(reflection) self.module.model.current_broadband_measurement.add_tune_and_match(
reflection
)
self.module.nqrduck_signal.emit("start_measurement", None) self.module.nqrduck_signal.emit("start_measurement", None)
QApplication.processEvents() QApplication.processEvents()
self.module.model.waiting_for_tune_and_match = False self.module.model.waiting_for_tune_and_match = False
def received_LUT(self, LUT : Measurement) -> None: def received_LUT(self, LUT: Measurement) -> None:
"""This slot is called when the LUT data is received from the nqrduck module. """This slot is called when the LUT data is received from the nqrduck module.
Args: Args:
LUT (Measurement): LUT data. LUT (Measurement): LUT data.
""" """
@ -62,7 +81,7 @@ class BroadbandController(ModuleController):
self.change_start_frequency(self.module.model.LUT.start_frequency) self.change_start_frequency(self.module.model.LUT.start_frequency)
self.change_stop_frequency(self.module.model.LUT.stop_frequency) self.change_stop_frequency(self.module.model.LUT.stop_frequency)
self.change_frequency_step(self.module.model.LUT.frequency_step) self.change_frequency_step(self.module.model.LUT.frequency_step)
@pyqtSlot(str) @pyqtSlot(str)
def set_frequency(self, value: str) -> None: def set_frequency(self, value: str) -> None:
"""Emits the set frequency signal to the nqrduck module. """Emits the set frequency signal to the nqrduck module.
@ -150,6 +169,7 @@ class BroadbandController(ModuleController):
@pyqtSlot() @pyqtSlot()
def on_broadband_measurement_added(self) -> None: def on_broadband_measurement_added(self) -> None:
"""This slot is called when a single measurement is added to the broadband measurement. """This slot is called when a single measurement is added to the broadband measurement.
It then checks if there are more frequencies to measure and if so, starts the next measurement. It then checks if there are more frequencies to measure and if so, starts the next measurement.
Furthermore it updates the plots. Furthermore it updates the plots.
""" """
@ -157,11 +177,11 @@ class BroadbandController(ModuleController):
# Check if there are more frequencies to measure # Check if there are more frequencies to measure
if not self.module.model.current_broadband_measurement.is_complete(): if not self.module.model.current_broadband_measurement.is_complete():
# Get the next frequency to measure # Get the next frequency to measure
next_frequency = ( next_frequency = self.module.model.current_broadband_measurement.get_next_measurement_frequency()
self.module.model.current_broadband_measurement.get_next_measurement_frequency()
)
logger.debug("Next frequency: " + str(next_frequency)) logger.debug("Next frequency: " + str(next_frequency))
QTimer.singleShot(500, lambda: self.start_single_measurement(next_frequency)) QTimer.singleShot(
500, lambda: self.start_single_measurement(next_frequency)
)
QApplication.processEvents() QApplication.processEvents()
else: else:
self.module.view.add_info_text("Broadband measurement finished.") self.module.view.add_info_text("Broadband measurement finished.")
@ -171,14 +191,16 @@ class BroadbandController(ModuleController):
"""This slot is called when the LUT is deleted.""" """This slot is called when the LUT is deleted."""
self.module.model.LUT = None self.module.model.LUT = None
def start_single_measurement(self, frequency : float) -> None: def start_single_measurement(self, frequency: float) -> None:
"""Starts a single measurement. """Starts a single measurement.
Args: Args:
frequency (float): Frequency in MHz. frequency (float): Frequency in MHz.
""" """
logger.debug("Starting single measurement.") logger.debug("Starting single measurement.")
self.module.view.add_info_text("Starting measurement at frequency: " + str(frequency)) self.module.view.add_info_text(
"Starting measurement at frequency: " + str(frequency)
)
# First set the frequency of the spectrometer # First set the frequency of the spectrometer
self.module.nqrduck_signal.emit("set_frequency", str(frequency)) self.module.nqrduck_signal.emit("set_frequency", str(frequency))
QApplication.processEvents() QApplication.processEvents()
@ -186,7 +208,7 @@ class BroadbandController(ModuleController):
if self.module.model.LUT is not None: if self.module.model.LUT is not None:
self.module.model.waiting_for_tune_and_match = True self.module.model.waiting_for_tune_and_match = True
# We need the entry number of the LUT for the current frequency # We need the entry number of the LUT for the current frequency
self.module.nqrduck_signal.emit("set_tune_and_match", frequency * 1e-6) self.module.nqrduck_signal.emit("set_tune_and_match", frequency * 1e-6)
QApplication.processEvents() QApplication.processEvents()
else: else:
@ -194,31 +216,32 @@ class BroadbandController(ModuleController):
self.module.model.waiting_for_tune_and_match = False self.module.model.waiting_for_tune_and_match = False
QApplication.processEvents() QApplication.processEvents()
def save_measurement(self, file_name : str) -> None: def save_measurement(self, file_name: str) -> None:
"""Saves the current broadband measurement to a file. """Saves the current broadband measurement to a file.
Args: Args:
file_name (str): Name of the file. file_name (str): Name of the file.
""" """
logger.debug("Saving measurement to file: " + file_name) logger.debug("Saving measurement to file: " + file_name)
self.module.view.add_info_text("Saving measurement to file: " + file_name) self.module.view.add_info_text("Saving measurement to file: " + file_name)
QApplication.processEvents() QApplication.processEvents()
with open(file_name, "w") as f: with open(file_name, "w") as f:
json.dump(self.module.model.current_broadband_measurement.to_json(), f) json.dump(self.module.model.current_broadband_measurement.to_json(), f)
def load_measurement(self, file_name: str) -> None:
def load_measurement(self, file_name : str) -> None:
"""Loads a broadband measurement from a file. """Loads a broadband measurement from a file.
Args: Args:
file_name (str): Name of the file. file_name (str): Name of the file.
""" """
logger.debug("Loading measurement from file: " + file_name) logger.debug("Loading measurement from file: " + file_name)
with open(file_name, "r") as f: with open(file_name) as f:
measurement = json.load(f) measurement = json.load(f)
self.module.model.current_broadband_measurement = self.module.model.BroadbandMeasurement.from_json(measurement) self.module.model.current_broadband_measurement = (
self.module.model.BroadbandMeasurement.from_json(measurement)
)
self.module.view.add_info_text("Measurement loaded.") self.module.view.add_info_text("Measurement loaded.")
self.module.view.on_broadband_measurement_added() self.module.view.on_broadband_measurement_added()
QApplication.processEvents() QApplication.processEvents()

View file

@ -1,3 +1,5 @@
"""This module contains the BroadbandModel class which is the model for the Broadband module."""
import logging import logging
import numpy as np import numpy as np
from collections import OrderedDict from collections import OrderedDict
@ -8,7 +10,23 @@ from nqrduck_spectrometer.measurement import Measurement
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class BroadbandModel(ModuleModel): class BroadbandModel(ModuleModel):
"""Model class for the Broadband module.
Attributes:
FILE_EXTENSION (str): The file extension for the broadband module.
MIN_FREQUENCY (float): The minimum frequency for the broadband module.
MAX_FREQUENCY (float): The maximum frequency for the broadband module.
DEFAULT_FREQUENCY_STEP (float): The default frequency step for the broadband module.
Signals:
start_frequency_changed: Signal that the start frequency has changed.
stop_frequency_changed: Signal that the stop frequency has changed.
frequency_step_changed: Signal that the frequency step has changed.
LUT_changed: Signal that the LUT has changed.
"""
FILE_EXTENSION = "broad" FILE_EXTENSION = "broad"
MIN_FREQUENCY = 30.0 MIN_FREQUENCY = 30.0
@ -21,6 +39,7 @@ class BroadbandModel(ModuleModel):
LUT_changed = pyqtSignal() LUT_changed = pyqtSignal()
def __init__(self, module) -> None: def __init__(self, module) -> None:
"""Initializes the BroadbandModel."""
super().__init__(module) super().__init__(module)
self.start_frequency = self.MIN_FREQUENCY self.start_frequency = self.MIN_FREQUENCY
self.stop_frequency = self.MAX_FREQUENCY self.stop_frequency = self.MAX_FREQUENCY
@ -31,6 +50,7 @@ class BroadbandModel(ModuleModel):
@property @property
def start_frequency(self): def start_frequency(self):
"""The start frequency for the broadband measurement."""
return self._start_frequency return self._start_frequency
@start_frequency.setter @start_frequency.setter
@ -40,6 +60,7 @@ class BroadbandModel(ModuleModel):
@property @property
def stop_frequency(self): def stop_frequency(self):
"""The stop frequency for the broadband measurement."""
return self._stop_frequency return self._stop_frequency
@stop_frequency.setter @stop_frequency.setter
@ -49,8 +70,9 @@ class BroadbandModel(ModuleModel):
@property @property
def frequency_step(self): def frequency_step(self):
"""The frequency step for the broadband measurement."""
return self._frequency_step return self._frequency_step
@frequency_step.setter @frequency_step.setter
def frequency_step(self, value): def frequency_step(self, value):
self._frequency_step = value self._frequency_step = value
@ -58,27 +80,34 @@ class BroadbandModel(ModuleModel):
@property @property
def current_broadband_measurement(self): def current_broadband_measurement(self):
"""The current broadband measurement."""
return self._current_broadband_measurement return self._current_broadband_measurement
@current_broadband_measurement.setter @current_broadband_measurement.setter
def current_broadband_measurement(self, value): def current_broadband_measurement(self, value):
self._current_broadband_measurement = value self._current_broadband_measurement = value
@property @property
def LUT(self): def LUT(self):
"""The LUT for the broadband measurement."""
return self._LUT return self._LUT
@LUT.setter @LUT.setter
def LUT(self, value): def LUT(self, value):
self._LUT = value self._LUT = value
self.LUT_changed.emit() self.LUT_changed.emit()
class BroadbandMeasurement(QObject): class BroadbandMeasurement(QObject):
"""This class represents a single broadband measurement.""" """This class represents a single broadband measurement.
Signals:
received_measurement: Signal that a measurement has been received.
"""
received_measurement = pyqtSignal() received_measurement = pyqtSignal()
def __init__(self, frequencies, frequency_step) -> None: def __init__(self, frequencies, frequency_step) -> None:
"""Initializes the BroadbandMeasurement."""
super().__init__() super().__init__()
self._single_frequency_measurements = OrderedDict() self._single_frequency_measurements = OrderedDict()
for frequency in frequencies: for frequency in frequencies:
@ -87,58 +116,69 @@ class BroadbandModel(ModuleModel):
self.frequency_step = frequency_step self.frequency_step = frequency_step
self.reflection = {} self.reflection = {}
def add_measurement(self, measurement : "Measurement") -> None: def add_measurement(self, measurement: "Measurement") -> None:
"""This method adds a single measurement to the broadband measurement. """This method adds a single measurement to the broadband measurement.
Args: Args:
measurement (Measurement): The measurement object.""" measurement (Measurement): The measurement object.
logger.debug("Adding measurement to broadband measurement at frequency: %s" % str(measurement.target_frequency)) """
self._single_frequency_measurements[measurement.target_frequency] = measurement logger.debug(
f"Adding measurement to broadband measurement at frequency: {str(measurement.target_frequency)}"
)
self._single_frequency_measurements[measurement.target_frequency] = (
measurement
)
self.assemble_broadband_spectrum() self.assemble_broadband_spectrum()
self.received_measurement.emit() self.received_measurement.emit()
QApplication.processEvents() QApplication.processEvents()
def is_complete(self) -> bool: def is_complete(self) -> bool:
"""This method checks if all frequencies have been measured. """This method checks if all frequencies have been measured.
Returns: Returns:
bool: True if all frequencies have been measured, False otherwise.""" bool: True if all frequencies have been measured, False otherwise.
"""
for measurement in self._single_frequency_measurements.values(): for measurement in self._single_frequency_measurements.values():
if measurement is None: if measurement is None:
return False return False
return True return True
def get_next_measurement_frequency(self) -> float: def get_next_measurement_frequency(self) -> float:
"""This method returns the next frequency that has to be measured. """This method returns the next frequency that has to be measured.
Returns: Returns:
float: The next frequency that has to be measured.""" float: The next frequency that has to be measured.
"""
for frequency, measurement in self._single_frequency_measurements.items(): for frequency, measurement in self._single_frequency_measurements.items():
if measurement is None: if measurement is None:
return frequency return frequency
def get_last_completed_measurement(self) -> "Measurement": def get_last_completed_measurement(self) -> "Measurement":
""" This method returns the last completed measurement. """This method returns the last completed measurement.
Returns: Returns:
Measurement: The last completed measurement.""" Measurement: The last completed measurement.
"""
for frequency, measurement in reversed(self._single_frequency_measurements.items()): for frequency, measurement in reversed(
self._single_frequency_measurements.items()
):
if measurement is not None: if measurement is not None:
return measurement return measurement
def get_finished_percentage(self) -> float: def get_finished_percentage(self) -> float:
"""Get the percentage of measurements that have been finished. """Get the percentage of measurements that have been finished.
Returns: Returns:
float: The percentage of measurements that have been finished.""" float: The percentage of measurements that have been finished.
"""
finished_measurements = 0 finished_measurements = 0
for measurement in self._single_frequency_measurements.values(): for measurement in self._single_frequency_measurements.values():
if measurement is not None: if measurement is not None:
finished_measurements += 1 finished_measurements += 1
return finished_measurements / len(self._single_frequency_measurements) * 100 return (
finished_measurements / len(self._single_frequency_measurements) * 100
)
def assemble_broadband_spectrum(self) -> None: def assemble_broadband_spectrum(self) -> None:
"""This method assembles the broadband spectrum from the single frequency measurement data in frequency domain.""" """This method assembles the broadband spectrum from the single frequency measurement data in frequency domain."""
# First we get all of the single frequency measurements that have already been measured # First we get all of the single frequency measurements that have already been measured
@ -147,89 +187,152 @@ class BroadbandModel(ModuleModel):
if measurement is not None: if measurement is not None:
single_frequency_measurements.append(measurement) single_frequency_measurements.append(measurement)
logger.debug("Assembling broadband spectrum from %d single frequency measurements." % len(single_frequency_measurements)) logger.debug(
"Assembling broadband spectrum from %d single frequency measurements."
% len(single_frequency_measurements)
)
fdy_assembled = np.array([]) fdy_assembled = np.array([])
fdx_assembled = np.array([]) fdx_assembled = np.array([])
# We cut out step_size / 2 around the IF of the spectrum and assemble the broadband spectrum # We cut out step_size / 2 around the IF of the spectrum and assemble the broadband spectrum
for measurement in single_frequency_measurements: for measurement in single_frequency_measurements:
# This finds the center of the spectrum if the IF is not 0 it will cut out step_size / 2 around the IF # This finds the center of the spectrum if the IF is not 0 it will cut out step_size / 2 around the IF
logger.debug("IF frequency: %f" % measurement.IF_frequency) logger.debug(f"IF frequency: {measurement.IF_frequency:f}")
logger.debug(measurement.fdx) logger.debug(measurement.fdx)
offset = measurement.IF_frequency * 1e-6 offset = measurement.IF_frequency * 1e-6
logger.debug("Offset: %f" % offset) logger.debug(f"Offset: {offset:f}")
#center = np.where(measurement.fdx == offset)[0][0] # center = np.where(measurement.fdx == offset)[0][0]
# Find closest to offset # Find closest to offset
center = self.find_nearest(measurement.fdx, offset) center = self.find_nearest(measurement.fdx, offset)
logger.debug("Center: %d" % center) logger.debug("Center: %d" % center)
# This finds the nearest index of the lower and upper frequency step # This finds the nearest index of the lower and upper frequency step
logger.debug("Frequency step: %f" % self.frequency_step) logger.debug(f"Frequency step: {self.frequency_step:f}")
logger.debug(measurement.fdx) logger.debug(measurement.fdx)
idx_xf_lower = self.find_nearest(measurement.fdx, offset - ((self.frequency_step/2) * 1e-6)) idx_xf_lower = self.find_nearest(
idx_xf_upper = self.find_nearest(measurement.fdx, offset + ((self.frequency_step/2) * 1e-6)) measurement.fdx, offset - ((self.frequency_step / 2) * 1e-6)
)
idx_xf_upper = self.find_nearest(
measurement.fdx, offset + ((self.frequency_step / 2) * 1e-6)
)
# This interpolates the y values of the lower and upper frequency step # This interpolates the y values of the lower and upper frequency step
yf_interp_lower = np.interp(offset-self.frequency_step/2 * 1e-6, [measurement.fdx[idx_xf_lower], measurement.fdx[center]], yf_interp_lower = np.interp(
[abs(measurement.fdy)[idx_xf_lower], abs(measurement.fdy)[center]]) offset - self.frequency_step / 2 * 1e-6,
[measurement.fdx[idx_xf_lower], measurement.fdx[center]],
yf_interp_upper = np.interp(offset+self.frequency_step/2 * 1e-6, [measurement.fdx[center], measurement.fdx[idx_xf_upper]], [abs(measurement.fdy)[idx_xf_lower], abs(measurement.fdy)[center]],
[abs(measurement.fdy)[center], abs(measurement.fdy)[idx_xf_lower]]) )
yf_interp_upper = np.interp(
offset + self.frequency_step / 2 * 1e-6,
[measurement.fdx[center], measurement.fdx[idx_xf_upper]],
[abs(measurement.fdy)[center], abs(measurement.fdy)[idx_xf_lower]],
)
try: try:
# We take the last point of the previous spectrum and the first point of the current spectrum and average them # We take the last point of the previous spectrum and the first point of the current spectrum and average them
fdy_assembled[-1] = (fdy_assembled[-1] + yf_interp_lower) / 2 fdy_assembled[-1] = (fdy_assembled[-1] + yf_interp_lower) / 2
# Then we append the data from idx_xf_lower + 1 (because of the averaged datapoint) to idx_xf_upper # Then we append the data from idx_xf_lower + 1 (because of the averaged datapoint) to idx_xf_upper
fdy_assembled = np.append(fdy_assembled, abs(measurement.fdy)[idx_xf_lower+1:idx_xf_upper-1]) fdy_assembled = np.append(
fdy_assembled,
abs(measurement.fdy)[idx_xf_lower + 1 : idx_xf_upper - 1],
)
fdy_assembled = np.append(fdy_assembled, yf_interp_upper) fdy_assembled = np.append(fdy_assembled, yf_interp_upper)
# We append the frequency values of the current spectrum and shift them by the target frequency # We append the frequency values of the current spectrum and shift them by the target frequency
fdx_assembled = np.append(fdx_assembled, -self.frequency_step/2 * 1e-6 + measurement.target_frequency * 1e-6) fdx_assembled = np.append(
fdx_assembled = np.append(fdx_assembled, measurement.fdx[idx_xf_lower + 1:idx_xf_upper - 1] + measurement.target_frequency * 1e-6 - offset) fdx_assembled,
-self.frequency_step / 2 * 1e-6
+ measurement.target_frequency * 1e-6,
)
fdx_assembled = np.append(
fdx_assembled,
measurement.fdx[idx_xf_lower + 1 : idx_xf_upper - 1]
+ measurement.target_frequency * 1e-6
- offset,
)
# On the first run we will get an Index Error # On the first run we will get an Index Error
except IndexError: except IndexError:
fdy_assembled = np.array([yf_interp_lower]) fdy_assembled = np.array([yf_interp_lower])
fdy_assembled = np.append(fdy_assembled, abs(measurement.fdy)[idx_xf_lower+1:idx_xf_upper-1]) fdy_assembled = np.append(
fdy_assembled,
abs(measurement.fdy)[idx_xf_lower + 1 : idx_xf_upper - 1],
)
fdy_assembled = np.append(fdy_assembled, yf_interp_upper) fdy_assembled = np.append(fdy_assembled, yf_interp_upper)
first_time_values = (measurement.fdx[idx_xf_lower:idx_xf_upper] + measurement.target_frequency * 1e-6 - offset) first_time_values = (
first_time_values[0] = -self.frequency_step/2 * 1e-6 + measurement.target_frequency * 1e-6 measurement.fdx[idx_xf_lower:idx_xf_upper]
first_time_values[-1] = +self.frequency_step/2 * 1e-6 + measurement.target_frequency * 1e-6 + measurement.target_frequency * 1e-6
- offset
)
first_time_values[0] = (
-self.frequency_step / 2 * 1e-6
+ measurement.target_frequency * 1e-6
)
first_time_values[-1] = (
+self.frequency_step / 2 * 1e-6
+ measurement.target_frequency * 1e-6
)
fdx_assembled = np.array(first_time_values) fdx_assembled = np.array(first_time_values)
self.broadband_data_fdx = fdx_assembled.flatten() self.broadband_data_fdx = fdx_assembled.flatten()
self.broadband_data_fdy = fdy_assembled.flatten() self.broadband_data_fdy = fdy_assembled.flatten()
def add_tune_and_match(self, magnitude) -> None: def add_tune_and_match(self, magnitude) -> None:
"""This method adds the tune and match values to the last completed measurement. """This method adds the tune and match values to the last completed measurement.
Args: Args:
magnitude (float): The magnitude of the tune and match values.""" magnitude (float): The magnitude of the tune and match values.
"""
logger.debug("Adding tune and match values toat next measurement frequency") logger.debug("Adding tune and match values toat next measurement frequency")
next_measurement_frequency = self.get_next_measurement_frequency() next_measurement_frequency = self.get_next_measurement_frequency()
self.reflection[next_measurement_frequency] = magnitude self.reflection[next_measurement_frequency] = magnitude
def find_nearest(self, array: np.array, value: float) -> int:
"""This method finds the nearest value in an array to a given value.
def find_nearest(self, array, value) -> int: Args:
array (np.array): The array to search in.
value (float): The value to search for.
Returns:
int: The index of the nearest value in the array.
"""
array = np.asarray(array) array = np.asarray(array)
idx = (np.abs(array - value)).argmin() idx = (np.abs(array - value)).argmin()
return idx return idx
def to_json(self): def to_json(self):
"""Converts the broadband measurement to a json-compatible format.""" """Converts the broadband measurement to a json-compatible format.
Returns:
dict: The json-compatible format of the broadband measurement.
"""
return { return {
"single_frequency_measurements": [measurement.to_json() for measurement in self.single_frequency_measurements.values()], "single_frequency_measurements": [
"reflection": self.reflection measurement.to_json()
for measurement in self.single_frequency_measurements.values()
],
"reflection": self.reflection,
} }
@classmethod @classmethod
def from_json(cls, json): def from_json(cls, json: dict):
"""Converts the json format to a broadband measurement.""" """Converts the json format to a broadband measurement.
Args:
json (dict): The json format of the broadband measurement.
Returns:
BroadbandMeasurement: The broadband measurement object.
"""
# We create a broadband measurement object with the frequencies and frequency step from the first single frequency measurement # We create a broadband measurement object with the frequencies and frequency step from the first single frequency measurement
frequencies = [measurement["target_frequency"] for measurement in json["single_frequency_measurements"]] frequencies = [
measurement["target_frequency"]
for measurement in json["single_frequency_measurements"]
]
# We need to calculate the frequency step from the first two measurements # We need to calculate the frequency step from the first two measurements
frequency_step = frequencies[1] - frequencies[0] frequency_step = frequencies[1] - frequencies[0]
@ -238,7 +341,9 @@ class BroadbandModel(ModuleModel):
# We add all of the single frequency measurements to the broadband measurement # We add all of the single frequency measurements to the broadband measurement
for measurement in json["single_frequency_measurements"]: for measurement in json["single_frequency_measurements"]:
broadband_measurement.add_measurement(Measurement.from_json(measurement)) broadband_measurement.add_measurement(
Measurement.from_json(measurement)
)
# We assemble the broadband spectrum # We assemble the broadband spectrum
broadband_measurement.assemble_broadband_spectrum() broadband_measurement.assemble_broadband_spectrum()
@ -249,14 +354,14 @@ class BroadbandModel(ModuleModel):
def single_frequency_measurements(self) -> dict: def single_frequency_measurements(self) -> dict:
"""This property contains the dict of all frequencies that have to be measured.""" """This property contains the dict of all frequencies that have to be measured."""
return self._single_frequency_measurements return self._single_frequency_measurements
@single_frequency_measurements.setter @single_frequency_measurements.setter
def single_frequency_measurements(self, value): def single_frequency_measurements(self, value):
self._single_frequency_measurements = value self._single_frequency_measurements = value
@property @property
def broadband_data_fdx(self): def broadband_data_fdx(self):
""" This property contains the broadband data and is assembled by the differen single_frequency measurements in frequency domain.""" """This property contains the broadband data and is assembled by the different single_frequency measurements in frequency domain."""
return self._broadband_data_fdx return self._broadband_data_fdx
@broadband_data_fdx.setter @broadband_data_fdx.setter
@ -265,9 +370,9 @@ class BroadbandModel(ModuleModel):
@property @property
def broadband_data_fdy(self): def broadband_data_fdy(self):
""" This property contains the broadband data and is assembled by the differen single_frequency measurements in frequency domain.""" """This property contains the broadband data and is assembled by the different single_frequency measurements in frequency domain."""
return self._broadband_data_fdy return self._broadband_data_fdy
@broadband_data_fdy.setter @broadband_data_fdy.setter
def broadband_data_fdy(self, value): def broadband_data_fdy(self, value):
self._broadband_data_fdy = value self._broadband_data_fdy = value

View file

@ -1,3 +1,4 @@
"""This module contains the view of the broadband module."""
import logging import logging
from datetime import datetime from datetime import datetime
from PyQt6.QtCore import pyqtSlot, pyqtSignal, Qt from PyQt6.QtCore import pyqtSlot, pyqtSignal, Qt
@ -10,9 +11,15 @@ logger = logging.getLogger(__name__)
class BroadbandView(ModuleView): class BroadbandView(ModuleView):
"""View class for the Broadband module.
Signals:
start_broadband_measurement: Signal to start a broadband measurement.
"""
start_broadband_measurement = pyqtSignal() start_broadband_measurement = pyqtSignal()
def __init__(self, module): def __init__(self, module):
"""Initializes the BroadbandView."""
super().__init__(module) super().__init__(module)
widget = QWidget() widget = QWidget()
@ -21,14 +28,16 @@ class BroadbandView(ModuleView):
self.widget = widget self.widget = widget
logger.debug( logger.debug(
"Facecolor %s" % str(self._ui_form.broadbandPlot.canvas.ax.get_facecolor()) f"Facecolor {str(self._ui_form.broadbandPlot.canvas.ax.get_facecolor())}"
) )
self.connect_signals() self.connect_signals()
# Add logos # Add logos
self._ui_form.start_measurementButton.setIcon(Logos.Play_16x16()) self._ui_form.start_measurementButton.setIcon(Logos.Play_16x16())
self._ui_form.start_measurementButton.setIconSize(self._ui_form.start_measurementButton.size()) self._ui_form.start_measurementButton.setIconSize(
self._ui_form.start_measurementButton.size()
)
self._ui_form.exportButton.setIcon(Logos.Save16x16()) self._ui_form.exportButton.setIcon(Logos.Save16x16())
self._ui_form.exportButton.setIconSize(self._ui_form.exportButton.size()) self._ui_form.exportButton.setIconSize(self._ui_form.exportButton.size())
@ -67,9 +76,13 @@ class BroadbandView(ModuleView):
) )
self.module.model.stop_frequency_changed.connect(self.on_stop_frequency_change) self.module.model.stop_frequency_changed.connect(self.on_stop_frequency_change)
self.module.model.frequency_step_changed.connect(self.on_frequency_step_change) self.module.model.frequency_step_changed.connect(self.on_frequency_step_change)
self._ui_form.start_measurementButton.clicked.connect(self.start_measurement_clicked) self._ui_form.start_measurementButton.clicked.connect(
self.start_broadband_measurement.connect(self.module._controller.start_broadband_measurement) self.start_measurement_clicked
)
self.start_broadband_measurement.connect(
self.module._controller.start_broadband_measurement
)
self._ui_form.averagesEdit.editingFinished.connect( self._ui_form.averagesEdit.editingFinished.connect(
lambda: self.on_editing_finished(self._ui_form.averagesEdit.text()) lambda: self.on_editing_finished(self._ui_form.averagesEdit.text())
@ -95,10 +108,10 @@ class BroadbandView(ModuleView):
@pyqtSlot() @pyqtSlot()
def start_measurement_clicked(self) -> None: def start_measurement_clicked(self) -> None:
"""This method is called when the start measurement button is clicked. """This method is called when the start measurement button is clicked.
It shows a dialog asking the user if he really wants to start the measurement. It shows a dialog asking the user if he really wants to start the measurement.
If the user clicks yes the start_broadband_measurement signal is emitted. If the user clicks yes the start_broadband_measurement signal is emitted.
""" """
# Create a QMessageBox object # Create a QMessageBox object
msg_box = QMessageBox(parent=self) msg_box = QMessageBox(parent=self)
msg_box.setText("Start the measurement?") msg_box.setText("Start the measurement?")
@ -119,10 +132,13 @@ class BroadbandView(ModuleView):
@pyqtSlot() @pyqtSlot()
def on_save_button_clicked(self) -> None: def on_save_button_clicked(self) -> None:
"""This method is called when the save button is clicked. """This method is called when the save button is clicked.
It shows a file dialog to the user to select a file to save the measurement to. It shows a file dialog to the user to select a file to save the measurement to.
""" """
logger.debug("Save button clicked.") logger.debug("Save button clicked.")
file_manager = self.QFileManager(self.module.model.FILE_EXTENSION, parent=self.widget) file_manager = self.QFileManager(
self.module.model.FILE_EXTENSION, parent=self.widget
)
file_name = file_manager.saveFileDialog() file_name = file_manager.saveFileDialog()
if file_name: if file_name:
self.module.controller.save_measurement(file_name) self.module.controller.save_measurement(file_name)
@ -130,10 +146,13 @@ class BroadbandView(ModuleView):
@pyqtSlot() @pyqtSlot()
def on_load_button_clicked(self) -> None: def on_load_button_clicked(self) -> None:
"""This method is called when the load button is clicked. """This method is called when the load button is clicked.
It shows a file dialog to the user to select a file to load the measurement from. It shows a file dialog to the user to select a file to load the measurement from.
""" """
logger.debug("Load button clicked.") logger.debug("Load button clicked.")
file_manager = self.QFileManager(self.module.model.FILE_EXTENSION, parent=self.widget) file_manager = self.QFileManager(
self.module.model.FILE_EXTENSION, parent=self.widget
)
file_name = file_manager.loadFileDialog() file_name = file_manager.loadFileDialog()
if file_name: if file_name:
self.module.controller.load_measurement(file_name) self.module.controller.load_measurement(file_name)
@ -176,7 +195,11 @@ class BroadbandView(ModuleView):
@pyqtSlot(float) @pyqtSlot(float)
def on_start_frequency_change(self, start_frequency: float) -> None: def on_start_frequency_change(self, start_frequency: float) -> None:
"""This method is called when the start frequency is changed. """This method is called when the start frequency is changed.
It adjusts the view to the new start frequency. It adjusts the view to the new start frequency.
Args:
start_frequency (float) : The new start frequency.
""" """
logger.debug( logger.debug(
"Adjusting view to new start frequency: " + str(start_frequency * 1e-6) "Adjusting view to new start frequency: " + str(start_frequency * 1e-6)
@ -189,7 +212,11 @@ class BroadbandView(ModuleView):
@pyqtSlot(float) @pyqtSlot(float)
def on_stop_frequency_change(self, stop_frequency: float) -> None: def on_stop_frequency_change(self, stop_frequency: float) -> None:
"""This method is called when the stop frequency is changed. """This method is called when the stop frequency is changed.
It adjusts the view to the new stop frequency. It adjusts the view to the new stop frequency.
Args:
stop_frequency (float) : The new stop frequency.
""" """
logger.debug( logger.debug(
"Adjusting view to new stop frequency: " + str(stop_frequency * 1e-6) "Adjusting view to new stop frequency: " + str(stop_frequency * 1e-6)
@ -200,22 +227,32 @@ class BroadbandView(ModuleView):
self._ui_form.stop_frequencyField.setText(str(stop_frequency * 1e-6)) self._ui_form.stop_frequencyField.setText(str(stop_frequency * 1e-6))
@pyqtSlot(float) @pyqtSlot(float)
def on_frequency_step_change(self, frequency_step : float) -> None: def on_frequency_step_change(self, frequency_step: float) -> None:
"""This method is called when the frequency step is changed. """This method is called when the frequency step is changed.
It adjusts the view to the new frequency step. It adjusts the view to the new frequency step.
Args:
frequency_step (float) : The new frequency step.
""" """
logger.debug("Adjusting view to new frequency step: " + str(frequency_step * 1e-6)) logger.debug(
self._ui_form.broadbandPlot.canvas.ax.set_xlim(right=frequency_step*1e-6) "Adjusting view to new frequency step: " + str(frequency_step * 1e-6)
)
self._ui_form.broadbandPlot.canvas.ax.set_xlim(right=frequency_step * 1e-6)
self._ui_form.broadbandPlot.canvas.draw() self._ui_form.broadbandPlot.canvas.draw()
self._ui_form.broadbandPlot.canvas.flush_events() self._ui_form.broadbandPlot.canvas.flush_events()
# Fix float representation # Fix float representation
frequency_step = str("{:.2f}".format(frequency_step * 1e-6)) frequency_step = str(f"{frequency_step * 1e-6:.2f}")
self._ui_form.frequencystepEdit.setText(frequency_step) self._ui_form.frequencystepEdit.setText(frequency_step)
@pyqtSlot() @pyqtSlot()
def on_editing_finished(self, value: str) -> None: def on_editing_finished(self, value: str) -> None:
"""This method is called when the user finished editing a field. """This method is called when the user finished editing a field.
It sets the value of the field in the model. It sets the value of the field in the model.
Args:
value (str) : The value of the field.
""" """
logger.debug("Editing finished by.") logger.debug("Editing finished by.")
self.sender().setStyleSheet("") self.sender().setStyleSheet("")
@ -225,6 +262,7 @@ class BroadbandView(ModuleView):
@pyqtSlot() @pyqtSlot()
def on_set_averages_failure(self) -> None: def on_set_averages_failure(self) -> None:
"""This method is called when the averages could not be set. """This method is called when the averages could not be set.
It sets the border of the averages field to red indicating that the entered value was not valid. It sets the border of the averages field to red indicating that the entered value was not valid.
""" """
logger.debug("Set averages failure.") logger.debug("Set averages failure.")
@ -233,6 +271,7 @@ class BroadbandView(ModuleView):
@pyqtSlot() @pyqtSlot()
def on_set_frequency_step_failure(self) -> None: def on_set_frequency_step_failure(self) -> None:
"""This method is called when the frequency step could not be set. """This method is called when the frequency step could not be set.
It sets the border of the frequency step field to red indicating that the entered value was not valid. It sets the border of the frequency step field to red indicating that the entered value was not valid.
""" """
logger.debug("Set frequency step failure.") logger.debug("Set frequency step failure.")
@ -241,13 +280,12 @@ class BroadbandView(ModuleView):
@pyqtSlot() @pyqtSlot()
def on_broadband_measurement_added(self) -> None: def on_broadband_measurement_added(self) -> None:
"""This method is called when a new broadband measurement is added to the model. """This method is called when a new broadband measurement is added to the model.
It updates the plots and the progress bar. It updates the plots and the progress bar.
""" """
# Get last measurement from the broadband measurement object that is not None # Get last measurement from the broadband measurement object that is not None
logger.debug("Updating broadband plot.") logger.debug("Updating broadband plot.")
measurement = ( measurement = self.module.model.current_broadband_measurement.get_last_completed_measurement()
self.module.model.current_broadband_measurement.get_last_completed_measurement()
)
td_plotter = self._ui_form.time_domainPlot.canvas.ax td_plotter = self._ui_form.time_domainPlot.canvas.ax
fd_plotter = self._ui_form.frequency_domainPlot.canvas.ax fd_plotter = self._ui_form.frequency_domainPlot.canvas.ax
@ -257,14 +295,49 @@ class BroadbandView(ModuleView):
fd_plotter.clear() fd_plotter.clear()
broadband_plotter.clear() broadband_plotter.clear()
td_plotter.plot(measurement.tdx, measurement.tdy.real, label="Real", linestyle="-", alpha=0.35, color="red") td_plotter.plot(
td_plotter.plot(measurement.tdx, measurement.tdy.imag, label="Imaginary", linestyle="-", alpha=0.35, color="green") measurement.tdx,
td_plotter.plot(measurement.tdx, abs(measurement.tdy), label="Magnitude", color="blue") measurement.tdy.real,
label="Real",
linestyle="-",
alpha=0.35,
color="red",
)
td_plotter.plot(
measurement.tdx,
measurement.tdy.imag,
label="Imaginary",
linestyle="-",
alpha=0.35,
color="green",
)
td_plotter.plot(
measurement.tdx, abs(measurement.tdy), label="Magnitude", color="blue"
)
td_plotter.legend() td_plotter.legend()
fd_plotter.plot(measurement.fdx * 1e-6, measurement.fdy.real, label="Real", linestyle="-", alpha=0.35, color="red") fd_plotter.plot(
fd_plotter.plot(measurement.fdx * 1e-6, measurement.fdy.imag, label="Imaginary", linestyle="-", alpha=0.35, color="green") measurement.fdx * 1e-6,
fd_plotter.plot(measurement.fdx * 1e-6, abs(measurement.fdy), label="Magnitude", color="blue") measurement.fdy.real,
label="Real",
linestyle="-",
alpha=0.35,
color="red",
)
fd_plotter.plot(
measurement.fdx * 1e-6,
measurement.fdy.imag,
label="Imaginary",
linestyle="-",
alpha=0.35,
color="green",
)
fd_plotter.plot(
measurement.fdx * 1e-6,
abs(measurement.fdy),
label="Magnitude",
color="blue",
)
fd_plotter.legend() fd_plotter.legend()
# Plot real and imag part again here in time and frequency domain # Plot real and imag part again here in time and frequency domain
@ -274,18 +347,28 @@ class BroadbandView(ModuleView):
) )
# Plot S11 values on the twin axis of the broadband plot # Plot S11 values on the twin axis of the broadband plot
frequencies = self.module.model.current_broadband_measurement.reflection.keys() frequencies = self.module.model.current_broadband_measurement.reflection.keys()
frequencies = [frequency * 1e-6 for frequency in frequencies] frequencies = [frequency * 1e-6 for frequency in frequencies]
reflection_values = self.module.model.current_broadband_measurement.reflection.values() reflection_values = (
self.module.model.current_broadband_measurement.reflection.values()
)
if reflection_values: if reflection_values:
self._ui_form.broadbandPlot.canvas.S11ax = self._ui_form.broadbandPlot.canvas.ax.twinx() self._ui_form.broadbandPlot.canvas.S11ax = (
self._ui_form.broadbandPlot.canvas.ax.twinx()
)
S11plotter = self._ui_form.broadbandPlot.canvas.S11ax S11plotter = self._ui_form.broadbandPlot.canvas.S11ax
S11plotter.clear() S11plotter.clear()
# Make second axis for S11 value # Make second axis for S11 value
self._ui_form.broadbandPlot.canvas.S11ax.set_ylabel("S11 in dB") self._ui_form.broadbandPlot.canvas.S11ax.set_ylabel("S11 in dB")
self._ui_form.broadbandPlot.canvas.S11ax.set_ylim([-40, 0]) self._ui_form.broadbandPlot.canvas.S11ax.set_ylim([-40, 0])
S11plotter.plot(frequencies, reflection_values, color="red", marker="x", linestyle="None") S11plotter.plot(
frequencies,
reflection_values,
color="red",
marker="x",
linestyle="None",
)
self.set_timedomain_labels() self.set_timedomain_labels()
self.set_frequencydomain_labels() self.set_frequencydomain_labels()
@ -320,14 +403,14 @@ class BroadbandView(ModuleView):
self._ui_form.frequencystepEdit.setEnabled(True) self._ui_form.frequencystepEdit.setEnabled(True)
self._ui_form.activeLUTLabel.setText("None") self._ui_form.activeLUTLabel.setText("None")
def add_info_text(self, text : str) -> None: def add_info_text(self, text: str) -> None:
"""Add a text to the info box with a timestamp. """Add a text to the info box with a timestamp.
Args: Args:
text (str): The text to add to the info box. text (str): The text to add to the info box.
""" """
timestamp = datetime.now().strftime("%H:%M:%S") timestamp = datetime.now().strftime("%H:%M:%S")
text = "[%s] %s" % (timestamp, text) text = f"[{timestamp}] {text}"
text_label = QLabel(text) text_label = QLabel(text)
text_label.setStyleSheet("font-size: 25px;") text_label.setStyleSheet("font-size: 25px;")
self._ui_form.scrollAreaWidgetContents.layout().addWidget(text_label) self._ui_form.scrollAreaWidgetContents.layout().addWidget(text_label)