Skip to content

Beam attenuation#35

Open
Dan Henriksen (dihenriksen) wants to merge 38 commits into
mainfrom
beam-attenuation
Open

Beam attenuation#35
Dan Henriksen (dihenriksen) wants to merge 38 commits into
mainfrom
beam-attenuation

Conversation

@dihenriksen

@dihenriksen Dan Henriksen (dihenriksen) commented May 8, 2026

Copy link
Copy Markdown
Contributor

Mainly, this PR creates the ability to attenuate a beam by giving control over a set of attenuators:

  • Made AttenuatorBank, Attenuator, and other helper classes
  • Implemented movable protocol for those classes to control individual attenuators, and to set total attenuation
  • Tests for attenuation functionality

Also fixed various other issues:

  • Ignore test_eiger_async tests for now since they are very broken
  • Update ophyd-async version to match what is in cdi-profile-collection
  • Fix style problems
  • Remove python 3.10 from supported python versions due to compatibility issues with the latest version of ophyd-async

@dihenriksen Dan Henriksen (dihenriksen) marked this pull request as ready for review May 12, 2026 02:55

@jwlodek Jakub Wlodek (jwlodek) left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Marked some suggestions. Mainly I'd recommend making both device classes Movables and re-work the current methods for changing states into a set method override.

Comment thread src/cditools/attenuator.py Outdated
Comment thread src/cditools/attenuator.py Outdated
Comment thread src/cditools/attenuator.py Outdated
Comment thread src/cditools/attenuator.py Outdated
Comment thread src/cditools/attenuator.py Outdated
Comment thread .github/workflows/cd.yml
Comment thread src/cditools/attenuator.py Outdated
Comment thread src/cditools/attenuator.py Outdated
Comment thread src/cditools/attenuator.py Outdated
Comment thread src/cditools/attenuator.py Outdated
Comment thread src/cditools/attenuator.py
Comment thread src/cditools/attenuator.py Outdated
Comment thread src/cditools/attenuator.py Outdated
"""
status = {}
active_attens = []
en = self.photon_energy

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we are already in an async function here so

Suggested change
en = self.photon_energy
en = await self.energy.readback.value()

I may be wrong about the exact name, ophyd-async has a verb for "get me the one true number for this or explode".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

energy is an ophyd object, so await does not work

Comment thread src/cditools/attenuator.py Outdated
Comment thread src/cditools/attenuator.py Outdated
…in material and thickness to constructor; added pixi format task; pass energy into utility methods; remove property reading energy
…st one in linear time; also it is easier to read
if egu == "KeV":
photon_energy = photon_energy * 1e3
elif egu != "eV":
msg = "Photon energy units must be eV or KeV"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
msg = "Photon energy units must be eV or KeV"
msg = f"Photon energy units must be eV or KeV (not {egu=})"

def get_egu(self):
return self.energy.egu

async def read(self): # type: ignore[reportUnknownParameterType]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This needs a matched describe (unless ophyd async is providing more magic than I think it is)

"""
status = {}
active_attens = []
energy = self.get_photon_energy()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

use async flavor here for access to the hardware.

active_attens.append(atten)
transmission = atten.transmission(energy, egu) if is_active else 0
status[atten.name] = {"active": is_active, "transmission": transmission}
status["active_attenuators"] = [a.num for a in active_attens]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is going to be variable length which will give tiled fits (for now). Better to provide either fixed length list of bools or strings "in" and "out".

coros.append(atten.open())
await asyncio.gather(*coros)

def find_closest_transmission(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Check with Garth if we want "closest" or "closest with at least" or "closest with at most" semantics on this. My knee-jerk guess would be "closest with at least" on the logic of if you are cutting the flux down it is because you want to protect something (sample or detector) so it is better to over attenuate than under attenuate.

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

Successfully merging this pull request may close these issues.

3 participants