Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Energy widgets #219

Merged
merged 23 commits into from
Jun 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
541e778
Added more controls to the Firefly energy window.
canismarko May 17, 2024
e70bacc
Partial conversion of the energy positioner to include the monos and ID.
canismarko May 18, 2024
3d9720c
Added an APSUndulator device with new PVs (derived signals don't work).
canismarko May 22, 2024
8b24315
Re-factored the energy positioner to directly include the devices as …
canismarko May 25, 2024
b029a33
Updated Monochromator device so that the prefix now includes the trai…
canismarko May 26, 2024
7346948
Rewrote the tests and caQtDM support so it works with the new energy …
canismarko May 26, 2024
a9ab845
Added a TweakDisplay PyDM widget with controls for tweaking a signal …
canismarko May 26, 2024
31dec36
Fixed firefly app so that --no-instrument allows application to load.
canismarko May 26, 2024
94388b5
Merge branch 'main' into energy_widgets
canismarko May 27, 2024
86e1576
Merge branch 'main' into energy_widgets
canismarko May 27, 2024
a0b135f
Re-factored the energy positioner window in Firefly.
canismarko May 28, 2024
df4ba3a
Added offset readback to the energy window.
canismarko May 28, 2024
7145a93
Fixed the undulator DerivedSignal components.
canismarko Jun 6, 2024
f9bce08
Fixed the undulator device derived signals.
canismarko Jun 7, 2024
6e04e22
Fixed precision on signals for energy positioner window.
canismarko Jun 7, 2024
945110f
Added labels to the mono and ID devices.
canismarko Jun 7, 2024
e040cde
Tested the PlanarUndulator device with real undulator at 25-ID.
yannachen Jun 23, 2024
f405e95
Merge branch 'main' into energy_widgets
canismarko Jun 24, 2024
4ed0f9d
Black, isort, and flake8.
canismarko Jun 24, 2024
40d35ae
Updated example iconfig files to match the new energy positioner schema.
canismarko Jun 24, 2024
894e63a
The energy property now has a limits property that reads from the der…
canismarko Jun 28, 2024
156efb6
The energy positioner now properly sets the ID energy in keV instead …
canismarko Jun 28, 2024
ce8f5cd
Firefly energy display now uses the energy positioner limits to deter…
canismarko Jun 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/firefly/application.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import asyncio
import logging
import subprocess
import warnings
from collections import OrderedDict
from functools import partial
from pathlib import Path
Expand Down Expand Up @@ -551,11 +552,10 @@ def prepare_queue_client(self, client=None, api=None):
if api is None:
try:
api = queueserver_api()
except InvalidConfiguration:
log.error(
"Could not load queueserver API "
"configuration from iconfig.toml file."
)
except InvalidConfiguration as exc:
msg = f"Could not create queue client. Missing key {exc}"
log.warning(msg)
warnings.warn(msg)
return
# Create the client object
if client is None:
Expand Down
79 changes: 63 additions & 16 deletions src/firefly/energy.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,76 @@
import logging
import warnings

import qtawesome as qta
from bluesky_queueserver_api import BPlan
from ophydregistry import ComponentNotFound
from pydm.widgets.label import PyDMLabel
from pydm.widgets.line_edit import PyDMLineEdit
from qtpy import QtCore, QtWidgets
from qtpy.QtWidgets import QDialogButtonBox, QFormLayout, QLineEdit, QVBoxLayout
from xraydb.xraydb import XrayDB

from firefly import display
from haven import exceptions, load_config, registry
from haven import load_config, registry

log = logging.getLogger(__name__)


class EnergyCalibrationDialog(QtWidgets.QDialog):
"""A dialog box for calibrating the energy."""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

self.setWindowTitle("Energy calibration")

self.layout = QVBoxLayout()
self.setLayout(self.layout)
# Widgets for inputting calibration parameters
self.form_layout = QFormLayout()
self.layout.addLayout(self.form_layout)
self.form_layout.addRow(
"Energy readback:", PyDMLabel(self, init_channel="haven://energy.readback")
)
self.form_layout.addRow(
"Energy setpoint:",
PyDMLineEdit(self, init_channel="haven://energy.setpoint"),
)
self.form_layout.addRow(
"Calibrated energy:",
canismarko marked this conversation as resolved.
Show resolved Hide resolved
QLineEdit(),
)
# Button for accept/close
buttons = QDialogButtonBox.Apply | QDialogButtonBox.Close
self.buttonBox = QDialogButtonBox(buttons)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
self.layout.addWidget(self.buttonBox)


class EnergyDisplay(display.FireflyDisplay):
caqtdm_mono_ui_file = "/net/s25data/xorApps/ui/DCMControlCenter.ui"
caqtdm_id_ui_file = (
"/net/s25data/xorApps/epics/synApps_6_2/ioc/25ida/25idaApp/op/ui/IDControl.ui"
)
min_energy = 4000
max_energy = 33000
stylesheet_danger = (
"background: rgb(220, 53, 69); color: white; border-color: rgb(220, 53, 69)"
)
stylesheet_normal = ""
energy_positioner = None
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For line 56 and 57, the max and min energy for Si (111) is from 4 to 40 keV, for ML it is 5~22 depending on the gap? Should we distinguish ML from (111) here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will re-write this to get the min/max values from the monochromator.

Eventually, we will also need a check when setting the energy to ensure it's within range for the mono/ID/2° mono.

For now, we'll use 5 -- 38keV for 25-ID.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to derive the range of valid energies from the limits on the setpoint of the mono energy and ID energy. This way, we can set those values properly in EPICS and everything just kind of works.

Right now the range is 10 eV to 35000 eV, which is wrong, but it's wrong because the limits are not set properly. The current limits are:

  • mono: 0 ev to 35,000 eV
  • undulator: 0.01 keV to 100 keV


def __init__(self, args=None, macros={}, **kwargs):
# Load X-ray database for calculating edge energies
self.xraydb = XrayDB()
super().__init__(args=args, macros=macros, **kwargs)

def customize_device(self):
try:
self.energy_positioner = registry.find("energy")
except ComponentNotFound:
warnings.warn("Could not find energy positioner.")
log.warning("Could not find energy positioner.")

def prepare_caqtdm_actions(self):
"""Create QActions for opening mono/ID caQtDM panels.

Expand All @@ -43,10 +86,6 @@ def prepare_caqtdm_actions(self):
action.triggered.connect(self.launch_mono_caqtdm)
action.setIcon(qta.icon("fa5s.wrench"))
action.setToolTip("Launch the caQtDM panel for the monochromator.")
try:
registry.find(name="monochromator")
except exceptions.ComponentNotFound:
action.setDisabled(True)
self.caqtdm_actions.append(action)
# Create an action for launching the ID caQtDM file
action = QtWidgets.QAction(self)
Expand All @@ -59,25 +98,26 @@ def prepare_caqtdm_actions(self):

def launch_mono_caqtdm(self):
config = load_config()
prefix = config["monochromator"]["ioc"] + ":"
mono = registry.find(name="monochromator")
ID = registry.find(name="undulator")
mono = self.energy_positioner.monochromator
ID = self.energy_positioner.undulator
prefix = mono.prefix
caqtdm_macros = {
"P": prefix,
"MONO": config["monochromator"]["ioc_branch"],
"BRAGG": mono.bragg.prefix.replace(prefix, ""),
"GAP": mono.gap.prefix.replace(prefix, ""),
"ENERGY": mono.energy.prefix.replace(prefix, ""),
"OFFSET": mono.offset.prefix.replace(prefix, ""),
"IDENERGY": ID.energy.pvname,
"IDENERGY": ID.energy.prefix,
}
self.launch_caqtdm(macros=caqtdm_macros, ui_file=self.caqtdm_mono_ui_file)

def launch_id_caqtdm(self):
"""Launch the pre-built caQtDM UI file for the ID."""
config = load_config()
prefix = config["undulator"]["ioc"]
# Strip leading "ID" from the mono IOC since caQtDM adds it
prefix = self.energy_positioner.undulator.prefix
# caQtDM doesn't expect the trailing ";"
prefix = prefix.rstrip(":")
# Strip leading "ID" from the ID IOC since caQtDM adds it
prefix = prefix.strip("ID")
caqtdm_macros = {
# No idea what "M", and "D" do, they're not in the UI
Expand Down Expand Up @@ -105,16 +145,23 @@ def customize_ui(self):
combo_box = self.ui.edge_combo_box
ltab = self.xraydb.tables["xray_levels"]
edges = self.xraydb.query(ltab)
min_energy, max_energy = self.energy_positioner.limits
edges = edges.filter(
ltab.c.absorption_edge < self.max_energy,
ltab.c.absorption_edge > self.min_energy,
ltab.c.absorption_edge < max_energy,
ltab.c.absorption_edge > min_energy,
)
items = [
f"{r.element} {r.iupac_symbol} ({int(r.absorption_edge)} eV)"
for r in edges.all()
]
combo_box.addItems(["Select edge…", *items])
combo_box.activated.connect(self.select_edge)
# Respond to the "calibrate" button
self.ui.calibrate_button.clicked.connect(self.show_calibrate_dialog)

def show_calibrate_dialog(self):
dialog = EnergyCalibrationDialog(self)
dialog.exec()

@QtCore.Slot(int)
def select_edge(self, index):
Expand Down
Loading