Skip to content

Commit 48a995a

Browse files
committed
Add ordinate computation and related functionality
Fix #125 Fix #126
1 parent 56f023f commit 48a995a

File tree

9 files changed

+133
-18
lines changed

9 files changed

+133
-18
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ See DataLab [roadmap page](https://datalab-platform.com/en/contributing/roadmap.
1515
* Added the "Select tool" to editor's toolbar, to allow the user to switch between the "Select" and "Draw" tools easily without having to use the plot toolbar on the top of the window
1616
* Signal processing features ("Processing" menu):
1717
* New "X-Y mode" feature: this feature simulates the behavior of the X-Y mode of an oscilloscope, i.e. it allows to plot one signal as a function of another signal (e.g. X as a function of Y)
18+
* New abscissa and ordinate find features:
19+
* "First abscissa at y=..." feature: this feature allows to find the first abscissa value of a signal at a given y value (e.g. the abscissa value of a signal at y=0)
20+
* "Ordinate at x=..." feature: this feature allows to find the ordinate value of a signal at a given x value (e.g. the ordinate value of a signal at x=0)
21+
* Each feature has its own dialog box, which allows to set the y or x value to be used for the search with a slider or a text box
22+
* This closes [Issue #125](https://github.com./DataLab-Platform/DataLab/issues/125) and [Issue #126](https://github.com./DataLab-Platform/DataLab/issues/126)
1823
* Public API (local or remote):
1924
* Add `group_id` and `set_current` arguments to `add_signal`, `add_image` and `add_object` methods:
2025
* This concerns the `LocalProxy`, `AbstractCDLControl`, `RemoteClient`, `RemoteServer` and `CDLMainWindow` classes

cdl/algorithms/signal.py

+25
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,31 @@ def find_first_x_at_y_value(x: np.ndarray, y: np.ndarray, y_value: float) -> flo
569569
return np.nan # not found
570570

571571

572+
def find_y_at_x_value(x: np.ndarray, y: np.ndarray, x_value: float) -> float:
573+
"""Find the y value at a given x value using linear interpolation.
574+
575+
Args:
576+
x: Monotonic X data
577+
y: Y data (may contain NaNs)
578+
x_value: The x value to find the corresponding y value for
579+
580+
Returns:
581+
The interpolated y value at the given x, or `nan` if not computable
582+
"""
583+
if np.isnan(x_value):
584+
return np.nan
585+
586+
# Filter out NaNs
587+
valid = ~(np.isnan(x) | np.isnan(y))
588+
x_valid = x[valid]
589+
y_valid = y[valid]
590+
591+
if len(x_valid) == 0 or x_value < x_valid[0] or x_value > x_valid[-1]:
592+
return np.nan
593+
594+
return float(np.interp(x_value, x_valid, y_valid))
595+
596+
572597
def find_x_at_value(x: np.ndarray, y: np.ndarray, value: float) -> np.ndarray:
573598
"""Find the x values where the y value is the closest to the given value using
574599
linear interpolation to deduce the precise x value.

cdl/computation/signal.py

+24
Original file line numberDiff line numberDiff line change
@@ -1611,6 +1611,30 @@ def compute_x_at_y(obj: SignalObj, p: FindAbscissaParam) -> ResultProperties:
16111611
)
16121612

16131613

1614+
class FindOrdinateParam(gds.DataSet):
1615+
"""Parameter dataset for ordinate finding"""
1616+
1617+
x = gds.FloatItem(_("Abscissa"), default=0)
1618+
1619+
1620+
def compute_y_at_x(obj: SignalObj, p: FindOrdinateParam) -> ResultProperties:
1621+
"""
1622+
Compute the smallest y-value at a given x-value for a signal object.
1623+
1624+
Args:
1625+
obj: The signal object containing x and y data.
1626+
p: The parameter dataset for finding the ordinate.
1627+
1628+
Returns:
1629+
An object containing the y-value.
1630+
"""
1631+
return calc_resultproperties(
1632+
f"y|x={p.x}",
1633+
obj,
1634+
{"y = %g {.yunit}": lambda xy: alg.find_y_at_x_value(xy[0], xy[1], p.x)},
1635+
)
1636+
1637+
16141638
def compute_stats(obj: SignalObj) -> ResultProperties:
16151639
"""Compute statistics on a signal
16161640

cdl/core/gui/actionhandler.py

+7
Original file line numberDiff line numberDiff line change
@@ -1036,6 +1036,13 @@ def cra_fit(title, fitdlgfunc, iconname, tip: str | None = None):
10361036
"(linear interpolation)"
10371037
),
10381038
)
1039+
self.new_action(
1040+
_("Ordinate at x=..."),
1041+
triggered=self.panel.processor.compute_y_at_x,
1042+
tip=_(
1043+
"Compute the ordinate at a given x value " "(linear interpolation)"
1044+
),
1045+
)
10391046
self.new_action(
10401047
_("Peak detection"),
10411048
separator=True,

cdl/core/gui/processor/signal.py

+17
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,23 @@ def compute_x_at_y(
781781
return
782782
return self.compute_10(cps.compute_x_at_y, param)
783783

784+
@qt_try_except()
785+
def compute_y_at_x(
786+
self, param: cps.FindOrdinateParam | None = None
787+
) -> dict[str, ResultProperties]:
788+
"""Compute y at x with :py:func:`cdl.computation.signal.compute_y_at_x`."""
789+
if param is None:
790+
obj = self.panel.objview.get_sel_objects(include_groups=True)[0]
791+
dlg = signalcursor.SignalCursorDialog(
792+
obj, cursor_orientation="vertical", parent=self.panel.parent()
793+
)
794+
if exec_dialog(dlg):
795+
param = cps.FindOrdinateParam()
796+
param.x = dlg.get_x_value()
797+
else:
798+
return
799+
return self.compute_10(cps.compute_y_at_x, param)
800+
784801
@qt_try_except()
785802
def compute_sampling_rate_period(self) -> dict[str, ResultProperties]:
786803
"""Compute sampling rate and period (mean and std)

cdl/locale/fr/LC_MESSAGES/cdl.po

+21-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
msgid ""
55
msgstr ""
66
"Project-Id-Version: PACKAGE VERSION\n"
7-
"POT-Creation-Date: 2025-04-13 10:06+0200\n"
7+
"POT-Creation-Date: 2025-04-13 11:34+0200\n"
88
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
99
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1010
"Language-Team: LANGUAGE <[email protected]>\n"
@@ -793,6 +793,9 @@ msgstr ""
793793
msgid "Ordinate"
794794
msgstr "Coordonnées"
795795

796+
msgid "Abscissa"
797+
msgstr "Abscisse"
798+
796799
msgid "Full scale"
797800
msgstr "Pleine échelle"
798801

@@ -1214,6 +1217,12 @@ msgid "Compute the first abscissa at a given y value (linear interpolation)"
12141217
msgstr ""
12151218
"Calcule la première abscisse à une valeur y donnée (interpolation linéaire)"
12161219

1220+
msgid "Ordinate at x=..."
1221+
msgstr "Ordonnée à x=..."
1222+
1223+
msgid "Compute the ordinate at a given x value (linear interpolation)"
1224+
msgstr "Calcule l'ordonnée à une valeur x donnée (interpolation linéaire)"
1225+
12171226
msgid "Peak detection"
12181227
msgstr "Détection de pics"
12191228

@@ -2279,9 +2288,6 @@ msgstr "LMH"
22792288
msgid "FW"
22802289
msgstr "LH"
22812290

2282-
msgid "Find abscissa"
2283-
msgstr "Trouver l'abscisse"
2284-
22852291
msgid "Bandwidth"
22862292
msgstr "Bande passante"
22872293

@@ -3362,6 +3368,17 @@ msgstr "Sélectionner la valeur X avec le curseur"
33623368
msgid "Select Y value with cursor"
33633369
msgstr "Sélectionner la valeur Y avec le curseur"
33643370

3371+
msgid "Apply"
3372+
msgstr ""
3373+
3374+
#, fuzzy
3375+
msgid "Apply cursor position"
3376+
msgstr "Position de la barre d'outils des graphiques"
3377+
3378+
#, fuzzy
3379+
msgid "Cursor position"
3380+
msgstr "Position du centre"
3381+
33653382
msgid "Minimum distance:"
33663383
msgstr "Distance minimale :"
33673384

cdl/tests/features/signals/analysis_unit_test.py

+15
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,25 @@ def test_signal_x_at_y() -> None:
8484
check_scalar_result("x|y=0.5", df["x"][0], 0.0)
8585

8686

87+
@pytest.mark.validation
88+
def test_signal_y_at_x() -> None:
89+
"""Validation test for the ordinate finding computation."""
90+
newparam = cdl.obj.new_signal_param(
91+
stype=cdl.obj.SignalTypes.TRIANGLE, xmin=0.0, xmax=10.0, size=101
92+
)
93+
obj = cdl.obj.create_signal_from_param(newparam)
94+
if obj is None:
95+
raise ValueError("Failed to create test signal")
96+
param = cps.FindOrdinateParam.create(x=2.5)
97+
df = cps.compute_y_at_x(obj, param).to_dataframe()
98+
check_scalar_result("y|x=0.5", df["y"][0], 1.0)
99+
100+
87101
if __name__ == "__main__":
88102
test_signal_bandwidth_3db()
89103
test_dynamic_parameters()
90104
test_signal_sampling_rate_period()
91105
test_signal_contrast()
92106
test_signal_x_at_minmax()
93107
test_signal_x_at_y()
108+
test_signal_y_at_x()

cdl/tests/features/signals/select_xy_cursor_unit_test.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from typing import Literal
1111

1212
import numpy as np
13+
import pytest
1314
from guidata.qthelpers import exec_dialog, qt_app_context
1415

1516
from cdl.algorithms.signal import find_first_x_at_y_value
@@ -18,10 +19,11 @@
1819
from cdl.widgets.signalcursor import SignalCursorDialog
1920

2021

22+
@pytest.mark.parametrize("cursor_orientation", ["horizontal", "vertical"])
2123
def test_signal_cursor_selection(
2224
cursor_orientation: Literal["horizontal", "vertical"],
2325
) -> None:
24-
"""Signal cursor selection unit test."""
26+
"""Parametrized signal cursor selection unit test."""
2527
sig = create_paracetamol_signal()
2628
with qt_app_context():
2729
dlg = SignalCursorDialog(signal=sig, cursor_orientation=cursor_orientation)

doc/locale/fr/LC_MESSAGES/contributing/changelog.po

+16-13
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ msgid ""
77
msgstr ""
88
"Project-Id-Version: DataLab \n"
99
"Report-Msgid-Bugs-To: \n"
10-
"POT-Creation-Date: 2025-04-08 11:05+0200\n"
10+
"POT-Creation-Date: 2025-04-13 11:52+0200\n"
1111
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1212
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1313
"Language: fr\n"
@@ -57,6 +57,21 @@ msgstr "Fonctionnalités de traitement du signal (menu \"Traitement\") :"
5757
msgid "New \"X-Y mode\" feature: this feature simulates the behavior of the X-Y mode of an oscilloscope, i.e. it allows to plot one signal as a function of another signal (e.g. X as a function of Y)"
5858
msgstr "Nouvelle fonctionnalité \"Mode X-Y\" : cette fonctionnalité simule le comportement du mode X-Y d'un oscilloscope, c'est-à-dire qu'elle permet de tracer un signal en fonction d'un autre signal (par exemple X en fonction de Y)"
5959

60+
msgid "New abscissa and ordinate find features:"
61+
msgstr "Nouvelles fonctionnalités de recherche d'abscisse et d'ordonnée :"
62+
63+
msgid "\"First abscissa at y=...\" feature: this feature allows to find the first abscissa value of a signal at a given y value (e.g. the abscissa value of a signal at y=0)"
64+
msgstr "Fonctionnalité \"Abscisse à y=...\" : cette fonctionnalité permet de trouver la première valeur d'abscisse d'un signal à une valeur y donnée (par exemple, la valeur d'abscisse d'un signal à y=0)"
65+
66+
msgid "\"Ordinate at x=...\" feature: this feature allows to find the ordinate value of a signal at a given x value (e.g. the ordinate value of a signal at x=0)"
67+
msgstr "Fonctionnalité \"Ordonnée à x=...\" : cette fonctionnalité permet de trouver la valeur d'ordonnée d'un signal à une valeur x donnée (par exemple, la valeur d'ordonnée d'un signal à x=0)"
68+
69+
msgid "Each feature has its own dialog box, which allows to set the y or x value to be used for the search with a slider or a text box"
70+
msgstr "Chaque fonctionnalité a sa propre boîte de dialogue, qui permet de définir la valeur y ou x à utiliser pour la recherche avec un curseur ou une zone de texte"
71+
72+
msgid "This closes [Issue #125](https://github.com./DataLab-Platform/DataLab/issues/125) and [Issue #126](https://github.com./DataLab-Platform/DataLab/issues/126)"
73+
msgstr "Ceci clotûre les tickets [Issue #125](https://github.com./DataLab-Platform/DataLab/issues/125) et [Issue #126](https://github.com./DataLab-Platform/DataLab/issues/126)"
74+
6075
msgid "Public API (local or remote):"
6176
msgstr "API publique (locale ou distante) :"
6277

@@ -129,9 +144,6 @@ msgstr "Correction de l'[Issue #165](https://github.com./DataLab-Platform/DataLab
129144
msgid "DataLab Version 0.19.0"
130145
msgstr "DataLab Version 0.19.0"
131146

132-
msgid "💥 New features and enhancements:"
133-
msgstr "💥 Nouvelles fonctionnalités et améliorations :"
134-
135147
msgid "Image operation features (\"Operations\" menu):"
136148
msgstr "Fonctionnalités d'opération sur les images (menu \"Opérations\") :"
137149

@@ -141,9 +153,6 @@ msgstr "Renommage du sous-menu \"Rotation\" en \"Symétrie ou rotation\""
141153
msgid "New \"Flip diagonally\" feature"
142154
msgstr "Nouvelle fonctionnalité \"Symétrie diagonale\""
143155

144-
msgid "Signal processing features (\"Processing\" menu):"
145-
msgstr "Fonctionnalités de traitement du signal (menu \"Traitement\") :"
146-
147156
msgid "New \"Convert to Cartesian coordinates\" feature"
148157
msgstr "Nouvelle fonctionnalité \"Convertir en coordonnées cartésiennes\""
149158

@@ -183,9 +192,6 @@ msgstr "Ajout de la méthode `compute_inverse` pour les processeurs d'image et d
183192
msgid "This closes [Issue #143](https://github.com./DataLab-Platform/DataLab/issues/143) - New feature: `1/x` for signals and images"
184193
msgstr "Ceci corrige l'[Issue #143](https://github.com./DataLab-Platform/DataLab/issues/143) - Nouvelle fonctionnalité : `1/x` pour les signaux et les images"
185194

186-
msgid "Public API (local or remote):"
187-
msgstr "API publique (locale ou distante) :"
188-
189195
msgid "Add `add_group` method with `title` and `select` arguments to create a new group in a data panel (e.g. Signal or Image panel) and eventually select it after creation:"
190196
msgstr "Ajout de la méthode `add_group` avec les arguments `title` et `select` pour créer un nouveau groupe dans un panneau de données (par exemple, panneau Signal ou Image) et éventuellement le sélectionner après sa création :"
191197

@@ -258,9 +264,6 @@ msgstr "La ROI polygonale n'est pas encore prise en charge"
258264
msgid "This closes [Issue #145](https://github.com./DataLab-Platform/DataLab/issues/145) - ROI editor: add manual input of the coordinates"
259265
msgstr "Ceci clotûre le ticket [Issue #145](https://github.com./DataLab-Platform/DataLab/issues/145) - Éditeur de ROI : ajouter la saisie manuelle des coordonnées"
260266

261-
msgid "🛠️ Bug fixes:"
262-
msgstr "🛠️ Corrections de bugs :"
263-
264267
msgid "Fixed [Issue #141](https://github.com./DataLab-Platform/DataLab/issues/141) - Image analysis: mask `nan` values when computing statistics, for example"
265268
msgstr "Correction de l'[Issue #141](https://github.com./DataLab-Platform/DataLab/issues/141) - Analyse d'image : masque des valeurs `nan` lors du calcul des statistiques, par exemple"
266269

0 commit comments

Comments
 (0)