From 4e0eeb7f565aa78aca983acf2358f755c2e2a043 Mon Sep 17 00:00:00 2001 From: Stefano Pierini Date: Thu, 28 May 2026 17:53:07 +0200 Subject: [PATCH 1/6] fix: prevent re-creation of ROIs during auto-recompute after post-processing --- datalab/gui/processor/base.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/datalab/gui/processor/base.py b/datalab/gui/processor/base.py index 0d4a659c..eb9bcddb 100644 --- a/datalab/gui/processor/base.py +++ b/datalab/gui/processor/base.py @@ -1445,6 +1445,12 @@ def compute_1_to_0( # Apply processor-specific post-processing on the result refresh_needed |= self.postprocess_1_to_0_result(obj, result) + # If post-processing created ROIs (e.g., contour detection), + # disable ROI creation in the stored parameters to prevent + # re-creation during auto-recompute on ROI change. + if refresh_needed and hasattr(param, "create_rois"): + param.create_rois = False + # Append result to result data for later display rdata.append(adapter, obj) From dcae828fe98736feaa8c37f0b752af5a95262985 Mon Sep 17 00:00:00 2001 From: Stefano Pierini Date: Fri, 29 May 2026 11:33:07 +0200 Subject: [PATCH 2/6] fix: ensure ROI index is within bounds before accessing title --- datalab/adapters_metadata/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datalab/adapters_metadata/common.py b/datalab/adapters_metadata/common.py index 979e9ff1..f3d0ce63 100644 --- a/datalab/adapters_metadata/common.py +++ b/datalab/adapters_metadata/common.py @@ -88,7 +88,7 @@ def append(self, adapter: BaseResultAdapter, obj: SignalObj | ImageObj) -> None: if "roi_index" in df.columns: i_roi = int(df.iloc[i_row_res]["roi_index"]) roititle = "" - if i_roi >= 0 and obj.roi is not None: + if i_roi >= 0 and obj.roi is not None and i_roi < len(obj.roi): roititle = obj.roi.get_single_roi_title(i_roi) ylabel += f"|{roititle}" self.ylabels.append(ylabel) From 77637e4796697f1bb8b86ac3de5eaca17cb09698 Mon Sep 17 00:00:00 2001 From: Stefano Pierini Date: Fri, 29 May 2026 16:49:34 +0200 Subject: [PATCH 3/6] fix: avoid ROI to reappear after complete suppression --- datalab/gui/processor/base.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/datalab/gui/processor/base.py b/datalab/gui/processor/base.py index eb9bcddb..79b4ed49 100644 --- a/datalab/gui/processor/base.py +++ b/datalab/gui/processor/base.py @@ -989,6 +989,13 @@ def auto_recompute_analysis( # Get the parameter from processing parameters param = proc_params.param + # Disable ROI creation during auto-recompute: detection functions store + # create_rois=True in their parameters, but auto-recompute should only + # update analysis results, not recreate ROIs (which would make them + # impossible to delete or modify). + if hasattr(param, "create_rois"): + param.create_rois = False + # Get the actual function from the function name feature = self.get_feature(proc_params.func_name) @@ -1445,12 +1452,6 @@ def compute_1_to_0( # Apply processor-specific post-processing on the result refresh_needed |= self.postprocess_1_to_0_result(obj, result) - # If post-processing created ROIs (e.g., contour detection), - # disable ROI creation in the stored parameters to prevent - # re-creation during auto-recompute on ROI change. - if refresh_needed and hasattr(param, "create_rois"): - param.create_rois = False - # Append result to result data for later display rdata.append(adapter, obj) From 49410ef080b0fc28e1b19f625a271c36899c6218 Mon Sep 17 00:00:00 2001 From: Stefano Pierini Date: Thu, 11 Jun 2026 15:22:22 +0200 Subject: [PATCH 4/6] Add confirmation request in case of ROI override --- datalab/gui/processor/base.py | 26 +++ datalab/gui/processor/image.py | 46 ++++ datalab/locale/fr/LC_MESSAGES/datalab.po | 3 + .../image/detection_roi_replace_unit_test.py | 200 ++++++++++++++++++ 4 files changed, 275 insertions(+) create mode 100644 datalab/tests/features/image/detection_roi_replace_unit_test.py diff --git a/datalab/gui/processor/base.py b/datalab/gui/processor/base.py index 79b4ed49..fd421fed 100644 --- a/datalab/gui/processor/base.py +++ b/datalab/gui/processor/base.py @@ -792,6 +792,30 @@ def register_computations(self) -> None: self.register_processing() self.register_analysis() + # pylint: disable=unused-argument + def preprocess_1_to_0( + self, + func: Callable, + param: gds.DataSet | None, + objs: list[SignalObj | ImageObj], + ) -> bool: + """Pre-check hook for 1-to-0 operations (hook method). + + This method is called before a 1-to-0 computation starts, before the + progress dialog is opened. Subclasses can override this method to perform + pre-checks or ask for user confirmation. Return ``False`` to abort the + computation. + + Args: + func: The computation function that will be called + param: Optional parameter set + objs: List of objects that will be processed + + Returns: + True to proceed with the computation, False to abort + """ + return True + # pylint: disable=unused-argument def postprocess_1_to_0_result( self, obj: SignalObj | ImageObj, result: GeometryResult | TableResult @@ -1401,6 +1425,8 @@ def compute_1_to_0( if target_objs is not None else self.panel.objview.get_sel_objects(include_groups=True) ) + if not self.preprocess_1_to_0(func, param, objs): + return None current_obj = self.panel.objview.get_current_object() title = func.__name__ if title is None else title refresh_needed = False diff --git a/datalab/gui/processor/image.py b/datalab/gui/processor/image.py index 8e4bbbca..733c97c6 100644 --- a/datalab/gui/processor/image.py +++ b/datalab/gui/processor/image.py @@ -8,6 +8,7 @@ from __future__ import annotations +import guidata.dataset as gds import numpy as np import sigima.params import sigima.proc.base as sipb @@ -25,6 +26,7 @@ ) from sigima.objects.scalar import GeometryResult, TableResult +from datalab import env from datalab.config import APP_NAME, _ from datalab.gui.processor.base import BaseProcessor from datalab.gui.processor.geometry_postprocess import ( @@ -55,6 +57,50 @@ def _wrap_geometric_transform(self, func, operation: str): """ return GeometricTransformWrapper(func, operation) + def preprocess_1_to_0( + self, + func, + param: gds.DataSet | None, + objs: list[ImageObj], + ) -> bool: + """Override to confirm ROI replacement before the progress bar opens. + + When the parameter has ``create_rois=True`` and at least one selected + image already has ROIs, the user is warned that the existing ROIs will + be replaced. + + Args: + func: The computation function that will be called + param: Optional parameter set + objs: List of image objects that will be processed + + Returns: + True to proceed with the computation, False to abort + """ + if ( + param is not None + and getattr(param, "create_rois", False) + and not env.execenv.unattended + and any(obj.roi is not None and not obj.roi.is_empty() for obj in objs) + ): + return ( + QW.QMessageBox.question( + self.mainwindow, + _("Warning"), + _( + "Regions of interest are already defined for this " + "image.

" + "Creating new ROIs from detection will replace the " + "existing ones, which will be lost.

" + "Do you want to continue?" + ), + QW.QMessageBox.Yes | QW.QMessageBox.No, + QW.QMessageBox.No, + ) + == QW.QMessageBox.Yes + ) + return True + def postprocess_1_to_0_result( self, obj: ImageObj, result: GeometryResult | TableResult ) -> bool: diff --git a/datalab/locale/fr/LC_MESSAGES/datalab.po b/datalab/locale/fr/LC_MESSAGES/datalab.po index 35af47c3..a70427dc 100644 --- a/datalab/locale/fr/LC_MESSAGES/datalab.po +++ b/datalab/locale/fr/LC_MESSAGES/datalab.po @@ -2430,6 +2430,9 @@ msgstr "Détection de taches basée sur SimpleBlobDetector d'OpenCV" msgid "Creating a ROI grid will overwrite any existing ROI.

Do you want to continue?" msgstr "La création d'une grille de ROI écrasera toute ROI existante.

Voulez-vous continuer ?" +msgid "Regions of interest are already defined for this image.

Creating new ROIs from detection will replace the existing ones, which will be lost.

Do you want to continue?" +msgstr "Des régions d'intérêt sont déjà définies pour cette image.

La création de nouvelles ROI par détection remplacera les existantes, qui seront perdues.

Voulez-vous continuer ?" + msgid "Extract ROI" msgstr "Extraire une ROI" diff --git a/datalab/tests/features/image/detection_roi_replace_unit_test.py b/datalab/tests/features/image/detection_roi_replace_unit_test.py new file mode 100644 index 00000000..3f701077 --- /dev/null +++ b/datalab/tests/features/image/detection_roi_replace_unit_test.py @@ -0,0 +1,200 @@ +# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file. + +""" +Detection ROI replacement confirmation test + +Testing the following: + - When create_rois=True and image already has ROIs, the preprocess hook + runs before the progress bar and can abort the computation + - In unattended mode (automated tests) the dialog is skipped and ROIs + are always replaced + - When create_rois=False, existing ROIs are left untouched + - When no existing ROIs are present, ROI creation proceeds normally + - When the user cancels the confirmation dialog, existing ROIs are preserved + - The confirmation dialog is shown only when ROIs already exist +""" + +# guitest: show + +from __future__ import annotations + +from unittest.mock import patch + +import sigima.params +import sigima.proc.image as sipi +from qtpy import QtWidgets as QW +from sigima.objects import NewImageParam, create_image_roi +from sigima.tests.data import create_multigaussian_image, create_peak_image + +from datalab.env import execenv +from datalab.tests import datalab_test_app_context +from datalab.tests.features.image.roi_app_test import IROI1, IROI2 + + +def _create_image_with_roi(): + """Return a multigaussian image that already has ROIs defined.""" + newparam = NewImageParam.create(height=200, width=200) + ima = create_multigaussian_image(newparam) + roi = create_image_roi("rectangle", IROI1) + roi.add_roi(create_image_roi("circle", IROI2)) + ima.roi = roi + return ima + + +def test_create_rois_no_existing_roi(): + """ROIs are created normally when the image has no pre-existing ROI.""" + with datalab_test_app_context() as win: + panel = win.imagepanel + ima = create_peak_image() + assert ima.roi is None + panel.add_object(ima) + + param = sigima.params.Peak2DDetectionParam.create(create_rois=True) + result = panel.processor.compute_peak_detection(param) + assert result is not None, "Peak detection should return results" + + obj = panel.objview.get_current_object() + assert obj.roi is not None, "ROI should be created when create_rois=True" + assert not obj.roi.is_empty(), "Created ROI should not be empty" + + +def test_create_rois_with_existing_roi_unattended(): + """In unattended mode, existing ROIs are silently replaced (no dialog).""" + with datalab_test_app_context() as win: + panel = win.imagepanel + ima = _create_image_with_roi() + initial_roi = ima.roi + panel.add_object(ima) + + # execenv.unattended is True in the test suite: the dialog is skipped + assert execenv.unattended, "This test requires unattended mode" + + param = sigima.params.Peak2DDetectionParam.create(create_rois=True) + result = panel.processor.compute_peak_detection(param) + assert result is not None, "Peak detection should return results" + + obj = panel.objview.get_current_object() + assert obj.roi is not None, "ROI should be present after detection" + assert obj.roi != initial_roi, ( + "Existing ROI should have been replaced in unattended mode" + ) + + +def test_create_rois_false_preserves_existing_roi(): + """When create_rois=False, existing ROIs are never touched.""" + with datalab_test_app_context() as win: + panel = win.imagepanel + ima = _create_image_with_roi() + panel.add_object(ima) + + obj = panel.objview.get_current_object() + roi_before = obj.roi + + param = sigima.params.Peak2DDetectionParam.create(create_rois=False) + panel.processor.compute_peak_detection(param) + + obj = panel.objview.get_current_object() + assert obj.roi == roi_before, ( + "Existing ROI must not be modified when create_rois=False" + ) + + +def test_dialog_shown_only_when_roi_exists(): + """The confirmation dialog is triggered only when the image already has ROIs. + + - With existing ROIs and create_rois=True: preprocess_1_to_0 calls the dialog + - Without existing ROIs and create_rois=True: preprocess_1_to_0 returns True + directly, without opening any dialog + """ + with datalab_test_app_context() as win: + panel = win.imagepanel + param = sigima.params.Peak2DDetectionParam.create(create_rois=True) + + # Case 1: image with no ROI — dialog must NOT be shown + ima_no_roi = create_peak_image() + panel.add_object(ima_no_roi) + objs_no_roi = panel.objview.get_sel_objects(include_groups=True) + + with patch.object(QW.QMessageBox, "question") as mock_question: + execenv.unattended = False + try: + result = panel.processor.preprocess_1_to_0( + sipi.peak_detection, param, objs_no_roi + ) + finally: + execenv.unattended = True + + assert result is True, "Should proceed when no existing ROIs" + mock_question.assert_not_called() + + # Case 2: image with existing ROI — dialog MUST be shown + ima_with_roi = _create_image_with_roi() + panel.add_object(ima_with_roi) + objs_with_roi = panel.objview.get_sel_objects(include_groups=True) + + with patch.object( + QW.QMessageBox, "question", return_value=QW.QMessageBox.Yes + ) as mock_question: + execenv.unattended = False + try: + result = panel.processor.preprocess_1_to_0( + sipi.peak_detection, param, objs_with_roi + ) + finally: + execenv.unattended = True + + assert result is True, "Should proceed when user confirms" + mock_question.assert_called_once() + + +def test_cancel_dialog_preserves_existing_roi(): + """When the user cancels the confirmation dialog, existing ROIs are preserved.""" + with datalab_test_app_context() as win: + panel = win.imagepanel + ima = _create_image_with_roi() + panel.add_object(ima) + + obj = panel.objview.get_current_object() + roi_before = obj.roi + + param = sigima.params.Peak2DDetectionParam.create(create_rois=True) + + # Simulate the user clicking "No" in the confirmation dialog + with patch.object(QW.QMessageBox, "question", return_value=QW.QMessageBox.No): + execenv.unattended = False + try: + result = panel.processor.compute_peak_detection(param) + finally: + execenv.unattended = True + + assert result is None, "Computation should be aborted when user cancels" + obj = panel.objview.get_current_object() + assert obj.roi == roi_before, ( + "Existing ROI must be preserved when user cancels the dialog" + ) + + +def test_preprocess_hook_abort_skipped_in_unattended(): + """preprocess_1_to_0 returns True in unattended mode (no blocking dialog).""" + with datalab_test_app_context() as win: + panel = win.imagepanel + ima = _create_image_with_roi() + panel.add_object(ima) + + assert execenv.unattended, "This test requires unattended mode" + + param = sigima.params.Peak2DDetectionParam.create(create_rois=True) + objs = panel.objview.get_sel_objects(include_groups=True) + + # In unattended mode the hook must always return True (no dialog shown) + result = panel.processor.preprocess_1_to_0(sipi.peak_detection, param, objs) + assert result is True, "preprocess_1_to_0 must return True in unattended mode" + + +if __name__ == "__main__": + test_create_rois_no_existing_roi() + test_create_rois_with_existing_roi_unattended() + test_create_rois_false_preserves_existing_roi() + test_dialog_shown_only_when_roi_exists() + test_cancel_dialog_preserves_existing_roi() + test_preprocess_hook_abort_skipped_in_unattended() From 1b7544e8714ebe62393526278b61c1f6b144f3ad Mon Sep 17 00:00:00 2001 From: Stefano Pierini Date: Fri, 12 Jun 2026 16:23:10 +0200 Subject: [PATCH 5/6] add missing test --- .../image/detection_roi_replace_unit_test.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/datalab/tests/features/image/detection_roi_replace_unit_test.py b/datalab/tests/features/image/detection_roi_replace_unit_test.py index 3f701077..b7a3f190 100644 --- a/datalab/tests/features/image/detection_roi_replace_unit_test.py +++ b/datalab/tests/features/image/detection_roi_replace_unit_test.py @@ -191,6 +191,43 @@ def test_preprocess_hook_abort_skipped_in_unattended(): assert result is True, "preprocess_1_to_0 must return True in unattended mode" +def test_auto_recompute_does_not_replace_rois(): + """auto_recompute_analysis must not recreate ROIs deleted by the user. + + Scenario: + 1. Run peak detection with create_rois=True → ROIs are created and the + analysis parameters (including create_rois=True) are stored in the + object's metadata. + 2. The user deletes the ROIs manually. + 3. auto_recompute_analysis is triggered (e.g. after a data change). + 4. The ROIs must NOT be recreated: auto_recompute_analysis disables + create_rois before calling compute_1_to_0. + """ + with datalab_test_app_context() as win: + panel = win.imagepanel + ima = create_peak_image() + panel.add_object(ima) + + # Step 1: run detection with ROI creation to store analysis params + param = sigima.params.Peak2DDetectionParam.create(create_rois=True) + panel.processor.compute_peak_detection(param) + + obj = panel.objview.get_current_object() + assert obj.roi is not None, "Peak detection should have created ROIs" + + # Step 2: user deletes the ROIs + obj.roi = None + + # Step 3 & 4: auto-recompute must NOT recreate the ROIs + panel.processor.auto_recompute_analysis(obj) + + obj = panel.objview.get_current_object() + assert obj.roi is None, ( + "auto_recompute_analysis must not recreate ROIs " + "(create_rois is disabled during auto-recompute)" + ) + + if __name__ == "__main__": test_create_rois_no_existing_roi() test_create_rois_with_existing_roi_unattended() @@ -198,3 +235,4 @@ def test_preprocess_hook_abort_skipped_in_unattended(): test_dialog_shown_only_when_roi_exists() test_cancel_dialog_preserves_existing_roi() test_preprocess_hook_abort_skipped_in_unattended() + test_auto_recompute_does_not_replace_rois() From b0ea4e6f905d46c4671d19d80f4eba9eee162a9f Mon Sep 17 00:00:00 2001 From: Stefano Pierini Date: Fri, 12 Jun 2026 16:35:37 +0200 Subject: [PATCH 6/6] add missing and fix translations --- datalab/locale/fr/LC_MESSAGES/datalab.po | 268 ++--------------------- 1 file changed, 22 insertions(+), 246 deletions(-) diff --git a/datalab/locale/fr/LC_MESSAGES/datalab.po b/datalab/locale/fr/LC_MESSAGES/datalab.po index a70427dc..7a176556 100644 --- a/datalab/locale/fr/LC_MESSAGES/datalab.po +++ b/datalab/locale/fr/LC_MESSAGES/datalab.po @@ -953,38 +953,6 @@ msgstr "Troncature de données uint32 en int32." msgid "No supported data available in HDF5 file(s)." msgstr "Aucune donnée prise en charge dans le(s) fichier(s) HDF5." -msgid "Generate macro" -msgstr "Générer une macro" - -msgid "No compute actions to export." -msgstr "Pas d'actions de calcul à exporter." - -#, python-format -msgid "Macro script copied to clipboard (%d actions)." -msgstr "Script de macro copié dans le presse-papier (%d actions)." - -msgid "" -"Do you really want to delete the selected items?\n" -"\n" -"Note: deleting an action also removes all subsequent actions in the same session." -msgstr "" -"Êtes-vous sûr de vouloir supprimer le(s) groupe(s) sélectionné(s) ?\n" -"\n" -"Remarque : la suppression d'une action entraîne également la suppression de toutes les actions suivantes dans la même session." - -msgid "Do you really want to delete the selected items?" -msgstr "Êtes-vous sûr de vouloir supprimer le(s) groupe(s) sélectionné(s) ?" - -msgid "Remove incompatible" -msgstr "Supprimer les actions incompatibles" - -msgid "All actions are compatible with the current workspace." -msgstr "Toutes les actions sont compatibles avec l'espace de travail actuel." - -#, python-format -msgid "%d incompatible action(s) will be removed. Continue?" -msgstr "%d action(s) incompatible(s) seront supprimées. Continuer ?" - msgid "Macro simple example" msgstr "Exemple simple de macro" @@ -1523,12 +1491,12 @@ msgstr "Supprimer les métadonnées" msgid "Some selected objects have regions of interest.
Do you want to delete them as well?" msgstr "Certains objets sélectionnés ont des régions d'intérêt.
Souhaitez-vous les supprimer également ?" -msgid "Group name:" -msgstr "Nom du groupe :" - msgid "New group" msgstr "Nouveau groupe" +msgid "Group name:" +msgstr "Nom du groupe :" + msgid "Rename object" msgstr "Renommer l'objet" @@ -1634,146 +1602,6 @@ msgstr "Annotation ajoutée" msgid "The label has been added as an annotation. You can edit or remove it using the annotation editing window.

Choosing to ignore this message will prevent it from being displayed again." msgstr "L'étiquette a été ajoutée comme annotation. Vous pouvez la modifier ou la supprimer en utilisant la fenêtre d'édition des annotations.

Ignorer ce message empêchera son affichage ultérieur." -#, python-format -msgid "“%s” has dependent operations but no valid source to reconnect to — downstream results are left unchanged." -msgstr "“%s” a des opérations dépendantes mais aucune source valide à reconnecter — les résultats en aval restent inchangés." - -msgid "Some operations could not be reconnected after deletion:" -msgstr "Certaines opérations n'ont pas pu être reconnectées après la suppression :" - -msgid "The current workspace state is not compatible with the action." -msgstr "Le statut actuel de l'espace de travail n'est pas compatible avec l'action." - -msgid "Parameters" -msgstr "Paramètres" - -msgid "History panel" -msgstr "Panneau d'historique" - -msgid "History files" -msgstr "Fichiers d'historique" - -msgid "Edit mode" -msgstr "Mode édition" - -msgid "Record mode" -msgstr "Mode enregistrement" - -msgid "New session" -msgstr "Nouvelle session" - -msgid "Start a new history session" -msgstr "Démarrer une nouvelle session d'historique" - -msgid "Open history file..." -msgstr "Ouvrir des fichiers HDF5..." - -msgid "Open history from a standalone .dlhist file" -msgstr "Ouvrir l'historique depuis un fichier .dlhist autonome" - -msgid "Save history file..." -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é" - -msgid "Previous step" -msgstr "Étape précédente" - -msgid "Select the previous action in the current session" -msgstr "Sélectionner l'action précédente dans la session en cours" - -msgid "Next step" -msgstr "Étape suivante" - -msgid "Select the next action in the current session" -msgstr "Sélectionner l'action suivante dans la session en cours" - -msgid "Generate a Python macro script from history" -msgstr "Générer un script Python macro à partir de l'historique" - -msgid "Remove actions incompatible with the current workspace" -msgstr "Supprimer les actions incompatibles avec l'espace de travail actuel" - -msgid "Restore parameters" -msgstr "Restaurer les paramètres" - -msgid "Restore original parameters (discard edit-mode changes)" -msgstr "Restaurer les paramètres d'origine (ignorer les modifications en mode édition)" - -msgid "Replay" -msgstr "Rejouer" - -msgid "Commit edit mode changes?" -msgstr "Valider les modifications du mode édition ?" - -msgid "" -"You are about to exit Edit mode.\n" -"\n" -"All parameter changes made during this session will be permanently kept.\n" -"This action cannot be undone — Restore will no longer be available.\n" -"\n" -"Do you want to continue?" -msgstr "" -"Vous êtes sur le point de quitter le mode Édition.\n" -"\n" -"Toutes les modifications de paramètres effectuées pendant cette session seront conservées de manière permanente.\n" -"Cette action est irréversible — la restauration ne sera plus disponible.\n" -"\n" -"Voulez-vous continuer ?" - -#, python-format -msgid "Action %s has been edited but its target output object(s) no longer exist — skipping." -msgstr "L'action %s a été modifiée mais son ou ses objets de sortie cibles n'existent plus — saut de l'action." - -#, python-format -msgid "Action %s uses pattern %r which is not recomputable yet." -msgstr "L'action %s utilise le modèle %r qui n'est pas encore retraitable." - -#, python-format -msgid "Recompute failed for action %s: %s" -msgstr "Le recalcul a échoué pour l'action %s : %s" - -#, python-format -msgid "" -"Action %(name)s skipped: plugin '%(loc)s' is missing.\n" -"Required parameter class: %(param)s\n" -"Reinstall the plugin to re-enable this action." -msgstr "" -"Action %(name)s ignorée : le plugin '%(loc)s' est manquant.\n" -"Classe de paramètre requise : %(param)s\n" -"Réinstallez le plugin pour réactiver cette action." - -#, python-format -msgid "Action %s: source object was deleted — skipping." -msgstr "L'action %s : l'objet source a été supprimé — saut de l'action." - -#, python-format -msgid "Action %s: all source objects were deleted — skipping." -msgstr "L'action %s : tous les objets source ont été supprimés — saut de l'action." - -#, python-format -msgid "Action %s: missing source(s) for output #%d — skipping." -msgstr "L'action %s : source(s) manquante(s) pour la sortie n°%d — saut de l'action." - -#, python-format -msgid "Action %s: source object(s) were deleted — skipping." -msgstr "L'action %s : objet(s) source supprimé(s) — saut de l'action." - -#, python-format -msgid "Action %s: %d analysed object(s) were deleted — skipping." -msgstr "L'action %s : %d objet(s) analysé(s) ont été supprimé(s) — saut de l'action." - -msgid "Cascade recompute" -msgstr "Retraiter" - -msgid "Some downstream actions could not be recomputed:" -msgstr "Certaines actions en aval n'ont pas pu être retraitées :" - msgid "Recent macros" msgstr "Macros récentes" @@ -1981,6 +1809,9 @@ msgstr "En mode 'pairwise', vous devez sélectionner des objets dans au moins de msgid "In pairwise mode, you need to select the same number of objects in each group." msgstr "En mode 'pairwise', vous devez sélectionner le même nombre d'objets dans chaque groupe." +msgid "Parameters" +msgstr "Paramètres" + #, python-format msgid "Calculating: %s" msgstr "Calcul : %s" @@ -2012,6 +1843,9 @@ msgstr "Supprimer la ROI" msgid "Are you sure you want to remove ROI '%s'?" msgstr "Êtes-vous sûr de vouloir supprimer la ROI '%s' ?" +msgid "Regions of interest are already defined for this image.

Creating new ROIs from detection will replace the existing ones, which will be lost.

Do you want to continue?" +msgstr "Des régions d'intérêt sont déjà définies pour cette image.

La création de nouvelles ROI par détection remplacera les existantes, qui seront perdues.

Voulez-vous continuer ?" + msgid "Sum" msgstr "Addition" @@ -2430,9 +2264,6 @@ msgstr "Détection de taches basée sur SimpleBlobDetector d'OpenCV" msgid "Creating a ROI grid will overwrite any existing ROI.

Do you want to continue?" msgstr "La création d'une grille de ROI écrasera toute ROI existante.

Voulez-vous continuer ?" -msgid "Regions of interest are already defined for this image.

Creating new ROIs from detection will replace the existing ones, which will be lost.

Do you want to continue?" -msgstr "Des régions d'intérêt sont déjà définies pour cette image.

La création de nouvelles ROI par détection remplacera les existantes, qui seront perdues.

Voulez-vous continuer ?" - msgid "Extract ROI" msgstr "Extraire une ROI" @@ -3290,32 +3121,6 @@ msgstr "C'est la fin de la visite guidée !" msgid "You can show the tour again, or close this dialog box." msgstr "Vous pouvez afficher la visite guidée à nouveau, ou fermer cette boîte de dialogue." -msgid "Save history file" -msgstr "Enregistrer le fichier d'historique" - -msgid "Open history file" -msgstr "Ouvrir le fichier d'historique" - -msgid "Imported" -msgstr "Importer" - -msgid "Replaying compound 'multiple_1_to_1' actions is not supported yet." -msgstr "La relecture des actions composées 'multiple_1_to_1' n'est pas encore prise en charge." - -msgid "Cannot replay 2-to-1 action: source object(s) missing." -msgstr "Impossible de relire l'action 2-à-1 : objet(s) source manquant(s)." - -#, python-format -msgid "Failed to deserialize history DataSet kwarg %r." -msgstr "Echec de la désérialisation de l'argument DataSet de l'historique %r." - -#, python-format -msgid "Failed to deserialize history DataSet-list kwarg %r." -msgstr "Echec de la désérialisation de l'argument DataSet-list de l'historique %r." - -msgid "Session" -msgstr "Session" - msgid "Registered plugins:" msgstr "Plugins enregistrés :" @@ -3382,9 +3187,6 @@ msgstr "Créer une image avec un anneau" msgid "Create image with a grid of gaussian spots" msgstr "Créer une image avec une grille de spots gaussiens" -msgid "New signal" -msgstr "Nouveau signal" - msgid "Host application" msgstr "Application hôte" @@ -3460,12 +3262,12 @@ msgstr "Cliquer sur OK pour démarrer la démo.

Note :
- La dé msgid "Click OK to end demo." msgstr "Cliquer sur OK pour terminer la démo." -msgid "Context" -msgstr "Contexte" - msgid "Error:" msgstr "Erreur:" +msgid "Context" +msgstr "Contexte" + #, python-format msgid "The file %s could not be read:" msgstr "Le fichier %s n'a pas pu être ouvert :" @@ -3682,18 +3484,18 @@ msgstr "Déplier la sélection" msgid "HDF5 Browser" msgstr "Explorateur HDF5" -msgid "Value" -msgstr "Valeur" - -msgid "Name" -msgstr "Nom" - msgid "Size" msgstr "Taille" msgid "Type" msgstr "Type" +msgid "Value" +msgstr "Valeur" + +msgid "Name" +msgstr "Nom" + msgid "Unsupported data" msgstr "Données non prises en charge" @@ -3730,24 +3532,6 @@ msgstr "Afficher uniquement les données prises en charge" msgid "Show values" msgstr "Afficher les valeurs" -msgid "Show details" -msgstr "Afficher les détails" - -msgid "Hide details" -msgstr "Masquer les détails" - -msgid "Date and time" -msgstr "Date et heure" - -msgid "Title" -msgstr "Titre" - -msgid "Action is compatible with the current workspace state." -msgstr "L'action est compatible avec l'état actuel de l'espace de travail." - -msgid "Action is not compatible with the current workspace state." -msgstr "L'action n'est pas compatible avec l'état actuel de l'espace de travail." - msgid "Image background selection" msgstr "Sélection de l'arrière-plan de l'image" @@ -4014,6 +3798,9 @@ msgstr "Tout sélectionner" msgid "Adding data to the plot" msgstr "Ajout des données au graphique" +msgid "Title" +msgstr "Titre" + msgid "X label" msgstr "Titre X" @@ -4104,18 +3891,6 @@ msgstr "Merci de sélectionner le fichier à importer." msgid "Example Wizard" msgstr "Assistant exemple" -msgid "Signal" -msgstr "Signal" - -msgid "Shape" -msgstr "Forme" - -msgid "Image" -msgstr "Image" - -msgid "Dimensions" -msgstr "Dimensions" - msgid "Minimum value" msgstr "Valeur minimum" @@ -4163,3 +3938,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..." +