From b75f027a77964d0c19105ebabf5e89a075bfc506 Mon Sep 17 00:00:00 2001 From: x01da Date: Tue, 23 Jun 2026 13:45:37 +0200 Subject: [PATCH] refactoring --- .../widgets/data_viewer/data_viewer.py | 5 +- .../widgets/data_viewer/loaders.py | 39 +--- .../widgets/data_viewer/panels/data_view.py | 213 ++++++------------ .../widgets/data_viewer/panels/scan_view.py | 123 +++++----- 4 files changed, 129 insertions(+), 251 deletions(-) diff --git a/debye_bec/bec_widgets/widgets/data_viewer/data_viewer.py b/debye_bec/bec_widgets/widgets/data_viewer/data_viewer.py index dd43d52..184a475 100644 --- a/debye_bec/bec_widgets/widgets/data_viewer/data_viewer.py +++ b/debye_bec/bec_widgets/widgets/data_viewer/data_viewer.py @@ -138,8 +138,7 @@ class DataViewer(BECWidget, QWidget): days, remainder = divmod(seconds, 86400) hours, remainder = divmod(remainder, 3600) - minutes, remainder = divmod(remainder, 60) - seconds, _ = divmod(remainder, 60) + minutes, seconds = divmod(remainder, 60) parts = [] if days: @@ -202,7 +201,7 @@ class DataViewer(BECWidget, QWidget): tags.append((comment, get_accent_colors().warning.name())) if status == "closed": tags.append((status, get_accent_colors().success.name())) - elif status == "halted": + elif status == "halted" or status == "aborted": tags.append((status, get_accent_colors().emergency.name())) else: tags.append((status, "#656365")) diff --git a/debye_bec/bec_widgets/widgets/data_viewer/loaders.py b/debye_bec/bec_widgets/widgets/data_viewer/loaders.py index 603cad3..5b260ab 100644 --- a/debye_bec/bec_widgets/widgets/data_viewer/loaders.py +++ b/debye_bec/bec_widgets/widgets/data_viewer/loaders.py @@ -13,7 +13,7 @@ import numpy as np class NodeInfo: """Describes a single node (group or dataset) inside a loaded file.""" - __slots__ = ("name", "path", "kind", "dtype", "shape", "display_hint") + __slots__ = ("name", "path", "kind", "dtype", "shape") def __init__( self, @@ -22,14 +22,12 @@ class NodeInfo: kind: Literal["group", "dataset"], dtype: str = "", shape: tuple[int, ...] = (), - display_hint: str = "", ): self.name = name self.path = path self.kind = kind self.dtype = dtype # e.g. "float", "int", "str", … self.shape = shape # empty tuple for scalars / groups - self.display_hint = display_hint # e.g. "image_native" — passed through to DataPanel class BaseFileLoader(ABC): @@ -74,11 +72,6 @@ class BaseFileLoader(ABC): return sum(1 for _ in self.iter_nodes(path)) -# ══════════════════════════════════════════════════════════════════════════════ -# HDF5 loader (replaces the inline h5py logic that was in HDF5Viewer) -# ══════════════════════════════════════════════════════════════════════════════ - - class HDF5Loader(BaseFileLoader): """Loader for HDF5 / NeXus files (.h5, .hdf5, .nxs, .nx).""" @@ -132,7 +125,7 @@ class HDF5Loader(BaseFileLoader): def read_dataset(self, path: str) -> np.ndarray: assert self._file is not None, "File not open" - return self._file[path][()], "h5" + return self._file[path][()] def child_count(self, path: str) -> int: assert self._file is not None, "File not open" @@ -145,9 +138,7 @@ class ImageLoader(BaseFileLoader): Loader for raster image files. The file is treated as a single, flat dataset. ``iter_nodes`` yields one - leaf node whose ``display_hint`` is set to ``"image_native"`` so that - ``DataPanel`` knows to render it as a native Qt image rather than pushing - it through the pyqtgraph pipeline. + leaf node. Requires: Pillow (``pip install Pillow``) """ @@ -194,34 +185,22 @@ class ImageLoader(BaseFileLoader): mode = img.mode # e.g. "RGB", "RGBA", "L", … name = os.path.basename(self._filepath) - yield NodeInfo( - name=name, - path="/image", - kind="dataset", - dtype=mode, - shape=(h, w), - display_hint="image_native", - ) + yield NodeInfo(name=name, path="/image", kind="dataset", dtype=mode, shape=(h, w)) def read_dataset(self, path: str) -> np.ndarray: - """Return the image as a uint8 numpy array (H × W × C or H × W).""" + """Return the image as a uint8 numpy array (H x W x C or H x W).""" from PIL import Image as _PILImage with _PILImage.open(self._filepath) as img: # type: ignore[arg-type] # Animated GIF → first frame only if hasattr(img, "n_frames") and img.n_frames > 1: img.seek(0) - return np.asarray(img), "image_native" + return np.asarray(img) def child_count(self, path: str) -> int: return 1 if path == "/" else 0 -# ══════════════════════════════════════════════════════════════════════════════ -# Loader registry -# ══════════════════════════════════════════════════════════════════════════════ - - class LoaderRegistry: """Maps file extensions to loader classes.""" @@ -245,6 +224,6 @@ class LoaderRegistry: # Default global registry — pre-populated with built-in loaders. -_registry = LoaderRegistry() -_registry.register(HDF5Loader) -_registry.register(ImageLoader) +registry = LoaderRegistry() +registry.register(HDF5Loader) +registry.register(ImageLoader) diff --git a/debye_bec/bec_widgets/widgets/data_viewer/panels/data_view.py b/debye_bec/bec_widgets/widgets/data_viewer/panels/data_view.py index d08bc40..e9b368c 100644 --- a/debye_bec/bec_widgets/widgets/data_viewer/panels/data_view.py +++ b/debye_bec/bec_widgets/widgets/data_viewer/panels/data_view.py @@ -1,5 +1,5 @@ """ -Data viewer +Data viewer displaying the data """ from typing import Literal, Optional @@ -32,23 +32,24 @@ from qtpy.QtWidgets import ( logger = bec_logger.logger +MAX_ROWS = 2000 +MAX_COLS = 500 + class DataView(QWidget): def __init__(self): super().__init__() self._layout = QVBoxLayout(self) - # Header - hdr = QHBoxLayout() - self.path_label = QLabel("Select a dataset from the tree") + header = QHBoxLayout() + self.path_label = QLabel("") self.path_label.setObjectName("path_label") self.info_label = QLabel("") self.info_label.setObjectName("info_label") - hdr.addWidget(self.path_label, 1) - hdr.addWidget(self.info_label) - self._layout.addLayout(hdr) + header.addWidget(self.path_label, 1) + header.addWidget(self.info_label) + self._layout.addLayout(header) - # View-mode selector mode_box = QGroupBox("View mode") mode_layout = QHBoxLayout(mode_box) mode_layout.setContentsMargins(8, 4, 8, 4) @@ -57,39 +58,71 @@ class DataView(QWidget): self.rb_image = QRadioButton("Image") self.rb_table = QRadioButton("Table") self.rb_auto.setChecked(True) - for rb in (self.rb_auto, self.rb_plot, self.rb_image, self.rb_table): + for rb in (self.rb_auto, self.rb_image, self.rb_plot, self.rb_table): mode_layout.addWidget(rb) rb.toggled.connect(self._on_mode_change) mode_layout.addStretch() self._layout.addWidget(mode_box) - # Content stack - self.stack = QWidget() - self.stack_layout = QVBoxLayout(self.stack) - self.stack_layout.setContentsMargins(0, 0, 0, 0) - self._layout.addWidget(self.stack, 1) + self.content = QWidget() + self.content_layout = QVBoxLayout(self.content) + self.content_layout.setContentsMargins(0, 0, 0, 0) + self._layout.addWidget(self.content, 1) self.plot_widget = None self.image_widget = None - self._current_data = None self.show_empty() - # ── helpers ─────────────────────────────────────────────────────────── + def apply_theme(self, theme: Optional[Literal["dark", "light"]] = None): + """ + Apply the theme + + Args: + theme (Optional[str]): Theme, either "dark", "light", or None. Defaults to None. + """ + if theme is None: + app = QApplication.instance() + theme = app.theme.theme # type: ignore + + bg_color = pg.getConfigOption("background") + fg_color = pg.getConfigOption("foreground") + if self.plot_widget is not None: + n_curves = len(self.plot_widget.listDataItems()) + colors = Colors.golden_angle_color( + colormap="plasma", num=max(10, n_curves + 1), format="HEX" + ) + for idx, curve in enumerate(self.plot_widget.listDataItems()): + curve.setPen(pg.mkPen(color=colors[idx])) + # Background + self.plot_widget.setBackground(bg_color) + # Axes (tick marks, tick labels, axis line) + for axis in ["left", "bottom", "right", "top"]: + ax = self.plot_widget.getAxis(axis) + ax.setPen(pg.mkPen(color=fg_color)) + ax.setTextPen(pg.mkPen(color=fg_color)) + + if self.image_widget is not None: + self.image_widget.getView().setBackgroundColor(bg_color) + self.image_widget.ui.histogram.setBackground(bg_color) + def _clear_stack(self): - while self.stack_layout.count(): - item = self.stack_layout.takeAt(0) + while self.content_layout.count(): + item = self.content_layout.takeAt(0) if item.widget(): item.widget().deleteLater() self.plot_widget = None self.image_widget = None def show_empty(self): + """Empties the content area.""" self._clear_stack() - lbl = QLabel("No data selected") - lbl.setObjectName("info_label") - lbl.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.stack_layout.addWidget(lbl) + empty_label = QLabel("No data selected") + empty_label.setObjectName("info_label") + empty_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.path_label.setText("") + self.info_label.setText("") + self.content_layout.addWidget(empty_label) def show_unsupported(self, path: str = "") -> None: """Display a friendly 'not implemented' message for unknown file types.""" @@ -100,7 +133,7 @@ class DataView(QWidget): lbl = QLabel("File type not supported") lbl.setObjectName("info_label") lbl.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.stack_layout.addWidget(lbl) + self.content_layout.addWidget(lbl) def _on_mode_change(self): if self._current_data is not None: @@ -115,8 +148,7 @@ class DataView(QWidget): return "table" return "auto" - # ── public ──────────────────────────────────────────────────────────── - def display(self, data, path: str = "", display_hint: str = "") -> None: + def display(self, data, path: str = "") -> None: """ Render *data* in the panel. @@ -126,10 +158,6 @@ class DataView(QWidget): A ``numpy.ndarray`` (or anything convertible to one). path: Human-readable label shown in the header. - display_hint: - Optional hint from the loader. ``"image_native"`` bypasses the - pyqtgraph pipeline and renders via a Qt ``QLabel``/``QPixmap`` - instead, which correctly handles RGB/RGBA colour images. """ self._current_data = data self.path_label.setText(path) @@ -137,15 +165,13 @@ class DataView(QWidget): if not isinstance(data, np.ndarray): data = np.array(data) - self.info_label.setText( - f"shape {data.shape} · dtype {data.dtype} · {data.size:,} elements" - ) + self.info_label.setText(f"shape {data.shape}, dtype {data.dtype}, {data.size} elements") mode = self._active_mode() if mode == "auto": if data.ndim <= 1 and data.size > 1: mode = "plot" - elif data.ndim <= 4 and min(data.shape) > 1: + elif data.ndim <= 4 and min(data.shape, default=0) > 1: mode = "image" else: mode = "table" @@ -157,76 +183,13 @@ class DataView(QWidget): else: self._show_table(data) - # ── Native raster image (RGB / RGBA / greyscale) ─────────────────────── - def _show_native_image(self, data: np.ndarray) -> None: - """ - Render a uint8 numpy array (H×W, H×W×3, or H×W×4) using a plain - Qt QLabel so that colour images display correctly without pyqtgraph's - colourmap pipeline. The image is scaled to fit the available space - while preserving the aspect ratio. - """ - from qtpy.QtGui import QImage - - self._clear_stack() - - arr = data - if arr.dtype != np.uint8: - # Normalise to 0–255 for display - lo, hi = arr.min(), arr.max() - arr = ((arr - lo) / max(hi - lo, 1) * 255).astype(np.uint8) - - if arr.ndim == 2: - # Greyscale → replicate to RGB so QImage is straightforward - arr = np.stack([arr, arr, arr], axis=-1) - - if arr.ndim == 3 and arr.shape[2] == 4: - fmt = QImage.Format.Format_RGBA8888 - else: - if arr.shape[2] != 3: - arr = arr[:, :, :3] - fmt = QImage.Format.Format_RGB888 - - h, w, ch = arr.shape - bytes_per_line = ch * w - arr_contiguous = np.ascontiguousarray(arr) - qimg = QImage(arr_contiguous.data, w, h, bytes_per_line, fmt) - pixmap = QPixmap.fromImage(qimg) - - label = QLabel() - label.setAlignment(Qt.AlignmentFlag.AlignCenter) - label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) - label.setMinimumSize(1, 1) - label.setPixmap( - pixmap.scaled( - label.size(), - Qt.AspectRatioMode.KeepAspectRatio, - Qt.TransformationMode.SmoothTransformation, - ) - ) - - # Re-scale when the panel is resized - def _on_resize(event, _lbl=label, _px=pixmap): - _lbl.setPixmap( - _px.scaled( - _lbl.size(), - Qt.AspectRatioMode.KeepAspectRatio, - Qt.TransformationMode.SmoothTransformation, - ) - ) - super(QLabel, _lbl).resizeEvent(event) # type: ignore[arg-type] - - label.resizeEvent = _on_resize # type: ignore[method-assign] - - self.stack_layout.addWidget(label) - - # ── 1-D line plot ────────────────────────────────────────────────────── def _show_plot_1d(self, data): self._clear_stack() is_2d = data.ndim == 2 if is_2d: - n_rows, n_cols = data.shape + n_rows, _ = data.shape row_data = data[0].astype(np.float32) else: row_data = data.reshape(-1).astype(np.float32) @@ -253,7 +216,6 @@ class DataView(QWidget): plot_item.enableAutoRange() # type: ignore[attr-defined] if is_2d: - # --- Slider --- slider = QSlider(Qt.Orientation.Vertical) slider.setMinimum(0) slider.setMaximum(n_rows - 1) @@ -294,45 +256,12 @@ class DataView(QWidget): h_layout.addWidget(slider_col) h_layout.addWidget(self.plot_widget) - self.stack_layout.addWidget(container) + self.content_layout.addWidget(container) else: - self.stack_layout.addWidget(self.plot_widget) + self.content_layout.addWidget(self.plot_widget) self.apply_theme() - def apply_theme(self, theme: Optional[Literal["dark", "light"]] = None): - """ - Apply the theme - - Args: - theme (Optional[str]): Theme, either "dark", "light", or None. Defaults to None. - """ - if theme is None: - app = QApplication.instance() - theme = app.theme.theme # type: ignore - - bg_color = pg.getConfigOption("background") - fg_color = pg.getConfigOption("foreground") - if self.plot_widget is not None: - n_curves = len(self.plot_widget.listDataItems()) - colors = Colors.golden_angle_color( - colormap="plasma", num=max(10, n_curves + 1), format="HEX" - ) - for idx, curve in enumerate(self.plot_widget.listDataItems()): - curve.setPen(pg.mkPen(color=colors[idx])) - # Background - self.plot_widget.setBackground(bg_color) - # Axes (tick marks, tick labels, axis line) - for axis in ["left", "bottom", "right", "top"]: - ax = self.plot_widget.getAxis(axis) - ax.setPen(pg.mkPen(color=fg_color)) - ax.setTextPen(pg.mkPen(color=fg_color)) - - if self.image_widget is not None: - self.image_widget.getView().setBackgroundColor(bg_color) - self.image_widget.ui.histogram.setBackground(bg_color) - - # ── 2-D image ────────────────────────────────────────────────────────── def _show_image_2d(self, data): self._clear_stack() @@ -372,11 +301,9 @@ class DataView(QWidget): ) set_image(img) - self.image_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) if stacked: - # --- Slider --- slider = QSlider(Qt.Orientation.Vertical) slider.setMinimum(0) slider.setMaximum(n_images - 1) @@ -414,18 +341,15 @@ class DataView(QWidget): h_layout.addWidget(slider_col) h_layout.addWidget(self.image_widget) - self.stack_layout.addWidget(container) + self.content_layout.addWidget(container) else: - self.stack_layout.addWidget(self.image_widget) + self.content_layout.addWidget(self.image_widget) self.apply_theme() - # ── Table ────────────────────────────────────────────────────────────── def _show_table(self, data): self._clear_stack() - MAX_ROWS, MAX_COLS = 2000, 500 - if data.ndim == 0: flat = data.reshape(1, 1) elif data.ndim == 1: @@ -440,18 +364,15 @@ class DataView(QWidget): show_cols = min(cols, MAX_COLS) if rows > MAX_ROWS or cols > MAX_COLS: - note = QLabel(f"⚠ Showing {show_rows}/{rows} rows × {show_cols}/{cols} cols") + note = QLabel(f"⚠ Showing {show_rows}/{rows} rows x {show_cols}/{cols} columns") note.setObjectName("info_label") note.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.stack_layout.addWidget(note) + self.content_layout.addWidget(note) table = QTableWidget(show_rows, show_cols) table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers) table.setSelectionMode(QAbstractItemView.SelectionMode.ContiguousSelection) table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Interactive) - table.horizontalHeader().setDefaultSectionSize(90) - table.verticalHeader().setDefaultSectionSize(22) - table.setFont(QFont("JetBrains Mono, Consolas, monospace", 10)) is_float = np.issubdtype(flat.dtype, np.floating) is_complex = np.iscomplexobj(flat) @@ -469,4 +390,4 @@ class DataView(QWidget): cell.setTextAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter) table.setItem(r, c, cell) - self.stack_layout.addWidget(table) + self.content_layout.addWidget(table) diff --git a/debye_bec/bec_widgets/widgets/data_viewer/panels/scan_view.py b/debye_bec/bec_widgets/widgets/data_viewer/panels/scan_view.py index 8fa327c..aa3ebe7 100644 --- a/debye_bec/bec_widgets/widgets/data_viewer/panels/scan_view.py +++ b/debye_bec/bec_widgets/widgets/data_viewer/panels/scan_view.py @@ -1,10 +1,7 @@ """ -File Viewer — qtpy + pyqtgraph -Supports pluggable file-format loaders (HDF5 built-in; extend via BaseFileLoader). +Scan viewer. Displays files of one or more scans in a tree view """ -from __future__ import annotations - import os from typing import Literal, Optional @@ -28,7 +25,9 @@ from qtpy.QtWidgets import ( QWidget, ) -from ..loaders import BaseFileLoader, LoaderRegistry, _registry +# pylint: disable=E0402 +from ..loaders import BaseFileLoader, registry +from ..widgets.qt_widgets import Group from .data_view import DataView logger = bec_logger.logger @@ -38,25 +37,54 @@ ICON_SIZE = 20 class ScanViewer(QMainWindow): """ - Generic file viewer. Supports any format registered in *registry*. + Generic scan viewer. Supports any format registered in *registry*. - Parameters - ---------- - filepath: - Optional path to open on startup. - registry: - ``LoaderRegistry`` to use. Defaults to the module-level ``_registry`` - which ships with ``HDF5Loader`` pre-registered. + Args: + filepath(str): Optional path to open on startup. """ - def __init__(self, filepath: Optional[str] = None, registry: Optional[LoaderRegistry] = None): + def __init__(self, filepath: Optional[str] = None): super().__init__() - self._registry = registry or _registry + self.registry = registry - # filepath -> (loader_instance, display_name) self._open_files: dict[str, tuple[BaseFileLoader, str]] = {} - self._build_ui() + central = QWidget() + self.setCentralWidget(central) + root_layout = QHBoxLayout(central) + + splitter = QSplitter(Qt.Orientation.Horizontal) + splitter.setChildrenCollapsible(False) + + left_pane = QWidget() + left_layout = QVBoxLayout(left_pane) + + self.tree = QTreeWidget() + self.tree.setMinimumWidth(250) + self.tree.setHeaderLabels(["Name", "Type", "Shape"]) + self.tree.header().setStretchLastSection(False) + self.tree.header().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) + self.tree.header().setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents) + self.tree.header().setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents) + self.tree.itemClicked.connect(self._on_item_clicked) + + left_layout.addWidget(self.tree, 1) + + self.data_panel = DataView() + + splitter.addWidget(left_pane) + splitter.addWidget(self.data_panel) + + splitter.setStretchFactor(0, 1) + splitter.setStretchFactor(1, 3) + splitter.setSizes([300, 900]) + splitter.setHandleWidth(6) + splitter.setChildrenCollapsible(False) + + self.scan_view_group = Group("Scan view", [splitter]) + + root_layout.addWidget(self.scan_view_group) + if filepath: self.load_files([filepath]) @@ -69,17 +97,15 @@ class ScanViewer(QMainWindow): """ self.data_panel.apply_theme(theme) - # ── public API ──────────────────────────────────────────────────────── - def load_files(self, filepaths: list[str]) -> None: """Open one or more files and add each as a top-level tree node.""" for fp in filepaths: if fp in self._open_files: continue # already loaded - loader = self._registry.get_loader(fp) + loader = self.registry.get_loader(fp) if loader is None: - supported = ", ".join(self._registry.supported_extensions) + supported = ", ".join(self.registry.supported_extensions) logger.warning("No loader found for %r. Supported extensions: %s", fp, supported) continue @@ -102,53 +128,12 @@ class ScanViewer(QMainWindow): self.data_panel.show_empty() def closeEvent(self, event): + """Close all""" for loader, _ in self._open_files.values(): loader.close() self._open_files.clear() super().closeEvent(event) - # ── UI construction ─────────────────────────────────────────────────── - - def _build_ui(self): - central = QWidget() - self.setCentralWidget(central) - - root_layout = QHBoxLayout(central) - - splitter = QSplitter(Qt.Orientation.Horizontal) - splitter.setChildrenCollapsible(False) - - # ── Left pane ── - left_pane = QWidget() - left_layout = QVBoxLayout(left_pane) - - self.tree = QTreeWidget() - self.tree.setMinimumWidth(250) - self.tree.setHeaderLabels(["Name", "Type", "Shape"]) - self.tree.header().setStretchLastSection(False) - self.tree.header().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) - self.tree.header().setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents) - self.tree.header().setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents) - self.tree.itemClicked.connect(self._on_item_clicked) - - left_layout.addWidget(self.tree, 1) - - # ── Right pane ── - self.data_panel = DataView() - - splitter.addWidget(left_pane) - splitter.addWidget(self.data_panel) - - splitter.setStretchFactor(0, 1) - splitter.setStretchFactor(1, 3) - splitter.setSizes([300, 900]) - splitter.setHandleWidth(6) - splitter.setChildrenCollapsible(False) - - root_layout.addWidget(splitter) - - # ── Tree helpers ────────────────────────────────────────────────────── - def _add_file_to_tree(self, filepath: str, loader: BaseFileLoader, display_name: str) -> None: """Add a single file as a new top-level node in the tree.""" dataset_icon = material_icon( @@ -163,7 +148,7 @@ class ScanViewer(QMainWindow): self._populate_tree(root_item, loader, "/") self.tree.addTopLevelItem(root_item) - # Expand first 2 levels + # Expand first 2 levels by default self.tree.expandItem(root_item) for i in range(root_item.childCount()): self.tree.expandItem(root_item.child(i)) @@ -206,8 +191,6 @@ class ScanViewer(QMainWindow): item.setForeground(1, QBrush(QColor(get_accent_colors().success.name()))) item.setForeground(2, QBrush(QColor("#656365"))) - # ── Event handling ──────────────────────────────────────────────────── - def _get_filepath_for_item(self, item: QTreeWidgetItem) -> str: """Walk up the tree to find the filepath stored on the root node.""" node = item @@ -226,9 +209,5 @@ class ScanViewer(QMainWindow): loader, _ = self._open_files[filepath] if kind == "dataset": - data, ftype = loader.read_dataset(path) - self.data_panel.display(data, path, ftype) - else: - # Group selected — show child count in status bar (optional) - count = loader.child_count(path) - self.statusBar().showMessage(f"{path} ({count} items)") + data = loader.read_dataset(path) + self.data_panel.display(data, path)