Skip to content

Commit eba8385

Browse files
committed
Plot results: Systematic Results group with source IDs in titles
- Always use "Results" group for all plot results operations - Include source object IDs in result signal titles - Use compact format (first 2, ..., last) for many objects - Update tests for systematic behavior
1 parent 45ed434 commit eba8385

File tree

4 files changed

+239
-6
lines changed

4 files changed

+239
-6
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,18 @@ See DataLab [roadmap page](https://datalab-platform.com/en/contributing/roadmap.
5252
* Clipboard-based workflow enables efficient annotation reuse across objects
5353
* Independent from ROI management - annotations and ROIs can coexist on same object
5454

55+
* **Plot results improvements**: Enhanced result plotting with better organization and informative titles
56+
* **Results group creation**: All plot results operations now automatically organize results in a dedicated "Results" group
57+
* First time plotting results creates a new "Results" group in the signal panel
58+
* Subsequent result plots reuse the same "Results" group for better organization
59+
* Keeps workspace clean by consolidating all results in one well-defined location
60+
* Consistent behavior regardless of whether groups or individual objects are selected
61+
* **Comprehensive result titles**: Result signal titles now include source object identifiers
62+
* For "one curve per title" mode: titles include all source object short IDs (e.g., "FWHM (s001, s002, s003): Δx = f(indices)")
63+
* For many source objects (>3): uses compact format showing first 2 IDs, "...", then last ID (e.g., "FWHM (s001, s002, ..., s010): Δx = f(indices)")
64+
* Makes it easy to identify which objects contributed to plotted results
65+
* Maintains consistency with "one curve per object" mode which already included IDs
66+
5567
* **Metadata management improvements**: Enhanced metadata handling with clipboard-based workflow
5668
* Reorganized Edit menu with new "Metadata" submenu grouping all metadata operations
5769
* **Paste metadata** action now only enabled when metadata clipboard is not empty

datalab/gui/panel/base.py

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2813,8 +2813,19 @@ def __add_result_signal(
28132813
title: str,
28142814
xaxis: str,
28152815
yaxis: str,
2816+
group_id: str | None = None,
28162817
) -> None:
2817-
"""Add result signal"""
2818+
"""Add result signal
2819+
2820+
Args:
2821+
x: X data
2822+
y: Y data
2823+
title: Signal title
2824+
xaxis: X axis label
2825+
yaxis: Y axis label
2826+
group_id: UUID of the group to add the signal to. If None, add to
2827+
current group.
2828+
"""
28182829
xdata = np.array(x, dtype=float)
28192830
ydata = np.array(y, dtype=float)
28202831

@@ -2824,7 +2835,7 @@ def __add_result_signal(
28242835
y=ydata,
28252836
labels=[xaxis, yaxis],
28262837
)
2827-
self.mainwindow.signalpanel.add_object(obj)
2838+
self.mainwindow.signalpanel.add_object(obj, group_id=group_id)
28282839

28292840
def __create_plot_result_param_class(self, rdata: ResultData) -> type[gds.DataSet]:
28302841
"""Create PlotResultParam class dynamically based on result data.
@@ -2878,6 +2889,7 @@ def __plot_result(
28782889
rdata: ResultData,
28792890
objs: list[SignalObj | ImageObj],
28802891
param: gds.DataSet | None = None,
2892+
result_group_id: str | None = None,
28812893
) -> None:
28822894
"""Plot results for a specific category
28832895
@@ -2886,6 +2898,8 @@ def __plot_result(
28862898
rdata: Result data
28872899
objs: List of objects
28882900
param: Plot result parameters. If None, show dialog to get parameters.
2901+
result_group_id: UUID of the group to add result signals to. If None,
2902+
add to current group.
28892903
"""
28902904
# Regrouping ResultShape results by their `title` attribute:
28912905
grouped_results: dict[str, list[GeometryAdapter | TableAdapter]] = {}
@@ -2940,6 +2954,18 @@ def __plot_result(
29402954
)
29412955
return
29422956
obj = objs[0]
2957+
# Build a string with source object short IDs (max 3, then use "...")
2958+
max_ids_to_show = 3
2959+
short_ids = [get_short_id(obj) for obj in objs]
2960+
if len(short_ids) <= max_ids_to_show:
2961+
source_ids = ", ".join(short_ids)
2962+
else:
2963+
# Show first 2, "...", then last one: "s001, s002, ..., s010"
2964+
source_ids = (
2965+
", ".join(short_ids[: max_ids_to_show - 1])
2966+
+ ", ..., "
2967+
+ short_ids[-1]
2968+
)
29432969
for i_roi in all_roi_indexes[0]:
29442970
roi_suffix = f"|ROI{int(i_roi + 1)}" if i_roi >= 0 else ""
29452971
for title, results in grouped_results.items(): # title
@@ -2953,7 +2979,12 @@ def __plot_result(
29532979
if i_roi >= 0:
29542980
roi_suffix = f"|{obj.roi.get_single_roi_title(int(i_roi))}"
29552981
self.__add_result_signal(
2956-
x, y, f"{title}{roi_suffix}", param.xaxis, param.yaxis
2982+
x,
2983+
y,
2984+
f"{title} ({source_ids}){roi_suffix}",
2985+
param.xaxis,
2986+
param.yaxis,
2987+
result_group_id,
29572988
)
29582989
else:
29592990
# One curve per result title, per object and per ROI
@@ -2974,7 +3005,9 @@ def __plot_result(
29743005
y = roi_data[param.yaxis].values
29753006
shid = get_short_id(objs[index])
29763007
stitle = f"{title} ({shid}){roi_suffix}"
2977-
self.__add_result_signal(x, y, stitle, param.xaxis, param.yaxis)
3008+
self.__add_result_signal(
3009+
x, y, stitle, param.xaxis, param.yaxis, result_group_id
3010+
)
29783011

29793012
def plot_results(
29803013
self,
@@ -2993,6 +3026,17 @@ def plot_results(
29933026
objs = self.objview.get_sel_objects(include_groups=True)
29943027
rdatadict = create_resultdata_dict(objs)
29953028
if rdatadict:
3029+
# Always use or create a "Results" group for all plot results
3030+
rgroup_title = _("Results")
3031+
target_panel = self.mainwindow.signalpanel
3032+
try:
3033+
# Check if a "Results" group already exists in the signal panel
3034+
rgroup = target_panel.objmodel.get_group_from_title(rgroup_title)
3035+
except KeyError:
3036+
# Create the group if it doesn't exist
3037+
rgroup = target_panel.add_group(rgroup_title)
3038+
result_group_id = get_uuid(rgroup)
3039+
29963040
for category, rdata in rdatadict.items():
29973041
param = None
29983042
if kind is not None and xaxis is not None and yaxis is not None:
@@ -3003,7 +3047,7 @@ def plot_results(
30033047
param.xaxis = xaxis
30043048
param.yaxis = yaxis
30053049

3006-
self.__plot_result(category, rdata, objs, param)
3050+
self.__plot_result(category, rdata, objs, param, result_group_id)
30073051
else:
30083052
self.__show_no_result_warning()
30093053

datalab/tests/features/common/plot_results_app_test.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- Results with and without ROIs
1010
- Both signal and image panels
1111
- Multiple result types (scalar and geometry results)
12+
- Group selection creates a new result group
1213
"""
1314

1415
# guitest: show
@@ -21,7 +22,9 @@
2122
import sigima.params
2223
from sigima.tests import data as test_data
2324

25+
from datalab.config import _ as translate
2426
from datalab.env import execenv
27+
from datalab.objectmodel import get_uuid
2528
from datalab.tests import datalab_test_app_context
2629

2730

@@ -46,6 +49,7 @@ def test_plot_results_signals_one_curve_per_title():
4649
"""Test plot results feature with signals, one curve per title.
4750
4851
Create signals with single-value results (e.g., FWHM) and plot them.
52+
Verify that results are created in the "Results" group.
4953
"""
5054
with datalab_test_app_context() as win:
5155
panel = win.signalpanel
@@ -64,10 +68,34 @@ def test_plot_results_signals_one_curve_per_title():
6468
# (trade-off for noise robustness)
6569
panel.processor.run_feature("fwhm", sigima.params.FWHMParam())
6670

71+
# Get number of groups before plotting
72+
groups_before = len(panel.objmodel.get_groups())
73+
6774
panel.objview.selectAll()
6875
panel.show_results()
6976
panel.plot_results(kind="one_curve_per_title", xaxis="indices", yaxis="Δx")
7077

78+
# Verify a Results group was created
79+
groups_after = panel.objmodel.get_groups()
80+
assert len(groups_after) == groups_before + 1, (
81+
f"Expected {groups_before + 1} groups, got {len(groups_after)}"
82+
)
83+
84+
# Verify the new group is named "Results"
85+
from datalab.config import _ as translate
86+
87+
expected_title = translate("Results")
88+
result_group = groups_after[-1]
89+
assert result_group.title == expected_title, (
90+
f"Expected last group to be '{expected_title}', "
91+
f"got '{result_group.title}'"
92+
)
93+
94+
# Verify the Results group contains the result signal
95+
assert len(result_group) > 0, (
96+
"Results group should contain at least one result signal"
97+
)
98+
7199
fwhm_var_th = sigima.objects.create_signal("FWHM_Theoretical", x_th, y_th)
72100
panel.add_object(fwhm_var_th)
73101
panel.objview.select_objects((6, 7))
@@ -124,7 +152,123 @@ def test_plot_results_images_with_rois():
124152
panel.plot_results(kind="one_curve_per_title", xaxis="indices", yaxis="x")
125153

126154

155+
def test_plot_results_with_group_selection():
156+
"""Test plot results with Results group.
157+
158+
All plot results operations should create result signals in a reusable
159+
"Results" group for better organization.
160+
"""
161+
with datalab_test_app_context() as win:
162+
panel = win.signalpanel
163+
164+
with execenv.context(unattended=True):
165+
# Create a group with signals
166+
panel.add_group("Test Group")
167+
168+
# Add signals and compute FWHM
169+
for i, (sig, _) in enumerate(iterate_noisy_signals(3, a=10.0, sigma=0.01)):
170+
sig.title = f"Signal_{i + 1}"
171+
panel.add_object(sig)
172+
panel.processor.run_feature("fwhm", sigima.params.FWHMParam())
173+
174+
# Get the number of groups before plotting results
175+
groups_before = len(panel.objmodel.get_groups())
176+
177+
# Select the group (not individual objects)
178+
panel.objview.select_groups([1])
179+
180+
# Verify the group is selected
181+
sel_groups = panel.objview.get_sel_groups()
182+
assert len(sel_groups) == 1, (
183+
f"Expected 1 selected group, got {len(sel_groups)}"
184+
)
185+
186+
# Plot results - this should create or reuse a "Results" group
187+
panel.plot_results(kind="one_curve_per_title", xaxis="indices", yaxis="Δx")
188+
189+
# Verify a new group was created
190+
groups_after = panel.objmodel.get_groups()
191+
assert len(groups_after) == groups_before + 1, (
192+
f"Expected {groups_before + 1} groups, got {len(groups_after)}"
193+
)
194+
195+
# Check that the new group is named "Results" (or its translation)
196+
expected_title = translate("Results")
197+
result_group = groups_after[-1] # Last group should be Results
198+
assert result_group.title == expected_title, (
199+
f"Expected last group to be '{expected_title}', "
200+
f"got '{result_group.title}'"
201+
)
202+
203+
# Check that the Results group contains at least one result signal
204+
assert len(result_group) > 0, (
205+
"Results group should contain at least one result signal"
206+
)
207+
208+
# Verify that the result signal title includes source object short IDs
209+
result_signal = list(result_group)[0]
210+
# Should contain all three source signal IDs: s001, s002, s003
211+
# (s000 is the default group, so signals start at s001)
212+
assert "(s001, s002, s003)" in result_signal.title, (
213+
f"Result signal title should include source IDs (s001, s002, s003), "
214+
f"got '{result_signal.title}'"
215+
)
216+
217+
# Test that the group is reused: create another group and plot results
218+
test_group_2 = panel.add_group("Test Group 2")
219+
test_group_2_id = get_uuid(test_group_2)
220+
for i, (sig, _) in enumerate(iterate_noisy_signals(2, a=10.0, sigma=0.01)):
221+
sig.title = f"Signal2_{i + 1}"
222+
panel.add_object(sig, group_id=test_group_2_id)
223+
panel.processor.run_feature("fwhm", sigima.params.FWHMParam())
224+
225+
# Select the second group and plot results again
226+
panel.objview.select_groups([3])
227+
228+
# Plot results again
229+
num_results_before = len(result_group)
230+
panel.plot_results(kind="one_curve_per_title", xaxis="indices", yaxis="Δx")
231+
232+
# Verify no new group was created (reused existing Results group)
233+
groups_final = panel.objmodel.get_groups()
234+
assert len(groups_final) == len(groups_after), (
235+
"Results group should be reused, no new group created"
236+
)
237+
238+
# Verify more results were added to the existing Results group
239+
assert len(result_group) > num_results_before, (
240+
"More results should be added to the existing Results group"
241+
)
242+
243+
# Test with many objects (more than 3) to verify "..." is used
244+
test_group_3 = panel.add_group("Test Group 3")
245+
test_group_3_id = get_uuid(test_group_3)
246+
for i, (sig, _) in enumerate(iterate_noisy_signals(5, a=10.0, sigma=0.01)):
247+
sig.title = f"Signal3_{i + 1}"
248+
panel.add_object(sig, group_id=test_group_3_id)
249+
panel.processor.run_feature("fwhm", sigima.params.FWHMParam())
250+
251+
# Select the third group
252+
panel.objview.select_groups([4])
253+
254+
# Plot results
255+
num_results_before = len(result_group)
256+
panel.plot_results(kind="one_curve_per_title", xaxis="indices", yaxis="Δx")
257+
258+
# Verify the result signal title uses "..." for many objects
259+
new_results = list(result_group)[num_results_before:]
260+
assert len(new_results) > 0, "Should have new results"
261+
result_signal_many = new_results[0]
262+
# With 5 source signals, should show first 2 IDs, "...", then last ID
263+
# Format: "fwhm (s..., s..., ..., s...): ..."
264+
assert ", ..., " in result_signal_many.title, (
265+
f"Result signal title should use '...' before last ID, "
266+
f"got '{result_signal_many.title}'"
267+
)
268+
269+
127270
if __name__ == "__main__":
128271
test_plot_results_signals_one_curve_per_title()
129272
test_plot_results_images_one_curve_per_object()
130273
test_plot_results_images_with_rois()
274+
test_plot_results_with_group_selection()

doc/locale/fr/LC_MESSAGES/changelog.po

Lines changed: 34 additions & 1 deletion
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-11-07 18:27+0100\n"
10+
"POT-Creation-Date: 2025-11-09 09:59+0100\n"
1111
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1212
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1313
"Language: fr\n"
@@ -159,6 +159,39 @@ msgstr "Le flux de travail basé sur le presse-papiers permet une réutilisation
159159
msgid "Independent from ROI management - annotations and ROIs can coexist on same object"
160160
msgstr "Indépendant de la gestion des ROI - les annotations et les ROI peuvent coexister sur le même objet"
161161

162+
msgid "**Plot results improvements**: Enhanced result plotting with better organization and informative titles"
163+
msgstr "**Améliorations du tracé des résultats** : Tracé des résultats amélioré avec une meilleure organisation et des titres informatifs"
164+
165+
msgid "**Results group creation**: All plot results operations now automatically organize results in a dedicated \"Results\" group"
166+
msgstr "**Création de groupe de résultats** : Toutes les opérations de tracé des résultats organisent désormais automatiquement les résultats dans un groupe \"Résultats\" dédié"
167+
168+
msgid "First time plotting results creates a new \"Results\" group in the signal panel"
169+
msgstr "La première fois que les résultats sont tracés, un nouveau groupe \"Résultats\" est créé dans le panneau des signaux"
170+
171+
msgid "Subsequent result plots reuse the same \"Results\" group for better organization"
172+
msgstr "Les tracés de résultats suivants réutilisent le même groupe \"Résultats\" pour une meilleure organisation"
173+
174+
msgid "Keeps workspace clean by consolidating all results in one well-defined location"
175+
msgstr "Garde l'espace de travail propre en consolidant tous les résultats en un seul endroit bien défini"
176+
177+
msgid "Consistent behavior regardless of whether groups or individual objects are selected"
178+
msgstr "Comportement cohérent, que des groupes ou des objets individuels soient sélectionnés"
179+
180+
msgid "**Comprehensive result titles**: Result signal titles now include source object identifiers"
181+
msgstr "**Titres de résultats complets** : Les titres des signaux de résultats incluent désormais les identifiants des objets source"
182+
183+
msgid "For \"one curve per title\" mode: titles include all source object short IDs (e.g., \"FWHM (s001, s002, s003): Δx = f(indices)\")"
184+
msgstr "Pour le mode \"une courbe par titre\" : les titres incluent tous les identifiants courts des objets source (par exemple, \"FWHM (s001, s002, s003): Δx = f(indices)\")"
185+
186+
msgid "For many source objects (>3): uses compact format showing first 2 IDs, \"...\", then last ID (e.g., \"FWHM (s001, s002, ..., s010): Δx = f(indices)\")"
187+
msgstr "Pour de nombreux objets source (>3) : utilise un format compact montrant les 2 premiers identifiants, \"...\", puis le dernier identifiant (par exemple, \"FWHM (s001, s002, ..., s010): Δx = f(indices)\")"
188+
189+
msgid "Makes it easy to identify which objects contributed to plotted results"
190+
msgstr "Facilite l'identification des objets ayant contribué aux résultats tracés"
191+
192+
msgid "Maintains consistency with \"one curve per object\" mode which already included IDs"
193+
msgstr "Maintient la cohérence avec le mode \"une courbe par objet\" qui incluait déjà des identifiants"
194+
162195
msgid "**Metadata management improvements**: Enhanced metadata handling with clipboard-based workflow"
163196
msgstr "**Améliorations de la gestion des métadonnées** : Gestion des métadonnées améliorée avec un flux de travail basé sur le presse-papiers"
164197

0 commit comments

Comments
 (0)