Skip to content

Commit

Permalink
Adding setting of ndattributes_file XML to the Xspress3Detector staging.
Browse files Browse the repository at this point in the history
  • Loading branch information
canismarko committed Jan 17, 2025
1 parent 66b42af commit 97f87d9
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 22 deletions.
25 changes: 5 additions & 20 deletions docs/topic_guides/fluorescence_detectors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -78,26 +78,11 @@ let Bluesky know which values are in the HDF5 file and where. The
EPICS support for an Xspress3 detector provides several additional
values besides the spectra, many of them useful for dead-time
correction. These can be saved using the Ophyd-async NDAttribute
support. Haven includes a function for generating these parameters so
they can be set on the IOC in a way that allows Tiled to read the
resulting values from the HDF5 file. This only needs to be done once
for each detector IOC.
support. The Xspress3 device support in Haven will generate these
parameters and set them on the IOC during staging in a way that allows
Tiled to read the resulting values from the HDF5 file alongside the
image data itself.

Be sure to **give this detector a name**, since it will be used as the
column name in the database.

.. code-block:: python
from haven.devices.detectors.xspress import Xspress3Detector, ndattribute_params
from ophyd_async.plan_stubs import setup_ndattributes
from bluesky import RunEngine
# Assuming a 4-element detector
RE = RunEngine()
detector = Xspress3Detector(...)
await detector.connect()
params = ndattribute_params(device_name=detector.name, elements=range(4))
RE(setup_ndattributes(detector.drv, params))

.. note::

Expand All @@ -114,7 +99,7 @@ column name in the database.


Why can't I…
############
============

Previously, some steps were performed during data acquisition by the
IOC that have now been moved to other parts of the system. These
Expand Down
42 changes: 40 additions & 2 deletions src/haven/devices/detectors/xspress.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import asyncio
import xml.etree.ElementTree as ET
from collections.abc import Sequence

from ophyd_async.core import (
Expand All @@ -17,7 +18,7 @@
NDAttributeParam,
convert_ad_dtype_to_np,
)
from ophyd_async.epics.core import epics_signal_rw, epics_signal_x
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw, epics_signal_x

from .area_detectors import HavenDetector, default_path_provider

Expand All @@ -39,6 +40,7 @@ def __init__(self, prefix, name=""):
self.erase_on_start = epics_signal_rw(bool, f"{prefix}EraseOnStart")
self.erase = epics_signal_x(f"{prefix}ERASE")
self.deadtime_correction = epics_signal_rw(bool, f"{prefix}CTRL_DTC")
self.number_of_elements = epics_signal_r(int, f"{prefix}MaxSizeY_RBV")
super().__init__(prefix=prefix, name=name)


Expand All @@ -51,6 +53,14 @@ def get_deadtime(self, exposure: float) -> float:
# include
return 0.001

async def setup_ndattributes(self, device_name: str):
num_elements = await self._drv.number_of_elements.get_value()
params = ndattribute_params(
device_name=device_name, elements=range(num_elements)
)
xml = ndattribute_xml(params)
await self._drv.nd_attributes_file.set(xml)

@AsyncStatus.wrap
async def prepare(self, trigger_info: TriggerInfo):
await asyncio.gather(
Expand Down Expand Up @@ -126,12 +136,40 @@ def __init__(

@AsyncStatus.wrap
async def stage(self) -> None:
await asyncio.gather(
self._old_xml_file, *_ = await asyncio.gather(
self.drv.nd_attributes_file.get_value(),
super().stage(),
self._controller.setup_ndattributes(device_name=self.name),
self.drv.erase_on_start.set(False),
self.drv.erase.trigger(),
)

@AsyncStatus.wrap
async def unstage(self) -> None:
await super().unstage()
if self._old_xml_file is not None:
# Restore the original XML attributes file
await self.drv.nd_attributes_file.set(self._old_xml_file)
self._old_xml_file = None


def ndattribute_xml(params):
"""Convert a set of NDAttribute params to XML."""
root = ET.Element("Attributes")
for ndattribute in params:
ET.SubElement(
root,
"Attribute",
name=ndattribute.name,
type="PARAM",
source=ndattribute.param,
addr=str(ndattribute.addr),
datatype=ndattribute.datatype.value,
description=ndattribute.description,
)
xml_text = ET.tostring(root, encoding="unicode")
return xml_text


def ndattribute_params(
device_name: str, elements: Sequence[int]
Expand Down
17 changes: 17 additions & 0 deletions src/haven/tests/test_xspress.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import asyncio
import xml.etree.ElementTree as ET
from pathlib import Path

import pytest
Expand Down Expand Up @@ -80,6 +81,22 @@ async def test_ndattribute_params():
assert len(params) == n_elem * n_params


async def test_stage_ndattributes(detector):
num_elem = 8
set_mock_value(detector.drv.number_of_elements, num_elem)
set_mock_value(detector.drv.nd_attributes_file, "XSP3.xml")
await detector.stage()
xml_mock = get_mock_put(detector.drv.nd_attributes_file)
assert xml_mock.called
# Test that the XML is correct
args, kwargs = xml_mock.call_args
tree = ET.fromstring(args[0])
assert len(tree) == num_elem * 9
# Check that the XML file gets reset when unstaged
await detector.unstage()
assert xml_mock.call_args[0][0] == "XSP3.xml"


# -----------------------------------------------------------------------------
# :author: Mark Wolfman
# :email: [email protected]
Expand Down

0 comments on commit 97f87d9

Please sign in to comment.