Add scan control xas widget, update to digital twin #38
No files matched your search
@@ -18,6 +18,7 @@ dependencies = [
|
||||
"scipy",
|
||||
"xrt",
|
||||
"websockets",
|
||||
"xraydb",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
||||
@@ -0,0 +1,388 @@
|
||||
[BECDockArea]
|
||||
acceptDrops=false
|
||||
accessibleDescription=
|
||||
accessibleIdentifier=
|
||||
accessibleName=
|
||||
autoFillBackground=false
|
||||
baseSize=@Size(0 0)
|
||||
contextMenuPolicy=@Variant(\0\0\0\x7f\0\0\0\x16Qt::ContextMenuPolicy\0\0\0\0\x1)
|
||||
cursor=@Variant(\0\0\0J\0\0)
|
||||
enabled=true
|
||||
focusPolicy=@Variant(\0\0\0\x7f\0\0\0\x10Qt::FocusPolicy\0\0\0\0\0)
|
||||
geometry=@Rect(0 0 3378 1879)
|
||||
inputMethodHints=@Variant(\0\0\0\x7f\0\0\0\x14Qt::InputMethodHint\0\0\0\0\0)
|
||||
layoutDirection=@Variant(\0\0\0\x7f\0\0\0\x14Qt::LayoutDirection\0\0\0\0\0)
|
||||
maximumSize=@Size(16777215 16777215)
|
||||
minimumSize=@Size(0 0)
|
||||
mode=creator
|
||||
mouseTracking=false
|
||||
sizeIncrement=@Size(0 0)
|
||||
sizePolicy=@Variant(\0\0\0K\0\0\0U)
|
||||
statusTip=
|
||||
tabletTracking=false
|
||||
toolTip=
|
||||
toolTipDuration=-1
|
||||
whatsThis=
|
||||
windowFilePath=
|
||||
windowModality=@Variant(\0\0\0\x7f\0\0\0\x13Qt::WindowModality\0\0\0\0\0)
|
||||
windowModified=false
|
||||
windowOpacity=1
|
||||
windowTitle=BEC - bec
|
||||
workspace_is_locked=false
|
||||
|
||||
[BECDockArea.ads%3A%3ACDockManager.ads%3A%3ACDockSplitter.ads%3A%3ACDockAreaWidget.BECStatusBox.dockWidgetScrollArea.qt_scrollarea_viewport.BECStatusBox]
|
||||
acceptDrops=false
|
||||
accessibleDescription=
|
||||
accessibleIdentifier=
|
||||
accessibleName=
|
||||
autoFillBackground=true
|
||||
baseSize=@Size(0 0)
|
||||
compact_view=false
|
||||
contextMenuPolicy=@Variant(\0\0\0\x7f\0\0\0\x16Qt::ContextMenuPolicy\0\0\0\0\x1)
|
||||
cursor=@Variant(\0\0\0J\0\0)
|
||||
enabled=true
|
||||
expand_popup=true
|
||||
focusPolicy=@Variant(\0\0\0\x7f\0\0\0\x10Qt::FocusPolicy\0\0\0\0\0)
|
||||
geometry=@Rect(0 0 328 1800)
|
||||
inputMethodHints=@Variant(\0\0\0\x7f\0\0\0\x14Qt::InputMethodHint\0\0\0\0\0)
|
||||
label=BEC Servers
|
||||
layoutDirection=@Variant(\0\0\0\x7f\0\0\0\x14Qt::LayoutDirection\0\0\0\0\0)
|
||||
maximumSize=@Size(16777215 16777215)
|
||||
minimumSize=@Size(0 0)
|
||||
mouseTracking=false
|
||||
sizeIncrement=@Size(0 0)
|
||||
sizePolicy=@Variant(\0\0\0K\0\0\0U)
|
||||
statusTip=
|
||||
tabletTracking=false
|
||||
toolTip=
|
||||
toolTipDuration=-1
|
||||
tooltip=BEC servers health status
|
||||
whatsThis=
|
||||
windowFilePath=
|
||||
windowModality=@Variant(\0\0\0\x7f\0\0\0\x13Qt::WindowModality\0\0\0\0\0)
|
||||
windowModified=false
|
||||
windowOpacity=1
|
||||
windowTitle=
|
||||
|
||||
[BECDockArea.ads%3A%3ACDockManager.ads%3A%3ACDockSplitter.ads%3A%3ACDockAreaWidget.DataViewer.dockWidgetScrollArea.qt_scrollarea_viewport.DataViewer]
|
||||
acceptDrops=false
|
||||
accessibleDescription=
|
||||
accessibleIdentifier=
|
||||
accessibleName=
|
||||
autoFillBackground=true
|
||||
baseSize=@Size(0 0)
|
||||
contextMenuPolicy=@Variant(\0\0\0\x7f\0\0\0\x16Qt::ContextMenuPolicy\0\0\0\0\x1)
|
||||
cursor=@Variant(\0\0\0J\0\0)
|
||||
enabled=true
|
||||
focusPolicy=@Variant(\0\0\0\x7f\0\0\0\x10Qt::FocusPolicy\0\0\0\0\0)
|
||||
geometry=@Rect(0 0 949 1800)
|
||||
inputMethodHints=@Variant(\0\0\0\x7f\0\0\0\x14Qt::InputMethodHint\0\0\0\0\0)
|
||||
layoutDirection=@Variant(\0\0\0\x7f\0\0\0\x14Qt::LayoutDirection\0\0\0\0\0)
|
||||
maximumSize=@Size(16777215 16777215)
|
||||
minimumSize=@Size(0 0)
|
||||
mouseTracking=false
|
||||
sizeIncrement=@Size(0 0)
|
||||
sizePolicy=@Variant(\0\0\0K\0\0\0U)
|
||||
statusTip=
|
||||
tabletTracking=false
|
||||
toolTip=
|
||||
toolTipDuration=-1
|
||||
whatsThis=
|
||||
windowFilePath=
|
||||
windowModality=@Variant(\0\0\0\x7f\0\0\0\x13Qt::WindowModality\0\0\0\0\0)
|
||||
windowModified=false
|
||||
windowOpacity=1
|
||||
windowTitle=Data Viewer
|
||||
|
||||
[BECDockArea.ads%3A%3ACDockManager.ads%3A%3ACDockSplitter.ads%3A%3ACDockAreaWidget.Waveform.dockWidgetScrollArea.qt_scrollarea_viewport.Waveform]
|
||||
acceptDrops=false
|
||||
accessibleDescription=
|
||||
accessibleIdentifier=
|
||||
accessibleName=
|
||||
autoFillBackground=true
|
||||
auto_range_x=false
|
||||
auto_range_y=false
|
||||
baseSize=@Size(0 0)
|
||||
color_palette=plasma
|
||||
contextMenuPolicy=@Variant(\0\0\0\x7f\0\0\0\x16Qt::ContextMenuPolicy\0\0\0\0\x1)
|
||||
cursor=@Variant(\0\0\0J\0\0)
|
||||
curve_json="[\n {\n \"widget_class\": \"Curve\",\n \"gui_id\": \"Curve_2026_08_11_10_42_45_404183\",\n \"parent_id\": \"Waveform_2026_08_11_10_31_12_268515\",\n \"label\": \"nidaq-nidaq_smpl_abs\",\n \"color\": \"#6a00a8\",\n \"symbol\": \"o\",\n \"symbol_color\": \"#6a00a8\",\n \"symbol_size\": 7,\n \"pen_width\": 4,\n \"pen_style\": \"solid\",\n \"source\": \"device\",\n \"signal\": {\n \"device\": \"nidaq\",\n \"signal\": \"nidaq_smpl_abs\",\n \"dap\": null,\n \"dap_oversample\": 1,\n \"dap_parameters\": null\n },\n \"scan_id\": null,\n \"scan_number\": null,\n \"current_x_mode\": null,\n \"parent_label\": null\n },\n {\n \"widget_class\": \"Curve\",\n \"gui_id\": \"Curve_2026_08_11_10_42_45_548304\",\n \"parent_id\": \"Waveform_2026_08_11_10_31_12_268515\",\n \"label\": \"nidaq-nidaq_ref_abs\",\n \"color\": \"#cc4977\",\n \"symbol\": \"o\",\n \"symbol_color\": \"#cc4977\",\n \"symbol_size\": 7,\n \"pen_width\": 4,\n \"pen_style\": \"solid\",\n \"source\": \"device\",\n \"signal\": {\n \"device\": \"nidaq\",\n \"signal\": \"nidaq_ref_abs\",\n \"dap\": null,\n \"dap_oversample\": 1,\n \"dap_parameters\": null\n },\n \"scan_id\": null,\n \"scan_number\": null,\n \"current_x_mode\": null,\n \"parent_label\": null\n }\n]"
|
||||
enable_fps_monitor=false
|
||||
enable_popups=true
|
||||
enable_side_panel=false
|
||||
enable_toolbar=true
|
||||
enabled=true
|
||||
focusPolicy=@Variant(\0\0\0\x7f\0\0\0\x10Qt::FocusPolicy\0\0\0\0\0)
|
||||
geometry=@Rect(0 0 1205 1800)
|
||||
inner_axes=true
|
||||
inputMethodHints=@Variant(\0\0\0\x7f\0\0\0\x14Qt::InputMethodHint\0\0\0\0\0)
|
||||
invert_x=false
|
||||
invert_y=false
|
||||
layoutDirection=@Variant(\0\0\0\x7f\0\0\0\x14Qt::LayoutDirection\0\0\0\0\0)
|
||||
legend_label_size=9
|
||||
lock_aspect_ratio=false
|
||||
max_dataset_size_mb=10
|
||||
maximumSize=@Size(16777215 16777215)
|
||||
minimal_crosshair_precision=3
|
||||
minimumSize=@Size(0 0)
|
||||
mouseTracking=false
|
||||
outer_axes=false
|
||||
signal_x=nidaq_energy
|
||||
sizeIncrement=@Size(0 0)
|
||||
sizePolicy=@Variant(\0\0\0K\0\0\0U)
|
||||
skip_large_dataset_check=false
|
||||
skip_large_dataset_warning=false
|
||||
statusTip=
|
||||
tabletTracking=false
|
||||
title=
|
||||
toolTip=
|
||||
toolTipDuration=-1
|
||||
whatsThis=
|
||||
windowFilePath=
|
||||
windowModality=@Variant(\0\0\0\x7f\0\0\0\x13Qt::WindowModality\0\0\0\0\0)
|
||||
windowModified=false
|
||||
windowOpacity=1
|
||||
windowTitle=
|
||||
x_grid=false
|
||||
x_label=
|
||||
x_limits=@Variant(\0\0\0\x1a\xbf\xa6\xae\x82\xdfX\xde\x43?\xf0\xb5t\x16\xfa\xc6\xf2)
|
||||
x_log=false
|
||||
x_mode=nidaq
|
||||
y_grid=false
|
||||
y_label=
|
||||
y_limits=@Variant(\0\0\0\x1a\xbf\xa9\x30]\xe2\xfe\xd2\0?\xf0\xc9\x82\xef\x17\xf6\x90)
|
||||
y_log=false
|
||||
|
||||
[BECDockArea.ads%3A%3ACDockManager.ads%3A%3ACDockSplitter.ads%3A%3ACDockSplitter.ads%3A%3ACDockAreaWidget.BECQueue.dockWidgetScrollArea.qt_scrollarea_viewport.BECQueue]
|
||||
acceptDrops=false
|
||||
accessibleDescription=
|
||||
accessibleIdentifier=
|
||||
accessibleName=
|
||||
autoFillBackground=true
|
||||
baseSize=@Size(0 0)
|
||||
compact_view=false
|
||||
contextMenuPolicy=@Variant(\0\0\0\x7f\0\0\0\x16Qt::ContextMenuPolicy\0\0\0\0\x1)
|
||||
cursor=@Variant(\0\0\0J\0\0)
|
||||
enabled=true
|
||||
expand_popup=true
|
||||
focusPolicy=@Variant(\0\0\0\x7f\0\0\0\x10Qt::FocusPolicy\0\0\0\0\0)
|
||||
geometry=@Rect(0 0 848 297)
|
||||
hide_toolbar=false
|
||||
inputMethodHints=@Variant(\0\0\0\x7f\0\0\0\x14Qt::InputMethodHint\0\0\0\0\0)
|
||||
label=BEC Queue
|
||||
layoutDirection=@Variant(\0\0\0\x7f\0\0\0\x14Qt::LayoutDirection\0\0\0\0\0)
|
||||
maximumSize=@Size(16777215 16777215)
|
||||
minimumSize=@Size(0 0)
|
||||
mouseTracking=false
|
||||
sizeIncrement=@Size(0 0)
|
||||
sizePolicy=@Variant(\0\0\0K\0\0\0U)
|
||||
statusTip=
|
||||
tabletTracking=false
|
||||
toolTip=
|
||||
toolTipDuration=-1
|
||||
tooltip=BEC Queue status
|
||||
whatsThis=
|
||||
windowFilePath=
|
||||
windowModality=@Variant(\0\0\0\x7f\0\0\0\x13Qt::WindowModality\0\0\0\0\0)
|
||||
windowModified=false
|
||||
windowOpacity=1
|
||||
windowTitle=
|
||||
|
||||
[BECDockArea.ads%3A%3ACDockManager.ads%3A%3ACDockSplitter.ads%3A%3ACDockSplitter.ads%3A%3ACDockAreaWidget.ScanControlXAS.dockWidgetScrollArea.qt_scrollarea_viewport.ScanControlXAS]
|
||||
acceptDrops=false
|
||||
accessibleDescription=
|
||||
accessibleIdentifier=
|
||||
accessibleName=
|
||||
allowed_scans=xas_simple_scan, xas_simple_scan_with_xrd, xas_advanced_scan, xas_advanced_scan_with_xrd
|
||||
autoFillBackground=true
|
||||
baseSize=@Size(0 0)
|
||||
contextMenuPolicy=@Variant(\0\0\0\x7f\0\0\0\x16Qt::ContextMenuPolicy\0\0\0\0\x1)
|
||||
current_scan=xas_simple_scan
|
||||
cursor=@Variant(\0\0\0J\0\0)
|
||||
enabled=true
|
||||
focusPolicy=@Variant(\0\0\0\x7f\0\0\0\x10Qt::FocusPolicy\0\0\0\0\0)
|
||||
geometry=@Rect(0 0 848 1100)
|
||||
hide_add_remove_buttons=false
|
||||
hide_arg_box=false
|
||||
hide_kwarg_boxes=false
|
||||
hide_metadata=false
|
||||
hide_optional_metadata=false
|
||||
hide_scan_control_buttons=false
|
||||
hide_scan_selection_combobox=false
|
||||
hide_scan_selector_settings_button=false
|
||||
inputMethodHints=@Variant(\0\0\0\x7f\0\0\0\x14Qt::InputMethodHint\0\0\0\0\0)
|
||||
layoutDirection=@Variant(\0\0\0\x7f\0\0\0\x14Qt::LayoutDirection\0\0\0\0\0)
|
||||
maximumSize=@Size(16777215 16777215)
|
||||
minimumSize=@Size(0 0)
|
||||
mouseTracking=false
|
||||
sizeIncrement=@Size(0 0)
|
||||
sizePolicy=@Variant(\0\0\0K\0\0\0U)
|
||||
statusTip=
|
||||
tabletTracking=false
|
||||
toolTip=
|
||||
toolTipDuration=-1
|
||||
whatsThis=
|
||||
windowFilePath=
|
||||
windowModality=@Variant(\0\0\0\x7f\0\0\0\x13Qt::WindowModality\0\0\0\0\0)
|
||||
windowModified=false
|
||||
windowOpacity=1
|
||||
windowTitle=
|
||||
|
||||
[BECDockArea.ads%3A%3ACDockManager.ads%3A%3ACDockSplitter.ads%3A%3ACDockSplitter.ads%3A%3ACDockAreaWidget.ScanProgressBar.dockWidgetScrollArea.qt_scrollarea_viewport.ScanProgressBar]
|
||||
acceptDrops=false
|
||||
accessibleDescription=
|
||||
accessibleIdentifier=
|
||||
accessibleName=
|
||||
autoFillBackground=true
|
||||
baseSize=@Size(0 0)
|
||||
contextMenuPolicy=@Variant(\0\0\0\x7f\0\0\0\x16Qt::ContextMenuPolicy\0\0\0\0\x1)
|
||||
cursor=@Variant(\0\0\0J\0\0)
|
||||
enabled=true
|
||||
focusPolicy=@Variant(\0\0\0\x7f\0\0\0\x10Qt::FocusPolicy\0\0\0\0\0)
|
||||
geometry=@Rect(0 0 848 317)
|
||||
inputMethodHints=@Variant(\0\0\0\x7f\0\0\0\x14Qt::InputMethodHint\0\0\0\0\0)
|
||||
layoutDirection=@Variant(\0\0\0\x7f\0\0\0\x14Qt::LayoutDirection\0\0\0\0\0)
|
||||
maximumSize=@Size(16777215 16777215)
|
||||
minimumSize=@Size(0 0)
|
||||
mouseTracking=false
|
||||
show_elapsed_time=false
|
||||
show_remaining_time=false
|
||||
show_source_label=false
|
||||
sizeIncrement=@Size(0 0)
|
||||
sizePolicy=@Variant(\0\0\0K\0\0\0U)
|
||||
statusTip=
|
||||
tabletTracking=false
|
||||
toolTip=
|
||||
toolTipDuration=-1
|
||||
whatsThis=
|
||||
windowFilePath=
|
||||
windowModality=@Variant(\0\0\0\x7f\0\0\0\x13Qt::WindowModality\0\0\0\0\0)
|
||||
windowModified=false
|
||||
windowOpacity=1
|
||||
windowTitle=
|
||||
|
||||
[BECDockArea.dockSettingsAction]
|
||||
autoRepeat=true
|
||||
checkable=false
|
||||
checked=false
|
||||
enabled=true
|
||||
iconText=Dock settings
|
||||
iconVisibleInMenu=true
|
||||
menuRole=@Variant(\0\0\0\x7f\0\0\0\x12QAction::MenuRole\0\0\0\0\x1)
|
||||
priority=@Variant(\0\0\0\x7f\0\0\0\x12QAction::Priority\0\0\0\0\x80)
|
||||
shortcut=
|
||||
shortcutContext=@Variant(\0\0\0\x7f\0\0\0\x14Qt::ShortcutContext\0\0\0\0\x1)
|
||||
shortcutVisibleInContextMenu=true
|
||||
statusTip=
|
||||
text=Dock settings
|
||||
toolTip=Dock settings
|
||||
whatsThis=
|
||||
|
||||
[Perspectives]
|
||||
1\Name=BEC - bec
|
||||
1\State="@ByteArray(\0\0\x3\x37x\xda\x95\x92_O\x83\x30\x14\xc5\xdf\xfd\x14\x37\xbcO\xc6\x18\xc9L\x18\v0\xf7\xa8.\xec\x8f\xaf\x15\xae\xa4\x11Z\xd3\x96\x39\x8d\x1f\xde\x16\x97\xbal`\xc2\x13\xf4\x9c\xc3\xef\xde\x1c\x1a.\x8eu\x5\a\x14\x92r6w\xbc\xdb\xb1\x3\xc8r^PV\xce\x9d\xed\x66\x35\x9a\x39\x8b(\\\xab\xb8\x38\x10\x96\x63\xb1\xe4\xf9\x9b\xf6\xb2O\xa9\xb0\x86\x9d\xfd\xd0\x81\xad\x44\x61\xcf\x1a\x93r\xa6\beZ1v\x14\xda\x33\xac*NT;`\xac\xf5\xec\xbd\xa2Ji\xf9QP\xd4\x11\xd5\x2\xbe\r\xa0\x61j\xeeL{3#\x9b\xf1u&\x16H`C^\xdaq\x90\x36\x42\xa0\xb1\xb2\x9c\x30\x33Z\xf0\xea\x39\xcetnO\x8b\x12\x15<\x90\x1a\xaf\\H+.\xb1\x30\x8b\xb9Q\xe8\x1a\x64/8\xb9O\xd7\r6x\x89\xb4\xfa\x10\x98\xd9\xe3I\xf0R\xa0\x94\t\x11]k\x9e\xdb\x9d\xe8\x8c~\xa1\x8c<\xcf\xf\xc0\xf7'\xe0\a\x13\b\xdd_Q?O\r\xf6n\xb0'\a|\xe5\xa2\xbe\x1cm\xf5\x81\xdd\x64\xfa/52\xe1\xc7\x8e~\xfe\xbc!\xd0%QdG\xf1\x3\xaf\xea\x39s\xfeif\x16L\xc1\x9bx\x9eng\nwA\xd0\xd9\x8ek\xaf\xa9~\xef\xb9\xf6\xd1\xcd\xf\x82\x9c\vE)"
|
||||
size=1
|
||||
|
||||
[mainWindow]
|
||||
DockingState="@ByteArray(\0\0\x3\x37x\xda\x95\x92_O\x83\x30\x14\xc5\xdf\xfd\x14\x37\xbcO\xc6\x18\xc9L\x18\v0\xf7\xa8.\xec\x8f\xaf\x15\xae\xa4\x11Z\xd3\x96\x39\x8d\x1f\xde\x16\x97\xbal`\xc2\x13\xf4\x9c\xc3\xef\xde\x1c\x1a.\x8eu\x5\a\x14\x92r6w\xbc\xdb\xb1\x3\xc8r^PV\xce\x9d\xed\x66\x35\x9a\x39\x8b(\\\xab\xb8\x38\x10\x96\x63\xb1\xe4\xf9\x9b\xf6\xb2O\xa9\xb0\x86\x9d\xfd\xd0\x81\xad\x44\x61\xcf\x1a\x93r\xa6\beZ1v\x14\xda\x33\xac*NT;`\xac\xf5\xec\xbd\xa2Ji\xf9QP\xd4\x11\xd5\x2\xbe\r\xa0\x61j\xeeL{3#\x9b\xf1u&\x16H`C^\xdaq\x90\x36\x42\xa0\xb1\xb2\x9c\x30\x33Z\xf0\xea\x39\xcetnO\x8b\x12\x15<\x90\x1a\xaf\\H+.\xb1\x30\x8b\xb9Q\xe8\x1a\x64/8\xb9O\xd7\r6x\x89\xb4\xfa\x10\x98\xd9\xe3I\xf0R\xa0\x94\t\x11]k\x9e\xdb\x9d\xe8\x8c~\xa1\x8c<\xcf\xf\xc0\xf7'\xe0\a\x13\b\xdd_Q?O\r\xf6n\xb0'\a|\xe5\xa2\xbe\x1cm\xf5\x81\xdd\x64\xfa/52\xe1\xc7\x8e~\xfe\xbc!\xd0%QdG\xf1\x3\xaf\xea\x39s\xfeif\x16L\xc1\x9bx\x9eng\nwA\xd0\xd9\x8ek\xaf\xa9~\xef\xb9\xf6\xd1\xcd\xf\x82\x9c\vE)"
|
||||
Geometry=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\0\0\0\0\0\0\0\0\0\0\r1\0\0\aV\0\0\0\0\0\0\0\0\xff\xff\xff\xff\xff\xff\xff\xff\0\0\0\0\0\0\0\0\xf\0\0\0\0\0\0\0\0\0\0\0\r1\0\0\aV)
|
||||
State=@Variant(\0\0\0\x7f\0\0\0\x18PySide::PyObjectWrapper\0\0\0\0\xf\x80\x4\x95\x4\0\0\0\0\0\0\0\x43\0\x94.)
|
||||
|
||||
[manifest]
|
||||
widgets\1\closable=true
|
||||
widgets\1\floatable=true
|
||||
widgets\1\floating=false
|
||||
widgets\1\floating_abs_h=0
|
||||
widgets\1\floating_abs_w=0
|
||||
widgets\1\floating_abs_x=0
|
||||
widgets\1\floating_abs_y=0
|
||||
widgets\1\floating_rel_h=0
|
||||
widgets\1\floating_rel_w=0
|
||||
widgets\1\floating_rel_x=0
|
||||
widgets\1\floating_rel_y=0
|
||||
widgets\1\floating_screen=
|
||||
widgets\1\movable=true
|
||||
widgets\1\object_name=BECQueue
|
||||
widgets\1\widget_class=BECQueue
|
||||
widgets\2\closable=true
|
||||
widgets\2\floatable=true
|
||||
widgets\2\floating=false
|
||||
widgets\2\floating_abs_h=0
|
||||
widgets\2\floating_abs_w=0
|
||||
widgets\2\floating_abs_x=0
|
||||
widgets\2\floating_abs_y=0
|
||||
widgets\2\floating_rel_h=0
|
||||
widgets\2\floating_rel_w=0
|
||||
widgets\2\floating_rel_x=0
|
||||
widgets\2\floating_rel_y=0
|
||||
widgets\2\floating_screen=
|
||||
widgets\2\movable=true
|
||||
widgets\2\object_name=BECStatusBox
|
||||
widgets\2\widget_class=BECStatusBox
|
||||
widgets\3\closable=true
|
||||
widgets\3\floatable=true
|
||||
widgets\3\floating=false
|
||||
widgets\3\floating_abs_h=0
|
||||
widgets\3\floating_abs_w=0
|
||||
widgets\3\floating_abs_x=0
|
||||
widgets\3\floating_abs_y=0
|
||||
widgets\3\floating_rel_h=0
|
||||
widgets\3\floating_rel_w=0
|
||||
widgets\3\floating_rel_x=0
|
||||
widgets\3\floating_rel_y=0
|
||||
widgets\3\floating_screen=
|
||||
widgets\3\movable=true
|
||||
widgets\3\object_name=DataViewer
|
||||
widgets\3\widget_class=DataViewer
|
||||
widgets\4\closable=true
|
||||
widgets\4\floatable=true
|
||||
widgets\4\floating=false
|
||||
widgets\4\floating_abs_h=0
|
||||
widgets\4\floating_abs_w=0
|
||||
widgets\4\floating_abs_x=0
|
||||
widgets\4\floating_abs_y=0
|
||||
widgets\4\floating_rel_h=0
|
||||
widgets\4\floating_rel_w=0
|
||||
widgets\4\floating_rel_x=0
|
||||
widgets\4\floating_rel_y=0
|
||||
widgets\4\floating_screen=
|
||||
widgets\4\movable=true
|
||||
widgets\4\object_name=ScanControlXAS
|
||||
widgets\4\widget_class=ScanControlXAS
|
||||
widgets\5\closable=true
|
||||
widgets\5\floatable=true
|
||||
widgets\5\floating=false
|
||||
widgets\5\floating_abs_h=0
|
||||
widgets\5\floating_abs_w=0
|
||||
widgets\5\floating_abs_x=0
|
||||
widgets\5\floating_abs_y=0
|
||||
widgets\5\floating_rel_h=0
|
||||
widgets\5\floating_rel_w=0
|
||||
widgets\5\floating_rel_x=0
|
||||
widgets\5\floating_rel_y=0
|
||||
widgets\5\floating_screen=
|
||||
widgets\5\movable=true
|
||||
widgets\5\object_name=ScanProgressBar
|
||||
widgets\5\widget_class=ScanProgressBar
|
||||
widgets\6\closable=true
|
||||
widgets\6\floatable=true
|
||||
widgets\6\floating=false
|
||||
widgets\6\floating_abs_h=0
|
||||
widgets\6\floating_abs_w=0
|
||||
widgets\6\floating_abs_x=0
|
||||
widgets\6\floating_abs_y=0
|
||||
widgets\6\floating_rel_h=0
|
||||
widgets\6\floating_rel_w=0
|
||||
widgets\6\floating_rel_x=0
|
||||
widgets\6\floating_rel_y=0
|
||||
widgets\6\floating_screen=
|
||||
widgets\6\movable=true
|
||||
widgets\6\object_name=Waveform
|
||||
widgets\6\widget_class=Waveform
|
||||
widgets\size=6
|
||||
|
||||
[profile]
|
||||
created_at=2026-08-11T08:44:05Z
|
||||
quick_select=true
|
||||
screenshot="@ByteArray(\x89PNG\r\n\x1a\n\0\0\0\rIHDR\0\0\r2\0\0\aW\b\x6\0\0\0H5e\xd1\0\0\0\tpHYs\0\0\xe\xc4\0\0\xe\xc4\x1\x95+\xe\x1b\0\0 \0IDATx\x9c\xec\xddw|M\xf7\x1f\xc7\xf1\xd7\xcd\x8eMb\x87\xa0\xb6\xd8\xc4\xde$\xb6\xd6l\xabjSjU\xabV\x15U\xab\xaa\x46\x87\xaa-\xa8\xbdW\bAk\xd6\x8a\xd5\xd8\x33\x8a\x90H\xc8^\xbf?R\xb7\xf2K$\x11\x89$\xbc\x9f\x8fG\x1f\xcd=\xdf\xef\xf9~?\xf7\x9c\xafs\xef\x39\xf7|\xce\xd7P\xd0\xbeH\x14\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\")\xc8$\xb5\x3\x10\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x91\x37\x9f\x12\x99\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44\x44$\xc5)\x91IDDDDDDDDDDDDDDDDDDDDDDDDDR\x9c\x12\x99\xd2\x90\x92%KP\xb2\x64\x89\xd4\xe\x43$\xd1\x34\x66\xd3'\xed\xb7\xb7\x97\xf6\xbd\x88\x88\x88\x88\x88\x88\x88\x88\x88\xbcN\xba&)\"\"\"\"\"\"\"\"\"\xf2\xf6\xd0\x35\x61I,%2\x89\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88H\x8aS\"\x93\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\xa4\x38\xb3\xd4\xe@DDDDDDDDDDDDD$o\x9e<d\xcc\x94\x11\x33\x33\xfd\x84)\"\"\"\"\"\"\"\"\"\x10\x1c\x12\x42P@\x10\xbe\x8f}\t\r\rM\xedpD$\x99\xe8W\0\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11I5\x16\x16V\xd8\x17*\xc0\xa3\x87\x8f\xf0\xff\xe7\t\x11\x11\x11\xa9\x1d\x92\x88\x88\x88\x88\x88\x88\x88\x88\x88\xa4\x1\x96\x16\x16\x98\x99\x99R\xa8\x90=\xf7\xef?\xc0\xcf\xcf/\xb5\x43\x12y%%K\x96\xc0\xd3\xf3\x62j\x87\x91\xeaLR;\0\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11y{\xd9\x17\xb2\xc3\xe7\xd1\x43\x2\x3\x3\x95\xc4$\"\"\"\"\"\"\"\"\"F!\xa1\xa1\x4\x4\x6q\xef\xde\x3ls\xda\xa4v8\"\x92L\x94\xc8$\"\"\"\"\"\"\"\"\"\"\"\"\"\xa9\"o\x9e<<z\xe8\x43@@Pj\x87\"\"\"\"\"\"\"\"\"\"IP\xa8\x90}\x8a\xf7\x11\x1a\x1a\x82\xef\xa3\xc7\xe4\xc9\x93'\xc5\xfb\x12\x91\x94\xa7\x44&\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11I\x15\x19\x33\x65$$$$\xb5\xc3\x10\x11\x11\x11\x11\x11\x11\x11\x11\x91\x34.$4\x84L\x99\x33\xa5v\x18\"\x92\f\x94\xc8$\"\"\"\"\"\"\"\"\"\"\"\"\"\xa9\xc2\xcc\xcc\x8c\x88\x88\x88\xd4\xe\x43\x44\x44\x44\x44\x44\x44\x44\x44\x44\xd2\xb8\x88\x88\b\xccLMS;\fIA\x6\x83\x1\x83\xc1\xf0\xda\xd6\x93\xd4\x63\x96\xda\x1\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\xbc\xaa\x16-\x9aS\xb6\xac\x43\xa2\xeb\x9f:y\n\xd7]\xbbS0\"I\f\x83\xc1@\xf7\xee]1\x18\f,\\\xb8\x18\x13\x13\x13\x86\r\xfb\x9c\x9d;\\9\xed\xe1\x11\xa3n\x85\xf2\xe5i\xda\xcc\x99i\xd3\xa6\x13\x19\x19I\xef^=\xb1\xb0\xb0\xe0\xe7_\xe6\x10\x15\x15\x95\xac\x31u\xe9\xd2\x99|\xf9\xf2\xc5X~\xf7\xee]\\\\\x96'k_\xa9\xc5\xc1\xa1\f\0\xe7\xce\x9d\x7f\xad\xfd*\x91IDDDDDDDDDDDDDDDDDDDDDDD\xd2\xbd\xb2\x65\x1d\xa8_\xaf.7o\xdeJ\xb0n\x86\f\x19pvj\x82\xb5\xb5\x35\x1b\x37mN\x91x\n\x17.D\xf5\xea\xd5\xf8\xfd\xf7U)\xd2~B\xf2\xe5\xcbK\xf3\xe6\xcd\xb0\xb5\xb5\xe5\xf2\xe5\xcbl\xdd\xba\x9d\x90\x90\x90T\x89%1>\xea\xfc!\x6\x83\x81\x45\x8b\x96\xe0\xec\xd4\x84\xb3g\xcf\xc5Jd\xcao\x97\x1fg\xa7&\xcc\x98\x31\x8b\x8f?\xfe\x88\xf>\xe8\xc4\xf2\x15\xbf'\x7f,\x1f}H\xf7n]\xb9r\xe5\n\x91\x91\xd1IK&&&8;5\xc1`0\xb0\x64\x89K\xb2\xf7\xf9\xba\xd5\xafW\xfP\"\x93\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88H\x92\xdc\xbcy\x8b~\xfd\a\x90-[\xb6\x17\xd6y\xfa\xf4)\x11\x11\xe1\x8c\x1c\x31\x9c\xc1\x83\a\x2${2S\xe1\xc2\x85\xf8\xe1\x87\xef\xf1\xf5\xf1\x65\xe3\xc6\xcd\x4\x5\x5%k\xfb\t)V\xac(?\xce\x9eIHH\b\xf7\xef?\xa0q\xa3\x46\x38;9\xd1\xff\xd3\x81\x84\x87\x87\xbf\xd6X\x12\x12\x15\x15\xc5\xa2\x45K\0\xe8\xf2Qg\xccLM\x13\\\xa7G\x8fnt\xec\xd0\x9e\x65\xcbW\xb0p\xe1\xe2W\x9a!\xc9\xd9\xa9\t\x1d:\xb4\xfbo\x81\xc1\xc0;E\x8a\xb0l\xd9\n\x16,\\\x14\xb3\xdf\xee\xdd\xe8\xd6\xf5\x63\xea\xd6\xa9\x43TT\xa4q\xf9\xba\xf5\x1b\xd9\xb1\x63g\x92\x63x\x1d\xec\xed\v&\x98\xe4goo\xcf\xcd\x9b\x37S4\xe\x93\x14m=\x1eu\xeb\xd6\xc6}\xefn\xea\xd6\xad\x9d\xa4\xf2\xf8\f\xfb\x62(\xdb\xb6nz\xd5\x10\xe5_\xae;\xb7\x31\x64\xf0\x80$\x97K\xca\xd8\xe3\xe6J\xff~}S;\x8c\x34)\xb5\xc7\xac\x8e\x41/O\x9f\t\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\x92\\\xb2\x65\xcb\xc6\xba\xb5\xab^\xf8_\xbdzu\x89\x88\x88\x64\xf2\x94\xa9\xec\xd9\xb3\x97\xc1\x83\a\xe2\xec\xd4$\xd9\xfa/R\xa4\x30\x33~\x98\x8e\xaf\x8f/C?\x1f\xf6\xda\x93\x98\0z\xf5\xec\xc1\x3oo>\xec\xfc\x31}\xfa\xf6\xe3\x8b/\xbe\xa4X\xb1\xa2\x34i\xd2\xf8\xb5\xc7\x92\x18&&&,_\xfe;\xabW\xaf\xa1S\xa7\x8e\t\xd6\xef\xd8\xa1=\xabW\xaf\x61\xd9\xb2\x15\x98\x98\xbcZjL\xb6\xec\xd9)\\\xb8\x30\x17/^\x8a\xfe\xcf\xf3\"\x8b\x16/a\xe1\xa2\xc5\xb1\xea.Z\xbc\x84\x45\x8b\x97\xe0\xe9\xe9i\xac_\xb8pa\xb2\xc7\x93\x38\x97\x16\x14,X\x90\xb9\xbf\xfe\x42\xad\x9a\x35\x8c\xcb\xf6\xed\xdf\xcf\xbe\xfd\xfb\x8d\xaf\xeb\xd5\xab\xcbos\x7f\xc1\xde\xbe`\x8a\xc6\x92\xac\x33\x32\xd9\xe5\xcf\x87\x8b\xcb\x12\xa2\xa2\xa2\xe8\xf4\xfe\x87x{?L\xce\xe6\x8d\xa6N\x99\x88\xa3\xa3#\0\x91\x91\x91<z\xf4\x88\x83\a\xf\xf1\xeb\xdcyiz\x9a\xb3\xb4\x64\xc5\xf2\xa5\xe4\xcd\x9b\x97\xe9\xd3\x7f`\xeb\xb6\x1d)\xd2G\xd7\x8f?\xa2[\xb7\xae\xc6\xd7O\x9f>\xe5\xfe\xfd\xfb\xb8\xb9\xed\x61\xLine truncated
|
||||
screenshot_at=2026-08-11T08:44:05Z
|
||||
@@ -15,6 +15,7 @@ logger = bec_logger.logger
|
||||
_Widgets = {
|
||||
"DataViewer": "DataViewer",
|
||||
"DigitalTwin": "DigitalTwin",
|
||||
"ScanControlXAS": "ScanControlXAS",
|
||||
}
|
||||
|
||||
|
||||
@@ -64,3 +65,28 @@ class DigitalTwin(RPCBase):
|
||||
"""
|
||||
Detach the widget from its parent dock widget (if widget is in the dock), making it a floating widget.
|
||||
"""
|
||||
|
||||
|
||||
class ScanControlXAS(RPCBase):
|
||||
"""Main widget of Scan Control XAS"""
|
||||
|
||||
_IMPORT_MODULE = "superxas_bec.bec_widgets.widgets.scan_control_xas.scan_control_xas"
|
||||
|
||||
@rpc_call
|
||||
def attach(self):
|
||||
"""
|
||||
None
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def detach(self):
|
||||
"""
|
||||
Detach the widget from its parent dock widget (if widget is in the dock), making it a floating widget.
|
||||
"""
|
||||
|
||||
@rpc_timeout(None)
|
||||
@rpc_call
|
||||
def screenshot(self, file_name: "str | None" = None):
|
||||
"""
|
||||
Take a screenshot of the dock area and save it to a file.
|
||||
"""
|
||||
@@ -7,9 +7,14 @@ from __future__ import annotations
|
||||
designer_plugins = {
|
||||
"DataViewer": ("superxas_bec.bec_widgets.widgets.data_viewer.data_viewer", "DataViewer"),
|
||||
"DigitalTwin": ("superxas_bec.bec_widgets.widgets.digital_twin.digital_twin", "DigitalTwin"),
|
||||
"ScanControlXAS": (
|
||||
"superxas_bec.bec_widgets.widgets.scan_control_xas.scan_control_xas",
|
||||
"ScanControlXAS",
|
||||
),
|
||||
}
|
||||
|
||||
widget_icons = {
|
||||
"DataViewer": "find_in_page",
|
||||
"DigitalTwin": "lightbulb",
|
||||
"ScanControlXAS": "tune",
|
||||
}
|
||||
@@ -35,6 +35,7 @@ from qtpy.QtWidgets import (
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from ..edge_selector import EdgeSelector
|
||||
from .beamline import get_beamline_id
|
||||
from .calculations.calc_positions import calc_positions
|
||||
from .calculations.calc_sideview import calc_sideview
|
||||
@@ -66,6 +67,11 @@ logger = bec_logger.logger
|
||||
OFFSET_FILE_X01DA = Path(__file__).with_name("x01da_offsets.yaml")
|
||||
OFFSET_FILE_X10DA = Path(__file__).with_name("x10da_offsets.yaml")
|
||||
|
||||
X01DA_E_MIN = 4500
|
||||
X01DA_E_MAX = 60000
|
||||
X10DA_E_MIN = 4500
|
||||
X10DA_E_MAX = 35000
|
||||
|
||||
|
||||
class DigitalTwin(BECWidget, QWidget):
|
||||
"""
|
||||
@@ -140,6 +146,7 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
self.setWindowTitle("Digital Twin")
|
||||
self.resize(1450, 950)
|
||||
|
||||
self.input.edge_selector.clicked_connect(self.open_edge_selector)
|
||||
self.input.energy.value_changed_connect(self.calc_assistant)
|
||||
self.input.sldi_hacc.value_changed_connect(self.calc_assistant)
|
||||
self.input.sldi_vacc.value_changed_connect(self.calc_assistant)
|
||||
@@ -162,6 +169,7 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
self.settings.load_offsets.clicked_connect(self.load_offsets)
|
||||
self.settings.show_offsets.clicked_connect(self.show_offsets)
|
||||
|
||||
self.edge_selector_energy = 0.0
|
||||
self.bragg_angle = 0.0
|
||||
self.qy = 0.0
|
||||
self.offsets = {}
|
||||
@@ -326,6 +334,7 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
self.calc_fm_ideal_pitch()
|
||||
self.calc_mo1_energy_resolution()
|
||||
case "energy":
|
||||
self.update_edge_selected()
|
||||
self.calc_mo1_bragg_angle()
|
||||
self.calc_cm_crit_pitch()
|
||||
self.calc_cm_reflectivity()
|
||||
@@ -789,6 +798,24 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
surfaces = calc_surfaces(config)
|
||||
self.surface_plots.update_surfaces(scene="reality", data=surfaces)
|
||||
|
||||
@SafeSlot()
|
||||
def open_edge_selector(self, *_):
|
||||
match self.beamline:
|
||||
case "x01da":
|
||||
dlg = EdgeSelector(self, llim=X01DA_E_MIN, hlim=X01DA_E_MAX)
|
||||
case "x10da":
|
||||
dlg = EdgeSelector(self, llim=X10DA_E_MIN, hlim=X10DA_E_MAX)
|
||||
case _:
|
||||
dlg = EdgeSelector(self)
|
||||
if dlg.exec_():
|
||||
self.input.edge_selected.setText(f"{dlg.selected_element}, {dlg.selected_edge}-edge")
|
||||
self.edge_selector_energy = dlg.selected_energy
|
||||
self.input.energy.set_number(dlg.selected_energy)
|
||||
|
||||
def update_edge_selected(self):
|
||||
if self.input.energy.value() != self.edge_selector_energy:
|
||||
self.input.edge_selected.setText("-")
|
||||
|
||||
def calc_mo1_energy_resolution(self):
|
||||
"""
|
||||
Calculates the energy resolution of the monochromator
|
||||
|
||||
@@ -5,10 +5,17 @@ Panel for user inputs of the digital twin widget
|
||||
from typing import Union
|
||||
|
||||
# pylint: disable=E0611
|
||||
from qtpy.QtWidgets import QVBoxLayout, QWidget
|
||||
from qtpy.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget
|
||||
|
||||
from ..types import BeamlineId
|
||||
from ..widgets.qt_widgets import Button, ComboBox, Group, InputNumberField, NumberIndicator
|
||||
from ..widgets.qt_widgets import (
|
||||
Button,
|
||||
ComboBox,
|
||||
Group,
|
||||
InputNumberField,
|
||||
NumberIndicator,
|
||||
TextIndicator,
|
||||
)
|
||||
|
||||
|
||||
class InputPanel(QWidget):
|
||||
@@ -28,11 +35,28 @@ class InputPanel(QWidget):
|
||||
# Adapt to reality
|
||||
self.adapt_reality = Button(label_button="Adapt to reality", enabled=True)
|
||||
|
||||
# Energy
|
||||
# edge_selector_layout = QVBoxLayout()
|
||||
# edge_selector_label = QLabel("Absorption edge:")
|
||||
# self.edge_selector_button = QPushButton("Choose")
|
||||
# self.edge_label = QLabel("No edge selected")
|
||||
# edge_selector_layout.addWidget(edge_selector_label)
|
||||
# edge_selector_layout.addWidget(self.edge_label)
|
||||
# edge_selector_layout.addWidget(self.edge_selector_button)
|
||||
# edge_selector_layout.addStretch()
|
||||
|
||||
# Edge selector
|
||||
self.edge_selector = Button("Absorption edge", "Choose", enabled=True)
|
||||
|
||||
# Chosen absorption edge
|
||||
self.edge_selected = TextIndicator("Chosen edge")
|
||||
|
||||
# Direct Energy Input
|
||||
self.energy = InputNumberField(
|
||||
"energy", "Energy", unit="eV", init=8979, decimals=0, single_step=100, ll=4000, hl=65000
|
||||
)
|
||||
|
||||
self.energy_group = Group("Energy", [self.edge_selector, self.edge_selected, self.energy])
|
||||
|
||||
# FE Slits Acceptance
|
||||
self.sldi_hacc = InputNumberField(
|
||||
"h_acc",
|
||||
@@ -156,7 +180,7 @@ class InputPanel(QWidget):
|
||||
"User Input",
|
||||
[
|
||||
self.adapt_reality,
|
||||
self.energy,
|
||||
self.energy_group,
|
||||
self.sldi_ass_group,
|
||||
self.cm_ass_group,
|
||||
self.mo1_ass_group,
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
"""
|
||||
Edge Selector: Custom BEC dialog to select an absorption edge.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
import xraydb
|
||||
from bec_lib import bec_logger
|
||||
from bec_widgets.utils.bec_dispatcher import BECDispatcher
|
||||
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,
|
||||
QComboBox,
|
||||
QDialog,
|
||||
QGridLayout,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QVBoxLayout,
|
||||
)
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
ELEMENTS = {
|
||||
# period 1
|
||||
"H": (1, 1),
|
||||
"He": (1, 18),
|
||||
# period 2
|
||||
"Li": (2, 1),
|
||||
"Be": (2, 2),
|
||||
"B": (2, 13),
|
||||
"C": (2, 14),
|
||||
"N": (2, 15),
|
||||
"O": (2, 16),
|
||||
"F": (2, 17),
|
||||
"Ne": (2, 18),
|
||||
# period 3
|
||||
"Na": (3, 1),
|
||||
"Mg": (3, 2),
|
||||
"Al": (3, 13),
|
||||
"Si": (3, 14),
|
||||
"P": (3, 15),
|
||||
"S": (3, 16),
|
||||
"Cl": (3, 17),
|
||||
"Ar": (3, 18),
|
||||
# period 4
|
||||
"K": (4, 1),
|
||||
"Ca": (4, 2),
|
||||
"Sc": (4, 3),
|
||||
"Ti": (4, 4),
|
||||
"V": (4, 5),
|
||||
"Cr": (4, 6),
|
||||
"Mn": (4, 7),
|
||||
"Fe": (4, 8),
|
||||
"Co": (4, 9),
|
||||
"Ni": (4, 10),
|
||||
"Cu": (4, 11),
|
||||
"Zn": (4, 12),
|
||||
"Ga": (4, 13),
|
||||
"Ge": (4, 14),
|
||||
"As": (4, 15),
|
||||
"Se": (4, 16),
|
||||
"Br": (4, 17),
|
||||
"Kr": (4, 18),
|
||||
# period 5
|
||||
"Rb": (5, 1),
|
||||
"Sr": (5, 2),
|
||||
"Y": (5, 3),
|
||||
"Zr": (5, 4),
|
||||
"Nb": (5, 5),
|
||||
"Mo": (5, 6),
|
||||
"Tc": (5, 7),
|
||||
"Ru": (5, 8),
|
||||
"Rh": (5, 9),
|
||||
"Pd": (5, 10),
|
||||
"Ag": (5, 11),
|
||||
"Cd": (5, 12),
|
||||
"In": (5, 13),
|
||||
"Sn": (5, 14),
|
||||
"Sb": (5, 15),
|
||||
"Te": (5, 16),
|
||||
"I": (5, 17),
|
||||
"Xe": (5, 18),
|
||||
# period 6
|
||||
"Cs": (6, 1),
|
||||
"Ba": (6, 2),
|
||||
"La": (6, 3),
|
||||
# lanthanides (row 8)
|
||||
"Ce": (8, 4),
|
||||
"Pr": (8, 5),
|
||||
"Nd": (8, 6),
|
||||
"Pm": (8, 7),
|
||||
"Sm": (8, 8),
|
||||
"Eu": (8, 9),
|
||||
"Gd": (8, 10),
|
||||
"Tb": (8, 11),
|
||||
"Dy": (8, 12),
|
||||
"Ho": (8, 13),
|
||||
"Er": (8, 14),
|
||||
"Tm": (8, 15),
|
||||
"Yb": (8, 16),
|
||||
"Lu": (8, 17),
|
||||
# period 6 continued
|
||||
"Hf": (6, 4),
|
||||
"Ta": (6, 5),
|
||||
"W": (6, 6),
|
||||
"Re": (6, 7),
|
||||
"Os": (6, 8),
|
||||
"Ir": (6, 9),
|
||||
"Pt": (6, 10),
|
||||
"Au": (6, 11),
|
||||
"Hg": (6, 12),
|
||||
"Tl": (6, 13),
|
||||
"Pb": (6, 14),
|
||||
"Bi": (6, 15),
|
||||
"Po": (6, 16),
|
||||
"At": (6, 17),
|
||||
"Rn": (6, 18),
|
||||
# period 7
|
||||
"Fr": (7, 1),
|
||||
"Ra": (7, 2),
|
||||
"Ac": (7, 3),
|
||||
# actinides (row 9)
|
||||
"Th": (9, 4),
|
||||
"Pa": (9, 5),
|
||||
"U": (9, 6),
|
||||
"Np": (9, 7),
|
||||
"Pu": (9, 8),
|
||||
"Am": (9, 9),
|
||||
"Cm": (9, 10),
|
||||
"Bk": (9, 11),
|
||||
"Cf": (9, 12),
|
||||
}
|
||||
|
||||
|
||||
class EdgeSelector(QDialog):
|
||||
"""BEC widget to display a selection dialog where the user can select and edge and an element"""
|
||||
|
||||
def __init__(self, parent=None, llim: int | float = 0, hlim: int | float = 1e6):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Absorption Edge Selector")
|
||||
self._buttons = {}
|
||||
self.selected_edge = None
|
||||
self.selected_element = None
|
||||
self.selected_energy = None
|
||||
self._llim = llim
|
||||
self._hlim = hlim
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
text = QLabel("Select edge and element.")
|
||||
|
||||
edge_layout = QHBoxLayout()
|
||||
edge_label = QLabel("Edge")
|
||||
self._edge = QComboBox()
|
||||
self._edge.addItems(["K", "L1", "L2", "L3"])
|
||||
edge_layout.addWidget(edge_label)
|
||||
edge_layout.addWidget(self._edge)
|
||||
edge_layout.addStretch()
|
||||
|
||||
grid_layout = QGridLayout()
|
||||
grid_layout.setContentsMargins(0, 0, 0, 0)
|
||||
grid_layout.setSpacing(1)
|
||||
|
||||
for element, (row, col) in ELEMENTS.items():
|
||||
button = QPushButton(element)
|
||||
button.setFixedSize(42, 32)
|
||||
button.setCheckable(True)
|
||||
button.setAutoDefault(False)
|
||||
button.setDefault(False)
|
||||
button.clicked.connect(lambda checked=False, e=element: self._element_changed(e))
|
||||
grid_layout.addWidget(button, row - 1, col - 1)
|
||||
self._buttons[element] = button
|
||||
|
||||
res_layout = QHBoxLayout()
|
||||
self._res = QLabel("")
|
||||
res_layout.addStretch()
|
||||
res_layout.addWidget(self._res)
|
||||
res_layout.addStretch()
|
||||
|
||||
button_layout = QHBoxLayout()
|
||||
self._cancel_button = QPushButton("Cancel")
|
||||
self._cancel_button.clicked.connect(self.reject)
|
||||
self._cancel_button.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().default.name()}; color: white;}}"
|
||||
)
|
||||
self._select_button = QPushButton("Select")
|
||||
self._select_button.setEnabled(False)
|
||||
self._select_button.clicked.connect(self.accept)
|
||||
self._select_button.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().default.name()}; color: white;}}"
|
||||
)
|
||||
|
||||
button_layout.addStretch()
|
||||
button_layout.addWidget(self._cancel_button)
|
||||
button_layout.addWidget(self._select_button)
|
||||
button_layout.addStretch()
|
||||
|
||||
layout.addWidget(text)
|
||||
layout.addLayout(edge_layout)
|
||||
layout.addLayout(grid_layout)
|
||||
layout.addLayout(res_layout)
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
self.adjustSize()
|
||||
self.setFixedSize(self.sizeHint())
|
||||
|
||||
self._edge.activated.connect(self._edge_changed)
|
||||
self._edge_changed()
|
||||
|
||||
@SafeSlot()
|
||||
def _edge_changed(self, *_):
|
||||
self.selected_edge = self._edge.currentText()
|
||||
for element, button in self._buttons.items():
|
||||
if "L" in self.selected_edge and element in ["H", "He"]:
|
||||
button.setDisabled(True)
|
||||
button.setChecked(False)
|
||||
elif self.selected_edge in ["L2", "L3"] and element == "Li":
|
||||
button.setDisabled(True)
|
||||
button.setChecked(False)
|
||||
else:
|
||||
energy = xraydb.xray_edge(element, self.selected_edge, True)
|
||||
if self.selected_element == element:
|
||||
self.selected_energy = energy
|
||||
if energy is not None:
|
||||
if energy < self._llim or energy > self._hlim:
|
||||
button.setChecked(False)
|
||||
button.setDisabled(True)
|
||||
else:
|
||||
button.setEnabled(True)
|
||||
else:
|
||||
button.setChecked(False)
|
||||
button.setDisabled(True)
|
||||
self._set_select_text()
|
||||
|
||||
@SafeSlot()
|
||||
def _element_changed(self, element: str | None = None):
|
||||
if element is None:
|
||||
if self.selected_element is None:
|
||||
return
|
||||
element = self.selected_element
|
||||
for e, btn in self._buttons.items():
|
||||
if e != element:
|
||||
btn.setChecked(False)
|
||||
self._buttons[element].setChecked(True)
|
||||
self.selected_element = element
|
||||
self.selected_energy = xraydb.xray_edge(element, self.selected_edge, True)
|
||||
self._select_button.setEnabled(True)
|
||||
self._set_select_text()
|
||||
|
||||
def _set_select_text(self):
|
||||
if self.selected_energy is not None:
|
||||
if self._buttons[self.selected_element].isChecked():
|
||||
self._enable_button(self._select_button, True)
|
||||
self._select_button.setText(
|
||||
f"Selected {self.selected_element}, {self.selected_edge}-edge, {self.selected_energy:0.1f} eV"
|
||||
)
|
||||
else:
|
||||
self._enable_button(self._select_button, False)
|
||||
self._select_button.setText("Select")
|
||||
else:
|
||||
self._enable_button(self._select_button, False)
|
||||
|
||||
@staticmethod
|
||||
def _enable_button(button: QPushButton, enable: bool):
|
||||
if enable:
|
||||
button.setEnabled(True)
|
||||
button.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().default.name()}; color: white;}}"
|
||||
)
|
||||
else:
|
||||
button.setDisabled(True)
|
||||
button.setStyleSheet(
|
||||
"QPushButton {{background-color: rgb(120, 120, 120); color: white;}}"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
apply_theme("light")
|
||||
dispatcher = BECDispatcher(gui_id="edge_selector")
|
||||
win = EdgeSelector()
|
||||
win.show()
|
||||
sys.exit(app.exec_())
|
||||
Whitespace-only changes.
@@ -0,0 +1,264 @@
|
||||
from typing import Literal, Optional
|
||||
|
||||
from bec_widgets.utils.colors import get_accent_colors
|
||||
from bec_widgets.utils.error_popups import SafeSlot
|
||||
from qtpy.QtCore import Signal
|
||||
|
||||
# pylint: disable=E0611
|
||||
from qtpy.QtWidgets import (
|
||||
QApplication,
|
||||
QComboBox,
|
||||
QDialog,
|
||||
QGridLayout,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QSizePolicy,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
LABEL_WIDTH = 118
|
||||
ROW_MARGINS = (4, 0, 4, 0)
|
||||
ROW_SPACING = 6
|
||||
|
||||
|
||||
class Button(QWidget):
|
||||
def __init__(self, label=None, label_button: str = "", enabled=False):
|
||||
super().__init__()
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(*ROW_MARGINS)
|
||||
layout.setSpacing(ROW_SPACING)
|
||||
if label is not None:
|
||||
self.label = QLabel(label)
|
||||
self.label.setFixedWidth(LABEL_WIDTH)
|
||||
layout.addWidget(self.label)
|
||||
self.button = QPushButton(label_button)
|
||||
self.enable_button(enabled)
|
||||
layout.addWidget(self.button)
|
||||
|
||||
def clicked_connect(self, func):
|
||||
"""Connect a function to the button press."""
|
||||
self.button.clicked.connect(func)
|
||||
|
||||
def enable_button(self, enable: bool = False):
|
||||
if enable:
|
||||
self.button.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().default.name()}; color: white;}}"
|
||||
)
|
||||
self.button.setEnabled(True)
|
||||
else: # disabled
|
||||
self.button.setStyleSheet(
|
||||
"QPushButton {{background-color: rgb(120, 120, 120); color: white;}}"
|
||||
)
|
||||
self.button.setDisabled(True)
|
||||
|
||||
def setText(self, text):
|
||||
self.button.setText(text)
|
||||
|
||||
|
||||
class ComboBox(QWidget):
|
||||
def __init__(self, identifier="", label="", enums=None):
|
||||
super().__init__()
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(*ROW_MARGINS)
|
||||
layout.setSpacing(ROW_SPACING)
|
||||
|
||||
self.identifier = identifier
|
||||
|
||||
self.label = QLabel(label)
|
||||
self.label.setFixedWidth(LABEL_WIDTH)
|
||||
self.label.setWordWrap(True)
|
||||
self.label.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred)
|
||||
layout.addWidget(self.label)
|
||||
|
||||
self.value = QComboBox()
|
||||
self.value.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
||||
|
||||
for entry in enums or []:
|
||||
self.value.addItem(entry)
|
||||
|
||||
layout.addWidget(self.value)
|
||||
|
||||
def set_current_text(self, text):
|
||||
self.value.setCurrentText(text)
|
||||
|
||||
def currentText(self) -> str:
|
||||
return self.value.currentText()
|
||||
|
||||
def has_focus(self) -> bool:
|
||||
return QApplication.focusWidget() is self.value.view()
|
||||
|
||||
def activated_connect(self, func):
|
||||
"""Connect a function to the Enter/Return key press."""
|
||||
self.value.activated.connect(func)
|
||||
|
||||
def setDisabled(self, disable):
|
||||
self.value.setDisabled(disable)
|
||||
|
||||
|
||||
class TextIndicator(QWidget):
|
||||
def __init__(self, identifier="", label="", text=""):
|
||||
super().__init__()
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(*ROW_MARGINS)
|
||||
layout.setSpacing(ROW_SPACING)
|
||||
|
||||
self.identifier = identifier
|
||||
|
||||
self.label = QLabel(label)
|
||||
self.label.setFixedWidth(LABEL_WIDTH)
|
||||
self.label.setWordWrap(True)
|
||||
self.label.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred)
|
||||
layout.addWidget(self.label)
|
||||
|
||||
self.value = QLabel(text)
|
||||
self.value.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
||||
|
||||
layout.addWidget(self.value)
|
||||
|
||||
def setText(self, text):
|
||||
self.value.setText(text)
|
||||
|
||||
def text(self) -> str:
|
||||
return self.value.text()
|
||||
|
||||
|
||||
ELEMENTS = {
|
||||
# period 4
|
||||
"Sc": (4, 3),
|
||||
"Ti": (4, 4),
|
||||
"V": (4, 5),
|
||||
"Cr": (4, 6),
|
||||
"Mn": (4, 7),
|
||||
"Fe": (4, 8),
|
||||
"Co": (4, 9),
|
||||
"Ni": (4, 10),
|
||||
"Cu": (4, 11),
|
||||
"Zn": (4, 12),
|
||||
"Ga": (4, 13),
|
||||
"Ge": (4, 14),
|
||||
"As": (4, 15),
|
||||
"Se": (4, 16),
|
||||
"Br": (4, 17),
|
||||
"Kr": (4, 18),
|
||||
# period 5
|
||||
"Rb": (5, 1),
|
||||
"Sr": (5, 2),
|
||||
"Y": (5, 3),
|
||||
"Zr": (5, 4),
|
||||
"Nb": (5, 5),
|
||||
"Mo": (5, 6),
|
||||
"Tc": (5, 7),
|
||||
"Ru": (5, 8),
|
||||
"Rh": (5, 9),
|
||||
"Pd": (5, 10),
|
||||
"Ag": (5, 11),
|
||||
"Cd": (5, 12),
|
||||
"In": (5, 13),
|
||||
"Sn": (5, 14),
|
||||
"Sb": (5, 15),
|
||||
"Te": (5, 16),
|
||||
"I": (5, 17),
|
||||
"Xe": (5, 18),
|
||||
# period 6
|
||||
"Cs": (6, 1),
|
||||
"Ba": (6, 2),
|
||||
"La": (8, 3),
|
||||
"Ce": (8, 4),
|
||||
"Pr": (8, 5),
|
||||
"Nd": (8, 6),
|
||||
"Pm": (8, 7),
|
||||
"Sm": (8, 8),
|
||||
"Eu": (8, 9),
|
||||
"Gd": (8, 10),
|
||||
"Tb": (8, 11),
|
||||
"Dy": (8, 12),
|
||||
"Ho": (8, 13),
|
||||
"Er": (8, 14),
|
||||
"Tm": (8, 15),
|
||||
"Pb": (6, 14),
|
||||
"Bi": (6, 15),
|
||||
"Po": (6, 16),
|
||||
"At": (6, 17),
|
||||
"Rn": (6, 18),
|
||||
# period 7
|
||||
"Fr": (7, 1),
|
||||
"Ra": (7, 2),
|
||||
"Ac": (9, 3),
|
||||
"Th": (9, 4),
|
||||
"Pa": (9, 5),
|
||||
"U": (9, 6),
|
||||
}
|
||||
|
||||
|
||||
class PeriodicTableDialog(QDialog):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.selected = None
|
||||
|
||||
layout = QGridLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(1)
|
||||
|
||||
for element, (row, col) in ELEMENTS.items():
|
||||
button = QPushButton(element)
|
||||
button.setFixedSize(42, 32)
|
||||
button.clicked.connect(lambda checked=False, e=element: self.select(e))
|
||||
layout.addWidget(button, row - 1, col - 1)
|
||||
|
||||
@SafeSlot()
|
||||
def select(self, element):
|
||||
self.selected = element
|
||||
self.accept()
|
||||
|
||||
|
||||
class ElementSelector(QWidget):
|
||||
|
||||
dialogClosed = Signal()
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(*ROW_MARGINS)
|
||||
layout.setSpacing(ROW_SPACING)
|
||||
self.label = QLabel("Element")
|
||||
layout_selection = QHBoxLayout(self)
|
||||
self.button = QPushButton("Select")
|
||||
self.button.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
||||
self.button.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().default.name()}; color: white;}}"
|
||||
)
|
||||
layout_selection.addWidget(self.button)
|
||||
layout.addWidget(self.label)
|
||||
layout.addLayout(layout_selection)
|
||||
self.current_element = ""
|
||||
|
||||
self.button.clicked.connect(self.open_table)
|
||||
|
||||
def open_table(self):
|
||||
dialog = PeriodicTableDialog(self)
|
||||
if dialog.exec_():
|
||||
self.current_element = dialog.selected
|
||||
self.dialogClosed.emit()
|
||||
|
||||
def currentElement(self):
|
||||
return self.current_element
|
||||
|
||||
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
|
||||
|
||||
self.button.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().default.name()}; color: white;}}"
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
def main(): # pragma: no cover
|
||||
from qtpy import PYSIDE6
|
||||
|
||||
if not PYSIDE6:
|
||||
print("PYSIDE6 is not available in the environment. Cannot patch designer.")
|
||||
return
|
||||
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
||||
|
||||
from .scan_control_xas_plugin import ScanControlXASPlugin
|
||||
|
||||
QPyDesignerCustomWidgetCollection.addCustomWidget(ScanControlXASPlugin())
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
main()
|
||||
@@ -0,0 +1,421 @@
|
||||
"""
|
||||
Scan Control XAS: Custom BEC widget for Scan Control XAS scans.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from typing import Literal, Optional
|
||||
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
from bec_lib import bec_logger
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
from bec_widgets.utils.bec_dispatcher import BECDispatcher
|
||||
from bec_widgets.utils.colors import Colors, apply_theme, get_accent_colors
|
||||
from bec_widgets.utils.error_popups import SafeSlot
|
||||
from bec_widgets.widgets.control.scan_control.scan_control import ScanControl
|
||||
from qtpy.QtCore import Qt
|
||||
|
||||
# pylint: disable=E0611
|
||||
from qtpy.QtWidgets import (
|
||||
QApplication,
|
||||
QCheckBox,
|
||||
QFrame,
|
||||
QGroupBox,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QScrollArea,
|
||||
QSizePolicy,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
# pylint: disable=E0402
|
||||
from ....devices.mo1_bragg.mo1_bragg_utils import compute_spline
|
||||
from ..edge_selector import EdgeSelector
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
ALLOWED_SCANS = [
|
||||
"xas_simple_scan",
|
||||
"xas_simple_scan_with_xrd",
|
||||
"xas_advanced_scan",
|
||||
"xas_advanced_scan_with_xrd",
|
||||
]
|
||||
|
||||
PLOT_RESOLUTION = 100
|
||||
H = 6.62606957e-34
|
||||
E = 1.602176634e-19
|
||||
C = 299792458
|
||||
|
||||
X01DA_E_MIN = 4500
|
||||
X01DA_E_MAX = 60000
|
||||
X10DA_E_MIN = 4500
|
||||
X10DA_E_MAX = 35000
|
||||
|
||||
|
||||
class ScanControlXAS(ScanControl):
|
||||
"""
|
||||
Main widget of Scan Control XAS
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._xas_scans_helper_group = QGroupBox("XAS scan helper")
|
||||
self._xas_scans_helper_group.setSizePolicy(
|
||||
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed
|
||||
)
|
||||
xas_scans_helper_layout = QVBoxLayout(self._xas_scans_helper_group)
|
||||
self.xas_scans_helper_widget = XASScansHelper(self)
|
||||
xas_scans_helper_layout.addWidget(self.xas_scans_helper_widget)
|
||||
super().__init__(
|
||||
allowed_scans=ALLOWED_SCANS, default_scan="xas_simple_scan", *args, **kwargs
|
||||
)
|
||||
self._show_motion_profile(self._selected_scan)
|
||||
|
||||
self.d_spacing = 0
|
||||
self.bec_dispatcher.connect_slot(
|
||||
self._update_d_spacing, MessageEndpoints.device_readback("mo1_bragg")
|
||||
)
|
||||
|
||||
@SafeSlot(dict, dict)
|
||||
def _update_d_spacing(self, msg: dict, _: dict):
|
||||
d_spacing = msg["signals"].get("mo1_bragg_crystal_current_d_spacing")["value"]
|
||||
if d_spacing != self.d_spacing:
|
||||
self.d_spacing = d_spacing
|
||||
self.xas_scans_helper_widget.update_plot(d_spacing=d_spacing)
|
||||
|
||||
def _add_metadata_form(self):
|
||||
self.layout.addWidget(self._xas_scans_helper_group) # type: ignore
|
||||
super()._add_metadata_form()
|
||||
self._connect_scan_parameter_signals()
|
||||
|
||||
def on_scan_selection_changed(self, scan_name: str):
|
||||
super().on_scan_selection_changed(scan_name)
|
||||
self._show_motion_profile(scan_name)
|
||||
if scan_name in ALLOWED_SCANS:
|
||||
self._connect_scan_parameter_signals()
|
||||
self._scan_parameter_changed()
|
||||
|
||||
def _show_motion_profile(self, scan_name):
|
||||
if scan_name in ALLOWED_SCANS:
|
||||
self.xas_scans_helper_widget.setVisible(True)
|
||||
else:
|
||||
self.xas_scans_helper_widget.setVisible(False)
|
||||
|
||||
def _connect_scan_parameter_signals(self):
|
||||
for box in self.kwarg_boxes:
|
||||
for widget in box.findChildren(QWidget):
|
||||
try:
|
||||
if hasattr(widget, "valueChanged"):
|
||||
widget.valueChanged.connect(
|
||||
self._scan_parameter_changed, Qt.ConnectionType.UniqueConnection
|
||||
)
|
||||
elif hasattr(widget, "textChanged"):
|
||||
widget.textChanged.connect(
|
||||
self._scan_parameter_changed, Qt.ConnectionType.UniqueConnection
|
||||
)
|
||||
except TypeError:
|
||||
# Raised if a connection would not be unique anymore, i.e. if
|
||||
# connect_scan_parameter_signals is called more than once with
|
||||
# the same kwarg_boxes
|
||||
pass
|
||||
|
||||
@SafeSlot()
|
||||
def _scan_parameter_changed(self, *_):
|
||||
params = self.get_scan_parameters()[1]
|
||||
self.xas_scans_helper_widget.update_plot(
|
||||
scan_name=self._selected_scan, scan_parameters=params
|
||||
)
|
||||
|
||||
def apply_theme(self, theme: Literal["dark", "light"]):
|
||||
"""
|
||||
Apply the theme
|
||||
|
||||
Args:
|
||||
theme (str): Theme, either "dark" or "light"
|
||||
"""
|
||||
self.xas_scans_helper_widget.apply_theme(theme)
|
||||
|
||||
|
||||
class XASScansHelper(QWidget):
|
||||
"""
|
||||
Additional widget specifically for XAS scans
|
||||
"""
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__()
|
||||
self._parent_widget = parent
|
||||
self.beamline = ""
|
||||
|
||||
outer_layout = QVBoxLayout(self)
|
||||
outer_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
|
||||
edge_selector_layout = QHBoxLayout()
|
||||
edge_selector_label = QLabel("Absorption edge:")
|
||||
self.edge_selector_button = QPushButton("Choose")
|
||||
self.edge_label = QLabel("No edge selected")
|
||||
edge_selector_layout.addWidget(edge_selector_label)
|
||||
edge_selector_layout.addWidget(self.edge_label)
|
||||
edge_selector_layout.addWidget(self.edge_selector_button)
|
||||
edge_selector_layout.addStretch()
|
||||
|
||||
scan_rng_layout = QHBoxLayout()
|
||||
self.scan_rng_ckbox = QCheckBox(self)
|
||||
self.scan_rng_ckbox.setCheckState(Qt.CheckState.Checked)
|
||||
scan_rng_label = QLabel("Auto adjust scan range to absorption edge")
|
||||
scan_rng_layout.addWidget(self.scan_rng_ckbox)
|
||||
scan_rng_layout.addWidget(scan_rng_label)
|
||||
scan_rng_layout.addStretch()
|
||||
|
||||
plot_layout = QHBoxLayout()
|
||||
plot_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.plot_pos = self._make_plot(left_label="Energy<br>[eV]", bottom_label="Time [s]")
|
||||
self.plot_vel = self._make_plot(
|
||||
left_label="Scan Velocity<br>[eV/s]", bottom_label="Energy [eV]"
|
||||
)
|
||||
plot_layout.addWidget(self.plot_pos)
|
||||
plot_layout.addWidget(self.plot_vel)
|
||||
|
||||
self.edge_energy = None
|
||||
|
||||
self.edge_selection = QHBoxLayout()
|
||||
self.edge_selection.addLayout(edge_selector_layout)
|
||||
self.edge_selection.addStretch(1)
|
||||
|
||||
outer_layout.addLayout(self.edge_selection)
|
||||
outer_layout.addLayout(scan_rng_layout)
|
||||
outer_layout.addLayout(plot_layout)
|
||||
|
||||
self.scan_name = None
|
||||
self.scan_parameters = None
|
||||
self.d_spacing = None
|
||||
|
||||
self.edge_selector_button.clicked.connect(self._update_edge)
|
||||
self.scan_rng_ckbox.stateChanged.connect(self._update_auto_scan_rng)
|
||||
|
||||
self.apply_theme()
|
||||
|
||||
def _make_plot(self, left_label: str, bottom_label: str) -> pg.PlotWidget:
|
||||
plot = pg.PlotWidget()
|
||||
plot.setLabel("left", left_label)
|
||||
plot.setLabel("bottom", bottom_label)
|
||||
plot.setMouseEnabled(x=False, y=False)
|
||||
plot.setMenuEnabled(False)
|
||||
plot.hideButtons()
|
||||
plot.getAxis("left").enableAutoSIPrefix(False)
|
||||
plot.getAxis("bottom").enableAutoSIPrefix(False)
|
||||
plot.setMinimumHeight(150)
|
||||
plot.setMaximumHeight(150)
|
||||
plot.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
return plot
|
||||
|
||||
def _add_edge_marker(self, plot, x_arr, y_arr, energy_arr, edge_marker):
|
||||
if self.edge_energy is None:
|
||||
return
|
||||
|
||||
idx = int(np.argmin(np.abs(np.asarray(energy_arr) - self.edge_energy)))
|
||||
if idx == 0 or idx == len(energy_arr) - 1:
|
||||
return
|
||||
x_marker = x_arr[idx]
|
||||
y_marker = y_arr[idx]
|
||||
|
||||
scatter = pg.ScatterPlotItem(
|
||||
[x_marker],
|
||||
[y_marker],
|
||||
symbol=edge_marker,
|
||||
size=20,
|
||||
brush=pg.mkBrush("r"),
|
||||
pen=pg.mkPen("r"),
|
||||
)
|
||||
plot.addItem(scatter, ignoreBounds=True)
|
||||
|
||||
label = pg.TextItem(self.edge_label.text(), anchor=(0, 1.5), color="r")
|
||||
label.setPos(x_marker, y_marker)
|
||||
plot.addItem(label, ignoreBounds=True)
|
||||
|
||||
@SafeSlot()
|
||||
def _update_edge(self, *_):
|
||||
if self.beamline == "":
|
||||
hostname = self._parent_widget.client._hostname
|
||||
start = hostname.find("x")
|
||||
if start != -1:
|
||||
self.beamline = hostname[start : start + 5]
|
||||
match self.beamline:
|
||||
case "x01da":
|
||||
dlg = EdgeSelector(self, llim=X01DA_E_MIN, hlim=X01DA_E_MAX)
|
||||
case "x10da":
|
||||
dlg = EdgeSelector(self, llim=X10DA_E_MIN, hlim=X10DA_E_MAX)
|
||||
case _:
|
||||
dlg = EdgeSelector(self)
|
||||
if dlg.exec_():
|
||||
self.edge_energy = dlg.selected_energy
|
||||
self.edge_label.setText(
|
||||
f"{dlg.selected_element}, {dlg.selected_edge}-edge, {dlg.selected_energy:0.1f} eV"
|
||||
)
|
||||
self._update_auto_scan_rng()
|
||||
self.update_plot()
|
||||
|
||||
@SafeSlot()
|
||||
def _update_auto_scan_rng(self, *_):
|
||||
if self.scan_rng_ckbox.isChecked() and self.edge_energy is not None:
|
||||
self._parent_widget._restore_kwargs(
|
||||
{
|
||||
"start": self.edge_energy - 200,
|
||||
"stop": self.edge_energy + 1000,
|
||||
"e_kink": self.edge_energy + 1000,
|
||||
}
|
||||
)
|
||||
|
||||
@SafeSlot()
|
||||
def update_plot(self, *_, scan_name=None, scan_parameters=None, d_spacing=None, **__):
|
||||
"""Update the plots. Parameters which are not defined will default to None,
|
||||
in which case it will use the internal values if available.
|
||||
|
||||
Args:
|
||||
scan_name(str): Scan name, e.g. "xas_simple_scan"
|
||||
Defaults to None
|
||||
scan_parameters(dict): Scan parameters from ScanControl
|
||||
Defaults to None
|
||||
d_spacing(float): d-spacing in Angstrom
|
||||
Defaults to None
|
||||
"""
|
||||
if scan_name is not None:
|
||||
self.scan_name = scan_name
|
||||
if scan_parameters is not None:
|
||||
self.scan_parameters = scan_parameters
|
||||
if d_spacing is not None:
|
||||
self.d_spacing = d_spacing
|
||||
if self.scan_name is None:
|
||||
return
|
||||
if self.scan_parameters is None:
|
||||
return
|
||||
if self.d_spacing is None:
|
||||
return
|
||||
if d_spacing == 0:
|
||||
return
|
||||
|
||||
x_time = np.linspace(0, self.scan_parameters["scan_time"], PLOT_RESOLUTION)
|
||||
|
||||
self.plot_pos.clear()
|
||||
self.plot_vel.clear()
|
||||
|
||||
start_angle = self._energy_to_angle(self.scan_parameters["stop"])
|
||||
stop_angle = self._energy_to_angle(self.scan_parameters["start"])
|
||||
if start_angle == stop_angle:
|
||||
return
|
||||
if "xas_simple_scan" in self.scan_name:
|
||||
amp = (stop_angle - start_angle) / 2
|
||||
two_pi_f = 2 * np.pi * 1 / (2 * self.scan_parameters["scan_time"])
|
||||
pos = (start_angle + stop_angle) / 2 + amp * np.cos(x_time * two_pi_f)
|
||||
vel = two_pi_f * amp * np.sin(x_time * two_pi_f)
|
||||
energy = self._angle_to_energy(pos)
|
||||
vel_e = self._velocity_to_energy_per_s(pos, vel)
|
||||
|
||||
self.plot_pos.plot(x_time, energy)
|
||||
self._add_edge_marker(self.plot_pos, x_time, energy, energy, "_")
|
||||
|
||||
self.plot_vel.plot(energy, -vel_e)
|
||||
self._add_edge_marker(self.plot_vel, energy, -vel_e, energy, "|")
|
||||
|
||||
if "xas_advanced_scan" in self.scan_name:
|
||||
if (
|
||||
self.scan_parameters["e_kink"] < self.scan_parameters["start"]
|
||||
or self.scan_parameters["e_kink"] > self.scan_parameters["stop"]
|
||||
):
|
||||
return
|
||||
if self.scan_parameters["scan_time"] == 0:
|
||||
return
|
||||
e_kink_deg = self._energy_to_angle(self.scan_parameters["e_kink"])
|
||||
pos, vel, t = compute_spline(
|
||||
start_angle,
|
||||
stop_angle,
|
||||
self.scan_parameters["p_kink"],
|
||||
e_kink_deg,
|
||||
self.scan_parameters["scan_time"],
|
||||
)
|
||||
x_time = np.cumsum(t) / 1000
|
||||
energy = self._angle_to_energy(pos)
|
||||
vel_e = self._velocity_to_energy_per_s(pos, vel)
|
||||
|
||||
time_flipped = -np.flip(x_time) + x_time[-1]
|
||||
energy_flipped = np.flip(energy)
|
||||
self.plot_pos.plot(time_flipped, energy_flipped)
|
||||
self._add_edge_marker(self.plot_pos, time_flipped, energy_flipped, energy_flipped, "_")
|
||||
|
||||
self.plot_vel.plot(energy, -vel_e)
|
||||
self._add_edge_marker(self.plot_vel, energy, -vel_e, energy, "|")
|
||||
|
||||
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
|
||||
|
||||
self.edge_selector_button.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().default.name()}; color: white;}}"
|
||||
)
|
||||
|
||||
bg_color = pg.getConfigOption("background")
|
||||
fg_color = pg.getConfigOption("foreground")
|
||||
|
||||
for plot in (self.plot_pos, self.plot_vel):
|
||||
plot.setBackground(bg_color)
|
||||
for axis in ("left", "bottom", "right", "top"):
|
||||
ax = plot.getAxis(axis)
|
||||
ax.setPen(pg.mkPen(color=fg_color))
|
||||
ax.setTextPen(pg.mkPen(color=fg_color))
|
||||
|
||||
curves = plot.listDataItems()
|
||||
colors = Colors.golden_angle_color(colormap="plasma", num=len(curves), format="HEX")
|
||||
for curve, color in zip(curves, colors):
|
||||
if not isinstance(curve, pg.ScatterPlotItem):
|
||||
curve.setPen(pg.mkPen(color=color, width=2))
|
||||
|
||||
def _energy_to_angle(self, energy: int | float):
|
||||
if self.d_spacing is None:
|
||||
return 0
|
||||
if energy <= 0:
|
||||
return 0
|
||||
wl = C * H / (E * energy)
|
||||
val = wl / (2 * self.d_spacing * 1e-10)
|
||||
return np.asin(val) / np.pi * 180
|
||||
|
||||
def _angle_to_energy(self, angle: np.ndarray | int | float):
|
||||
if self.d_spacing is None:
|
||||
return 0
|
||||
if isinstance(angle, np.ndarray):
|
||||
if angle.any() <= 0:
|
||||
return 0
|
||||
else:
|
||||
if angle <= 0:
|
||||
return 0
|
||||
wl = 2 * self.d_spacing * 1e-10 * np.sin(angle / 180 * np.pi)
|
||||
return C * H / (E * wl)
|
||||
|
||||
def _velocity_to_energy_per_s(self, angle, velocity):
|
||||
if self.d_spacing is None:
|
||||
return 0
|
||||
return (
|
||||
-C
|
||||
* H
|
||||
* (np.pi / 180)
|
||||
* np.cos(angle / 180 * np.pi)
|
||||
/ (E * 2 * self.d_spacing * 1e-10 * np.sin(angle / 180 * np.pi) ** 2)
|
||||
) * velocity
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
apply_theme("light")
|
||||
dispatcher = BECDispatcher(gui_id="scan_control_advanced")
|
||||
win = ScanControlXAS()
|
||||
win.show()
|
||||
sys.exit(app.exec_())
|
||||
@@ -0,0 +1 @@
|
||||
{'files': ['scan_control_xas.py']}
|
||||
@@ -0,0 +1,57 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
from bec_widgets.utils.bec_designer import designer_material_icon
|
||||
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
||||
from qtpy.QtWidgets import QWidget
|
||||
|
||||
from .scan_control_xas import ScanControlXAS
|
||||
|
||||
DOM_XML = """
|
||||
<ui language='c++'>
|
||||
<widget class='ScanControlXAS' name='scan_control_xas'>
|
||||
</widget>
|
||||
</ui>
|
||||
"""
|
||||
|
||||
|
||||
class ScanControlXASPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._form_editor = None
|
||||
|
||||
def createWidget(self, parent):
|
||||
if parent is None:
|
||||
return QWidget()
|
||||
t = ScanControlXAS(parent)
|
||||
return t
|
||||
|
||||
def domXml(self):
|
||||
return DOM_XML
|
||||
|
||||
def group(self):
|
||||
return ""
|
||||
|
||||
def icon(self):
|
||||
return designer_material_icon(ScanControlXAS.ICON_NAME)
|
||||
|
||||
def includeFile(self):
|
||||
return "scan_control_xas"
|
||||
|
||||
def initialize(self, form_editor):
|
||||
self._form_editor = form_editor
|
||||
|
||||
def isContainer(self):
|
||||
return False
|
||||
|
||||
def isInitialized(self):
|
||||
return self._form_editor is not None
|
||||
|
||||
def name(self):
|
||||
return "ScanControlXAS"
|
||||
|
||||
def toolTip(self):
|
||||
return "ScanControlXAS"
|
||||
|
||||
def whatsThis(self):
|
||||
return self.toolTip()
|
||||
@@ -178,7 +178,7 @@ class XASAdvancedScan(XASSimpleScan):
|
||||
stop: Annotated[float, ScanArgument(display_name="Stop Energy", description="Stop energy.", units=Units.eV)],
|
||||
scan_time: Annotated[float, ScanArgument(display_name="Scan Time", description="Time for one scan cycle.", units=Units.s, ge=0)],
|
||||
scan_duration: Annotated[float, ScanArgument(display_name="Scan Duration", description="Total scan duration.", units=Units.s, ge=0)],
|
||||
p_kink: Annotated[float, ScanArgument(display_name="P Kink", description="Position of the kink.", ge=0)],
|
||||
p_kink: Annotated[float, ScanArgument(display_name="P Kink", description="Position of the kink.", ge=0, le=100, units=Units.percent)],
|
||||
e_kink: Annotated[float, ScanArgument(display_name="E Kink", description="Energy of the kink.", units=Units.eV)],
|
||||
motor: Annotated[DeviceBase | None, ScanArgument(display_name="Motor", description="Bragg motor device.")] = None,
|
||||
daq: Annotated[DeviceBase | None, ScanArgument(display_name="DAQ", description="NIDAQ device.")] = None,
|
||||
|
||||
Reference in new issue
Block a user