Beamline Backends¶
Beamline backends tell orGUI how to open a scan, read its motor/counter metadata, and find the detector images. They are needed because beamlines often store the same physical information under different names or in different file layouts.
Most users only need to select an existing backend. New backend files are useful when data from another beamline or a changed beamline file format should be loaded into orGUI.
Using A Backend¶
There are two normal ways to select a backend in GUI mode.
Config File¶
Add a [backend] section to the orGUI config file. To load a backend Python
file:
[backend]
file = ./backend/P212_backend.py
The path is resolved relative to the config file. When the config is loaded,
orGUI loads the Python file, finds its Scan subclass, adds it to the
backend selector, selects it, and disables auto-detection.
Alternatively, select a backend that is already registered in orGUI:
[backend]
beamtime = id31_default
GUI Selection¶
The same selection can be made in the Scan data dock described in
GUI Overview.
Open the
Scan datawindow.Disable
auto detectin the lower backend controls.Choose an existing backend from the
Backenddrop-down, or click...to load a backend Python file.Select the scan file and scan number.
Click the open-scan button or double click a compatible scan in the file tree.
Auto-detection is convenient when the file metadata can identify a known beamtime. Disable it when using a custom backend file or when the automatic choice is wrong for the current data.
What A Backend Does¶
A backend normalizes beamline-specific data into the conventions expected by
orGUI. The examples in examples/backend show the usual structure:
read scan metadata from a Nexus/HDF5/SPEC-like file
map beamline motor names to orGUI motor attributes
identify the scan axis
locate the detector image files
return one image at a time as a numpy array
expose counters that should be copied into integrated output files
The base class is orgui.backend.scans.Scan. A backend file loaded through
the GUI must define exactly one subclass of Scan.
Required Scan Attributes¶
The backend constructor should populate these attributes:
axisnameName of the scanned axis. The most fully supported scan axes are
"th"and"mu".axisNumpy array with the scan-axis values. Values are user-facing motor values in degrees.
thTheta motor values in degrees.
omegaInternal omega-convention values in degrees. In the current orGUI diffractometer convention, this is usually
omega = -th.muIncidence-angle motor values in degrees.
titleHuman-readable scan title.
nameOptional unique scan identifier used as a key in output files.
Required Scan Methods¶
__init__(hdffilepath_orNode=None, scanno=None)Read the selected file or already-open HDF5 node and populate scan metadata. The examples handle both a file path and a node selected in the GUI file browser.
parse_h5_node(cls, node)Return a dictionary with at least
scannoandnamefor a scan node double clicked in the Nexus tree.__len__()Return the number of images or points in the scan.
get_raw_img(i)Return an image object for image number
i. The returned object must have animgattribute containing the image as a numpy array.__getitem__(i)Optional but convenient. The examples implement this by returning
get_raw_img(i).
Image Objects¶
The minimal image contract is small:
class MyImage:
def __init__(self, filename):
with fabio.open(filename) as img:
self.img = img.data.astype(np.float64)
self.header = img.header
self.motors = {}
self.counters = {}
Only img is required for image display and integration. motors and
counters are optional dictionaries for additional metadata.
Auxiliary Counters¶
Backends can expose counters that should be copied into the integration output
database by overriding the auxillary_counters property. The spelling is
intentional and must match the API name:
@property
def auxillary_counters(self):
return ["exposure_time", "time", "diode"]
After integration, orGUI looks for attributes with these names on the Scan
object and copies matching values into the database.
Minimal Backend Skeleton¶
This skeleton shows the shape of a custom backend. Real backends usually need
beamline-specific path handling and motor-name conversion, as shown in
examples/backend/P212_backend.py and examples/backend/CHESS_QM2.py.
import os
import fabio
import numpy as np
import silx.io
from orgui.backend.scans import Scan
class ExampleImage:
def __init__(self, filename):
with fabio.open(filename) as img:
self.img = img.data.astype(np.float64)
self.header = img.header
self.motors = {}
self.counters = {}
class ExampleBackend(Scan):
def __init__(self, hdffilepath_orNode=None, scanno=None):
if hdffilepath_orNode is None:
return
if isinstance(hdffilepath_orNode, str):
filepath = os.path.abspath(hdffilepath_orNode)
h5file = silx.io.open(filepath)
close_file = True
else:
filepath = hdffilepath_orNode.local_filename
h5file = hdffilepath_orNode.file
close_file = False
try:
scan_name = str(scanno)
scan = h5file[scan_name]
self.name = scan_name
self.title = scan["title"][()]
# Replace these paths and motor names with beamline-specific
# metadata parsing.
measurement = scan["measurement"]
self.th = np.asarray(measurement["th"], dtype=np.float64)
self.omega = -self.th
self.mu = np.asarray(measurement["mu"], dtype=np.float64)
self.axisname = "th"
self.axis = self.th
self.nopoints = len(self.axis)
image_folder = os.path.join(os.path.dirname(filepath), "images")
self.image_paths = [
os.path.join(image_folder, f"image_{i:05d}.cbf")
for i in range(self.nopoints)
]
finally:
if close_file:
h5file.close()
@classmethod
def parse_h5_node(cls, node):
return {
"scanno": int(node.basename),
"name": node.local_name,
}
def __len__(self):
return self.nopoints
def get_raw_img(self, i):
return ExampleImage(self.image_paths[i])
def __getitem__(self, i):
return self.get_raw_img(i)
Example Backend Patterns¶
examples/backend/P212_backend.pyReads a P21.2-style HDF5 scan, finds available detector cameras, maps beamline motors to six-circle equivalents, derives
thandmu, and constructs detector image paths from detector metadata.examples/backend/CHESS_QM2.pyReads CHESS QM2-style HDF5 metadata, maps scan motors to orGUI motor attributes, searches beamline image folders for matching Pilatus CBF images, and parses useful values from the Pilatus header.
Both examples illustrate the same principle: keep beamline-specific naming and
file-layout logic inside the backend, and expose a normalized Scan object to
the rest of orGUI.
Implementation API¶
For the formal base classes and method signatures, see Backend API.