wip
CI for debye_bec / test (push) Successful in 57s

This commit is contained in:
x01da
2026-06-13 21:59:30 +02:00
parent a1ce2a43ec
commit 9506df600d
3 changed files with 118 additions and 63 deletions
@@ -67,6 +67,11 @@ class DataViewer(BECWidget, QWidget):
self.current_row = 0
logger.info(self.client.acl)
logger.info(self.client.active_account)
logger.info(self.client.proc)
logger.info(self.client.username)
self.input.scan_sel.currentItemChanged_connect(self.scan_sel_changed)
self.input.load_button.clicked_connect(self.load_dataset)
@@ -75,11 +80,13 @@ class DataViewer(BECWidget, QWidget):
self.current_row = kwargs["value"]().row()
@SafeSlot()
def load_dataset(self, *_):
def load_dataset(
self, *_
): # TODO: Check scan file components for combined xas/xrd scans. Is the Pilatus file in there as well?
scan = self.history[self.current_row]
file = scan["file_components"][0] + b"_master." + scan["file_components"][1]
logger.info(file.decode())
self.viewer.load_file(file.decode())
self.viewer.load_files([file.decode()])
@SafeSlot()
def on_history_update(self, *_):
@@ -89,19 +96,32 @@ class DataViewer(BECWidget, QWidget):
# and no scan has finished yet. Limit the history to the latest 20 scans.
max_scans = min(len(self.client.history), 20)
for n in range(1, max_scans): # last scans, up to 20
logger.info(self.client.history[-n].metadata)
scan_data = self.client.history[-n].metadata["bec"]
# logger.info(scan_data)
scan_number = scan_data["scan_number"]
scan_name = scan_data["scan_name"]
comment = scan_data["metadata"]["user_metadata"]["comment"]
sample_name = scan_data["metadata"]["user_metadata"]["sample_name"]
# start = scan_data[]
# end = scan_data[]
self.history.append(
{
"scan_number": scan_data["scan_number"],
"scan_name": scan_data["scan_name"],
"user_metadata": scan_data["user_metadata"],
"scan_number": scan_number,
"scan_name": scan_name,
"comment": comment,
"sample_name": sample_name,
"file_components": scan_data["file_components"],
}
)
self.input.scan_sel.addTaggedItem(
label=str(scan_data["scan_number"]), tags=[(scan_data["scan_name"], "#4A90D9")]
)
tags = []
tags.append((scan_name, "#4A90D9"))
if sample_name != "":
tags.append((sample_name, "#4A10D9"))
if comment != "":
tags.append((comment, "#FA10D9"))
self.input.scan_sel.addTaggedItem(label=str(scan_number), tags=tags)
# logger.info(f"Scan history: {self.history}")
class InputPanel(QWidget):
@@ -4,46 +4,50 @@ Selection works natively; no popup, no paintEvent hacks.
"""
import sys
from qtpy.QtCore import QPoint, QRect, QSize, Qt
from qtpy.QtGui import QColor, QFont, QFontMetrics, QPainter, QPen
from qtpy.QtWidgets import (
QApplication, QListWidget, QListWidgetItem, QStyledItemDelegate,
QStyleOptionViewItem, QWidget, QVBoxLayout, QLabel, QStyle,
QApplication,
QLabel,
QListWidget,
QListWidgetItem,
QStyle,
QStyledItemDelegate,
QStyleOptionViewItem,
QVBoxLayout,
QWidget,
)
from qtpy.QtGui import QPainter, QColor, QFont, QPen, QFontMetrics
from qtpy.QtCore import Qt, QRect, QSize, QPoint
# ── Design tokens ──────────────────────────────────────────────────────────────
ITEM_HEIGHT = 40
H_PAD = 12
TAG_H_PAD = 7
TAG_V_PAD = 3
TAG_GAP = 5
LABEL_TAG_GAP = 12
CORNER_RADIUS = 4
FONT_FAMILY = "Segoe UI"
LABEL_FONT_SIZE = 10
TAG_FONT_SIZE = 8
TAG_TEXT_COLOR = "#FFFFFF"
ITEM_HEIGHT = 30
H_PAD = 12
TAG_H_PAD = 7
TAG_V_PAD = 3
TAG_GAP = 5
LABEL_TAG_GAP = 12
CORNER_RADIUS = 4
TAG_TEXT_COLOR = "#FFFFFF"
# ── Enum compat (PySide6 nested vs PyQt5 flat) ────────────────────────────────
try:
_DEMIBOLD = QFont.Weight.DemiBold
_MEDIUM = QFont.Weight.Medium
_MEDIUM = QFont.Weight.Medium
except AttributeError:
_DEMIBOLD = QFont.DemiBold
_MEDIUM = QFont.Medium
_MEDIUM = QFont.Medium
_AlignVCenter = Qt.AlignmentFlag.AlignVCenter if hasattr(Qt, "AlignmentFlag") else Qt.AlignVCenter
_AlignCenter = Qt.AlignmentFlag.AlignCenter if hasattr(Qt, "AlignmentFlag") else Qt.AlignCenter
_UserRole = Qt.ItemDataRole.UserRole if hasattr(Qt, "ItemDataRole") else Qt.UserRole
_NoPen = Qt.PenStyle.NoPen if hasattr(Qt, "PenStyle") else Qt.NoPen
_AA = (QPainter.RenderHint.Antialiasing
if hasattr(QPainter, "RenderHint") else QPainter.Antialiasing)
_AlignCenter = Qt.AlignmentFlag.AlignCenter if hasattr(Qt, "AlignmentFlag") else Qt.AlignCenter
_UserRole = Qt.ItemDataRole.UserRole if hasattr(Qt, "ItemDataRole") else Qt.UserRole
_NoPen = Qt.PenStyle.NoPen if hasattr(Qt, "PenStyle") else Qt.NoPen
_AA = QPainter.RenderHint.Antialiasing if hasattr(QPainter, "RenderHint") else QPainter.Antialiasing
try:
_State_Selected = QStyle.StateFlag.State_Selected
_State_Selected = QStyle.StateFlag.State_Selected
_State_MouseOver = QStyle.StateFlag.State_MouseOver
except AttributeError:
_State_Selected = QStyle.State_Selected
_State_Selected = QStyle.State_Selected
_State_MouseOver = QStyle.State_MouseOver
@@ -57,7 +61,7 @@ class TaggedDelegate(QStyledItemDelegate):
painter.save()
is_selected = bool(option.state & _State_Selected)
is_hover = bool(option.state & _State_MouseOver)
is_hover = bool(option.state & _State_MouseOver)
# Background
if is_selected:
@@ -67,16 +71,23 @@ class TaggedDelegate(QStyledItemDelegate):
else:
painter.fillRect(option.rect, option.palette.base())
label_text = index.data(Qt.ItemDataRole.DisplayRole
if hasattr(Qt, "ItemDataRole") else Qt.DisplayRole) or ""
label_text = (
index.data(
Qt.ItemDataRole.DisplayRole if hasattr(Qt, "ItemDataRole") else Qt.DisplayRole
)
or ""
)
tags: list = index.data(_UserRole) or []
# Label
label_font = QFont(FONT_FAMILY, LABEL_FONT_SIZE)
label_font = painter.font() # inherit default font
label_font.setWeight(_DEMIBOLD)
painter.setFont(label_font)
label_color = (option.palette.highlightedText().color()
if is_selected else option.palette.text().color())
label_color = (
option.palette.highlightedText().color()
if is_selected
else option.palette.text().color()
)
painter.setPen(QPen(label_color))
fm = QFontMetrics(label_font)
@@ -85,7 +96,7 @@ class TaggedDelegate(QStyledItemDelegate):
painter.drawText(QPoint(option.rect.left() + H_PAD, label_y), label_text)
# Tag pills
tag_font = QFont(FONT_FAMILY, TAG_FONT_SIZE)
tag_font = painter.font() # inherit default font
tag_font.setWeight(_MEDIUM)
fm_tag = QFontMetrics(tag_font)
@@ -122,7 +133,7 @@ class TaggedListWidget(QListWidget):
def __init__(self, parent=None) -> None:
super().__init__(parent)
self.setItemDelegate(TaggedDelegate(self))
self.setMouseTracking(True) # enables hover highlight
self.setMouseTracking(True) # enables hover highlight
def addTaggedItem(self, label: str, tags: list | None = None) -> QListWidgetItem:
"""
@@ -154,11 +165,11 @@ if __name__ == "__main__":
layout.setSpacing(12)
lst = TaggedListWidget()
lst.addTaggedItem("NumPy", tags=[("1.26", "#2563EB"), ("stable", "#16A34A")])
lst.addTaggedItem("Pandas", tags=[("2.2", "#7C3AED"), ("deprecated", "#DC2626")])
lst.addTaggedItem("PyTorch", tags=[("2.3", "#2563EB"), ("cuda", "#DC2626")])
lst.addTaggedItem("scikit-learn", tags=[("1.4", "#0891B2"), ("stable", "#16A34A")])
lst.addTaggedItem("Matplotlib", tags=[("3.8", "#D97706")])
lst.addTaggedItem("NumPy", tags=[("1.26", "#2563EB"), ("stable", "#16A34A")])
lst.addTaggedItem("Pandas", tags=[("2.2", "#7C3AED"), ("deprecated", "#DC2626")])
lst.addTaggedItem("PyTorch", tags=[("2.3", "#2563EB"), ("cuda", "#DC2626")])
lst.addTaggedItem("scikit-learn", tags=[("1.4", "#0891B2"), ("stable", "#16A34A")])
lst.addTaggedItem("Matplotlib", tags=[("3.8", "#D97706")])
lst.addTaggedItem("Plain item — no tags")
layout.addWidget(lst)
@@ -166,7 +177,7 @@ if __name__ == "__main__":
def on_selection():
label = lst.currentItem().text() if lst.currentItem() else ""
tags = lst.currentTags()
tags = lst.currentTags()
tag_str = " ".join(t for t, _ in tags) if tags else "none"
info.setText(f"Selected: <b>{label}</b> tags: {tag_str}")
@@ -174,4 +185,4 @@ if __name__ == "__main__":
layout.addWidget(info)
window.show()
sys.exit(app.exec())
sys.exit(app.exec())
@@ -355,15 +355,18 @@ class DataPanel(QWidget):
class HDF5Viewer(QMainWindow):
def __init__(self, filepath=None):
super().__init__()
self.filepath = filepath
self.h5file = None
self.h5files = {} # filepath -> h5py.File
self._build_ui()
if filepath:
self.load_files([filepath])
def load_file(self, f):
self.filepath = f
self.h5file = h5py.File(f, "r")
filename = f.split("/")[-1]
self._load_tree(filename)
def load_files(self, filepaths: list[str]):
"""Open one or more HDF5 files and add each as a top-level tree node."""
for f in filepaths:
if f in self.h5files:
continue # already loaded
self.h5files[f] = h5py.File(f, "r")
self._add_file_to_tree(f)
def _build_ui(self):
central = QWidget()
@@ -394,7 +397,6 @@ class HDF5Viewer(QMainWindow):
splitter.addWidget(left_pane)
splitter.addWidget(self.data_panel)
# Initial proportions: 25% / 75%
splitter.setStretchFactor(0, 1)
splitter.setStretchFactor(1, 3)
splitter.setSizes([300, 900])
@@ -403,17 +405,20 @@ class HDF5Viewer(QMainWindow):
root_layout.addWidget(splitter)
def _load_tree(self, filename):
self.tree.clear()
def _add_file_to_tree(self, filepath: str):
"""Add a single file as a new top-level node in the tree."""
h5file = self.h5files[filepath]
filename = filepath.split("/")[-1]
dataset_icon = material_icon("dataset", size=(ICON_SIZE, ICON_SIZE), color="#2980b9")
root_item = QTreeWidgetItem(self.tree, [filename, "Group", ""])
root_item.setIcon(0, dataset_icon)
root_item.setData(0, Qt.UserRole, "/")
root_item.setData(0, Qt.UserRole + 1, "group")
root_item.setData(0, Qt.UserRole + 2, filepath) # so clicks know which file
root_item.setForeground(0, QColor(ACCENT))
populate_tree(root_item, self.h5file)
populate_tree(root_item, h5file)
self.tree.addTopLevelItem(root_item)
# Expand first 2 levels
@@ -423,21 +428,40 @@ class HDF5Viewer(QMainWindow):
self.tree.setCurrentItem(root_item)
def _get_filepath_for_item(self, item: QTreeWidgetItem) -> str:
"""Walk up the tree to find the filepath stored on the root node."""
node = item
while node.parent():
node = node.parent()
return node.data(0, Qt.UserRole + 2)
def _on_item_clicked(self, item, _col):
path = item.data(0, Qt.UserRole)
kind = item.data(0, Qt.UserRole + 1)
if not path:
filepath = self._get_filepath_for_item(item)
if not path or not filepath:
return
obj = self.h5file[path] if path != "/" else self.h5file
h5file = self.h5files[filepath]
obj = h5file[path] if path != "/" else h5file
if kind == "dataset":
ds = self.h5file[path]
data = ds[()]
data = h5file[path][()]
self.data_panel.display(data, path)
else:
n = len(obj) if isinstance(obj, h5py.Group) else 0
def clear_files(self):
"""Close all open HDF5 files and reset the tree."""
for h5file in self.h5files.values():
h5file.close()
self.h5files.clear()
self.tree.clear()
self.data_panel._show_empty() # TODO: If it works, make function public!
def closeEvent(self, event):
self.h5file.close()
for h5file in self.h5files.values():
h5file.close()
self.h5files.clear()
super().closeEvent(event)