Merge pull request #23 from nqrduck/development

Development
This commit is contained in:
Julia P 2024-05-27 19:33:05 +02:00 committed by GitHub
commit 04231158ef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 474 additions and 64 deletions

View file

@ -1,5 +1,11 @@
# Changelog # Changelog
## Version 0.0.12 (27-05-2024)
- Implemented loading and saving of settings and default settings (`6496ec6824da33fa41a38e26fdcac9a03ceb51fb`)
- Added fitting of measurement data (`d43639c2f9796d5055dd01bd2f36bae43877bfe8`)
## Version 0.0.11 (20-05-2024) ## Version 0.0.11 (20-05-2024)
- Measurements are now run in a separate worker thread to prevent the GUI from freezing (`27865aa6d44158e74c0e537be8407c12b4e3725b`) - Measurements are now run in a separate worker thread to prevent the GUI from freezing (`27865aa6d44158e74c0e537be8407c12b4e3725b`)

View file

@ -7,7 +7,7 @@ allow-direct-references = true
[project] [project]
name = "nqrduck-spectrometer" name = "nqrduck-spectrometer"
version = "0.0.11" version = "0.0.12"
authors = [ authors = [
{ name="jupfi", email="support@nqruck.cool" }, { name="jupfi", email="support@nqruck.cool" },
] ]

View file

@ -1,7 +1,10 @@
"""Base class for all spectrometer controllers.""" """Base class for all spectrometer controllers."""
import logging
import ast
from nqrduck.module.module_controller import ModuleController from nqrduck.module.module_controller import ModuleController
logger = logging.getLogger(__name__)
class BaseSpectrometerController(ModuleController): class BaseSpectrometerController(ModuleController):
"""The base class for all spectrometer controllers.""" """The base class for all spectrometer controllers."""
@ -10,6 +13,52 @@ class BaseSpectrometerController(ModuleController):
"""Initializes the spectrometer controller.""" """Initializes the spectrometer controller."""
super().__init__(module) super().__init__(module)
def on_loading(self):
"""Called when the module is loading."""
logger.debug("Loading spectrometer controller")
self.module.model.load_default_settings()
def save_settings(self, path: str) -> None:
"""Saves the settings of the spectrometer."""
# We get the different settings objects from the model
settings = self.module.model.settings
json = {}
json["name"] = self.module.model.name
for category in settings.keys():
for setting in settings[category]:
json[setting.name] = setting.value
with open(path, "w") as f:
f.write(str(json))
def load_settings(self, path: str) -> None:
"""Loads the settings of the spectrometer."""
with open(path) as f:
json = f.read()
# string to dict
json = ast.literal_eval(json)
module_name = self.module.model.name
json_name = json["name"]
# For some reason the notification is shown twice
if module_name != json_name:
message = f"Module: {module_name} not compatible with module specified in settings file: {json_name}. Did you select the correct settings file?"
self.module.nqrduck_signal.emit("notification", ["Error", message])
return
settings = self.module.model.settings
for category in settings.keys():
for setting in settings[category]:
if setting.name in json:
setting.value = json[setting.name]
else:
message = f"Setting {setting.name} not found in settings file. A change in settings might have broken compatibility."
self.module.nqrduck_signal.emit("notification", ["Error", message])
def start_measurement(self): def start_measurement(self):
"""Starts the measurement. """Starts the measurement.

View file

@ -2,6 +2,7 @@
import logging import logging
from collections import OrderedDict from collections import OrderedDict
from PyQt6.QtCore import QSettings
from PyQt6.QtGui import QPixmap from PyQt6.QtGui import QPixmap
from nqrduck.module.module_model import ModuleModel from nqrduck.module.module_model import ModuleModel
from .settings import Setting from .settings import Setting
@ -22,6 +23,8 @@ class BaseSpectrometerModel(ModuleModel):
pulse_parameter_options (OrderedDict) : The pulse parameter options of the spectrometer pulse_parameter_options (OrderedDict) : The pulse parameter options of the spectrometer
""" """
SETTING_FILE_EXTENSION = "setduck"
settings: OrderedDict settings: OrderedDict
pulse_parameter_options: OrderedDict pulse_parameter_options: OrderedDict
@ -99,6 +102,29 @@ class BaseSpectrometerModel(ModuleModel):
super().__init__(module) super().__init__(module)
self.settings = OrderedDict() self.settings = OrderedDict()
self.pulse_parameter_options = OrderedDict() self.pulse_parameter_options = OrderedDict()
self.default_settings = QSettings("nqrduck-spectrometer", "nqrduck")
def set_default_settings(self) -> None:
"""Sets the default settings of the spectrometer."""
self.default_settings.clear()
for category in self.settings.keys():
for setting in self.settings[category]:
setting_string = f"{self.module.model.name},{setting.name}"
self.default_settings.setValue(setting_string, setting.value)
logger.debug(f"Setting default value for {setting_string} to {setting.value}")
def load_default_settings(self) -> None:
"""Load the default settings of the spectrometer."""
for category in self.settings.keys():
for setting in self.settings[category]:
setting_string = f"{self.module.model.name},{setting.name}"
if self.default_settings.contains(setting_string):
logger.debug(f"Loading default value for {setting_string}")
setting.value = self.default_settings.value(setting_string)
def clear_default_settings(self) -> None:
"""Clear the default settings of the spectrometer."""
self.default_settings.clear()
def add_setting(self, setting: Setting, category: str) -> None: def add_setting(self, setting: Setting, category: str) -> None:
"""Adds a setting to the spectrometer. """Adds a setting to the spectrometer.

View file

@ -8,6 +8,8 @@ from PyQt6.QtWidgets import (
QSizePolicy, QSizePolicy,
QSpacerItem, QSpacerItem,
QVBoxLayout, QVBoxLayout,
QPushButton,
QDialog,
) )
from nqrduck.module.module_view import ModuleView from nqrduck.module.module_view import ModuleView
from nqrduck.assets.icons import Logos from nqrduck.assets.icons import Logos
@ -82,7 +84,7 @@ class BaseSpectrometerView(ModuleView):
layout.addWidget(edit_widget) layout.addWidget(edit_widget)
layout.addStretch(1) layout.addStretch(1)
layout.addWidget(icon_label) layout.addWidget(icon_label)
# Add the layout to the vertical layout of the widget # Add the layout to the vertical layout of the widget
category_layout.addLayout(layout) category_layout.addLayout(layout)
@ -91,3 +93,108 @@ class BaseSpectrometerView(ModuleView):
# Push all the settings to the top of the widget # Push all the settings to the top of the widget
self._ui_form.verticalLayout.addStretch(1) self._ui_form.verticalLayout.addStretch(1)
# Now we add a save and load button to the widget
self.button_layout = QHBoxLayout()
# Default Settings Button
self.default_button = QPushButton("Default Settings")
self.default_button.clicked.connect(self.on_default_button_clicked)
self.button_layout.addWidget(self.default_button)
# Save Button
self.save_button = QPushButton("Save Settings")
self.save_button.setIcon(Logos.Save16x16())
self.save_button.setIconSize(Logos.Save16x16().availableSizes()[0])
self.save_button.clicked.connect(self.on_save_button_clicked)
self.button_layout.addWidget(self.save_button)
# Load Button
self.load_button = QPushButton("Load Settings")
self.load_button.setIcon(Logos.Load16x16())
self.load_button.clicked.connect(self.on_load_button_clicked)
self.button_layout.addWidget(self.load_button)
self.load_button.setIconSize(Logos.Load16x16().availableSizes()[0])
self.button_layout.addStretch(1)
self._ui_form.verticalLayout.addLayout(self.button_layout)
def on_save_button_clicked(self):
"""This method is called when the save button is clicked."""
logger.debug("Save button clicked")
# Open a dialog to save the settings to a file
file_manager = self.FileManager(
extension=self.module.model.SETTING_FILE_EXTENSION, parent=self
)
path = file_manager.saveFileDialog()
if path:
self.module.controller.save_settings(path)
def on_load_button_clicked(self):
"""This method is called when the load button is clicked."""
logger.debug("Load button clicked")
# Open a dialog to load the settings from a file
file_manager = self.FileManager(
extension=self.module.model.SETTING_FILE_EXTENSION, parent=self
)
path = file_manager.loadFileDialog()
self.module.controller.load_settings(path)
if path:
self.module.controller.load_settings(path)
def on_default_button_clicked(self):
"""This method is called when the default button is clicked."""
logger.debug("Default button clicked")
dialog = self.DefaultSettingsDialog(self)
dialog.exec()
class DefaultSettingsDialog(QDialog):
"""Dialog to set or clear the default settings of the spectrometer."""
def __init__(self, parent=None):
"""Initializes the default settings dialog."""
super().__init__(parent)
self.parent = parent
self.setWindowTitle("Default Settings")
self.layout = QVBoxLayout()
# Either we set the current settings as default
self.set_current_button = QPushButton("Set Current Settings as Default")
self.set_current_button.clicked.connect(
self.on_set_current_button_clicked
)
# Or we clear the default settings
self.clear_button = QPushButton("Clear Default Settings")
self.clear_button.clicked.connect(
self.on_clear_button_clicked
)
self.layout.addWidget(self.set_current_button)
self.layout.addWidget(self.clear_button)
self.setLayout(self.layout)
# Ok Button
self.ok_button = QPushButton("Ok")
self.ok_button.clicked.connect(self.accept)
self.layout.addWidget(self.ok_button)
def on_set_current_button_clicked(self):
"""This method is called when the set current button is clicked."""
logger.debug("Set current button clicked")
self.parent.module.model.set_default_settings()
# Show notification that the settings have been set as default
self.parent.module.nqrduck_signal.emit(
"notification", ["Info", "Settings have been set as default."]
)
def on_clear_button_clicked(self):
"""This method is called when the clear button is clicked."""
logger.debug("Clear button clicked")
self.parent.module.model.clear_default_settings()
# Show notification that the default settings have been cleared
self.parent.module.nqrduck_signal.emit(
"notification", ["Info", "Default settings have been cleared."]
)

View file

@ -43,6 +43,8 @@ class SpectrometerController(ModuleController):
logger.debug("Adding spectrometer to spectrometer model: %s", module_name) logger.debug("Adding spectrometer to spectrometer model: %s", module_name)
self._module.model.add_spectrometers(module_name, module) self._module.model.add_spectrometers(module_name, module)
module.controller.on_loading()
self._module.view.create_menu_entry() self._module.view.create_menu_entry()
def process_signals(self, key: str, value: object) -> None: def process_signals(self, key: str, value: object) -> None:

View file

@ -1,7 +1,8 @@
"""Class for handling measurement data.""" """This module defines the measurement data structure and the fit class for measurement data."""
import logging import logging
import numpy as np import numpy as np
from scipy.optimize import curve_fit
from nqrduck.helpers.signalprocessing import SignalProcessing as sp from nqrduck.helpers.signalprocessing import SignalProcessing as sp
from nqrduck.helpers.functions import Function from nqrduck.helpers.functions import Function
@ -28,8 +29,8 @@ class Measurement:
target_frequency (float): Target frequency of the measurement. target_frequency (float): Target frequency of the measurement.
frequency_shift (float): Frequency shift of the measurement. frequency_shift (float): Frequency shift of the measurement.
IF_frequency (float): Intermediate frequency of the measurement. IF_frequency (float): Intermediate frequency of the measurement.
xf (np.array): Frequency axis for the x axis of the measurement data. fdx (np.array): Frequency axis for the x axis of the measurement data.
yf (np.array): Frequency axis for the y axis of the measurement data. fdy (np.array): Frequency axis for the y axis of the measurement data.
""" """
def __init__( def __init__(
@ -46,69 +47,89 @@ class Measurement:
self.tdx = tdx self.tdx = tdx
self.tdy = tdy self.tdy = tdy
self.target_frequency = target_frequency self.target_frequency = target_frequency
self.fdx, self.fdy = sp.fft(tdx, tdy, frequency_shift) self.frequency_shift = frequency_shift
self.IF_frequency = IF_frequency self.IF_frequency = IF_frequency
self.fdx, self.fdy = sp.fft(tdx, tdy, frequency_shift)
self.fits = []
def apodization(self, function: Function): def apodization(self, function: Function) -> "Measurement":
"""Applies apodization to the measurement data. """Applies apodization to the measurement data.
Args: Args:
function (Function): Apodization function. function (Function): Apodization function.
returns: Returns:
Measurement : The apodized measurement. Measurement: The apodized measurement.
""" """
# Get the y data weights from the function
duration = (self.tdx[-1] - self.tdx[0]) * 1e-6 duration = (self.tdx[-1] - self.tdx[0]) * 1e-6
resolution = duration / len(self.tdx) resolution = duration / len(self.tdx)
logger.debug("Resolution: %s", resolution) logger.debug("Resolution: %s", resolution)
y_weight = function.get_pulse_amplitude(duration, resolution) y_weight = function.get_pulse_amplitude(duration, resolution)
tdy_apodized = self.tdy * y_weight
tdy_measurement = self.tdy * y_weight
apodized_measurement = Measurement( apodized_measurement = Measurement(
self.name, self.name,
self.tdx, self.tdx,
tdy_measurement, tdy_apodized,
target_frequency=self.target_frequency, target_frequency=self.target_frequency,
IF_frequency=self.IF_frequency, IF_frequency=self.IF_frequency,
) )
return apodized_measurement return apodized_measurement
# Data saving and loading def add_fit(self, fit: "Fit") -> None:
"""Adds a fit to the measurement.
def to_json(self): Args:
"""Converts the measurement to a json-compatible format. fit (Fit): The fit to add.
"""
self.fits.append(fit)
def delete_fit(self, fit: "Fit") -> None:
"""Deletes a fit from the measurement.
Args:
fit (Fit): The fit to delete.
"""
self.fits.remove(fit)
def edit_fit_name(self, fit: "Fit", name: str) -> None:
"""Edits the name of a fit.
Args:
fit (Fit): The fit to edit.
name (str): The new name.
"""
logger.debug(f"Editing fit name to {name}.")
fit.name = name
def to_json(self) -> dict:
"""Converts the measurement to a JSON-compatible format.
Returns: Returns:
dict : The measurement in json-compatible format. dict: The measurement in JSON-compatible format.
""" """
return { return {
"name": self.name, "name": self.name,
"tdx": self.tdx.tolist(), "tdx": self.tdx.tolist(),
"tdy": [ "tdy": [[x.real, x.imag] for x in self.tdy],
[x.real, x.imag] for x in self.tdy
], # Convert complex numbers to list
"target_frequency": self.target_frequency, "target_frequency": self.target_frequency,
"IF_frequency": self.IF_frequency, "IF_frequency": self.IF_frequency,
"fits": [fit.to_json() for fit in self.fits],
} }
@classmethod @classmethod
def from_json(cls, json: dict): def from_json(cls, json: dict) -> "Measurement":
"""Converts the json format to a measurement. """Converts the JSON format to a measurement.
Args: Args:
json (dict) : The measurement in json-compatible format. json (dict): The measurement in JSON-compatible format.
Returns: Returns:
Measurement : The measurement. Measurement: The measurement.
""" """
tdy = np.array([complex(y[0], y[1]) for y in json["tdy"]]) tdy = np.array([complex(y[0], y[1]) for y in json["tdy"]])
return cls( measurement = cls(
json["name"], json["name"],
np.array(json["tdx"]), np.array(json["tdx"]),
tdy, tdy,
@ -116,58 +137,227 @@ class Measurement:
IF_frequency=json["IF_frequency"], IF_frequency=json["IF_frequency"],
) )
# Measurement data for fit_json in json["fits"]:
measurement.add_fit(Fit.from_json(fit_json, measurement))
return measurement
# Properties for encapsulation
@property @property
def name(self): def name(self) -> str:
"""Name of the measurement.""" """Name of the measurement."""
return self._name return self._name
@name.setter @name.setter
def name(self, value): def name(self, value: str) -> None:
self._name = value self._name = value
@property @property
def tdx(self): def tdx(self) -> np.array:
"""Time axis for the x axis of the measurement data.""" """Time domain data for the measurement (x)."""
return self._tdx return self._tdx
@tdx.setter @tdx.setter
def tdx(self, value): def tdx(self, value: np.array) -> None:
self._tdx = value self._tdx = value
@property @property
def tdy(self): def tdy(self) -> np.array:
"""Time axis for the y axis of the measurement data.""" """Time domain data for the measurement (y)."""
return self._tdy return self._tdy
@tdy.setter @tdy.setter
def tdy(self, value): def tdy(self, value: np.array) -> None:
self._tdy = value self._tdy = value
@property @property
def fdx(self): def fdx(self) -> np.array:
"""Frequency axis for the x axis of the measurement data.""" """Frequency domain data for the measurement (x)."""
return self._fdx return self._fdx
@fdx.setter @fdx.setter
def fdx(self, value): def fdx(self, value: np.array) -> None:
self._fdx = value self._fdx = value
@property @property
def fdy(self): def fdy(self) -> np.array:
"""Frequency axis for the y axis of the measurement data.""" """Frequency domain data for the measurement (y)."""
return self._fdy return self._fdy
@fdy.setter @fdy.setter
def fdy(self, value): def fdy(self, value: np.array) -> None:
self._fdy = value self._fdy = value
# Pulse parameters
@property @property
def target_frequency(self): def target_frequency(self) -> float:
"""Target frequency of the measurement.""" """Target frequency of the measurement."""
return self._target_frequency return self._target_frequency
@target_frequency.setter @target_frequency.setter
def target_frequency(self, value): def target_frequency(self, value: float) -> None:
self._target_frequency = value self._target_frequency = value
@property
def fits(self) -> list:
"""Fits of the measurement."""
return self._fits
@fits.setter
def fits(self, value: list) -> None:
self._fits = value
class Fit:
"""The fit class for measurement data. A fit can be performed on either the frequency or time domain data.
A measurement can have multiple fits.
"""
subclasses = []
def __init_subclass__(cls, **kwargs) -> None:
"""Adds the subclass to the list of subclasses."""
super().__init_subclass__(**kwargs)
cls.subclasses.append(cls)
def __init__(self, name: str, domain: str, measurement: Measurement) -> None:
"""Initializes the fit."""
self.name = name
self.domain = domain
self.measurement = measurement
self.fit()
def fit(self) -> None:
"""Fits the measurement data and sets the fit parameters and covariance."""
if self.domain == "time":
x = self.measurement.tdx
y = self.measurement.tdy
elif self.domain == "frequency":
x = self.measurement.fdx
y = self.measurement.fdy
else:
raise ValueError("Domain not recognized.")
initial_guess = self.initial_guess()
self.parameters, self.covariance = curve_fit(
self.fit_function, x, abs(y), p0=initial_guess
)
self.x = x
self.y = self.fit_function(x, *self.parameters)
def fit_function(self, x: np.array, *parameters) -> np.array:
"""The fit function.
Args:
x (np.array): The x data.
*parameters: The fit parameters.
Returns:
np.array: The y data.
"""
raise NotImplementedError
def initial_guess(self) -> list:
"""Initial guess for the fit.
Returns:
list: The initial guess.
"""
raise NotImplementedError
def to_json(self) -> dict:
"""Converts the fit to a JSON-compatible format.
Returns:
dict: The fit in JSON-compatible format.
"""
return {
"name": self.name,
"class": self.__class__.__name__,
}
@classmethod
def from_json(cls, data: dict, measurement: Measurement) -> "Fit":
"""Converts the JSON format to a fit.
Args:
data (dict): The fit in JSON-compatible format.
measurement (Measurement): The measurement.
Returns:
Fit: The fit.
"""
for subclass in cls.subclasses:
if subclass.__name__ == data["class"]:
return subclass(name=data["name"], measurement=measurement)
raise ValueError(f"Subclass {data['class']} not found.")
@property
def x(self) -> np.array:
"""The x data of the fit."""
return self._x
@x.setter
def x(self, value: np.array) -> None:
self._x = value
@property
def y(self) -> np.array:
"""The y data of the fit."""
return self._y
@y.setter
def y(self, value: np.array) -> None:
self._y = value
class T2StarFit(Fit):
"""T2* fit for measurement data."""
def __init__(self, measurement: Measurement, name: str = "T2*") -> None:
"""Initializes the T2* fit."""
super().__init__(name, "time", measurement)
def fit(self) -> None:
"""Fits the measurement data and sets the fit parameters and covariance."""
super().fit()
self.parameters = {
"S0": self.parameters[0],
"T2Star": self.parameters[1],
"covariance": self.covariance,
}
def fit_function(self, t: np.array, S0: float, T2Star: float) -> np.array:
"""The T2* fit function used for curve fitting."""
return S0 * np.exp(-t / T2Star)
def initial_guess(self) -> list:
"""Initial guess for the T2* fit."""
return [1, 1]
class LorentzianFit(Fit):
"""Lorentzian fit for measurement data."""
def __init__(self, measurement: Measurement, name: str = "Lorentzian") -> None:
"""Initializes the Lorentzian fit."""
super().__init__(name, "frequency", measurement)
def fit(self) -> None:
"""Fits the measurement data and sets the fit parameters and covariance."""
super().fit()
self.parameters = {
"S0": self.parameters[0],
"T2Star": self.parameters[1],
"covariance": self.covariance,
}
logger.debug("Lorentzian fit parameters: %s", self.parameters)
def fit_function(self, f: np.array, S0: float, T2Star: float) -> np.array:
"""The Lorentzian fit function used for curve fitting."""
return S0 / (1 + (2 * np.pi * f * T2Star) ** 2)
def initial_guess(self) -> list:
"""Initial guess for the Lorentzian fit."""
return [1, 1]

View file

@ -36,6 +36,7 @@ class Setting(QObject):
description (str): A description of the setting. description (str): A description of the setting.
default: The default value of the setting. default: The default value of the setting.
""" """
self.widget = None
super().__init__() super().__init__()
self.name = name self.name = name
self.description = description self.description = description
@ -81,34 +82,41 @@ class Setting(QObject):
lambda x=widget, s=self: s.on_value_changed(x.text()) lambda x=widget, s=self: s.on_value_changed(x.text())
) )
return widget return widget
class NumericalSetting(Setting): class NumericalSetting(Setting):
"""A setting that is a numerical value. """A setting that is a numerical value.
It can additionally have a minimum and maximum value. It can additionally have a minimum and maximum value.
""" """
def __init__(self, name: str, description: str, default, min_value = None, max_value = None ) -> None:
def __init__(
self, name: str, description: str, default, min_value=None, max_value=None
) -> None:
"""Create a new numerical setting.""" """Create a new numerical setting."""
super().__init__(name, self.description_limit_info(description, min_value, max_value), default) super().__init__(
name,
self.description_limit_info(description, min_value, max_value),
default,
)
def description_limit_info(self, description: str, min_value, max_value) -> str: def description_limit_info(self, description: str, min_value, max_value) -> str:
"""Updates the description with the limits of the setting if there are any. """Updates the description with the limits of the setting if there are any.
Args: Args:
description (str): The description of the setting. description (str): The description of the setting.
min_value: The minimum value of the setting. min_value: The minimum value of the setting.
max_value: The maximum value of the setting. max_value: The maximum value of the setting.
Returns: Returns:
str: The description of the setting with the limits. str: The description of the setting with the limits.
""" """
if min_value is not None and max_value is not None: if min_value is not None and max_value is not None:
description += (f"\n (min: {min_value}, max: {max_value})") description += f"\n (min: {min_value}, max: {max_value})"
elif min_value is not None: elif min_value is not None:
description += (f"\n (min: {min_value})") description += f"\n (min: {min_value})"
elif max_value is not None: elif max_value is not None:
description += (f"\n (max: {max_value})") description += f"\n (max: {max_value})"
return description return description
@ -121,7 +129,7 @@ class FloatSetting(NumericalSetting):
description (str) : A description of the setting description (str) : A description of the setting
min_value : The minimum value of the setting min_value : The minimum value of the setting
max_value : The maximum value of the setting max_value : The maximum value of the setting
spin_box : A tuple with two booleans that determine if a spin box is used if the second value is True, a slider will be created as well. spin_box : A tuple with two booleans that determine if a spin box is used if the second value is True, a slider will be created as well.
""" """
DEFAULT_LENGTH = 100 DEFAULT_LENGTH = 100
@ -133,13 +141,19 @@ class FloatSetting(NumericalSetting):
description: str, description: str,
min_value: float = None, min_value: float = None,
max_value: float = None, max_value: float = None,
spin_box: tuple = (False, False) spin_box: tuple = (False, False),
) -> None: ) -> None:
"""Create a new float setting.""" """Create a new float setting."""
self.spin_box = spin_box
super().__init__(name, description, default, min_value, max_value) super().__init__(name, description, default, min_value, max_value)
if spin_box[0]: if spin_box[0]:
self.widget = DuckSpinBox(min_value=min_value, max_value=max_value, slider=spin_box[1], double_box=True) self.widget = DuckSpinBox(
min_value=min_value,
max_value=max_value,
slider=spin_box[1],
double_box=True,
)
self.widget.spin_box.setValue(default) self.widget.spin_box.setValue(default)
else: else:
self.widget = DuckFloatEdit(min_value=min_value, max_value=max_value) self.widget = DuckFloatEdit(min_value=min_value, max_value=max_value)
@ -169,6 +183,12 @@ class FloatSetting(NumericalSetting):
self._value = float(value) self._value = float(value)
self.settings_changed.emit() self.settings_changed.emit()
if self.widget:
if self.spin_box[0]:
self.widget.spin_box.setValue(self._value)
else:
self.widget.setText(str(self._value))
class IntSetting(NumericalSetting): class IntSetting(NumericalSetting):
"""A setting that is an Integer. """A setting that is an Integer.
@ -189,13 +209,15 @@ class IntSetting(NumericalSetting):
description: str, description: str,
min_value=None, min_value=None,
max_value=None, max_value=None,
spin_box: tuple = (False, False) spin_box: tuple = (False, False),
) -> None: ) -> None:
"""Create a new int setting.""" """Create a new int setting."""
self.spin_box = spin_box
super().__init__(name, description, default, min_value, max_value) super().__init__(name, description, default, min_value, max_value)
if self.spin_box[0]:
if spin_box[0]: self.widget = DuckSpinBox(
self.widget = DuckSpinBox(min_value=min_value, max_value=max_value, slider=spin_box[1]) min_value=min_value, max_value=max_value, slider=spin_box[1]
)
self.widget.spin_box.setValue(default) self.widget.spin_box.setValue(default)
else: else:
self.widget = DuckIntEdit(min_value=min_value, max_value=max_value) self.widget = DuckIntEdit(min_value=min_value, max_value=max_value)
@ -225,7 +247,11 @@ class IntSetting(NumericalSetting):
value = int(float(value)) value = int(float(value))
self._value = value self._value = value
self.settings_changed.emit() self.settings_changed.emit()
if self.widget:
if self.spin_box[0]:
self.widget.spin_box.setValue(value)
else:
self.widget.setText(str(value))
class BooleanSetting(Setting): class BooleanSetting(Setting):
@ -253,6 +279,8 @@ class BooleanSetting(Setting):
def value(self, value): def value(self, value):
try: try:
self._value = bool(value) self._value = bool(value)
if self.widget:
self.widget.setChecked(self._value)
self.settings_changed.emit() self.settings_changed.emit()
except ValueError: except ValueError:
raise ValueError("Value must be a bool") raise ValueError("Value must be a bool")
@ -307,6 +335,8 @@ class SelectionSetting(Setting):
try: try:
if value in self.options: if value in self.options:
self._value = value self._value = value
if self.widget:
self.widget.setCurrentText(value)
self.settings_changed.emit() self.settings_changed.emit()
else: else:
raise ValueError("Value must be one of the options") raise ValueError("Value must be one of the options")