wip
CI for debye_bec / test (push) Successful in 56s
CI for debye_bec / test (pull_request) Failing after 34s

This commit is contained in:
x01da
2026-06-14 14:42:22 +02:00
parent 9506df600d
commit 16fe41dc78
3 changed files with 105 additions and 64 deletions
@@ -3,6 +3,7 @@ Data Viewer: Custom BEC widget to view data from scans.
"""
import sys
from datetime import datetime
from functools import partial
from bec_lib import bec_logger
@@ -74,6 +75,7 @@ class DataViewer(BECWidget, QWidget):
self.input.scan_sel.currentItemChanged_connect(self.scan_sel_changed)
self.input.load_button.clicked_connect(self.load_dataset)
self.input.unload_button.clicked_connect(self.unload_all_datasets)
@SafeSlot()
def scan_sel_changed(self, *_, **kwargs):
@@ -88,6 +90,31 @@ class DataViewer(BECWidget, QWidget):
logger.info(file.decode())
self.viewer.load_files([file.decode()])
@SafeSlot()
def unload_all_datasets(self, *_):
self.viewer.clear_files()
def duration_string(self, start: str, end: str) -> str:
start_dt = datetime.fromisoformat(start)
end_dt = datetime.fromisoformat(end)
seconds = abs(int((end_dt - start_dt).total_seconds()))
days, remainder = divmod(seconds, 86400)
hours, remainder = divmod(remainder, 3600)
minutes, _ = divmod(remainder, 60)
parts = []
if days:
parts.append(f"{days}d")
if hours:
parts.append(f"{hours}h")
if minutes:
parts.append(f"{minutes}min")
return " ".join(parts) if parts else "<1min"
@SafeSlot()
def on_history_update(self, *_):
self.history = []
@@ -96,15 +123,17 @@ 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)
# logger.info(self.client.history[-n].metadata["bec"]["status"])
start_time = self.client.history[-n].metadata["start_time"]
end_time = self.client.history[-n].metadata["end_time"]
logger.info(type(start_time))
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[]
status = scan_data["status"]
self.history.append(
{
"scan_number": scan_number,
@@ -112,6 +141,9 @@ class DataViewer(BECWidget, QWidget):
"comment": comment,
"sample_name": sample_name,
"file_components": scan_data["file_components"],
"start_time": start_time,
"end_time": end_time,
"status": status,
}
)
tags = []
@@ -120,8 +152,15 @@ class DataViewer(BECWidget, QWidget):
tags.append((sample_name, "#4A10D9"))
if comment != "":
tags.append((comment, "#FA10D9"))
if status == "closed":
tags.append((status, "#1F7023"))
elif status == "halted":
tags.append((status, "#A33047"))
else:
tags.append((status, "#656365"))
tags.append((self.duration_string(start_time, end_time), "#656365"))
self.input.scan_sel.addTaggedItem(label=str(scan_number), tags=tags)
# logger.info(f"Scan history: {self.history}")
logger.info(f"Scan history: {self.history}")
class InputPanel(QWidget):
@@ -135,9 +174,12 @@ class InputPanel(QWidget):
# Scan selection
self.scan_sel = ListWidget("scan_sel", "Scan", ["Si", "Rh", "Pt"])
self.load_button = Button(label_button="Load Dataset", enabled=True)
self.unload_button = Button(label_button="Unload all", enabled=True)
# Assemble complete scan selection group
self.input_group = Group("Scan selection", [self.scan_sel, self.load_button])
self.input_group = Group(
"Scan selection", [self.scan_sel, self.load_button, self.unload_button]
)
self._layout.addWidget(self.input_group)
self._layout.addStretch()
@@ -149,40 +149,3 @@ class TaggedListWidget(QListWidget):
def currentTags(self) -> list:
item = self.currentItem()
return item.data(_UserRole) or [] if item else []
# ── Demo ──────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setStyle("Fusion")
window = QWidget()
window.setWindowTitle("TaggedListWidget demo")
window.resize(420, 320)
layout = QVBoxLayout(window)
layout.setContentsMargins(24, 24, 24, 24)
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("Plain item — no tags")
layout.addWidget(lst)
info = QLabel()
def on_selection():
label = lst.currentItem().text() if lst.currentItem() else ""
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}")
lst.currentItemChanged.connect(lambda *_: on_selection())
layout.addWidget(info)
window.show()
sys.exit(app.exec())
@@ -18,6 +18,7 @@ from qtpy.QtWidgets import (
QMainWindow,
QRadioButton,
QSizePolicy,
QSlider,
QSplitter,
QTableWidget,
QTableWidgetItem,
@@ -55,7 +56,7 @@ pg.setConfigOptions(
def _styled_plot_widget(**kwargs) -> pg.PlotWidget:
"""Return a PlotWidget already themed to match the dark palette."""
pw = pg.PlotWidget(**kwargs)
pw.setBackground(pg.mkColor(*PG_PLOT_BG))
# pw.setBackground(pg.mkColor(*PG_PLOT_BG))
ax = pw.getPlotItem()
for side in ("left", "bottom", "right", "top"):
ax.getAxis(side).setPen(pg.mkPen(color=PG_GRID, width=1))
@@ -65,7 +66,7 @@ def _styled_plot_widget(**kwargs) -> pg.PlotWidget:
# ── HDF5 Tree helpers ──────────────────────────────────────────────────────
TYPE_ICONS = {"group": "📂", "dataset": "📊"}
# TYPE_ICONS = {"group": "📂", "dataset": "📊"}
def dtype_tag(ds):
@@ -234,43 +235,78 @@ class DataPanel(QWidget):
def _show_plot_1d(self, data):
self._clear_stack()
flat = data.reshape(-1).astype(
np.float32
) # TODO: Works, but I would prefer 1d plot + row selection like h5web in vscode does
x = np.arange(flat.size, dtype=np.float32)
is_2d = data.ndim == 2
if is_2d:
n_rows, n_cols = data.shape
row_data = data[0].astype(np.float32)
else:
row_data = data.reshape(-1).astype(np.float32)
x = np.arange(row_data.size, dtype=np.float32)
pw = _styled_plot_widget()
pw.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
plot_item = pw.getPlotItem()
# Prevent intermediate auto-range recalculations during setup
plot_item.setAutoVisible(y=False)
curve = pg.PlotDataItem(
x,
flat,
pen=pg.mkPen(color=PG_ACCENT, width=1.6),
antialias=False, # critical for 10M points
x, row_data, pen=pg.mkPen(color=PG_ACCENT, width=1.6), antialias=False
)
pw.addItem(curve)
# ---- performance optimizations ----
curve.setDownsampling(auto=True, method="peak")
curve.setClipToView(True)
# optional but often helpful for very large datasets
curve.setSkipFiniteCheck(True)
# ---- labels ----
plot_item.setLabel("bottom", "index")
plot_item.setLabel("left", "value")
# enable autorange only after item is fully added
# plot_item.setLabel("bottom", "index")
# plot_item.setLabel("left", "value")
plot_item.enableAutoRange()
self.stack_layout.addWidget(pw)
if is_2d:
# --- Slider ---
slider = QSlider(Qt.Vertical)
slider.setMinimum(0)
slider.setMaximum(n_rows - 1)
slider.setValue(0)
# slider.setInvertedAppearance(True) # row 0 at top
slider.setFixedWidth(32)
# slider.setSingleStep(1)
slider.setPageStep(1)
row_label = QLabel("0")
row_label.setAlignment(Qt.AlignCenter)
row_label.setFixedWidth(32)
# row_label.setStyleSheet("color: #aaa; font-size: 10px;")
def on_row_changed(row):
row_label.setText(str(row))
new_data = data[row].astype(np.float32)
new_x = np.arange(new_data.size, dtype=np.float32)
curve.setData(new_x, new_data)
plot_item.enableAutoRange()
slider.valueChanged.connect(on_row_changed)
slider_col = QWidget()
slider_col.setFixedWidth(36)
col_layout = QVBoxLayout(slider_col)
col_layout.setContentsMargins(0, 0, 0, 0)
col_layout.setSpacing(2)
col_layout.addWidget(row_label)
col_layout.addWidget(slider)
container = QWidget()
h_layout = QHBoxLayout(container)
h_layout.setContentsMargins(0, 0, 0, 0)
h_layout.setSpacing(4)
h_layout.addWidget(slider_col)
h_layout.addWidget(pw)
self.stack_layout.addWidget(container)
else:
self.stack_layout.addWidget(pw)
# ── 2-D image ──────────────────────────────────────────────────────────
def _show_image_2d(self, data):