Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions doc/locale/fr/LC_MESSAGES/release_notes/release_1.01.po
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,39 @@ msgstr "Les annotations PlotPy existantes restent lisibles sans modifier l'objet
msgid "Canonical annotation identifiers, metadata, extensions, and persistent lock state are preserved across PlotPy editing round trips. Application-specific opaque annotation entries continue to coexist with graphical annotations."
msgstr "Les identifiants d'annotation canoniques, les métadonnées, les extensions et l'état de verrouillage persistant sont conservés lors des allers-retours d'édition PlotPy. Les entrées d'annotation opaques spécifiques à l'application continuent de coexister avec les annotations graphiques."

msgid "Main window"
msgstr ""

msgid "With Qt 5, the dock widget sizes saved on exit are now restored at startup: the window is sized and laid out before its state is restored, so tabified docks no longer shrink to the default window size and leave the extra width to the central widget."
msgstr ""

msgid "Qt helpers"
msgstr ""

msgid "`block_signals` now restores the previous signal-blocking state of the widget and of its children instead of unblocking them unconditionally: nested blocking contexts and widgets already blocked by the caller keep their state."
msgstr ""

msgid "`sigimax_app_context` no longer schedules the unattended/screenshot close timer when the Qt event loop is not executed (`exec_loop=False`): the pending timer previously closed unrelated top-level widgets created later, such as the window of a subsequent test."
msgstr ""

msgid "Curve fitting dialogs"
msgstr "Fenêtres d'ajustement de courbes"

msgid "Interactive fitting dialogs now show the formula of the fitted model below the fit parameters, and parameter labels show the symbols used in the formula (e.g. \"Scale factor (x₀)\"), so that the parameters stored in the fitted signal metadata can be related to the model. `guifit` accepts a new `formula` argument for this purpose. This closes [Issue #9](https://github.com/DataLab-Platform/SigimaX/issues/9)."
msgstr "Les fenêtres d'ajustement interactif affichent désormais la formule du modèle ajusté sous les paramètres d'ajustement, et les libellés des paramètres indiquent les symboles utilisés dans la formule (par exemple « Facteur d'échelle (x₀) »), afin de pouvoir relier au modèle les paramètres enregistrés dans les métadonnées du signal ajusté. `guifit` accepte pour cela un nouvel argument `formula`. Cela corrige [l'issue #9](https://github.com/DataLab-Platform/SigimaX/issues/9)."

msgid "Polynomial and linear fit coefficients are now labelled `a`, `b`, `c`... as in the model formula and in the fitted signal metadata, instead of `c2`, `c1`, `c0`."
msgstr "Les coefficients des ajustements polynomial et linéaire sont désormais nommés `a`, `b`, `c`... comme dans la formule du modèle et dans les métadonnées du signal ajusté, au lieu de `c2`, `c1`, `c0`."

msgid "The Planckian fit dialog starts from a curve matching the data peak (its initial amplitude was about 147 times too low on blackbody data in physical units), and its \"width factor\" is now labelled \"temperature factor\": for wavelengths in meters, the temperature is `h c σ / (5 k_B x₀)`."
msgstr "La fenêtre d'ajustement planckien part d'une courbe correspondant au pic des données (son amplitude initiale était environ 147 fois trop faible sur des données de corps noir en unités physiques), et son « facteur de largeur » est désormais nommé « facteur de température » : pour des longueurs d'onde en mètres, la température vaut `h c σ / (5 k_B x₀)`."

msgid "The exponential fit dialog now evaluates the same model as Sigima, and its rate slider covers short time constants whatever the X unit (e.g. 1 ms with X in seconds)."
msgstr "La fenêtre d'ajustement exponentiel évalue désormais le même modèle que Sigima, et son curseur de taux couvre les constantes de temps courtes quelle que soit l'unité de X (par exemple 1 ms avec X en secondes)."

msgid "Requirements"
msgstr "Exigences"

msgid "Sigima 1.3.0 or later is required for the portable annotation model and PlotPy conversion helpers."
msgstr "Sigima 1.3.0 ou une version ultérieure est requise pour le modèle d'annotation portable et les helpers de conversion PlotPy."
msgid "Sigima 1.3.0 or later is required for the portable annotation model, PlotPy conversion helpers and curve fitting model formulas."
msgstr "Sigima 1.3.0 ou une version ultérieure est requise pour le modèle d'annotation portable, les helpers de conversion PlotPy et les formules des modèles d'ajustement de courbes."

10 changes: 8 additions & 2 deletions doc/release_notes/release_1.01.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@
* `block_signals` now restores the previous signal-blocking state of the widget and of its children instead of unblocking them unconditionally: nested blocking contexts and widgets already blocked by the caller keep their state.
* `sigimax_app_context` no longer schedules the unattended/screenshot close timer when the Qt event loop is not executed (`exec_loop=False`): the pending timer previously closed unrelated top-level widgets created later, such as the window of a subsequent test.

### Curve fitting dialogs ###

* Interactive fitting dialogs now show the formula of the fitted model below the fit parameters, and parameter labels show the symbols used in the formula (e.g. "Scale factor (x₀)"), so that the parameters stored in the fitted signal metadata can be related to the model. `guifit` accepts a new `formula` argument for this purpose. This closes [Issue #9](https://github.com/DataLab-Platform/SigimaX/issues/9).
* Polynomial and linear fit coefficients are now labelled `a`, `b`, `c`... as in the model formula and in the fitted signal metadata, instead of `c2`, `c1`, `c0`.
* The Planckian fit dialog starts from a curve matching the data peak (its initial amplitude was about 147 times too low on blackbody data in physical units), and its "width factor" is now labelled "temperature factor": for wavelengths in meters, the temperature is `h c σ / (5 k_B x₀)`.
* The exponential fit dialog now evaluates the same model as Sigima, and its rate slider covers short time constants whatever the X unit (e.g. 1 ms with X in seconds).

### Requirements ###

* Sigima 1.3.0 or later is required for the portable annotation model and
PlotPy conversion helpers.
* Sigima 1.3.0 or later is required for the portable annotation model, PlotPy conversion helpers and curve fitting model formulas.
8 changes: 6 additions & 2 deletions sigimax/locale/fr/LC_MESSAGES/sigimax.po
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,10 @@ msgstr "Connexion échouée : %s"
msgid "Contents of file"
msgstr "Contenu du fichier"

#, python-format
msgid "Model: %s"
msgstr "Modèle : %s"

msgid "Polymomial fit"
msgstr "Ajustement polynomial"

Expand Down Expand Up @@ -395,8 +399,8 @@ msgstr "Ajustement CDF"
msgid "Scale factor"
msgstr "Facteur d'échelle"

msgid "Width factor"
msgstr "Facteur de largeur"
msgid "Temperature factor"
msgstr "Facteur de température"

msgid "Planckian fit"
msgstr "Ajustement planckien"
Expand Down
107 changes: 106 additions & 1 deletion sigimax/tests/widgets/test_fitdialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@
import numpy as np
import pytest
from guidata.qthelpers import qt_app_context
from sigima.objects import NormalDistribution1DParam
from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
from sigima.objects import (
NormalDistribution1DParam,
PlanckParam,
create_signal_from_param,
)
from sigima.tests.data import create_noisy_signal, get_test_signal
from sigima.tools.signal import fitting, pulse
from sigima.tools.signal.peakdetection import peak_indices
Expand Down Expand Up @@ -258,5 +264,104 @@ def test_fit_dialog_bounds_contain_the_optimum(
)


FORMULA_CASES = (
(fdlg.linear_fit, None, fitting.LinearFitComputer.FORMULA),
(
lambda x, y: fdlg.polynomial_fit(x, y, 3),
None,
"y = a x<sup>3</sup> + b x<sup>2</sup> + c x + d",
),
(fdlg.gaussian_fit, None, fitting.GaussianFitComputer.FORMULA),
(fdlg.lorentzian_fit, None, fitting.LorentzianFitComputer.FORMULA),
(fdlg.voigt_fit, None, fitting.VoigtFitComputer.FORMULA),
(fdlg.multigaussian_fit, "peaks", fitting.MultiGaussianFitComputer.FORMULA),
(fdlg.multilorentzian_fit, "peaks", fitting.MultiLorentzianFitComputer.FORMULA),
(fdlg.exponential_fit, None, fitting.ExponentialFitComputer.FORMULA),
(fdlg.sinusoidal_fit, None, fitting.SinusoidalFitComputer.FORMULA),
(fdlg.cdf_fit, None, fitting.CDFFitComputer.FORMULA),
(fdlg.planckian_fit, None, fitting.PlanckianFitComputer.FORMULA),
(fdlg.twohalfgaussian_fit, None, fitting.TwoHalfGaussianFitComputer.FORMULA),
(
fdlg.piecewiseexponential_fit,
None,
fitting.DoubleExponentialFitComputer.FORMULA,
),
)


@pytest.mark.parametrize(("dialog", "extra", "formula"), FORMULA_CASES)
def test_fit_dialogs_show_model_formula(monkeypatch, dialog, extra, formula):
"""Every interactive fit dialog displays the formula of its Sigima model."""
captured = {}

def accept_initial_values(_x, _y, _fitfunc, fitparams, **kwargs):
captured.update(kwargs)
return [param.value for param in fitparams]

monkeypatch.setattr(fdlg, "guifit", accept_initial_values)
signal = get_test_signal("paracetamol.txt")
if extra == "peaks":
assert dialog(signal.x, signal.y, peak_indices(signal.y)) is not None
else:
assert dialog(signal.x, signal.y) is not None
assert captured["formula"] == formula


def test_guifit_displays_formula(monkeypatch):
"""`guifit` adds a selectable label showing the model formula."""
dialogs = []

def reject_dialog(dialog):
dialogs.append(dialog)
return False

monkeypatch.setattr(fdlg, "exec_dialog", reject_dialog)
x = np.linspace(0.0, 1.0, 50)
y = 2.0 * x + 1.0
with qt_app_context():
params = [fdlg.FitParam("a", 1.0, -5.0, 5.0), fdlg.FitParam("b", 0.0, -5, 5)]
formula = fitting.LinearFitComputer.FORMULA
result = fdlg.guifit(
x, y, lambda x, p: p[0] * x + p[1], params, formula=formula
)
assert result is None
label = dialogs[0].findChild(QW.QLabel, "fit_formula")
assert label is not None
assert formula in label.text()
assert label.textInteractionFlags() & QC.Qt.TextSelectableByMouse


def test_exponential_dialog_reaches_short_time_constants(monkeypatch):
"""The rate slider reaches b = -1000 for a 1 ms time constant (X in s).

The dialog model is Sigima's, so the stored parameters reproduce the curve.
"""
captured = __capture_fit_params(monkeypatch)
x = np.linspace(0.0, 5e-3, 500)
y = 2.0 * np.exp(-x / 1e-3) + 0.1

y_fitted, _params, fit_params = fdlg.exponential_fit(x, y)

rate = captured[1]
assert rate.min <= -1000.0 <= rate.max
np.testing.assert_allclose(fitting.evaluate_fit(x, **fit_params), y_fitted)


def test_planckian_dialog_starts_on_blackbody_peak(monkeypatch):
"""On blackbody data, the initial Planckian curve matches the data peak.

The initial amplitude used to be about 147 times too low, and the initial
scale factor could lie outside its slider range.
"""
captured = __capture_fit_params(monkeypatch)
signal = create_signal_from_param(PlanckParam.create(T=296.0))

y_initial, _params, _fit_params = fdlg.planckian_fit(signal.x, signal.y)

assert y_initial.max() == pytest.approx(signal.y.max(), rel=0.01)
for param in captured:
assert param.min <= param.value <= param.max, param.name


if __name__ == "__main__":
test_fit_dialog()
Loading
Loading