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

Uses cases for signals that derive from other signals #525

Open
canismarko opened this issue Aug 16, 2024 · 11 comments
Open

Uses cases for signals that derive from other signals #525

canismarko opened this issue Aug 16, 2024 · 11 comments

Comments

@canismarko
Copy link

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
Copy link
Author

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
Copy link
Author

canismarko commented Aug 16, 2024

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
Copy link
Author

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
Copy link
Author

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
Copy link
Member

Tom-Willemsen commented Aug 16, 2024

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
Copy link
Author

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
Copy link
Contributor

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
Copy link
Contributor

@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
Copy link
Contributor

In MX we have a use case at DiamondLightSource/dodal#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 DiamondLightSource/dodal#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.

@canismarko
Copy link
Author

I put this in mattermost a while back, but in case it's helpful to anyone here: we've been using a DerivedSignal backend for meeting most of our needs in the short term. It probably won't work for everyone's use case, though.

https://github.com/spc-group/haven/blob/main/src/haven/instrument/signal.py

@DominicOram
Copy link
Contributor

As discussed DiamondLightSource/dodal#789 (comment) my solution doesn't work for the case of subscribe. I think we should attempt to create a proper subscribe that will update when any of the signals you derive from update.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants