Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ classifiers = [
"Programming Language :: Python :: 3.12",
]
description = "Bluesky plans to be run on Diamond's test rigs e.g. ViSR, P45, etc"
dependencies = ["dls-dodal>=1.56.0", "scanspec>=1.0.0"]
dependencies = ["dls-dodal>=3.0.7", "scanspec>=1.0.0"]
dynamic = ["version"]
license.file = "LICENSE"
readme = "README.md"
Expand Down
6 changes: 6 additions & 0 deletions src/test_rig_bluesky/baselines/pandabox_baseline.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fmc_in.val5_capture: Mean
fmc_in.val5_dataset: spectroscopy_diodes-red
fmc_in.val6_capture: Mean
fmc_in.val6_dataset: spectroscopy_diodes-green
fmc_in.val7_capture: Mean
fmc_in.val7_dataset: spectroscopy_diodes-blue
Comment thread
tomkane-dls marked this conversation as resolved.
Resolved
35 changes: 28 additions & 7 deletions src/test_rig_bluesky/plans.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,26 @@ def fly_scan(
exposure_time: float = 0.1,
detector_deadtime: float = 2e-3 * 1.01,
detector_trigger: DetectorTrigger = DetectorTrigger.EXTERNAL_EDGE,
panda_whitelist: list[str] | None = None,
pandabrick_whitelist: list[str] | None = None,
plan_name: str = "fly_scan",
metadata: dict[str, Any] | None = None,
pandabox: HDFPanda | None = None,
diodes: Readable | None = None,
Comment thread
tomkane-dls marked this conversation as resolved.
Resolved
):
"""Fly scan a triggered detector along a spec."""
# The pandabox captures the diode counts, which only mean something alongside
# the amplifier range recorded from the diodes, so we need both or neither
if pandabox is not None and diodes is None:
raise ValueError("pandabox was given without diodes")
if diodes is not None and pandabox is None:
raise ValueError("diodes were given without pandabox")

detectors = [detector] if pandabox is None else [pandabox, detector]

yield from load_panda_settings(
panda=pandabrick,
design_name="pandabrick_baseline",
whitelist_pvs=panda_whitelist,
whitelist_pvs=pandabrick_whitelist,
)

scan_frame_duration = exposure_time
Expand All @@ -164,7 +176,7 @@ def fly_scan(
# Reproduce metadata present in spec_scan
_md = {
"plan_args": {
"detectors": {det.name for det in [pandabrick, detector]},
"detectors": {det.name for det in [pandabrick, *detectors]},
"spec": repr(fly_spec),
},
"plan_name": plan_name,
Expand Down Expand Up @@ -192,21 +204,28 @@ def fly_scan(

# @attach_data_session_metadata_decorator()
@bpp.run_decorator(md=_md)
@bpp.stage_decorator([pandabrick, pandabrick_seq_flyer, detector, pmac_flyer])
@bpp.stage_decorator([pandabrick, pandabrick_seq_flyer, *detectors, pmac_flyer])
def inner_plan():
if diodes is not None:
yield from bps.create(name="diodes")
yield from bps.read(diodes)

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 records one reading of the live values (and the gain, as configuration) at the start, in a diodes stream. Fine as a snapshot; just noting that the per-point diode data in fly scans comes only from the pandabox datasets, so their names are what a reader needs.

yield from bps.save()

# Prepare pmac with the trajectory
yield from bps.prepare(pmac_flyer, pmac_scan_info)
# prepare sequencer table
yield from bps.prepare(pandabrick_seq_flyer, scan_spec_info)
# prepare panda and hdf writer once, at start of scan
yield from bps.prepare(pandabrick, panda_hdf_info)
if pandabox is not None:
Comment thread
tomkane-dls marked this conversation as resolved.
Resolved
yield from bps.prepare(pandabox, panda_hdf_info)
Comment thread
tomkane-dls marked this conversation as resolved.
Resolved
# prepare detector and info
# waiting for this last prepare means all prepare functions will be complete
yield from bps.prepare(detector, detector_info, wait=True)

# Need to run this after detectors are prepared
yield from bps.declare_stream(
pandabrick, detector, name="primary", collect=True
pandabrick, *detectors, name="primary", collect=True
)

# Start the detectors and hdf writers acquiring.
Expand All @@ -215,6 +234,8 @@ def inner_plan():
# its own with wait=true.
yield from bps.kickoff(pandabrick)
yield from bps.kickoff(pandabrick_seq_flyer)
if pandabox is not None:
yield from bps.kickoff(pandabox)
yield from bps.kickoff(detector, wait=True)

# Start the trajectory.
Expand All @@ -226,11 +247,11 @@ def inner_plan():
pmac_flyer,
pandabrick_seq_flyer,
pandabrick,
detector,
*detectors,
),
dets=(
pandabrick,
detector,
*detectors,
),
stream_name="primary",
flush_period=0.5,
Expand Down
59 changes: 54 additions & 5 deletions src/test_rig_bluesky/spectroscopy_plans.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from bluesky.protocols import Movable
from bluesky.utils import MsgGenerator
from dodal.common import inject
from dodal.devices.beamlines.b01_1.diodes import RGBDiodes
from dodal.devices.current_amplifiers import EnzLocumRange
from dodal.devices.motors import XYZStage
from dodal.plans import spec_scan
from ophyd_async.epics.adaravis import AravisDetector
Expand All @@ -21,17 +23,37 @@
from ophyd_async.fastcs.panda import HDFPanda
from scanspec.specs import Line, Spec

from .plans import ScanType, fly_scan, load_settings, pandabrick, pmac, serialize_spec
from .plans import (
ScanType,
fly_scan,
load_panda_settings,
load_settings,
pandabrick,
pmac,
serialize_spec,
)

LOGGER = logging.getLogger(__name__)

spectroscopy_detector = inject("spectroscopy_detector")
sample_stage = inject("sample_stage")
spectroscopy_diodes = inject("spectroscopy_diodes")
pandabox = inject("pandabox")
sample_stage = inject("spectroscopy_stage")

# The encoder entries map to specific axes, so despite the PandA being shared
# hardware this list is spectroscopy's.
PANDA_WHITELIST = ["incenc-3-val_dataset", "incenc-2-val_dataset"]

# Only the diode channels, so the rest of the pandabox's setup is left alone
PANDABOX_WHITELIST = [
"fmc_in-val5_capture",
"fmc_in-val5_dataset",
"fmc_in-val6_capture",
"fmc_in-val6_dataset",
"fmc_in-val7_capture",
"fmc_in-val7_dataset",
]

MANTA_ACQUIRE_PERIOD_PAD = 1961e-6
MANTA_DETECTOR_DEADTIME = 2e-3 * 1.01

Expand All @@ -44,13 +66,28 @@ def spectroscopy(
sample_stage: XYZStage = sample_stage,
pmac: PmacIO = pmac,
pandabrick: HDFPanda = pandabrick,
pandabox: HDFPanda = pandabox,
spectroscopy_diodes: RGBDiodes = spectroscopy_diodes,
spec: Spec[Movable] | None = None,
exposure_time: float = 0.1,
amplifier_range: EnzLocumRange | None = None,
metadata: dict[str, Any] | None = None,
*,
scan_type: ScanType,
) -> MsgGenerator[None]:
"""Do a spectroscopy scan."""
"""Do a spectroscopy scan, acquiring from the camera and the RGB diodes."""
# The range is only recorded once per scan, as configuration, so it must not
# change between points
scan_range = amplifier_range or (
yield from bps.rd(spectroscopy_diodes.amplifier.range)
)
if scan_range == EnzLocumRange.AUTO:
raise ValueError("The diode amplifier must be on a fixed range, not Auto")

if amplifier_range is not None:
yield from bps.abs_set(
spectroscopy_diodes.amplifier.range, amplifier_range, wait=True
)

yield from load_settings(
device=spectroscopy_detector,
Expand Down Expand Up @@ -136,20 +173,28 @@ def spectroscopy(

if scan_type == ScanType.FLY:
LOGGER.info("Performing a fly scan.")
# Captures the diodes alongside the camera
yield from load_panda_settings(
panda=pandabox,
design_name="pandabox_baseline",
whitelist_pvs=PANDABOX_WHITELIST,
)
Comment on lines +176 to +181

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.

It seems slightly odd that we're loading settings for the pandabox here, while we load settings for the pandabrick inside the fly_scan plan. Is there a reason to keep it outside?

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.

I suppose because the pandabrick is used by both experiments but the pandabox is only used by spectroscopy.

but looking at things now, the split of what is in spectroscopy() and what is in fly_scan() is a little arbitrary. The idea was that fly_scan was the same core function needed by both spectroscopy and tomography, but now we are passing diodes and pandabox its lost a bit of this.

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.

Do you suggest injecting pandabox inside plans.py?

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.

I'm not sure which way would be better. With the pandabox and diodes inside fly_scan, I expected to see any settings loading happening there (as it does with the pandabrick). Is there a reason that we couldn't have something link extra_detectors, and expect the outer plan to set up any extra detectors (diodes and pandas for the spectroscopy scan in our case) before we set the fly scan going?

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.

After another look, that won't work either as the pandabox needs kicking off and you want to read the diodes beforehand.
I'm not sure there's an elegant solution here, other than stick a load of conditions into the generic scan.

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.

@EmsArnold I can't see an elegant way of doing this currently, a shared fly_scan seems to be getting more complicated and uglier, but i'm not exactly sure separating it out again for each plan is likely to make it better. I will investigate whether two completely separate plans might look better on a new PR once tomography is working.

yield from fly_scan(
spec,
spectroscopy_detector,
pmac=pmac,
pandabrick=pandabrick,
exposure_time=exposure_time,
detector_deadtime=MANTA_DETECTOR_DEADTIME,
panda_whitelist=PANDA_WHITELIST,
pandabrick_whitelist=PANDA_WHITELIST,
metadata=metadata,
pandabox=pandabox,
diodes=spectroscopy_diodes,
)
elif scan_type == ScanType.STEP:
LOGGER.info("Performing a step scan.")
yield from spec_scan(
{spectroscopy_detector, sample_stage},
{spectroscopy_detector, spectroscopy_diodes, sample_stage},
Comment thread
tomkane-dls marked this conversation as resolved.
Resolved
spec, # type: ignore
metadata=metadata,
)
Expand All @@ -160,6 +205,8 @@ def demo_spectroscopy(
sample_stage: XYZStage = sample_stage,
pmac: PmacIO = pmac,
pandabrick: HDFPanda = pandabrick,
pandabox: HDFPanda = pandabox,
spectroscopy_diodes: RGBDiodes = spectroscopy_diodes,
total_number_of_scan_points: int = 25,
grid_size: float = 5.0,
grid_origin_x: float = 0.0,
Expand Down Expand Up @@ -207,6 +254,8 @@ def demo_spectroscopy(
sample_stage=sample_stage,
pmac=pmac,
pandabrick=pandabrick,
pandabox=pandabox,
spectroscopy_diodes=spectroscopy_diodes,
spec=grid,
exposure_time=exposure_time,
metadata=metadata,
Expand Down
2 changes: 1 addition & 1 deletion src/test_rig_bluesky/tomography_plans.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def tomography(
exposure_time=exposure_time,
detector_deadtime=MAKO_DETECTOR_DEADTIME,
detector_trigger=TOMOGRAPHY_DETECTOR_TRIGGER,
panda_whitelist=PANDA_WHITELIST,
pandabrick_whitelist=PANDA_WHITELIST,
plan_name="tomography_fly_scan",
metadata=metadata,
)
Expand Down
14 changes: 7 additions & 7 deletions tests/system_tests/test_plans_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def test_snapshot(
params={
"devices": [
"spectroscopy_detector",
"sample_stage",
"spectroscopy_stage",
]
},
instrument_session=latest_commissioning_instrument_session,
Expand All @@ -31,8 +31,8 @@ def test_snapshot(
def test_spectroscopy_with_custom_trajectory(
bluesky_plan_runner: BlueskyPlanRunner, latest_commissioning_instrument_session: str
):
scan_spec = Line("sample_stage.x", 0.0, 5.0, 5) * Line(
"sample_stage.y", 2.0, 5.0, 3
scan_spec = Line("spectroscopy_stage.x", 0.0, 5.0, 5) * Line(
"spectroscopy_stage.y", 2.0, 5.0, 3
)
events = bluesky_plan_runner.run(
TaskRequest(
Expand Down Expand Up @@ -97,8 +97,8 @@ def test_generic_count(
def test_generic_scan(
bluesky_plan_runner: BlueskyPlanRunner, latest_commissioning_instrument_session: str
) -> None:
scan_spec = Line("sample_stage.x", 0.0, 5.0, 2) * Line(
"sample_stage.y", 2.0, 5.0, 2
scan_spec = Line("spectroscopy_stage.x", 0.0, 5.0, 2) * Line(
"spectroscopy_stage.y", 2.0, 5.0, 2
)
bluesky_plan_runner.run(
TaskRequest(
Expand All @@ -117,7 +117,7 @@ def test_generic_scan(
def test_spectroscopy_re():
run_engine = RunEngine()
spectroscopy_detector = b01_1.spectroscopy_detector.build(connect_immediately=True)
sample_stage = b01_1.sample_stage.build(connect_immediately=True)
sample_stage = b01_1.spectroscopy_stage.build(connect_immediately=True)

scan_spec = Line(sample_stage.y, 0, 5, 50) * Line(sample_stage.x, 2, 5, 30)

Expand Down Expand Up @@ -168,7 +168,7 @@ def test_spectroscopy_re_run_engine():
run_engine = RunEngine()

spectroscopy_detector = b01_1.spectroscopy_detector.build(connect_immediately=True)
sample_stage = b01_1.sample_stage.build(connect_immediately=True)
sample_stage = b01_1.spectroscopy_stage.build(connect_immediately=True)

scan_spec = Line(sample_stage.y, 0, 5, 50) * Line(sample_stage.x, 2, 5, 30)

Expand Down
Loading
Loading