Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
4709731
fix(history): stabilize UUID-backed title references
Daphilippe Sep 10, 2026
1458ac7
fix(history): remap action references during replay
Daphilippe Sep 10, 2026
cc27e3c
fix(hdf5): remap references on history import
Daphilippe Sep 10, 2026
ed04369
fix(gui): refresh panels after history import
Daphilippe Sep 11, 2026
6786eb6
fix(processor): preserve UUID mappings across operations
Daphilippe Sep 11, 2026
773a252
fix(webapi): preserve object identity and metadata
Daphilippe Sep 11, 2026
59bf9f7
fix(gui): size wrapped title delegate correctly
Daphilippe Sep 14, 2026
a2d9913
docs(settings): document history reference options
Daphilippe Sep 14, 2026
ecb9e7e
Merge branch 'develop' into feature/367-uuid-title-references
ThomasMalletCodra Oct 7, 2026
5630dd4
release note : put object reference dev note in the correct release n…
ThomasMalletCodra Oct 7, 2026
b4c59f3
fix(gui): replace remaining object short IDs with DataLab-Web labels
ThomasMalletCodra Oct 7, 2026
488db06
refactor(titles): store UUID8 references in titles, drop short IDs
ThomasMalletCodra Oct 7, 2026
7a8924a
Merge branch 'develop' into feature/367-uuid-title-references
ThomasMalletCodra Oct 14, 2026
4c6f23e
feat(titles): resolve nested source titles and fix plot title refresh
ThomasMalletCodra Oct 14, 2026
d24b78c
test(titles): cover source-title mode across operations and refreshes
ThomasMalletCodra Oct 14, 2026
46c98de
docs(titles): document nested source-title resolution and display scope
ThomasMalletCodra Oct 14, 2026
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
8 changes: 4 additions & 4 deletions datalab/adapters_metadata/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from datalab.adapters_metadata.geometry_adapter import GeometryAdapter
from datalab.adapters_metadata.table_adapter import TableAdapter
from datalab.config import Conf, _
from datalab.objectmodel import get_short_id
from datalab.objectmodel import get_short_uuid

if TYPE_CHECKING:
from qtpy.QtWidgets import QWidget
Expand Down Expand Up @@ -154,16 +154,16 @@ def append(self, adapter: BaseResultAdapter, obj: SignalObj | ImageObj) -> None:
self.results.append(adapter)
df = adapter.to_dataframe()
for i_row_res in range(len(df)):
sid = get_short_id(obj)
ylabel = f"{adapter.func_name}({sid})"
short_uuid = get_short_uuid(obj)
ylabel = f"{adapter.func_name}({short_uuid})"
if "roi_index" in df.columns:
i_roi = int(df.iloc[i_row_res]["roi_index"])
roititle = ""
if obj.roi is not None and 0 <= i_roi < len(obj.roi):
roititle = obj.roi.get_single_roi_title(i_roi)
ylabel += f"|{roititle}"
self.ylabels.append(ylabel)
self.short_ids.append(sid)
self.short_ids.append(short_uuid)


def have_results(objs: list[SignalObj | ImageObj]) -> bool:
Expand Down
8 changes: 8 additions & 0 deletions datalab/config/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,14 @@ def __init__(self) -> None:
default=(configtools.MONOSPACE, 8, False),
description="Monospace font used by history and analysis tabs.",
)
self.result_title_mode = EnumOptionField(
self,
"result_title_mode",
category="proc",
default="uuid",
choices=["uuid", "title"],
description="Reference display mode in generated object titles.",
)
self.history_auto_record = TypedOptionField(
self,
"history_auto_record",
Expand Down
13 changes: 11 additions & 2 deletions datalab/gui/h5io.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

from datalab.config import _
from datalab.env import execenv
from datalab.gui.panel.base import H5ImportBatch
from datalab.h5 import H5Importer
from datalab.h5.native import NativeH5Reader, NativeH5Writer
from datalab.utils.qthelpers import create_progress_bar, qt_try_loadsave_file
Expand Down Expand Up @@ -73,8 +74,12 @@ def open_file_headless(self, filename: str, reset_all: bool) -> bool:
reader = NativeH5Reader(filename)
if reset_all:
self.mainwindow.reset_all()
batches: list[H5ImportBatch] = []
for panel in self.mainwindow.panels:
panel.deserialize_from_hdf5(reader, reset_all)
batch = panel.deserialize_from_hdf5(reader, reset_all)
if isinstance(batch, H5ImportBatch):
batches.append(batch)
H5ImportBatch.finalize_imports(batches, refresh_panels=True)
reader.close()
return True
except KeyError:
Expand All @@ -87,15 +92,19 @@ def open_file(self, filename: str, import_all: bool, reset_all: bool) -> None:
reader = NativeH5Reader(filename)
if reset_all:
self.mainwindow.reset_all()
batches: list[H5ImportBatch] = []
with create_progress_bar(
self.mainwindow, self.__progbartitle(filename), 2
) as progress:
for idx, panel in enumerate(self.mainwindow.panels):
progress.setValue(idx + 1)
QW.QApplication.processEvents()
panel.deserialize_from_hdf5(reader, reset_all)
batch = panel.deserialize_from_hdf5(reader, reset_all)
if isinstance(batch, H5ImportBatch):
batches.append(batch)
if progress.wasCanceled():
break
H5ImportBatch.finalize_imports(batches, refresh_panels=True)
reader.close()
except KeyError:
if progress is not None:
Expand Down
65 changes: 60 additions & 5 deletions datalab/gui/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,12 @@
from datalab.gui.processor.preview import PreviewExecutorCache
from datalab.gui.settings import AI_OPTION_NAMES, edit_settings
from datalab.gui.welcome import WelcomePanel
from datalab.objectmodel import ObjectGroup, get_uuid
from datalab.objectmodel import (
UUID_REGEX,
ObjectGroup,
get_uuid,
resolve_title_references,
)
from datalab.plugins import (
PluginRegistry,
discover_plugins,
Expand Down Expand Up @@ -299,8 +304,18 @@ def get_object(

Raises:
KeyError: if object not found
ValueError: if multiple objects have the same title
TypeError: if index_id_title type is invalid
"""
if (
isinstance(nb_id_title, str)
and panel is None
and UUID_REGEX.fullmatch(nb_id_title)
):
obj = self.find_object_by_uuid(nb_id_title)
if obj is None or isinstance(obj, ObjectGroup):
raise KeyError(f"Invalid object index, id or title: {nb_id_title}")
return obj
panelw = self.__get_datapanel(panel)
if nb_id_title is None:
return panelw.objview.get_current_object()
Expand Down Expand Up @@ -340,11 +355,47 @@ def find_object_by_uuid(
for panel in (self.signalpanel, self.imagepanel):
if panel is not None:
try:
return panel.objmodel[uuid]
return panel.objmodel.get_object_or_group(uuid)
except KeyError:
continue
return None

def resolve_title_reference(
self, reference: str
) -> tuple[str, SignalObj | ImageObj | ObjectGroup] | None:
"""Resolve a title reference to ``(panel_str, object or group)``.

Args:
reference: ``<UUID8>`` (object) or ``g<UUID8>`` (group) reference

Returns:
The unique match across signal and image panels, or None if the
reference matches nothing or is ambiguous.
"""
matches = [
(panel.PANEL_STR_ID, obj)
for panel in (self.signalpanel, self.imagepanel)
for obj in panel.objmodel.find_by_title_reference(reference)
]
return matches[0] if len(matches) == 1 else None

def render_object_title(self, title: str) -> str:
"""Render an object title according to user settings."""
if Conf.result_title_mode.get() != "title":
return title

def resolve_source_title(reference: str) -> str | None:
resolved = self.resolve_title_reference(reference)
return None if resolved is None else resolved[1].title

return resolve_title_references(title, resolve_source_title)

def refresh_object_title_displays(self) -> None:
"""Refresh object titles in trees and shown plot items."""
for panel in (self.signalpanel, self.imagepanel):
panel.objview.update_tree()
panel.refresh_plot("selected", update_items=True, force=True)

@remote_controlled
def get_object_uuids(
self,
Expand Down Expand Up @@ -1637,6 +1688,9 @@ def __configure_panels(self) -> None:
panel.SIG_OBJECT_ADDED.connect(self.set_modified)
panel.SIG_OBJECT_REMOVED.connect(self.set_modified)
panel.SIG_OBJECT_MODIFIED.connect(self.set_modified)
for panel in (self.signalpanel, self.imagepanel):
panel.SIG_OBJECT_MODIFIED.connect(self.refresh_object_title_displays)
panel.SIG_OBJECT_REMOVED.connect(self.refresh_object_title_displays)
# Initializing common panel actions
self.autorefresh_action.setChecked(Conf.auto_refresh.get(True))
self.showfirstonly_action.setChecked(Conf.show_first_only.get(False))
Expand Down Expand Up @@ -2314,9 +2368,7 @@ def add_signal(
)
return self.add_object(obj, group_id, set_current, new_session_behavior)

# This API mirrors the image metadata accepted by create_image, so the
# argument count is part of the stable public interface rather than noise.
def add_image( # pylint: disable=too-many-arguments
def add_image(
self,
title: str,
data: np.ndarray,
Expand All @@ -2328,6 +2380,7 @@ def add_image( # pylint: disable=too-many-arguments
zlabel: str = "",
group_id: str = "",
set_current: bool = True,
*,
new_session_behavior: SessionBehavior | None = None,
) -> bool:
"""Add image data to DataLab.
Expand Down Expand Up @@ -2441,6 +2494,8 @@ def __edit_settings(self) -> None: # pylint: disable=too-many-branches,too-many
"max_cols_in_label",
):
refresh_signal_panel = refresh_image_panel = True
if option == "result_title_mode":
self.refresh_object_title_displays()
if option == "show_result_label":
for panel in (self.signalpanel, self.imagepanel):
panel.acthandler.show_label_action.setChecked(
Expand Down
Loading
Loading