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

Soft tigger STXM scan #35

Merged
merged 12 commits into from
Jul 12, 2024
18 changes: 18 additions & 0 deletions src/p99_bluesky/log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import logging
import sys

from dodal.log import LOGGER as dodal_logger
from dodal.log import (
DodalLogHandlers,
)

LOGGER = logging.getLogger("P99")
LOGGER.setLevel("DEBUG")
LOGGER.parent = dodal_logger
__logger_handlers: DodalLogHandlers | None = None

handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
LOGGER.addHandler(handler)
232 changes: 232 additions & 0 deletions src/p99_bluesky/plans/fast_scan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
import bluesky.plan_stubs as bps
import bluesky.preprocessors as bpp
from bluesky.preprocessors import (
finalize_wrapper,
)
from ophyd_async.epics.motion import Motor
from ophyd_async.protocols import AsyncReadable

from p99_bluesky.log import LOGGER


def fast_scan_1d(
dets: list[AsyncReadable],
motor: Motor,
start: float,
end: float,
motor_speed: float | None = None,
):
"""
One axis fast scan

Parameters
----------
detectors : list
list of 'readable' objects
motor : Motor (moveable, readable)

start: float
starting position.
end: float,
ending position

motor_speed: Optional[float] = None,
The speed of the motor during scan
"""

@bpp.stage_decorator(dets)
@bpp.run_decorator()
def inner_fast_scan_1d(

Check warning on line 39 in src/p99_bluesky/plans/fast_scan.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/fast_scan.py#L37-L39

Added lines #L37 - L39 were not covered by tests
dets: list[AsyncReadable],
motor: Motor,
start: float,
end: float,
motor_speed: float | None = None,
):
yield from check_within_limit([start, end], motor)
yield from _fast_scan_1d(dets, motor, start, end, motor_speed)

Check warning on line 47 in src/p99_bluesky/plans/fast_scan.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/fast_scan.py#L46-L47

Added lines #L46 - L47 were not covered by tests

yield from finalize_wrapper(

Check warning on line 49 in src/p99_bluesky/plans/fast_scan.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/fast_scan.py#L49

Added line #L49 was not covered by tests
plan=inner_fast_scan_1d(dets, motor, start, end, motor_speed),
final_plan=clean_up(),
)


def fast_scan_grid(
dets: list[AsyncReadable],
step_motor: Motor,
step_start: float,
step_end: float,
step_size: float,
scan_motor: Motor,
scan_start: float,
scan_end: float,
motor_speed: float | None = None,
snake_axes: bool = False,
):
"""
Same as fast_scan_1d with an extra axis to step through to from a grid

Parameters
----------
detectors : list
list of 'readable' objects
step_motor : Motor (moveable, readable)
scan_motor: Motor (moveable, readable)
start: float
starting position.
end: float,
ending position

motor_speed: Optional[float] = None,
The speed of the motor during scan
"""

@bpp.stage_decorator(dets)
@bpp.run_decorator()
def inner_fast_scan_grid(

Check warning on line 87 in src/p99_bluesky/plans/fast_scan.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/fast_scan.py#L85-L87

Added lines #L85 - L87 were not covered by tests
dets: list[AsyncReadable],
step_motor: Motor,
step_start: float,
step_end: float,
step_number: float,
scan_motor: Motor,
scan_start: float,
scan_end: float,
motor_speed: float | None = None,
snake_axes: bool = False,
):
Relm-Arrowny marked this conversation as resolved.
Show resolved Hide resolved
yield from check_within_limit([step_start, step_end], step_motor)
yield from check_within_limit([scan_start, scan_end], scan_motor)
step_size = (step_end - step_start) / step_number
step_counter = 1
if snake_axes:
while step_number >= step_counter:
yield from bps.mv(step_motor, step_start + step_size * step_counter)
yield from _fast_scan_1d(

Check warning on line 106 in src/p99_bluesky/plans/fast_scan.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/fast_scan.py#L99-L106

Added lines #L99 - L106 were not covered by tests
dets + [step_motor], scan_motor, scan_start, scan_end, motor_speed
)
step_counter += 1
yield from bps.mv(step_motor, step_start + step_size * step_counter)
yield from _fast_scan_1d(

Check warning on line 111 in src/p99_bluesky/plans/fast_scan.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/fast_scan.py#L109-L111

Added lines #L109 - L111 were not covered by tests
dets + [step_motor], scan_motor, scan_end, scan_start, motor_speed
)
step_counter += 1

Check warning on line 114 in src/p99_bluesky/plans/fast_scan.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/fast_scan.py#L114

Added line #L114 was not covered by tests
else:
while step_number >= step_counter:
yield from bps.mv(step_motor, step_start + step_size * step_counter)
Relm-Arrowny marked this conversation as resolved.
Show resolved Hide resolved
yield from _fast_scan_1d(

Check warning on line 118 in src/p99_bluesky/plans/fast_scan.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/fast_scan.py#L116-L118

Added lines #L116 - L118 were not covered by tests
dets + [step_motor], scan_motor, scan_start, scan_end, motor_speed
)
step_counter += 1

Check warning on line 121 in src/p99_bluesky/plans/fast_scan.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/fast_scan.py#L121

Added line #L121 was not covered by tests

yield from finalize_wrapper(

Check warning on line 123 in src/p99_bluesky/plans/fast_scan.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/fast_scan.py#L123

Added line #L123 was not covered by tests
plan=inner_fast_scan_grid(
dets,
step_motor,
step_start,
step_end,
step_size,
scan_motor,
scan_start,
scan_end,
motor_speed,
snake_axes,
),
final_plan=clean_up(),
)


def _fast_scan_1d(
dets: list[AsyncReadable],
motor: Motor,
start: float,
end: float,
motor_speed: float | None = None,
):
"""
The logic for one axis fast scan, see fast_scan_1d and fast_scan_grid

In this scan:
1) The motor moves to the starting point.
2) The motor speed is changed
3) The motor is set in motion toward the end point
4) During this movement detectors are triggered and read out until
the endpoint is reached or stopped.
5) Clean up, reset motor speed.

Note: This is purely software triggering which result in variable accuracy.
However, fast scan does not require encoder and hardware setup and should
work for all motor. It is most frequently use for alignment and
slow motion measurements.

Parameters
----------
detectors : list
list of 'readable' objects
motor : Motor (moveable, readable)

start: float
starting position.
end: float,
ending position

motor_speed: Optional[float] = None,
The speed of the motor during scan
"""

# read the current speed and store it
old_speed = yield from bps.rd(motor.velocity)

Check warning on line 179 in src/p99_bluesky/plans/fast_scan.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/fast_scan.py#L179

Added line #L179 was not covered by tests

def inner_fast_scan_1d(

Check warning on line 181 in src/p99_bluesky/plans/fast_scan.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/fast_scan.py#L181

Added line #L181 was not covered by tests
dets: list[AsyncReadable],
motor: Motor,
start: float,
end: float,
motor_speed: float | None = None,
):
LOGGER.info(f"Moving {motor.name} to start position = {start}.")
yield from bps.mv(motor, start) # move to start

Check warning on line 189 in src/p99_bluesky/plans/fast_scan.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/fast_scan.py#L188-L189

Added lines #L188 - L189 were not covered by tests

if motor_speed:
LOGGER.info(f"Set {motor.name} speed = {motor_speed}.")
yield from bps.abs_set(motor.velocity, motor_speed)

Check warning on line 193 in src/p99_bluesky/plans/fast_scan.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/fast_scan.py#L191-L193

Added lines #L191 - L193 were not covered by tests

LOGGER.info(f"Set {motor.name} to end position({end}) and begin scan.")
yield from bps.abs_set(motor.user_setpoint, end)

Check warning on line 196 in src/p99_bluesky/plans/fast_scan.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/fast_scan.py#L195-L196

Added lines #L195 - L196 were not covered by tests
# yield from bps.wait_for(motor.motor_done_move, False)
done = False

Check warning on line 198 in src/p99_bluesky/plans/fast_scan.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/fast_scan.py#L198

Added line #L198 was not covered by tests

while not done:
yield from bps.trigger_and_read(dets + [motor])
done = yield from bps.rd(motor.motor_done_move)
yield from bps.checkpoint()

Check warning on line 203 in src/p99_bluesky/plans/fast_scan.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/fast_scan.py#L200-L203

Added lines #L200 - L203 were not covered by tests

yield from finalize_wrapper(

Check warning on line 205 in src/p99_bluesky/plans/fast_scan.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/fast_scan.py#L205

Added line #L205 was not covered by tests
plan=inner_fast_scan_1d(dets, motor, start, end, motor_speed),
final_plan=reset_speed(old_speed, motor),
)


def check_within_limit(values: list, motor: Motor):
LOGGER.info(f"Check {motor.name} limits.")
lower_limit = yield from bps.rd(motor.low_limit_travel)
high_limit = yield from bps.rd(motor.high_limit_travel)
Relm-Arrowny marked this conversation as resolved.
Show resolved Hide resolved
for value in values:
if not lower_limit < value < high_limit:
raise ValueError(

Check warning on line 217 in src/p99_bluesky/plans/fast_scan.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/fast_scan.py#L212-L217

Added lines #L212 - L217 were not covered by tests
f"{motor.name} move request of {value} is beyond limits:"
f"{lower_limit} < {high_limit}"
)


def reset_speed(old_speed, motor: Motor):
LOGGER.info(f"Clean up: setting motor speed to {old_speed}.")
if old_speed:
yield from bps.abs_set(motor.velocity, old_speed)

Check warning on line 226 in src/p99_bluesky/plans/fast_scan.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/fast_scan.py#L224-L226

Added lines #L224 - L226 were not covered by tests


def clean_up():
LOGGER.info("Clean up")

Check warning on line 230 in src/p99_bluesky/plans/fast_scan.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/fast_scan.py#L230

Added line #L230 was not covered by tests
# possibly use to move back to starting position.
yield from bps.null()

Check warning on line 232 in src/p99_bluesky/plans/fast_scan.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/fast_scan.py#L232

Added line #L232 was not covered by tests
104 changes: 104 additions & 0 deletions src/p99_bluesky/plans/stxm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
from collections.abc import Iterator
from typing import Any

import bluesky.plan_stubs as bps
from ophyd_async.epics.motion import Motor

from p99_bluesky.devices.andor2Ad import Andor2Ad, Andor3Ad
from p99_bluesky.log import LOGGER
from p99_bluesky.plans.fast_scan import fast_scan_grid

"""
set parameter for fast_scan_grid

from detector count time calculate roughly how many data point can be done
if no step size for slow axis
assuming even distribution of points between two axis and work out the
step for step motor
from the fast scan speed calculate the motor speed needed to achieve those
point density.
if it is below the min speed, use min speed and place a warning:
recalculate the step size to fit within time flame and use min speed
warning
if it is above the max speed, use max
increase step size so it finishes on time.
warning that it will finish early


"""


def stxm_fast(
det: Andor2Ad | Andor3Ad,
count_time: float,
step_motor: Motor,
step_start: float,
step_end: float,
scan_motor: Motor,
scan_start: float,
scan_end: float,
plan_time: float,
# step_size: float,
):
num_data_point = plan_time / count_time
scan_range = abs(scan_start - scan_end)
step_range = abs(step_start - step_end)
ideal_step_size = 1.0 / ((num_data_point / (scan_range * step_range)) ** 0.5)
Relm-Arrowny marked this conversation as resolved.
Show resolved Hide resolved
ideal_velocity = ideal_step_size / count_time
LOGGER.info(f"{ideal_step_size} velocity = {ideal_velocity}.")
velocity, step_size = yield from get_velocity_and_step_size(

Check warning on line 49 in src/p99_bluesky/plans/stxm.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/stxm.py#L43-L49

Added lines #L43 - L49 were not covered by tests
scan_motor, ideal_velocity, ideal_step_size
)
LOGGER.info(f"{scan_motor.name} velocity = {velocity}.")
LOGGER.info(f"{step_motor.name} step size = {step_size}.")

Check warning on line 53 in src/p99_bluesky/plans/stxm.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/stxm.py#L52-L53

Added lines #L52 - L53 were not covered by tests
# yield from bps.abs_set(det.drv.acquire_time, count_time)
import math

Check warning on line 55 in src/p99_bluesky/plans/stxm.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/stxm.py#L55

Added line #L55 was not covered by tests
Relm-Arrowny marked this conversation as resolved.
Show resolved Hide resolved

num_of_step = math.ceil(step_range / step_size)
yield from fast_scan_grid(

Check warning on line 58 in src/p99_bluesky/plans/stxm.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/stxm.py#L57-L58

Added lines #L57 - L58 were not covered by tests
[det],
step_motor,
step_start,
step_end,
num_of_step,
scan_motor,
scan_start,
scan_end,
velocity,
snake_axes=True,
)


def get_velocity_and_step_size(
scan_motor: Motor, ideal_velocity: float, ideal_step_size
) -> Iterator[Any]:
max_velocity = yield from bps.rd(scan_motor.max_velocity)
min_velocity = 0.01 # yield from bps.rd(scan_motor.min_velocity)

Check warning on line 76 in src/p99_bluesky/plans/stxm.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/stxm.py#L75-L76

Added lines #L75 - L76 were not covered by tests
# if motor does not move fast enough increase step_motor step size
if ideal_velocity > max_velocity:
step_size = int(ideal_velocity / max_velocity * ideal_step_size)
ideal_velocity = max_velocity

Check warning on line 80 in src/p99_bluesky/plans/stxm.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/stxm.py#L78-L80

Added lines #L78 - L80 were not covered by tests
# if motor does not move slow enough decrease step_motor step size
# min_velocity not in motor atm need to add it
elif ideal_velocity < min_velocity:
step_size = int(ideal_velocity / min_velocity * ideal_step_size)
ideal_velocity = min_velocity

Check warning on line 85 in src/p99_bluesky/plans/stxm.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/stxm.py#L83-L85

Added lines #L83 - L85 were not covered by tests
else:
step_size = ideal_step_size
return ideal_velocity, step_size

Check warning on line 88 in src/p99_bluesky/plans/stxm.py

View check run for this annotation

Codecov / codecov/patch

src/p99_bluesky/plans/stxm.py#L87-L88

Added lines #L87 - L88 were not covered by tests


# from ophyd.sim import det

# fast_scan_grid(
# dets: list[AsyncReadable],
# step_motor: Motor,
# step_start: float,
# step_end: float,
# step_size: float,
# scan_motor: Motor,
# scan_start: float,
# scan_end: float,
# motor_speed: float | None = None,
# snake_axes: bool = False,
# ):
Relm-Arrowny marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 1 addition & 1 deletion tests/epics/soft_ioc/softsignal.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ async def soft_motor(prefix: str, name: str, unit: str = "mm"):

builder.aOut(
name + "VMAX",
initial_value=2,
initial_value=200,
)
builder.aOut(
name + "ACCL",
Expand Down
Loading
Loading