wip
CI for debye_bec / test (push) Successful in 1m2s

This commit is contained in:
x01da
2026-06-12 22:04:02 +02:00
parent c64e6867ff
commit a1ce2a43ec
2 changed files with 165 additions and 286 deletions
@@ -3,26 +3,20 @@ Data Viewer: Custom BEC widget to view data from scans.
"""
import sys
from functools import partial
from bec_lib import bec_logger
from bec_lib.endpoints import MessageEndpoints
from bec_widgets.utils.bec_dispatcher import BECDispatcher
from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.colors import apply_theme, get_accent_colors
from bec_widgets.utils.error_popups import SafeSlot
# pylint: disable=E0611
from qtpy.QtWidgets import (
QApplication,
QWidget,
)
from functools import partial
from bec_widgets.utils.colors import get_accent_colors
from qtpy.QtCore import Qt
# pylint: disable=E0611
from qtpy.QtGui import QFont
# pylint: disable=E0611
from qtpy.QtWidgets import (
QApplication,
QComboBox,
@@ -30,10 +24,10 @@ from qtpy.QtWidgets import (
QGroupBox,
QHBoxLayout,
QLabel,
QLayout,
QPushButton,
QVBoxLayout,
QWidget,
QLayout,
)
from debye_bec.bec_widgets.widgets.data_viewer.qt_widgets import TaggedListWidget
@@ -52,7 +46,7 @@ class DataViewer(BECWidget, QWidget):
def __init__(self, *arg, parent=None, **kwargs):
super().__init__(parent=parent, theme_update=True, *arg, **kwargs)
self.get_bec_shortcuts()
self.get_bec_shortcuts()
central = QWidget()
self.root_layout = QVBoxLayout(central)
@@ -60,17 +54,15 @@ class DataViewer(BECWidget, QWidget):
self.input = InputPanel()
self.viewer = HDF5Viewer()
self.root_layout.addWidget(self.input, alignment=Qt.AlignmentFlag.AlignTop)
self.root_layout.addWidget(self.viewer, alignment=Qt.AlignmentFlag.AlignTop)
self.root_layout.addWidget(self.input, 0)
self.root_layout.addWidget(self.viewer, 1)
self.setLayout(self.root_layout)
self.setWindowTitle("Data Viewer")
# self.resize(1800, 800)
self.history = []
self.bec_dispatcher.connect_slot(
self.on_history_update, MessageEndpoints.scan_history()
)
self.bec_dispatcher.connect_slot(self.on_history_update, MessageEndpoints.scan_history())
self.on_history_update()
self.current_row = 0
@@ -80,12 +72,12 @@ class DataViewer(BECWidget, QWidget):
@SafeSlot()
def scan_sel_changed(self, *_, **kwargs):
self.current_row = kwargs['value']().row()
self.current_row = kwargs["value"]().row()
@SafeSlot()
def load_dataset(self, *_):
scan = self.history[self.current_row]
file = scan['file_components'][0] + b'_master.' + scan['file_components'][1]
file = scan["file_components"][0] + b"_master." + scan["file_components"][1]
logger.info(file.decode())
self.viewer.load_file(file.decode())
@@ -93,20 +85,22 @@ class DataViewer(BECWidget, QWidget):
def on_history_update(self, *_):
self.history = []
self.input.scan_sel.clear()
for n in range(1, 10): # last 10 scans
scan_data = self.client.history[-n].metadata['bec']
# Get the length of the scan history, which is 0 when the bec server was started
# 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
scan_data = self.client.history[-n].metadata["bec"]
# logger.info(scan_data)
self.history.append({
'scan_number': scan_data['scan_number'],
'scan_name': scan_data['scan_name'],
'user_metadata': scan_data['user_metadata'],
'file_components': scan_data['file_components'],
})
self.history.append(
{
"scan_number": scan_data["scan_number"],
"scan_name": scan_data["scan_name"],
"user_metadata": scan_data["user_metadata"],
"file_components": scan_data["file_components"],
}
)
self.input.scan_sel.addTaggedItem(
label=str(scan_data['scan_number']),
tags=[
(scan_data['scan_name'], "#4A90D9"),
],
label=str(scan_data["scan_number"]), tags=[(scan_data["scan_name"], "#4A90D9")]
)
@@ -120,22 +114,15 @@ 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.load_button = Button(label_button="Load Dataset", 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._layout.addWidget(self.input_group)
self._layout.addStretch()
class Group(QGroupBox):
def __init__(self, label, widgets):
super().__init__(label)
@@ -1,126 +1,54 @@
#!/usr/bin/env python3
"""
HDF5 Viewer — PyQt5 + pyqtgraph + h5py
Usage: python hdf5_viewer.py <filename.h5>
HDF5 Viewer — qtpy + pyqtgraph + h5py
"""
import sys
import os
import numpy as np
import h5py
from qtpy.QtWidgets import (
QApplication, QMainWindow, QSplitter, QTreeWidget, QTreeWidgetItem,
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QTableWidget,
QTableWidgetItem, QAbstractItemView, QSizePolicy, QStatusBar,
QRadioButton, QGroupBox, QHeaderView,
)
from qtpy.QtCore import Qt
from qtpy.QtGui import QFont, QColor
from qtpy.QtWidgets import QStyle
import numpy as np
import pyqtgraph as pg
from bec_lib import bec_logger
from bec_qthemes import material_icon
from qtpy.QtCore import Qt
from qtpy.QtGui import QColor, QFont
from qtpy.QtWidgets import (
QAbstractItemView,
QGroupBox,
QHBoxLayout,
QHeaderView,
QLabel,
QMainWindow,
QRadioButton,
QSizePolicy,
QSplitter,
QTableWidget,
QTableWidgetItem,
QTreeWidget,
QTreeWidgetItem,
QVBoxLayout,
QWidget,
)
logger = bec_logger.logger
# ── Palette ────────────────────────────────────────────────────────────────
BG_DARK = "#1C1E26"
BG_MID = "#252731"
BG_PANEL = "#2D3040"
ACCENT = "#7C9CF5"
TEXT_PRI = "#E8EAF2"
ACCENT = "#7C9CF5"
TEXT_SEC = "#8A8FA8"
BORDER = "#3A3D50"
SEL_BG = "#3A4268"
GROUP_BG = "#21232D"
SUCCESS = "#5FCEA8"
SUCCESS = "#5FCEA8"
# pyqtgraph color tuples (R, G, B)
PG_BG = (28, 30, 38)
PG_PLOT_BG = (45, 48, 64)
PG_ACCENT = (124, 156, 245)
PG_GRID = (58, 61, 80)
PG_TEXT = (138, 143, 168)
STYLESHEET = f"""
QMainWindow, QWidget {{
background-color: {BG_DARK};
color: {TEXT_PRI};
# font-family: 'Inter', 'Segoe UI', 'Helvetica Neue', sans-serif;
# font-size: 13px;
}}
QSplitter::handle {{
background: {BORDER};
width: 1px; height: 1px;
}}
QTreeWidget {{
background-color: {BG_MID};
border: 1px solid {BORDER};
border-radius: 6px;
outline: none;
padding: 4px;
}}
QTreeWidget::item {{ padding: 4px 6px; border-radius: 4px; }}
QTreeWidget::item:hover {{ background: {BG_PANEL}; }}
QTreeWidget::item:selected {{ background: {SEL_BG}; color: {TEXT_PRI}; }}
QHeaderView::section {{
background-color: {BG_PANEL}; color: {TEXT_SEC};
border: none; padding: 4px 6px;
font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em;
}}
QTableWidget {{
background-color: {BG_MID};
border: 1px solid {BORDER};
border-radius: 6px;
gridline-color: {BORDER};
outline: none;
}}
QTableWidget::item {{ padding: 3px 8px; color: {TEXT_PRI}; }}
QTableWidget::item:selected {{ background: {SEL_BG}; }}
QTableCornerButton::section {{ background: {BG_PANEL}; border: none; }}
QLabel#section_title {{
font-size: 11px; font-weight: 600; color: {TEXT_SEC};
text-transform: uppercase; letter-spacing: 0.08em; padding: 2px 0 6px 2px;
}}
QLabel#info_label {{ color: {TEXT_SEC}; font-size: 12px; padding: 4px; }}
QLabel#path_label {{ color: {ACCENT}; font-size: 12px; font-weight: 500; padding: 2px 4px; }}
QPushButton {{
background-color: {BG_PANEL}; color: {TEXT_PRI};
border: 1px solid {BORDER}; border-radius: 5px; padding: 5px 14px; font-size: 12px;
}}
QPushButton:hover {{ background-color: {SEL_BG}; border-color: {ACCENT}; }}
QPushButton:pressed {{ background-color: {ACCENT}; color: white; }}
QRadioButton {{ color: {TEXT_PRI}; spacing: 6px; font-size: 12px; }}
QRadioButton::indicator {{
width: 14px; height: 14px; border-radius: 7px;
border: 2px solid {BORDER}; background: {BG_MID};
}}
QRadioButton::indicator:checked {{ background: {ACCENT}; border-color: {ACCENT}; }}
QGroupBox {{
background: {GROUP_BG}; border: 1px solid {BORDER}; border-radius: 6px;
margin-top: 10px; padding: 8px; font-size: 11px; color: {TEXT_SEC};
}}
QGroupBox::title {{
subcontrol-origin: margin; left: 8px; padding: 0 4px;
color: {TEXT_SEC}; text-transform: uppercase; letter-spacing: 0.06em; font-size: 10px;
}}
QStatusBar {{
background: {BG_PANEL}; color: {TEXT_SEC};
border-top: 1px solid {BORDER}; font-size: 11px;
}}
QScrollBar:vertical {{ background: {BG_MID}; width: 8px; border-radius: 4px; }}
QScrollBar::handle:vertical {{ background: {BORDER}; border-radius: 4px; min-height: 20px; }}
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {{ height: 0; }}
QScrollBar:horizontal {{ background: {BG_MID}; height: 8px; border-radius: 4px; }}
QScrollBar::handle:horizontal {{ background: {BORDER}; border-radius: 4px; min-width: 20px; }}
QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {{ width: 0; }}
"""
PG_BG = (28, 30, 38)
PG_PLOT_BG = (45, 48, 64)
PG_ACCENT = (124, 156, 245)
PG_GRID = (58, 61, 80)
PG_TEXT = (138, 143, 168)
ICON_SIZE = 20
# ── pyqtgraph global config ────────────────────────────────────────────────
pg.setConfigOptions(
background=pg.mkColor(*PG_BG),
foreground=pg.mkColor(*PG_TEXT),
antialias=True,
useOpenGL=False, # safer default; set True for large datasets if GPU available
useOpenGL=False, # safer default; set True for large datasets if GPU available
)
@@ -139,19 +67,26 @@ def _styled_plot_widget(**kwargs) -> pg.PlotWidget:
# ── HDF5 Tree helpers ──────────────────────────────────────────────────────
TYPE_ICONS = {"group": "📂", "dataset": "📊"}
def dtype_tag(ds):
d = ds.dtype
if np.issubdtype(d, np.integer): return "int"
if np.issubdtype(d, np.floating): return "float"
if np.issubdtype(d, np.complexfloating): return "complex"
if d.kind in ("S", "U", "O"): return "str"
if np.issubdtype(d, np.integer):
return "int"
if np.issubdtype(d, np.floating):
return "float"
if np.issubdtype(d, np.complexfloating):
return "complex"
if d.kind in ("S", "U", "O"):
return "str"
return str(d)
def populate_tree(parent_item, h5_obj):
style = QApplication.style()
folder_icon = style.standardIcon(QStyle.SP_DirIcon)
file_icon = style.standardIcon(QStyle.SP_FileIcon)
folder_icon = material_icon("folder", size=(ICON_SIZE, ICON_SIZE), color="#2980b9")
vector_icon = material_icon("show_chart", size=(ICON_SIZE, ICON_SIZE), color="#2980b9")
array_icon = material_icon("stacked_line_chart", size=(ICON_SIZE, ICON_SIZE), color="#2980b9")
scalar_icon = material_icon("point_scan", size=(ICON_SIZE, ICON_SIZE), color="#2980b9")
str_icon = material_icon("text_snippet", size=(ICON_SIZE, ICON_SIZE), color="#2980b9")
if not isinstance(h5_obj, h5py.Group):
return
@@ -174,10 +109,22 @@ def populate_tree(parent_item, h5_obj):
populate_tree(item, child)
elif isinstance(child, h5py.Dataset):
shape_str = "×".join(str(s) for s in child.shape) or "scalar"
shape_str = "x".join(str(s) for s in child.shape) or "scalar"
item = QTreeWidgetItem(parent_item, [key, dtype_tag(child), shape_str])
item.setIcon(0, file_icon)
dtype = dtype_tag(child)
if shape_str == "scalar":
if dtype == "str":
icon = str_icon
else:
icon = scalar_icon
elif "x" in shape_str:
icon = array_icon
else:
icon = vector_icon
item = QTreeWidgetItem(parent_item, [key, dtype, shape_str])
item.setIcon(0, icon)
item.setData(0, Qt.UserRole, child.name)
item.setData(0, Qt.UserRole + 1, "dataset")
@@ -191,8 +138,6 @@ class DataPanel(QWidget):
def __init__(self):
super().__init__()
self._layout = QVBoxLayout(self)
self._layout.setContentsMargins(0, 0, 0, 0)
self._layout.setSpacing(8)
# Header
hdr = QHBoxLayout()
@@ -208,8 +153,8 @@ class DataPanel(QWidget):
mode_box = QGroupBox("View mode")
mode_layout = QHBoxLayout(mode_box)
mode_layout.setContentsMargins(8, 4, 8, 4)
self.rb_auto = QRadioButton("Auto")
self.rb_plot = QRadioButton("Plot")
self.rb_auto = QRadioButton("Auto")
self.rb_plot = QRadioButton("Plot")
self.rb_image = QRadioButton("Image")
self.rb_table = QRadioButton("Table")
self.rb_auto.setChecked(True)
@@ -247,9 +192,12 @@ class DataPanel(QWidget):
self.display(self._current_data, self.path_label.text())
def _active_mode(self):
if self.rb_plot.isChecked(): return "plot"
if self.rb_image.isChecked(): return "image"
if self.rb_table.isChecked(): return "table"
if self.rb_plot.isChecked():
return "plot"
if self.rb_image.isChecked():
return "image"
if self.rb_table.isChecked():
return "table"
return "auto"
# ── public ────────────────────────────────────────────────────────────
@@ -273,7 +221,9 @@ class DataPanel(QWidget):
else:
mode = "table"
if mode == "plot":
if (
mode == "plot"
): # TODO disable plot and image if data is str, maybe completely disable manual mode and only allow image/plot for arrays
self._show_plot_1d(data)
elif mode == "image":
self._show_image_2d(data)
@@ -283,25 +233,42 @@ class DataPanel(QWidget):
# ── 1-D line plot ──────────────────────────────────────────────────────
def _show_plot_1d(self, data):
self._clear_stack()
flat = data.flatten().astype(float)
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)
pw = _styled_plot_widget()
pw.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
# Filled area under curve
x = np.arange(len(flat), dtype=float)
fill = pg.FillBetweenItem(
pg.PlotDataItem(x, flat),
pg.PlotDataItem(x, np.zeros_like(flat)),
brush=pg.mkBrush(124, 156, 245, 35),
)
pw.addItem(fill)
pw.plot(x, flat,
pen=pg.mkPen(color=PG_ACCENT, width=1.6),
antialias=True)
plot_item = pw.getPlotItem()
pw.getPlotItem().setLabel("bottom", "index")
pw.getPlotItem().setLabel("left", "value")
# 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
)
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.enableAutoRange()
self.stack_layout.addWidget(pw)
@@ -318,7 +285,7 @@ class DataPanel(QWidget):
# ImageView gives us colorbar + histogram + zoom for free
iv = pg.ImageView()
iv.setBackground(pg.mkColor(*PG_BG))
iv.getView().setBackgroundColor(pg.mkColor(*PG_BG))
iv.ui.histogram.setBackground(pg.mkColor(*PG_PLOT_BG))
iv.ui.roiBtn.hide()
iv.ui.menuBtn.hide()
@@ -365,13 +332,18 @@ class DataPanel(QWidget):
table.verticalHeader().setDefaultSectionSize(22)
table.setFont(QFont("JetBrains Mono, Consolas, monospace", 10))
is_float = np.issubdtype(flat.dtype, np.floating)
is_float = np.issubdtype(flat.dtype, np.floating)
is_complex = np.iscomplexobj(flat)
is_bytes = flat.dtype.kind == "S"
for r in range(show_rows):
for c in range(show_cols):
val = flat[r, c]
txt = f"{val:.6g}" if is_float else (f"{val:.4g}" if is_complex else str(val))
txt = (
f"{val:.6g}"
if is_float
else f"{val:.4g}" if is_complex else str(val.decode()) if is_bytes else str(val)
)
cell = QTableWidgetItem(txt)
cell.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
table.setItem(r, c, cell)
@@ -379,75 +351,32 @@ class DataPanel(QWidget):
self.stack_layout.addWidget(table)
# ── Attributes panel ───────────────────────────────────────────────────────
class AttrsPanel(QWidget):
def __init__(self):
super().__init__()
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
title = QLabel("Attributes")
title.setObjectName("section_title")
layout.addWidget(title)
self.table = QTableWidget(0, 2)
self.table.setHorizontalHeaderLabels(["Key", "Value"])
self.table.horizontalHeader().setStretchLastSection(True)
self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.table.verticalHeader().hide()
self.table.verticalHeader().setDefaultSectionSize(22)
layout.addWidget(self.table)
def set_attrs(self, attrs_dict):
self.table.setRowCount(0)
for key, val in attrs_dict.items():
row = self.table.rowCount()
self.table.insertRow(row)
self.table.setItem(row, 0, QTableWidgetItem(str(key)))
self.table.setItem(row, 1, QTableWidgetItem(str(val)))
# ── Main window ────────────────────────────────────────────────────────────
class HDF5Viewer(QMainWindow):
def __init__(self, filepath=None):
super().__init__()
self.filepath = filepath
self.h5file = None
# self.setWindowTitle(f"HDF5 Viewer — {os.path.basename(filepath)}")
# self.resize(1280, 800)
self.h5file = None
self._build_ui()
def load_file(self, f):
self.filepath = f
self.h5file = h5py.File(f, "r")
self._load_tree()
self.h5file = h5py.File(f, "r")
filename = f.split("/")[-1]
self._load_tree(filename)
def _build_ui(self):
central = QWidget()
self.setCentralWidget(central)
root_layout = QVBoxLayout(central)
root_layout.setContentsMargins(10, 10, 10, 6)
root_layout.setSpacing(6)
# Top bar
top_bar = QHBoxLayout()
# file_label = QLabel(f"📁 {self.filepath}")
# file_label.setObjectName("path_label")
# top_bar.addWidget(file_label)
top_bar.addStretch()
root_layout.addLayout(top_bar)
root_layout = QHBoxLayout(central)
# Main horizontal splitter
h_split = QSplitter(Qt.Horizontal)
h_split.setHandleWidth(2)
splitter = QSplitter(Qt.Horizontal)
splitter.setChildrenCollapsible(False)
# ── Left: tree + attrs ──
left_pane = QWidget()
# ── Left pane ──
left_pane = QWidget()
left_layout = QVBoxLayout(left_pane)
left_layout.setContentsMargins(0, 0, 0, 0)
left_layout.setSpacing(6)
tree_title = QLabel("Structure")
tree_title.setObjectName("section_title")
left_layout.addWidget(tree_title)
self.tree = QTreeWidget()
self.tree.setHeaderLabels(["Name", "Type", "Shape"])
@@ -455,36 +384,31 @@ class HDF5Viewer(QMainWindow):
self.tree.header().setSectionResizeMode(0, QHeaderView.Stretch)
self.tree.header().setSectionResizeMode(1, QHeaderView.ResizeToContents)
self.tree.header().setSectionResizeMode(2, QHeaderView.ResizeToContents)
self.tree.setIndentation(16)
self.tree.itemClicked.connect(self._on_item_clicked)
left_layout.addWidget(self.tree, 3)
self.attrs_panel = AttrsPanel()
left_layout.addWidget(self.attrs_panel, 1)
left_layout.addWidget(self.tree, 1)
h_split.addWidget(left_pane)
# ── Right: data panel ──
# ── Right pane ──
self.data_panel = DataPanel()
h_split.addWidget(self.data_panel)
h_split.setStretchFactor(0, 1)
h_split.setStretchFactor(1, 3)
h_split.setSizes([320, 960])
splitter.addWidget(left_pane)
splitter.addWidget(self.data_panel)
root_layout.addWidget(h_split, 1)
# Initial proportions: 25% / 75%
splitter.setStretchFactor(0, 1)
splitter.setStretchFactor(1, 3)
splitter.setSizes([300, 900])
splitter.setHandleWidth(6)
splitter.setChildrenCollapsible(False)
# self.status = QStatusBar()
# self.setStatusBar(self.status)
# self.status.showMessage(f"Loaded: {self.filepath}")
root_layout.addWidget(splitter)
def _load_tree(self):
def _load_tree(self, filename):
self.tree.clear()
style = QApplication.style()
folder_icon = style.standardIcon(QStyle.SP_DirIcon) # TODO Choose different icon for head
root_item = QTreeWidgetItem(self.tree, ["/", "Group", ""])
root_item.setIcon(0, folder_icon)
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.setForeground(0, QColor(ACCENT))
@@ -506,46 +430,14 @@ class HDF5Viewer(QMainWindow):
return
obj = self.h5file[path] if path != "/" else self.h5file
attrs = dict(obj.attrs) if obj.attrs else {}
self.attrs_panel.set_attrs(attrs)
if kind == "dataset":
ds = self.h5file[path]
ds = self.h5file[path]
data = ds[()]
self.data_panel.display(data, path)
# self.status.showMessage(
# f"{path} | shape {ds.shape} | dtype {ds.dtype}"
# )
else:
n = len(obj) if isinstance(obj, h5py.Group) else 0
# self.status.showMessage(
# f"{path} | Group | {n} items | {len(attrs)} attributes"
# )
def closeEvent(self, event):
self.h5file.close()
super().closeEvent(event)
# ── Entry point ────────────────────────────────────────────────────────────
def main():
if len(sys.argv) < 2:
print("Usage: python hdf5_viewer.py <file.h5>")
sys.exit(1)
filepath = sys.argv[1]
if not os.path.isfile(filepath):
print(f"Error: file not found — {filepath}")
sys.exit(1)
app = QApplication(sys.argv)
app.setApplicationName("HDF5 Viewer")
app.setStyleSheet(STYLESHEET)
window = HDF5Viewer(filepath)
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()