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
9 changes: 7 additions & 2 deletions datalab/adapters_plotpy/objects/signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import numpy as np
from guidata.dataset import restore_dataset, update_dataset
from plotpy.builder import make
from plotpy.items import CurveItem
from plotpy.items import CurveItem, ErrorBarCurveItem
from sigima.objects import SignalObj

from datalab.adapters_plotpy.objects.base import (
Expand Down Expand Up @@ -245,7 +245,12 @@ def update_item(self, item: CurveItem, data_changed: bool = True) -> None:
and isinstance(dx, np.ndarray)
and isinstance(dy, np.ndarray)
)
item.set_data(x.real, y.real, dx.real, dy.real)
if isinstance(item, ErrorBarCurveItem):
item.set_data(x.real, y.real, dx.real, dy.real)
else:
# xydata has 4 rows but dx/dy are all NaN (no real
# error bars) — the plot item is a plain CurveItem
item.set_data(x.real, y.real)
item.param.label = o.title
apply_downsampling(item)
# Reapply linewidth with smart clamping (data size may have changed)
Expand Down
12 changes: 12 additions & 0 deletions datalab/data/icons/processing/replace_nan.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions datalab/gui/actionhandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,7 @@ def create_first_actions(self):
with self.new_menu(_("Level adjustment"), icon_name="level_adjustment.svg"):
self.action_for("normalize")
self.action_for("clip")
self.action_for("replace_special_values")
self.new_action(
_("Offset correction"),
triggered=self.panel.processor.compute_offset_correction,
Expand Down
9 changes: 9 additions & 0 deletions datalab/gui/processor/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
from datalab.objectmodel import get_uuid
from datalab.utils.qthelpers import create_progress_bar, qt_try_except
from datalab.widgets import imagebackground
from datalab.widgets.replacespecialvalues import (
ReplaceSpecialValuesImageParamDL,
)


class ImageProcessor(BaseProcessor[ImageROI, ROI2DParam]):
Expand Down Expand Up @@ -279,6 +282,12 @@ def register_processing(self) -> None:
icon_name="normalize.svg",
)
self.register_1_to_1(sipi.clip, _("Clipping"), sipb.ClipParam, "clip.svg")
self.register_1_to_1(
sipi.replace_special_values,
_("Replace special values"),
ReplaceSpecialValuesImageParamDL,
"replace_nan.svg",
)
self.register_1_to_1(
sipi.offset_correction,
_("Offset correction"),
Expand Down
9 changes: 9 additions & 0 deletions datalab/gui/processor/signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
signaldeltax,
signalpeak,
)
from datalab.widgets.replacespecialvalues import (
ReplaceSpecialValuesSignalParamDL,
)


class SignalProcessor(BaseProcessor[SignalROI, ROI1DParam]):
Expand Down Expand Up @@ -236,6 +239,12 @@ def register_processing(self) -> None:
self.register_1_to_1(
sips.clip, _("Clipping"), sigima_base.ClipParam, "clip.svg"
)
self.register_1_to_1(
sips.replace_special_values,
_("Replace special values"),
ReplaceSpecialValuesSignalParamDL,
"replace_nan.svg",
)
self.register_1_to_1(
sips.offset_correction,
_("Offset correction"),
Expand Down
23 changes: 22 additions & 1 deletion datalab/locale/fr/LC_MESSAGES/datalab.po
Original file line number Diff line number Diff line change
Expand Up @@ -1677,7 +1677,6 @@ msgstr "Enregistrer l'historique dans un fichier HDF5..."
msgid "Save history to a standalone .dlhist file"
msgstr "Enregistrer l'historique dans un fichier .dlhist autonome"

#, python-format
msgid "Duplicate selected history action/session"
msgstr "Dupliquer l'objet %s sélectionné"

Expand Down Expand Up @@ -2169,6 +2168,9 @@ msgstr "Normaliser"
msgid "Clipping"
msgstr "Écrêtage"

msgid "Replace special values"
msgstr "Remplacer les valeurs spéciales"

msgid "Add Gaussian noise"
msgstr "Ajouter du bruit gaussien"

Expand Down Expand Up @@ -3772,6 +3774,21 @@ msgstr "Des journaux de bord ont été générés lors de la dernière session."
msgid "Log files are currently empty."
msgstr "Les journaux de bord sont vides."

msgid "Kernel / Mask preview"
msgstr "Aperçu du noyau / masque"

#, python-brace-format
msgid "{label} — Kernel / Mask preview"
msgstr "{label} — Aperçu du noyau / masque"

#, python-brace-format
msgid "±{n} points"
msgstr "±{n} points"

#, python-brace-format
msgid "±{n} rows × ±{n} columns"
msgstr "±{n} lignes × ±{n} colonnes"

msgid "Signal baseline selection"
msgstr "Sélection de la ligne de base du signal"

Expand Down Expand Up @@ -4113,6 +4130,9 @@ msgstr "Image"
msgid "Dimensions"
msgstr "Dimensions"

msgid "This image uses an integer data type, so it cannot contain NaN or infinite values. Replace special values is therefore not applicable."
msgstr "Cette image utilise un type de données entier, elle ne peut donc pas contenir de valeurs NaN ou infinies. Le remplacement des valeurs spéciales n'est donc pas applicable."

msgid "Minimum value"
msgstr "Valeur minimum"

Expand Down Expand Up @@ -4160,3 +4180,4 @@ msgstr "Aucun contour n'a été trouvé pour la plage de niveaux sélectionnée.

msgid "Show contour plot..."
msgstr "Afficher le tracé de contours..."

108 changes: 108 additions & 0 deletions datalab/tests/features/image/replace_special_values_app_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.

"""
Replace special values application test for images.

This test verifies DataLab processor integration for ``replace_special_values``.
Algorithm correctness (including Inf handling) is covered by Sigima unit tests.
Only NaN values are injected here to avoid Qt plot instability with Inf data.
"""

# pylint: disable=invalid-name # Allows short reference names like x, y, ...
# guitest: show

from __future__ import annotations

import numpy as np
import sigima.params
from qtpy import QtWidgets as QW
from sigima.enums import ReplacementStrategyImage as S
from sigima.objects import ImageObj, create_image

from datalab.tests import datalab_test_app_context
from datalab.widgets.replacespecialvalues import ReplaceSpecialValuesImageParamDL


def test_replace_special_values_image_app():
"""Test image replace special values through DataLab processor."""
with datalab_test_app_context(console=False) as win:
panel = win.imagepanel

# Use NaN-only data: Inf values destabilize Qt image plot rendering/teardown
data = np.array(
[
[1.0, np.nan, 3.0],
[4.0, 5.0, np.nan],
[7.0, 8.0, 9.0],
]
)
image = create_image("Image with NaN values", data)
panel.add_object(image)
panel.objview.select_objects([image])

param = sigima.params.ReplaceSpecialValuesImageParam.create(
nan_strategy=S.CONSTANT,
nan_constant_value=10.0,
)
panel.processor.run_feature("replace_special_values", param, edit=False)

result_objects = panel.objview.get_sel_objects()
assert len(result_objects) == 1
result = result_objects[0]
assert isinstance(result, ImageObj)
assert not np.isnan(result.data).any()
np.testing.assert_array_equal(
result.data,
np.array(
[
[1.0, 10.0, 3.0],
[4.0, 5.0, 10.0],
[7.0, 8.0, 9.0],
]
),
)


def test_replace_special_values_integer_image_app_noop():
"""Integer images should keep their data unchanged and emit a warning path."""
with datalab_test_app_context(console=False) as win:
panel = win.imagepanel

data = np.arange(9, dtype=np.uint16).reshape(3, 3)
image = create_image("Integer image", data)
panel.add_object(image)
panel.objview.select_objects([image])

param = sigima.params.ReplaceSpecialValuesImageParam.create(
nan_strategy=S.CONSTANT,
nan_constant_value=10.0,
)
panel.processor.run_feature("replace_special_values", param, edit=False)

result_objects = panel.objview.get_sel_objects()
assert len(result_objects) == 1
result = result_objects[0]
assert isinstance(result, ImageObj)
assert result is not image
assert result.data.dtype == np.uint16
np.testing.assert_array_equal(result.data, data)


def test_replace_special_values_integer_image_dialog_disabled():
"""The custom dialog should inform the user and disable validation."""
with datalab_test_app_context(console=False):
data = np.arange(9, dtype=np.uint16).reshape(3, 3)
image = create_image("Integer image", data)

param = ReplaceSpecialValuesImageParamDL.create()
param.update_from_obj(image)

dlg = param.create_dialog()
ok_button = dlg.findChild(QW.QDialogButtonBox).button(QW.QDialogButtonBox.Ok)
assert ok_button is not None
assert not ok_button.isEnabled()
dlg.close()


if __name__ == "__main__":
test_replace_special_values_image_app()
51 changes: 51 additions & 0 deletions datalab/tests/features/signal/replace_special_values_app_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.

"""
Replace special values application test for signals.

This test verifies DataLab processor integration for ``replace_special_values``.
Algorithm correctness (including Inf handling) is covered by Sigima unit tests.
Only NaN values are injected here to avoid Qt plot instability with Inf data.
"""

# pylint: disable=invalid-name # Allows short reference names like x, y, ...
# guitest: show

from __future__ import annotations

import numpy as np
import sigima.params
from sigima.enums import ReplacementStrategySignal as S
from sigima.objects import SignalObj, create_signal

from datalab.tests import datalab_test_app_context


def test_replace_special_values_signal_app():
"""Test signal replace special values through DataLab processor."""
with datalab_test_app_context(console=False) as win:
panel = win.signalpanel

# Use NaN-only data: Inf values destabilize Qt signal plot rendering
x = np.arange(5, dtype=float)
y = np.array([1.0, np.nan, 3.0, np.nan, 5.0])
sig = create_signal("Signal with NaN values", x, y)
panel.add_object(sig)
panel.objview.select_objects([sig])

param = sigima.params.ReplaceSpecialValuesSignalParam.create(
nan_strategy=S.CONSTANT,
nan_constant_value=10.0,
)
panel.processor.run_feature("replace_special_values", param, edit=False)

result_objects = panel.objview.get_sel_objects()
assert len(result_objects) == 1
result = result_objects[0]
assert isinstance(result, SignalObj)
assert not np.isnan(result.y).any()
np.testing.assert_array_equal(result.y, [1.0, 10.0, 3.0, 10.0, 5.0])


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