Skip to content

Commit e9d9dd2

Browse files
feat(applications): guide users to set the metadata methods expect (#383)
Assisted-by: Claude Opus 5.5
1 parent 0cbda5c commit e9d9dd2

14 files changed

Lines changed: 102 additions & 11 deletions

File tree

‎datalab/gui/applications.py‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"ApplicationsDialog",
4141
"RecipeCard",
4242
"get_application_plugins",
43+
"get_declared_metadata_keys",
4344
"get_plugin_icon",
4445
"record_application_use",
4546
"set_application_hidden",
@@ -312,6 +313,27 @@ def get_application_plugins() -> tuple[PluginBase, ...]:
312313
return tuple(sorted(plugins, key=lambda plugin: plugin.info.name.casefold()))
313314

314315

316+
def get_declared_metadata_keys(object_type: str) -> list[tuple[str, str]]:
317+
"""Return the metadata keys expected by the methods of application plugins
318+
319+
Args:
320+
object_type: type of the objects carrying the metadata ("signal" or
321+
"image")
322+
323+
Returns:
324+
``(key, description)`` pairs, in declaration order, without duplicates
325+
"""
326+
keys: dict[str, str] = {}
327+
for plugin in get_application_plugins():
328+
for recipe in plugin.get_recipes():
329+
for slot in recipe.inputs:
330+
if slot.object_type.value != object_type:
331+
continue
332+
for requirement in slot.metadata:
333+
keys.setdefault(requirement.key, requirement.description)
334+
return list(keys.items())
335+
336+
315337
def record_application_use(plugin_id: str) -> None:
316338
"""Remember an application as the most recently used one
317339

‎datalab/gui/main.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -995,6 +995,10 @@ def show_applications(self, plugin_id: str | None = None) -> None:
995995
panel.objview.SIG_SELECTION_CHANGED.connect(
996996
self.__applications_dialog.schedule_readiness_update
997997
)
998+
# Metadata edits change readiness without changing the selection
999+
panel.SIG_OBJECT_MODIFIED.connect(
1000+
self.__applications_dialog.schedule_readiness_update
1001+
)
9981002
self.tabwidget.currentChanged.connect(
9991003
self.__applications_dialog.schedule_readiness_update
10001004
)

‎datalab/gui/panel/base.py‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
from datalab.config import APP_NAME, Conf, _
7575
from datalab.env import execenv
7676
from datalab.gui import actionhandler, objectview
77+
from datalab.gui.applications import get_declared_metadata_keys
7778
from datalab.gui.newobject import (
7879
CREATION_PARAMETERS_OPTION,
7980
LEGACY_CREATION_PARAMETERS_OPTION,
@@ -2493,9 +2494,17 @@ def get_known_metadata_keys(self, objs: Sequence[TypeObj]) -> list[tuple[str, st
24932494
objs: selected objects
24942495
24952496
Returns:
2496-
``(key, description)`` pairs
2497+
``(key, description)`` pairs: the keys found on the objects, then the
2498+
keys expected by the methods of application plugins
24972499
"""
2498-
return collect_metadata_keys(objs)
2500+
keys = collect_metadata_keys(objs)
2501+
found = {key for key, _description in keys}
2502+
keys.extend(
2503+
(key, description)
2504+
for key, description in get_declared_metadata_keys(self.PANEL_STR_ID)
2505+
if key not in found
2506+
)
2507+
return keys
24992508

25002509
def add_metadata(self, param: AddMetadataParam | None = None) -> None:
25012510
"""Add metadata item to selected object(s)

‎datalab/gui/recipe_inputs.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,11 @@ def format_readiness(
144144
RecipeReadinessStatus.NO_INPUT: _("Select the input data in the workspace"),
145145
}
146146
reasons = [format_input_issue(issue, descriptor) for issue in readiness.issues]
147+
if any(
148+
issue.code is RecipeInputIssueCode.MISSING_METADATA
149+
for issue in readiness.issues
150+
):
151+
reasons.append(_("To set it, use Edit > Metadata > Add metadata..."))
147152
if readiness.status is not RecipeReadinessStatus.NO_INPUT:
148153
reasons.extend(
149154
format_diagnostic(diagnostic) for diagnostic in readiness.diagnostics

‎datalab/locale/fr/LC_MESSAGES/datalab.po‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4593,3 +4593,6 @@ msgstr "Multiplie les valeurs numériques, par ex. 0.001 pour convertir des ms e
45934593

45944594
msgid "unchanged"
45954595
msgstr "inchangé"
4596+
4597+
msgid "To set it, use Edit > Metadata > Add metadata..."
4598+
msgstr "Pour la définir, utilisez Édition > Métadonnées > Ajouter des métadonnées..."

‎datalab/tests/features/plugins/applications_dialog_test.py‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
from types import SimpleNamespace
88

9+
import numpy as np
10+
import sigima.objects
911
from guidata.configtools import get_icon
1012
from qtpy import QtCore as QC
1113
from qtpy import QtGui as QG
@@ -18,6 +20,7 @@
1820
from datalab.gui.applications import (
1921
ApplicationsDialog,
2022
get_application_plugins,
23+
get_declared_metadata_keys,
2124
record_application_use,
2225
set_application_hidden,
2326
set_application_pinned,
@@ -314,6 +317,7 @@ def test_application_page_shows_readiness_of_each_method() -> None:
314317
text = card.readiness_label.text()
315318
assert "cannot be analyzed" in text
316319
assert "Flat frames: metadata 'exposure_s' missing on 2" in text
320+
assert "Edit > Metadata > Add metadata..." in text
317321
# The method item shows the readiness without being opened
318322
toolbox = dialog.application_pages[0].toolbox
319323
assert "cannot be analyzed" in toolbox.itemToolTip(0)
@@ -515,6 +519,12 @@ def test_main_window_exposes_applications_catalog(monkeypatch) -> None:
515519
"exec",
516520
lambda dialog: executed.append(dialog),
517521
)
522+
updates: list[tuple] = []
523+
monkeypatch.setattr(
524+
main.ApplicationsDialog,
525+
"schedule_readiness_update",
526+
lambda _dialog, *args: updates.append(args),
527+
)
518528

519529
with datalab_test_app_context(console=False, exec_loop=False) as window:
520530
menu_actions = window.menuBar().actions()
@@ -541,6 +551,40 @@ def test_main_window_exposes_applications_catalog(monkeypatch) -> None:
541551

542552
assert window.findChildren(ApplicationsDialog) == [dialog]
543553

554+
# Editing objects (e.g. their metadata) re-assesses the methods
555+
updates.clear()
556+
window.imagepanel.SIG_OBJECT_MODIFIED.emit()
557+
assert updates
558+
559+
560+
def test_add_metadata_suggests_keys_declared_by_applications() -> None:
561+
"""Add metadata offers the keys expected by application methods"""
562+
application = _plugin(
563+
"org.example.camera",
564+
"Camera Characterization",
565+
frozenset({PluginCapability.APPLICATION}),
566+
recipes=(RECIPE,),
567+
)
568+
with datalab_test_app_context(console=False, exec_loop=False) as window:
569+
# DataLab unregisters the plugins on close: restore the registry first
570+
registry = PluginRegistry.get_plugins()
571+
previous_plugins = list(registry)
572+
registry[:] = [application]
573+
try:
574+
assert get_declared_metadata_keys("image") == [
575+
("exposure_s", "Exposure time")
576+
]
577+
assert get_declared_metadata_keys("signal") == []
578+
image = sigima.objects.create_image("Flat", np.zeros((2, 2)))
579+
image.metadata["gain"] = 2
580+
assert window.imagepanel.get_known_metadata_keys([image]) == [
581+
("gain", "e.g. 2"),
582+
("exposure_s", "Exposure time"),
583+
]
584+
assert window.signalpanel.get_known_metadata_keys([]) == []
585+
finally:
586+
registry[:] = previous_plugins
587+
544588

545589
def test_application_commands_delegate_to_plugin_contracts(monkeypatch) -> None:
546590
"""Each catalog command calls its plugin entry point and keeps the dialog."""

‎doc/features/common/overview.rst‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ A **Welcome** page is shown next to the signal and image views when DataLab star
4747

4848
When application plugins are installed, an **Applications** section at the top of the page shows their tiles: clicking the main tile of an application opens its page in the **Applications** catalog, or starts the main action provided by the plugin. An application may provide other tiles, for example to open an example directly: they are shown next to its main tile when the section has room for them, and otherwise move to the menu of the main tile. This menu, opened from the "…" button of the main tile or by right-clicking it, also lets you pin the application to the top of the section or hide it from the welcome page. Pinned applications come first, then recently used ones, then the others by name. The section is limited to two rows of tiles by default (this number may be changed in the :ref:`settings`): when more applications are installed, the last tile gives the number of applications left out and opens the catalog. The "Browse all applications..." button also opens the catalog, where a search field filters applications by name or description, and where the "Show on welcome page" and "Pin to the welcome page" check boxes of each application change the same preferences (see :ref:`about_plugins`).
4949

50-
On the page of an application, the methods are listed one below the other, followed by the tools and datasets of the application, if any: clicking a title opens its section and closes the one previously open. The colored dot before each method tells whether the current selection can be analyzed, and its tooltip gives the reason. The open method presents the inputs it expects and this status in detail. "Run on selection..." starts the method, and asks you to assign the selected objects to its inputs only when needed. The examples designed for a method are listed below it: "Try with this example" opens the example and runs the method with suitable parameters. The vertical strip between the application list and the page hides the list, or shows it again: the window shrinks or grows accordingly, and DataLab remembers this choice.
50+
On the page of an application, the methods are listed one below the other, followed by the tools and datasets of the application, if any: clicking a title opens its section and closes the one previously open. The colored dot before each method tells whether the current selection can be analyzed, and its tooltip gives the reason. The open method presents the inputs it expects and this status in detail. When metadata are missing, the status points to "Edit > Metadata > Add metadata...", and it is updated as soon as objects are modified. "Run on selection..." starts the method, and asks you to assign the selected objects to its inputs only when needed. The examples designed for a method are listed below it: "Try with this example" opens the example and runs the method with suitable parameters. The vertical strip between the application list and the page hides the list, or shows it again: the window shrinks or grows accordingly, and DataLab remembers this choice.
5151

5252
The Welcome page is a regular dockable panel: as soon as a signal or an image is created or opened, the corresponding view is brought to the front, and the Welcome page remains available as a tab. It may be closed like any other panel, and reopened at any time from the "Welcome page" entry of the "?" (Help) menu or from the "View" menu; it is also reopened automatically when the current panel becomes empty. The "Show welcome page when the current panel is empty" check box, at the bottom of the page, controls this automatic display, including at startup (this option is also available in the :ref:`settings`).
5353

‎doc/features/image/menu_edit.rst‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ sample names, processing steps, or any other custom information.
171171
When you select "Add metadata..." from the Edit menu, a dialog appears where you can:
172172

173173
- **Metadata key**: Enter the name of the metadata field to add (letters, digits, ``_``, ``.`` and ``-``, e.g. a plugin key such as ``plugin.org.example.my-plugin.gain``)
174-
- **Known keys**: Pick a key already present on the selected images to copy it into the metadata key
174+
- **Known keys**: Pick a key already present on the selected images, or a key expected by the methods of the installed application plugins, to copy it into the metadata key
175175
- **Value pattern**: Define a pattern for the metadata value using Python format strings
176176
- **Extraction pattern**: Optionally, extract the value from the formatted text with a Python regular expression: its first group, or the whole match if it has no group, becomes the value
177177
- **If no match**: Leave images without a match unchanged (default), or report an error

‎doc/features/signal/menu_edit.rst‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ sample names, processing steps, or any other custom information.
166166
When you select "Add metadata..." from the Edit menu, a dialog appears where you can:
167167

168168
- **Metadata key**: Enter the name of the metadata field to add (letters, digits, ``_``, ``.`` and ``-``, e.g. a plugin key such as ``plugin.org.example.my-plugin.gain``)
169-
- **Known keys**: Pick a key already present on the selected signals to copy it into the metadata key
169+
- **Known keys**: Pick a key already present on the selected signals, or a key expected by the methods of the installed application plugins, to copy it into the metadata key
170170
- **Value pattern**: Define a pattern for the metadata value using Python format strings
171171
- **Extraction pattern**: Optionally, extract the value from the formatted text with a Python regular expression: its first group, or the whole match if it has no group, becomes the value
172172
- **If no match**: Leave signals without a match unchanged (default), or report an error

0 commit comments

Comments
 (0)