bluesky / ophyd-async

Hardware abstraction for bluesky written using asyncio
https://blueskyproject.io/ophyd-async
BSD 3-Clause "New" or "Revised" License
8 stars 23 forks source link

Uses cases for signals that derive from other signals #525

Open canismarko opened 4 weeks ago

canismarko commented 4 weeks ago

Following up from a conversation on mattermost: this issue can hold use cases for a DerivedSignal class. I'll include ours as comments.

is there an ophyd-async way to derive signals from other signals, in the spirit of OG ophyd's DerivedSignal and pcdsdevices' MultiDerivedSignal?

canismarko commented 4 weeks ago

Calculated signals

We have several situations where we use one or more epics signals to calculate new values. For example, an ion chamber's scaler measures counts from the voltage-to-frequency convertor. We use a derived signal for calculating what the voltage was at the input to the V-to-F, and another one that uses this voltage to calculate the current at the input to the pre-amplifier. Eventually we will add another that calculates the photon counts based on this current.

https://github.com/spc-group/haven/blob/c31960b010ce22c6d4d7bd909458acdfd800b122/src/haven/instrument/ion_chamber.py#L39

canismarko commented 4 weeks ago

Standardizing Interfaces

XIA Shutter

We have a set of 4 filters arranged in a single XIA PFCU4 filter bank, and two of them are fitted with solid tungsten to work as a shutter. To operate the shutter I need to read the existing state of all four filters as bits and specifically set the two bits for the W filters to the correct position for the shutter to be open (XX10) or closed (XX01) where X is the existing state of the other two non-shutter filters.

I used a DerivedSignal with some bitmasking to create a shutter device that can operate like any other shutter using e.g. set(0) or set(1) to open and close the shutter respectively.

https://github.com/spc-group/haven/blob/c31960b010ce22c6d4d7bd909458acdfd800b122/src/haven/instrument/xia_pfcu.py#L58

Fluorescence Detector ROIs

We have two flavors of fluorescence detector, xspress3 and XIA XMAP. They both have 16+ ROIs that we can set. One flavor can set the low and high end of the ROI, while the other sets the low end and the size of the ROI. I want the interface to be consistent, so I added a MultiDerivedSignal so that they can both be set the same way.

https://github.com/spc-group/haven/blob/c31960b010ce22c6d4d7bd909458acdfd800b122/src/haven/instrument/xspress.py#L46

canismarko commented 4 weeks ago

Sharing Start/Stop Signals

To operate our undulators, there are PVs for setting the desired energy, gap, energy taper and gap taper. Then a single "start" PV exists for moving the undulator to all these targets. I wrote a device that has positioners for these 4 values so that I can do undulator.energy_taper.set(100) and it will move the undulator to that taper. To do this, each of those four positioners uses derived signals linking back to the undulator's start and stop signals so that they can all operate the undulator properly.

https://github.com/spc-group/haven/blob/c31960b010ce22c6d4d7bd909458acdfd800b122/src/haven/instrument/xray_source.py#L25

canismarko commented 4 weeks ago

Encoding Data in a PV as a string

This one is kludgy but it seems to work. I needed a way to indicate which ROIs on a detector represent something useful. I couldn't use a regular EPICS signal for this because not all the detectors have such a PV, and I could use a soft signal because I need don't have a fast way of sharing this signal's state between all the clients (e.g. our GUI, CLI, and queueserver).

My solution was that on detector's that don't have this PV, create a derived signal from the ROI label, and add a "\~" in front if the ROI is not meaningful, and to remove such a "\~" if it is. Then during staging, the device adjusts its read, config, and hinted signals to reflect this state.

https://github.com/spc-group/haven/blob/c31960b010ce22c6d4d7bd909458acdfd800b122/src/haven/instrument/fluorescence_detector.py#L91

Tom-Willemsen commented 4 weeks ago

Tagging onto this with some ideas that we'll likely need at ISIS...

Uncertainty calculation

It would be useful for us to have a clean way of generating uncertainties - while some devices provide this directly from the IOC as a PV, in many cases an uncertainty can be estimated using something like sqrt(counts).

In more complicated cases an uncertainty might depend on multiple other signals - e.g. on a power supply the uncertainty on voltage may depend on the source range (let's assume that's available as an enum PV and that they can overlap with each other) and the voltage itself - if the manufacturer specifies something like "within 0.5% in source range A", "within 1% in source range B".

Normalization

We'd like to be able to create a signal that is the normalization of one signal by another - where the two signals are not necessarily in the same Device and may be arbitrarily specified by the user (e.g. as plan arguments)

i.e. create a derived signal for x/y, suitable for use as a "detector" in a scan, without any up-front knowledge about what x or y are beyond the fact that they're readable/[triggerable]/[stageable]

canismarko commented 4 weeks ago

Creating a Overall Energy Positioner

I wrote a positioner that encapsulates the general concept of the energy of the X-ray beam. That way, we can use this single positioner for scans. It has children for the insertion device (if present), and one or more monochromators. I then use MultiDerivedSignals to a) set the ID and monos with proper offsets for the requested energy, and b) read the current energy based on the mono that is furthest downstream. Each beamline will have its own unique EnergyPositioner device class, but the rest of our plans can ignore these differences and just use it as an energy positioner.

https://github.com/spc-group/haven/blob/c31960b010ce22c6d4d7bd909458acdfd800b122/src/haven/instrument/energy_positioner.py#L20

DiamondJoseph commented 3 weeks ago

Could be a neat way of implementing lookup tables @stan-dot? SignalV which handles the IO to lookup K:V table when SignalK is set?

stan-dot commented 3 weeks ago

@DiamondJoseph this sounds too clever, atm we can just have it in a plan. I am not on a forever quest to do things in an increasingly neater way.

DominicOram commented 2 hours ago

In MX we have a use case at https://github.com/DiamondLightSource/dodal/issues/782 where we would like to take the state of ~6 PVs and combine them to give a single position.

I have implemented this at https://github.com/DiamondLightSource/dodal/pull/789, where I created a new kind of soft signal where, on initialization from the device, you provide a Callable that will calculate the read value. This only solves the problem in the single direction of combining the readings. We do the combining of the setting by overriding the set method in the Device itself. I was going to start work on putting this in ophyd-async proper but happy to wait if people think there are cleaner solutions or would like a more comprehensive solution straight out the bat. Note that if we're going to solve it in a different way there are a number of gotchas on that issue that we need to make sure the solution solves.

where the two signals are not necessarily in the same Device and may be arbitrarily specified by the user (e.g. as plan arguments)

Given the amount of variation that's already in the use-cases here I think that to try and get something we can iterate on can I suggest we start with just derived in the same device? I think you can probably then do this by making an ephemeral device later on.