diff --git a/src/artemis/devices/eiger.py b/src/artemis/devices/eiger.py index fab164855..d583e8fe2 100644 --- a/src/artemis/devices/eiger.py +++ b/src/artemis/devices/eiger.py @@ -110,7 +110,7 @@ def change_roi_mode(self, enable: bool): status.wait(10) if not status.success: - print("Failed to switch to ROI mode") + self.log.error("Failed to switch to ROI mode") def set_cam_pvs(self): self.cam.acquire_time.put(self.detector_params.exposure_time) diff --git a/src/artemis/devices/eiger_odin.py b/src/artemis/devices/eiger_odin.py index 41a9d4427..a58d1f54f 100644 --- a/src/artemis/devices/eiger_odin.py +++ b/src/artemis/devices/eiger_odin.py @@ -96,7 +96,7 @@ def clear_odin_errors(self): for node_number, node_pv in enumerate(self.nodes): error_message = node_pv.error_message.get() if len(error_message) != 0: - print(f"Clearing odin errors from node {node_number}") + self.log.info(f"Clearing odin errors from node {node_number}") node_pv.clear_errors.put(1) @@ -114,9 +114,9 @@ def check_odin_state(self) -> bool: if not is_initialised: raise Exception(error_message) if frames_dropped: - print(frames_dropped_details) + self.log.error(f"Frames dropped: {frames_dropped_details}") if frames_timed_out: - print(frames_timed_out_details) + self.log.error(f"Frames timed out: {frames_timed_out_details}") return is_initialised and not frames_dropped and not frames_timed_out diff --git a/src/artemis/devices/fast_grid_scan.py b/src/artemis/devices/fast_grid_scan.py index d7da3b170..8f5cd81f0 100644 --- a/src/artemis/devices/fast_grid_scan.py +++ b/src/artemis/devices/fast_grid_scan.py @@ -9,7 +9,8 @@ EpicsSignalWithRBV, Signal, ) -from ophyd.status import DeviceStatus, StatusBase, SubscriptionStatus +from ophyd.status import DeviceStatus, StatusBase +from ophyd.utils.epics_pvs import set_and_wait from dataclasses import dataclass from typing import Any @@ -20,6 +21,7 @@ ) from bluesky.plan_stubs import mv +from src.artemis.devices.status import await_value @dataclass @@ -176,13 +178,13 @@ def kickoff(self) -> StatusBase: def check_valid_and_scan(): try: - self.log.info("Waiting on position counter reset and valid settings") + self.log.debug("Waiting on position counter reset and valid settings") while self.is_invalid() or not self.position_counter.get() == 0: time.sleep(0.1) self.log.debug("Running scan") - running = SubscriptionStatus(self.status, lambda value: value == 1) - run_requested = self.run_cmd.set(1) - (run_requested and running).wait() + self.run_cmd.put(1) + self.log.debug("Waiting for scan to start") + await_value(self.status, 1).wait() st.set_finished() except Exception as e: st.set_exception(e) @@ -191,7 +193,7 @@ def check_valid_and_scan(): return st def stage(self) -> List[object]: - self.position_counter.put(0) + set_and_wait(self.position_counter, 0) return super().stage() def complete(self) -> DeviceStatus: diff --git a/src/artemis/devices/system_tests/test_gridscan_system.py b/src/artemis/devices/system_tests/test_gridscan_system.py index 4ee2ec6ad..b9a60b664 100644 --- a/src/artemis/devices/system_tests/test_gridscan_system.py +++ b/src/artemis/devices/system_tests/test_gridscan_system.py @@ -15,8 +15,45 @@ def fast_grid_scan(): @pytest.mark.s03 -def test_set_program_data_and_kickoff(fast_grid_scan: FastGridScan): +def test_when_program_data_set_and_staged_then_expected_images_correct( + fast_grid_scan: FastGridScan, +): RE = RunEngine() RE(set_fast_grid_scan_params(fast_grid_scan, GridScanParams(2, 2))) - kickoff_status = fast_grid_scan.kickoff() - kickoff_status.wait() + assert fast_grid_scan.expected_images.get() == 2 * 2 + fast_grid_scan.stage() + assert fast_grid_scan.position_counter.get() == 0 + + +@pytest.mark.s03 +def test_given_valid_params_when_kickoff_then_completion_status_increases_and_finishes( + fast_grid_scan: FastGridScan, +): + prev_current, prev_fraction = None, None + + def progress_watcher(*args, **kwargs): + nonlocal prev_current, prev_fraction + if "current" in kwargs.keys() and "fraction" in kwargs.keys(): + current, fraction = kwargs["current"], kwargs["fraction"] + if not prev_current: + prev_current, prev_fraction = current, fraction + else: + assert current > prev_current + assert fraction > prev_fraction + assert 0 < fraction < 1 + assert 0 < prev_fraction < 1 + + RE = RunEngine() + RE(set_fast_grid_scan_params(fast_grid_scan, GridScanParams(3, 3))) + fast_grid_scan.stage() + assert fast_grid_scan.position_counter.get() == 0 + + # S03 currently is giving 2* the number of expected images (see #13) + fast_grid_scan.expected_images.put(3 * 3 * 2) + + fast_grid_scan.kickoff() + complete_status = fast_grid_scan.complete() + complete_status.watch(progress_watcher) + complete_status.wait() + assert prev_current != None + assert prev_fraction != None diff --git a/src/artemis/fast_grid_scan_plan.py b/src/artemis/fast_grid_scan_plan.py new file mode 100644 index 000000000..230b4069c --- /dev/null +++ b/src/artemis/fast_grid_scan_plan.py @@ -0,0 +1,109 @@ +import os +import sys + +sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..")) + +from src.artemis.devices.eiger import DetectorParams, EigerDetector +from src.artemis.devices.fast_grid_scan import ( + FastGridScan, + GridScanParams, + set_fast_grid_scan_params, +) +import bluesky.preprocessors as bpp +import bluesky.plan_stubs as bps +from bluesky import RunEngine +from bluesky.callbacks import LiveTable +from bluesky.utils import ProgressBarManager + +from src.artemis.devices.zebra import Zebra +from src.artemis.devices.det_dim_constants import EIGER2_X_16M_SIZE +import argparse +from epics import caput +from src.artemis.devices.det_dist_to_beam_converter import ( + DetectorDistanceToBeamXYConverter, +) + +from ophyd.log import config_ophyd_logging +from bluesky.log import config_bluesky_logging + +from bluesky.plans import scan +from ophyd.sim import motor1 + +config_bluesky_logging(file="/tmp/bluesky.log", level="DEBUG") +config_ophyd_logging(file="/tmp/ophyd.log", level="DEBUG") + +# Clear odin errors and check initialised +# If in error clean up +# Setup beamline +# Store in ISPyB +# Start nxgen +# Start analysis run collection + +DETECTOR = EIGER2_X_16M_SIZE +USE_ROI = False +GRID_SCAN_PARAMS = GridScanParams( + x_steps=5, + y_steps=10, + x_step_size=0.1, + y_step_size=0.1, + dwell_time=0.2, + x_start=0.0, + y1_start=0.0, + z1_start=0.0, +) +DETECTOR_PARAMS = DetectorParams( + current_energy=100, + exposure_time=0.1, + acquisition_id="test", + directory="/tmp", + prefix="file_name", + detector_distance=100.0, + omega_start=0.0, + omega_increment=0.1, + num_images=10, +) +DET_TO_DIST_CONVERTER = DetectorDistanceToBeamXYConverter( + os.path.join( + os.path.dirname(__file__), "devices", "det_dist_to_beam_XY_converter.txt" + ) +) + + +@bpp.run_decorator() +def run_gridscan(fgs: FastGridScan, zebra: Zebra, eiger: EigerDetector): + # TODO: Check topup gate + yield from set_fast_grid_scan_params(fgs, GRID_SCAN_PARAMS) + + eiger.detector_size_constants = DETECTOR + eiger.use_roi_mode = USE_ROI + eiger.detector_params = DETECTOR_PARAMS + eiger.beam_xy_converter = DET_TO_DIST_CONVERTER + + @bpp.stage_decorator([zebra, eiger, fgs]) + def do_fgs(): + yield from bps.kickoff(fgs) + yield from bps.complete(fgs, wait=True) + + yield from do_fgs() + + +def do_scan(beamline_prefix: str): + fast_grid_scan = FastGridScan( + name="fgs", prefix=f"{beamline_prefix}-MO-SGON-01:FGS:" + ) + eiger = EigerDetector(name="eiger", prefix=f"{beamline_prefix}-EA-EIGER-01:") + zebra = Zebra(name="zebra", prefix=f"{beamline_prefix}-EA-ZEBRA-01:") + + RE = RunEngine({}) + RE.waiting_hook = ProgressBarManager() + + RE(run_gridscan(fast_grid_scan, zebra, eiger)) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument( + "--beamline", help="The beamline prefix this is being run on", default="BL03S" + ) + args = parser.parse_args() + do_scan(args.beamline)