-
Notifications
You must be signed in to change notification settings - Fork 11
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
949 make ophyd devices for the diagonstics for i10 #960
base: main
Are you sure you want to change the base?
949 make ophyd devices for the diagonstics for i10 #960
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #960 +/- ##
==========================================
+ Coverage 95.88% 97.53% +1.65%
==========================================
Files 145 152 +7
Lines 6240 6417 +177
==========================================
+ Hits 5983 6259 +276
+ Misses 257 158 -99 ☔ View full report in Codecov by Sentry. |
"""This is the standard webcam that can be found in ophyd_async | ||
with four extra centroid signal. There is also a change in data_type due to it being | ||
an older/different model""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like @coretl's opinion on this approach, whether we should make a configurable "super detector" in ophyd-async or accept variations like these
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be rolled into ophyd-async AravisDetector. Since bluesky/ophyd-async#606 this should now be possible. We pass stats
as a plugin to the init. I'm unsure why the datatype change was needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, thanks! I've added a bunch of suggestions based on how I would do a similar device in MX but obviously I don't know your usecase so feel free to reject suggestions if they don't work for you. Do you have some example plans that use the devices? I was expecting to see some in https://github.com/DiamondLightSource/i10-bluesky but couldn't, maybe I'm just missing them.
@@ -20,7 +26,38 @@ def __init__(self, prefix: str, name: str = "") -> None: | |||
) | |||
|
|||
|
|||
class BladeDrainCurrents(Device): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should: I wasn't aware of the technique of using drain current to measure flux (in MX we just use direct diode measurements). Can you expand on the docstring here (and in I10SlitsDrainCurrent
below) to explain why drain currents are useful? Ideally add a link to some further reading on the technique?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a very quick one, I will add the dostring later, The way we use drain current on mirror the absolute flux does not matter we use it to normalise the detector so it is always relative and it has the added advantage of you do not put anything in the beam path that is not needed. As for the slits/blade drain, we use it to correct correct the beam position(feedback). E.g. depending of the drain current values on the left and right blades we will move the mirror pitch to make sure it always in the right place.
I am not sure I would call it a technique, it is mostly photoelectric effect. In theory you can convert them into flux but there are a lot assumptions/corrections you have to make, for example the beam distribution, as we are not intercepting all of the beam, furthermore drain current also suffer form charging up and has a strong energy dependence, it will be a very big job to do all the corrections.
@@ -20,7 +26,38 @@ def __init__(self, prefix: str, name: str = "") -> None: | |||
) | |||
|
|||
|
|||
class BladeDrainCurrents(Device): | |||
""" "The drain current measurements on each blade.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@DiamondLightSource/bluesky-reviewers could someone from core weigh in on this? If they agree I will add the statement to https://diamondlightsource.github.io/dodal/main/reference/device-standards.html
Could: Presumably there is a lookup table or equation that takes the drain current and converts it to flux? In MX we have tried to have the ophyd
device interface be the things that are scientifically interesting, rather than hardware implementation details. So in this case I would put the conversion in the device itself and have soft signals that are flux
. Even better, I would try and get this pushed into the EPICS layer, particularly if the look up table rarely changes.
The reason for having scientific terms in ophyd devices is that when I read()
the device these values are what come out into documents and ultimately get put in the nexus file/LIMS. If we need to do the conversion to flux
we now need to do this downstream in multiple places rather than here in one.
This is likely a big-ish job so for now (assuming a core person agrees) can you just make the issue to track it?
|
||
|
||
class I10Slits(Slits): | ||
class I10SlitsBlades(Slits): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should: In MX we also have slits where we can address individual blades but so far we have never needed to. Do you definitely need to be able to? If yes then I think we should:
- Move this to the general
slits.py
and rename to be generic - Standardise on a naming scheme (at least at the
ophyd
layer) - in MX we call thering
blade inboard and thehall
bladeoutbound
This is certainly a new issue though, could you make one pleas, with an explanation of why you need individual blades?
self.positioner = DropDownStage( | ||
prefix=prefix, | ||
positioner_enum=positioner_enum, | ||
positioner_suffix=positioner_suffix, | ||
dropdown_pv_suffix=dropdown_pv_suffix, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could:
self.positioner = DropDownStage( | |
prefix=prefix, | |
positioner_enum=positioner_enum, | |
positioner_suffix=positioner_suffix, | |
dropdown_pv_suffix=dropdown_pv_suffix, | |
) | |
self.position = epics_signal_rw(positioner_enum, prefix + positioner_suffix + dropdown_pv_suffix) |
I think this might be a case where the extra layer makes things more complicated for the end user and a less readable plan i.e (and including the renames from above)
yield from bps.set(diagnostics.positioner.stage_drop_down, D3DropDown.NOTHING)
vs.
yield from bps.set(diagnostics.position, D3Position.NOTHING)
class I10CentroidDetector(StandardReadable, Triggerable): | ||
"""Detector to read out the centroid position, | ||
this is base off the SingleTriggerDetector in ophyd_async with added | ||
readable default""" | ||
|
||
def __init__( | ||
self, | ||
prefix: str, | ||
name: str = "", | ||
) -> None: | ||
self.drv = I10AravisDriverIO(prefix=prefix) | ||
self.add_readables( | ||
[self.drv.array_counter, self.drv.centroid_x, self.drv.centroid_y], | ||
Format.HINTED_UNCACHED_SIGNAL, | ||
) | ||
|
||
self.add_readables([self.drv.acquire_time], Format.CONFIG_SIGNAL) | ||
|
||
super().__init__(name=name) | ||
|
||
@AsyncStatus.wrap | ||
async def stage(self) -> None: | ||
await asyncio.gather( | ||
self.drv.image_mode.set(ImageMode.SINGLE), | ||
self.drv.wait_for_plugins.set(True), | ||
) | ||
await super().stage() | ||
|
||
@AsyncStatus.wrap | ||
async def trigger(self) -> None: | ||
await self.drv.acquire.set(True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should: I think you could just inherit from SingleTriggerDetector
then? Something like:
class I10CentroidDetector(StandardReadable, Triggerable): | |
"""Detector to read out the centroid position, | |
this is base off the SingleTriggerDetector in ophyd_async with added | |
readable default""" | |
def __init__( | |
self, | |
prefix: str, | |
name: str = "", | |
) -> None: | |
self.drv = I10AravisDriverIO(prefix=prefix) | |
self.add_readables( | |
[self.drv.array_counter, self.drv.centroid_x, self.drv.centroid_y], | |
Format.HINTED_UNCACHED_SIGNAL, | |
) | |
self.add_readables([self.drv.acquire_time], Format.CONFIG_SIGNAL) | |
super().__init__(name=name) | |
@AsyncStatus.wrap | |
async def stage(self) -> None: | |
await asyncio.gather( | |
self.drv.image_mode.set(ImageMode.SINGLE), | |
self.drv.wait_for_plugins.set(True), | |
) | |
await super().stage() | |
@AsyncStatus.wrap | |
async def trigger(self) -> None: | |
await self.drv.acquire.set(True) | |
class I10CentroidDetector(SingleTriggerDetector): | |
"""Detector to read out the centroid position, | |
this is based off the SingleTriggerDetector in ophyd_async with added | |
readable defaults""" | |
def __init__( | |
self, | |
prefix: str, | |
name: str = "", | |
) -> None: | |
driver = I10AravisDriverIO(prefix=prefix) | |
super().__init__(driver, [driver.centroid_x, driver.centroid_y], name) |
stage_write_enum: type[StrictEnum] = InOutTable, | ||
stage_read_enum: type[StrictEnum] = InOutReadBackTable, | ||
stage_read_suffix: str = "STA", | ||
stage_write_suffix: str = "CON", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should: I can't see anywhere in the code currently where we use different values for stage_write_enum
, stage_read_enum
, stage_read_suffix
or stage_write_suffix
than the defaults. I would just hardcode them in below until you have a case where they are different as it leads to lots of boilerplate
return mock_centroid_detector | ||
|
||
|
||
async def test_I10CentroidDetector_with_blueSky( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: My preference is to name tests very verbosely and explicitly so it's clear what you're testing. For example here I would name it something like test_when_I10CentroidDetector_is_counted_in_plan_then_emitted_document_contains_expected_values
. If you have vague names I think it leads to the temptation to make one big test that tests loads of different stuff, that test then becomes long to run and hard to debug
Fixes #949
Instructions to reviewer on how to test:
Checks for reviewer
dodal connect ${BEAMLINE}