Skip to content

Commit 51dfa33

Browse files
committed
fix(auto-recompute): optimize analysis to avoid O(n²) calculations when adding ROI to multiple images
Closes #283
1 parent e9a0ec5 commit 51dfa33

File tree

4 files changed

+123
-7
lines changed

4 files changed

+123
-7
lines changed

datalab/gui/processor/base.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -974,9 +974,10 @@ def auto_recompute_analysis(self, obj: SignalObj | ImageObj) -> None:
974974
# Get the actual function from the function name
975975
feature = self.get_feature(proc_params.func_name)
976976

977-
# Recompute the analysis operation silently
977+
# Recompute the analysis operation silently, only for this specific object
978+
# (not all selected objects, to avoid O(n²) behavior when called in a loop)
978979
with Conf.proc.show_result_dialog.temp(False):
979-
self.compute_1_to_0(feature.function, param, edit=False)
980+
self.compute_1_to_0(feature.function, param, edit=False, target_objs=[obj])
980981

981982
# Update the view
982983
obj_uuid = get_uuid(obj)
@@ -1332,12 +1333,14 @@ def compute_1_to_0(
13321333
title: str | None = None,
13331334
comment: str | None = None,
13341335
edit: bool | None = None,
1336+
target_objs: list[SignalObj | ImageObj] | None = None,
13351337
) -> ResultData:
13361338
"""Generic processing method: 1 object in → no object out.
13371339
1338-
Applies a function to each selected object, returning metadata or measurement
1339-
results (e.g. peak coordinates, statistical properties) without generating
1340-
new objects. Results are stored in the object's metadata and returned as a
1340+
Applies a function to each selected object (or specified target objects),
1341+
returning metadata or measurement results (e.g. peak coordinates, statistical
1342+
properties) without generating new objects. Results are stored in the object's
1343+
metadata and returned as a
13411344
ResultData instance.
13421345
13431346
Args:
@@ -1349,6 +1352,8 @@ def compute_1_to_0(
13491352
title: Optional progress bar title.
13501353
comment: Optional comment for parameter dialog.
13511354
edit: Whether to open the parameter editor before execution.
1355+
target_objs: Optional list of specific objects to process. If None,
1356+
processes all currently selected objects.
13521357
13531358
Returns:
13541359
ResultData instance containing the results for all processed objects.
@@ -1365,7 +1370,11 @@ def compute_1_to_0(
13651370
if param is not None:
13661371
if edit and not param.edit(parent=self.mainwindow):
13671372
return None
1368-
objs = self.panel.objview.get_sel_objects(include_groups=True)
1373+
objs = (
1374+
target_objs
1375+
if target_objs is not None
1376+
else self.panel.objview.get_sel_objects(include_groups=True)
1377+
)
13691378
current_obj = self.panel.objview.get_current_object()
13701379
title = func.__name__ if title is None else title
13711380
refresh_needed = False

datalab/tests/features/common/auto_analysis_recompute_unit_test.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,87 @@ def test_analysis_recompute_after_recompute_1_to_1():
235235
print("\n✓ Recompute_1_to_1 auto-analysis test passed!")
236236

237237

238+
def test_analysis_recompute_avoids_redundant_calculations():
239+
"""Test that auto-recompute doesn't cause O(n²) redundant calculations.
240+
241+
This test verifies that when multiple objects have ROIs modified simultaneously,
242+
the analysis is recomputed only once per object, not once per object × number
243+
of selected objects.
244+
245+
Regression test for bug: When N images were selected with statistics computed,
246+
adding a ROI would trigger N × N = N² calculations instead of N.
247+
"""
248+
with datalab_test_app_context(console=False) as win:
249+
panel = win.imagepanel
250+
251+
# Create multiple images (N = 5 for this test)
252+
n_images = 5
253+
size = 100
254+
images = []
255+
for i in range(n_images):
256+
param = Gauss2DParam.create(height=size, width=size, sigma=15)
257+
img = create_image_from_param(param)
258+
img.title = f"Test image {i + 1}"
259+
panel.add_object(img)
260+
images.append(img)
261+
262+
# Select all images
263+
panel.objview.select_objects(images)
264+
selected = panel.objview.get_sel_objects()
265+
assert len(selected) == n_images, f"Should have {n_images} selected objects"
266+
267+
# Compute statistics on all selected images
268+
with Conf.proc.show_result_dialog.temp(False):
269+
panel.processor.run_feature("centroid")
270+
271+
# Verify all images have centroid results
272+
for img in images:
273+
centroid = get_centroid_coords(img)
274+
assert centroid is not None, f"Image '{img.title}' should have centroid"
275+
276+
print(f"\nInitial statistics computed for {n_images} images")
277+
278+
# Track how many times compute_1_to_0 is called during auto-recompute
279+
# by counting the calls via a wrapper
280+
call_count = [0] # Use list to allow modification in closure
281+
original_compute_1_to_0 = panel.processor.compute_1_to_0
282+
283+
def counting_compute_1_to_0(*args, **kwargs):
284+
call_count[0] += 1
285+
return original_compute_1_to_0(*args, **kwargs)
286+
287+
panel.processor.compute_1_to_0 = counting_compute_1_to_0
288+
289+
try:
290+
# Add ROI to all selected images via edit_roi_graphically flow
291+
# Simulating what happens when user adds ROI in the editor
292+
roi = create_image_roi("rectangle", [25, 25, 50, 50])
293+
for img in images:
294+
img.roi = roi
295+
# Simulate the auto-recompute that happens after ROI modification
296+
panel.processor.auto_recompute_analysis(img)
297+
298+
# With the fix, compute_1_to_0 should be called exactly N times
299+
# (once per object), not N² times
300+
print(f"compute_1_to_0 was called {call_count[0]} times")
301+
assert call_count[0] == n_images, (
302+
f"compute_1_to_0 should be called exactly {n_images} times "
303+
f"(once per object), but was called {call_count[0]} times. "
304+
f"This suggests O(n²) redundant calculations."
305+
)
306+
307+
# Verify the target_objs parameter is being used correctly:
308+
# Each call should process only 1 object, not all selected objects
309+
# This is verified by checking that the call count matches n_images
310+
311+
finally:
312+
# Restore the original method
313+
panel.processor.compute_1_to_0 = original_compute_1_to_0
314+
315+
print(f"\n✓ Auto-recompute correctly called {n_images} times (no O(n²) issue)")
316+
317+
238318
if __name__ == "__main__":
239319
test_analysis_recompute_after_roi_change()
240320
test_analysis_recompute_after_recompute_1_to_1()
321+
test_analysis_recompute_avoids_redundant_calculations()

doc/locale/fr/LC_MESSAGES/release_notes/release_1.00.po

Lines changed: 19 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-12-13 18:48+0100\n"
10+
"POT-Creation-Date: 2025-12-15 16:33+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"
@@ -233,6 +233,24 @@ msgstr "Les utilisateurs peuvent désormais ajuster l'espacement des ROI indépe
233233
msgid "This closes [Issue #282](https://github.com/datalab-platform/datalab/issues/282) - Grid ROI Missing Spacing Parameters"
234234
msgstr "Ceci clôture [Issue #282](https://github.com/datalab-platform/datalab/issues/282) - Paramètres d'espacement manquants pour la ROI de grille"
235235

236+
msgid "**Analysis auto-recompute - Redundant O(n²) calculations when adding ROI:**"
237+
msgstr "**Recalcul automatique de l'analyse - Calculs redondants O(n²) lors de l'ajout de ROI :**"
238+
239+
msgid "Fixed severe performance issue where adding ROI to N selected images with analysis results (e.g., Statistics, Centroid) triggered N² calculations instead of N"
240+
msgstr "Correction d'un problème de performance sévère où l'ajout d'une ROI à N images sélectionnées avec des résultats d'analyse (p. ex. Statistiques, Centroïde) déclenchait N² calculs au lieu de N"
241+
242+
msgid "When 10 images were selected with pre-computed statistics, adding a ROI would show 10 progress bars of 10 calculations each (100 total) instead of a single progress bar with 10 calculations"
243+
msgstr "Lorsque 10 images étaient sélectionnées avec des statistiques pré-calculées, l'ajout d'une ROI affichait 10 barres de progression de 10 calculs chacune (100 au total) au lieu d'une seule barre de progression avec 10 calculs"
244+
245+
msgid "The issue occurred because `auto_recompute_analysis()` was calling `compute_1_to_0()` which by default processes all selected objects, not just the specific object being recomputed"
246+
msgstr "Le problème se produisait car `auto_recompute_analysis()` appelait `compute_1_to_0()` qui, par défaut, traite tous les objets sélectionnés, pas seulement l'objet spécifique en cours de recalcul"
247+
248+
msgid "Added `target_objs` parameter to `compute_1_to_0()` to allow specifying which objects to process, and updated `auto_recompute_analysis()` to use it"
249+
msgstr "Ajout du paramètre `target_objs` à `compute_1_to_0()` pour permettre de spécifier quels objets traiter, et mise à jour de `auto_recompute_analysis()` pour l'utiliser"
250+
251+
msgid "This closes [Issue #283](https://github.com/datalab-platform/datalab/issues/283) - Redundant O(n²) calculations when adding ROI to multiple images with analysis results"
252+
msgstr "Ceci clôture [Issue #283](https://github.com/datalab-platform/datalab/issues/283) - Calculs redondants O(n²) lors de l'ajout de ROI à plusieurs images avec des résultats d'analyse"
253+
236254
msgid "**Result visualization - Analysis result segments hard to see:**"
237255
msgstr "**Visualisation des résultats - Segments de résultats d'analyse difficiles à voir :**"
238256

doc/release_notes/release_1.00.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,14 @@
103103
* This fix is provided by Sigima 1.0.4
104104
* This closes [Issue #282](https://github.com/datalab-platform/datalab/issues/282) - Grid ROI Missing Spacing Parameters
105105

106+
**Analysis auto-recompute - Redundant O(n²) calculations when adding ROI:**
107+
108+
* Fixed severe performance issue where adding ROI to N selected images with analysis results (e.g., Statistics, Centroid) triggered N² calculations instead of N
109+
* When 10 images were selected with pre-computed statistics, adding a ROI would show 10 progress bars of 10 calculations each (100 total) instead of a single progress bar with 10 calculations
110+
* The issue occurred because `auto_recompute_analysis()` was calling `compute_1_to_0()` which by default processes all selected objects, not just the specific object being recomputed
111+
* Added `target_objs` parameter to `compute_1_to_0()` to allow specifying which objects to process, and updated `auto_recompute_analysis()` to use it
112+
* This closes [Issue #283](https://github.com/datalab-platform/datalab/issues/283) - Redundant O(n²) calculations when adding ROI to multiple images with analysis results
113+
106114
**Result visualization - Analysis result segments hard to see:**
107115

108116
* Fixed analysis result markers (FWHM, pulse features, etc.) being difficult or impossible to see on signal plots

0 commit comments

Comments
 (0)