Skip to content
This repository has been archived by the owner on Sep 2, 2024. It is now read-only.

Commit

Permalink
Merge branch 'main' into 480_better_status_logging
Browse files Browse the repository at this point in the history
  • Loading branch information
dperl-dls authored Jan 27, 2023
2 parents e2fc7dd + 27aea19 commit 7912a9a
Show file tree
Hide file tree
Showing 18 changed files with 367 additions and 108 deletions.
32 changes: 31 additions & 1 deletion fake_zocalo/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from pika.adapters.blocking_connection import BlockingChannel
from pika.spec import BasicProperties

NO_DIFFRACTION_ID = 1


def load_configuration_file(filename):
conf = yaml.safe_load(Path(filename).read_text())
Expand All @@ -23,7 +25,7 @@ def main():
config["host"], config["port"], config["vhost"], creds
)

result = {
single_crystal_result = {
"environment": {"ID": "6261b482-bef2-49f5-8699-eb274cd3b92e"},
"payload": [{"max_voxel": [1, 2, 3], "centre_of_mass": [1.2, 2.3, 3.4]}],
"recipe": {
Expand All @@ -41,6 +43,27 @@ def main():
"recipe-pointer": 1,
}

no_diffraction_result = {
"environment": {"ID": "6261b482-bef2-49f5-8699-eb274cd3b92e"},
"payload": [],
"recipe": {
"start": [
[1, [{"max_voxel": [1, 2, 3], "centre_of_mass": [1.2, 2.3, 3.4]}]]
],
"1": {
"service": "Send XRC results to GDA",
"queue": "xrc.i03",
"exchange": "results",
"parameters": {
"dcid": str(NO_DIFFRACTION_ID),
"dcgid": str(NO_DIFFRACTION_ID),
},
},
},
"recipe-path": [],
"recipe-pointer": 1,
}

def on_request(ch: BlockingChannel, method, props, body):
print(
f"recieved message: \n properties: \n\n {method} \n\n {props} \n\n{body}\n"
Expand All @@ -58,6 +81,13 @@ def on_request(ch: BlockingChannel, method, props, body):
delivery_mode=2,
headers={"workflows-recipe": True, "x-delivery-count": 1},
)

if message.get("parameters").get("ispyb_dcid") == NO_DIFFRACTION_ID:
result = no_diffraction_result
else:
result = single_crystal_result

print(f"Sending results {result}")
result_chan = conn.channel()
result_chan.basic_publish(
"results", "xrc.i03", json.dumps(result), resultprops
Expand Down
11 changes: 9 additions & 2 deletions src/artemis/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ def __init__(self, RE: RunEngine) -> None:
self.RE = RE
if VERBOSE_EVENT_LOGGING:
RE.subscribe(VerbosePlanExecutionLoggingCallback())
for plan in PLAN_REGISTRY:
PLAN_REGISTRY[plan]["setup"]()

def start(
self, experiment: Callable, parameters: FullParameters
Expand Down Expand Up @@ -144,11 +146,16 @@ def put(self, experiment: str, action: Actions):
status_and_message = StatusAndMessage(Status.FAILED, f"{action} not understood")
if action == Actions.START.value:
try:
plan = PLAN_REGISTRY.get(experiment)
if plan is None:
experiment_type = PLAN_REGISTRY.get(experiment)
if experiment_type is None:
raise PlanNotFound(
f"Experiment plan '{experiment}' not found in registry."
)
plan = experiment_type.get("run")
if plan is None:
raise PlanNotFound(
f"Experiment plan '{experiment}' has no \"run\" method."
)
parameters = FullParameters.from_json(request.data)
status_and_message = self.runner.start(plan, parameters)
except JSONDecodeError as e:
Expand Down
39 changes: 33 additions & 6 deletions src/artemis/devices/eiger.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from enum import Enum
from typing import Optional

from ophyd import Component, Device, EpicsSignalRO
from ophyd.areadetector.cam import EigerDetectorCam
Expand All @@ -9,6 +10,20 @@
from artemis.devices.status import await_value
from artemis.log import LOGGER

DETECTOR_PARAM_DEFAULTS = {
"current_energy": 100,
"exposure_time": 0.1,
"directory": "/tmp",
"prefix": "file_name",
"run_number": 0,
"detector_distance": 100.0,
"omega_start": 0.0,
"omega_increment": 0.0,
"num_images": 2000,
"use_roi_mode": False,
"det_dist_to_beam_converter_path": "src/artemis/devices/unit_tests/test_lookup_table.txt",
}


class EigerTriggerMode(Enum):
INTERNAL_SERIES = 0
Expand All @@ -28,14 +43,22 @@ class EigerDetector(Device):

filewriters_finished: SubscriptionStatus

def __init__(
self, detector_params: DetectorParams, name="Eiger Detector", *args, **kwargs
detector_params: Optional[DetectorParams] = None

@classmethod
def with_params(
cls,
params: DetectorParams = DetectorParams(**DETECTOR_PARAM_DEFAULTS),
name: str = "EigerDetector",
*args,
**kwargs,
):
super().__init__(name=name, *args, **kwargs)
self.detector_params = detector_params
self.check_detector_variables_set()
det = cls(name=name, *args, **kwargs)
det.set_detector_parameters(params)
return det

def check_detector_variables_set(self):
def set_detector_parameters(self, detector_params: DetectorParams):
self.detector_params = detector_params
if self.detector_params is None:
raise Exception("Parameters for scan must be specified")

Expand Down Expand Up @@ -88,6 +111,7 @@ def disable_roi_mode(self):
self.change_roi_mode(False)

def change_roi_mode(self, enable: bool):
assert self.detector_params is not None
detector_dimensions = (
self.detector_params.detector_size_constants.roi_size_pixels
if enable
Expand All @@ -106,6 +130,7 @@ def change_roi_mode(self, enable: bool):
self.log.error("Failed to switch to ROI mode")

def set_cam_pvs(self) -> AndStatus:
assert self.detector_params is not None
status = self.cam.acquire_time.set(self.detector_params.exposure_time)
status &= self.cam.acquire_period.set(self.detector_params.exposure_time)
status &= self.cam.num_exposures.set(1)
Expand All @@ -129,6 +154,7 @@ def set_odin_pvs(self):
return odin_status

def set_mx_settings_pvs(self) -> Status:
assert self.detector_params is not None
beam_x_pixels, beam_y_pixels = self.detector_params.get_beam_position_pixels(
self.detector_params.detector_distance
)
Expand Down Expand Up @@ -159,6 +185,7 @@ def set_detector_threshold(self, energy: float, tolerance: float = 0.1) -> Statu
return status

def set_num_triggers_and_captures(self) -> Status:
assert self.detector_params is not None
status = self.cam.num_images.set(1)
status &= self.cam.num_triggers.set(self.detector_params.num_images)
status &= self.odin.file_writer.num_capture.set(self.detector_params.num_images)
Expand Down
8 changes: 2 additions & 6 deletions src/artemis/devices/fast_grid_scan.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import threading
import time
from dataclasses import dataclass
from typing import List

from bluesky.plan_stubs import mv
from dataclasses_json import dataclass_json
Expand All @@ -14,7 +13,6 @@
Signal,
)
from ophyd.status import DeviceStatus, StatusBase
from ophyd.utils.epics_pvs import set_and_wait

from artemis.devices.motors import XYZLimitBundle
from artemis.devices.status import await_value
Expand Down Expand Up @@ -244,10 +242,6 @@ def scan():
threading.Thread(target=scan, daemon=True).start()
return st

def stage(self) -> List[object]:
set_and_wait(self.position_counter, 0)
return super().stage()

def complete(self) -> DeviceStatus:
return GridScanCompleteStatus(self)

Expand Down Expand Up @@ -284,4 +278,6 @@ def set_fast_grid_scan_params(scan: FastGridScan, params: GridScanParams):
params.z1_start,
scan.z2_start,
params.z2_start,
scan.position_counter,
0,
)
8 changes: 6 additions & 2 deletions src/artemis/devices/system_tests/test_gridscan_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
GridScanParams,
set_fast_grid_scan_params,
)
from artemis.experiment_plans.fast_grid_scan_plan import wait_for_fgs_valid


@pytest.fixture()
Expand All @@ -29,6 +30,10 @@ def test_when_program_data_set_and_staged_then_expected_images_correct(
def test_given_valid_params_when_kickoff_then_completion_status_increases_and_finishes(
fast_grid_scan: FastGridScan,
):
def set_and_wait_plan(fast_grid_scan: FastGridScan):
yield from set_fast_grid_scan_params(fast_grid_scan, GridScanParams(3, 3))
yield from wait_for_fgs_valid(fast_grid_scan)

prev_current, prev_fraction = None, None

def progress_watcher(*args, **kwargs):
Expand All @@ -44,8 +49,7 @@ def progress_watcher(*args, **kwargs):
assert 0 < prev_fraction < 1

RE = RunEngine()
RE(set_fast_grid_scan_params(fast_grid_scan, GridScanParams(3, 3)))
fast_grid_scan.stage()
RE(set_and_wait_plan(fast_grid_scan))
assert fast_grid_scan.position_counter.get() == 0

# S03 currently is giving 2* the number of expected images (see #13)
Expand Down
20 changes: 9 additions & 11 deletions src/artemis/devices/unit_tests/test_eiger.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@

@pytest.fixture
def fake_eiger():
FakeEigerDetector = make_fake_device(EigerDetector)
fake_eiger: EigerDetector = FakeEigerDetector(
detector_params=TEST_DETECTOR_PARAMS, name="test"
FakeEigerDetector: EigerDetector = make_fake_device(EigerDetector)
fake_eiger: EigerDetector = FakeEigerDetector.with_params(
params=TEST_DETECTOR_PARAMS, name="test"
)
return fake_eiger

Expand Down Expand Up @@ -94,27 +94,25 @@ def test_detector_threshold(
],
)
def test_check_detector_variables(
fake_eiger,
detector_params,
fake_eiger: EigerDetector,
detector_params: DetectorParams,
detector_size_constants,
beam_xy_converter,
expected_error_number,
):
fake_eiger.detector_params = detector_params

if detector_params is not None:
fake_eiger.detector_params.beam_xy_converter = beam_xy_converter
fake_eiger.detector_params.detector_size_constants = detector_size_constants
detector_params.beam_xy_converter = beam_xy_converter
detector_params.detector_size_constants = detector_size_constants

if expected_error_number != 0:
with pytest.raises(Exception) as e:
fake_eiger.check_detector_variables_set()
fake_eiger.set_detector_parameters(detector_params)
number_of_errors = str(e.value).count("\n") + 1

assert number_of_errors == expected_error_number
else:
try:
fake_eiger.check_detector_variables_set()
fake_eiger.set_detector_parameters(detector_params)
except Exception as e:
assert False, f"exception was raised {e}"

Expand Down
7 changes: 6 additions & 1 deletion src/artemis/experiment_plans/experiment_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

from artemis.experiment_plans import fast_grid_scan_plan

PLAN_REGISTRY: Dict[str, Callable] = {"fast_grid_scan": fast_grid_scan_plan.get_plan}
PLAN_REGISTRY: Dict[str, Dict[str, Callable]] = {
"fast_grid_scan": {
"setup": fast_grid_scan_plan.create_devices,
"run": fast_grid_scan_plan.get_plan,
}
}


class PlanNotFound(Exception):
Expand Down
55 changes: 37 additions & 18 deletions src/artemis/experiment_plans/fast_grid_scan_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,18 @@
from artemis.devices.undulator import Undulator
from artemis.exceptions import WarningException
from artemis.external_interaction.callbacks import FGSCallbackCollection
from artemis.parameters import ISPYB_PLAN_NAME, SIM_BEAMLINE, FullParameters
from artemis.parameters import (
ISPYB_PLAN_NAME,
SIM_BEAMLINE,
FullParameters,
get_beamline_prefixes,
)
from artemis.tracing import TRACER
from artemis.utils import Point3D

fast_grid_scan_composite: FGSComposite = None
eiger: EigerDetector = None


def read_hardware_for_ispyb(
undulator: Undulator,
Expand Down Expand Up @@ -110,7 +118,7 @@ def run_gridscan(
yield from set_fast_grid_scan_params(fgs_motors, parameters.grid_scan_params)
yield from wait_for_fgs_valid(fgs_motors)

@bpp.stage_decorator([eiger, fgs_motors])
@bpp.stage_decorator([eiger])
def do_fgs():
yield from bps.wait() # Wait for all moves to complete
yield from bps.kickoff(fgs_motors)
Expand Down Expand Up @@ -164,35 +172,44 @@ def gridscan_with_subscriptions(fgs_composite, detector, params):
)


def get_plan(
parameters: FullParameters, subscriptions: FGSCallbackCollection
) -> Callable:
"""Create the plan to run the grid scan based on provided parameters.
Args:
parameters (FullParameters): The parameters to run the scan.
Returns:
Generator: The plan for the gridscan
"""
artemis.log.LOGGER.info("Fetching composite plan")
def create_devices():
"""Creates the devices required for the plan and connect to them"""
global fast_grid_scan_composite, eiger
prefixes = get_beamline_prefixes()
artemis.log.LOGGER.info(
f"Creating devices for {prefixes.beamline_prefix} and {prefixes.insertion_prefix}"
)
fast_grid_scan_composite = FGSComposite(
insertion_prefix=parameters.insertion_prefix,
insertion_prefix=prefixes.insertion_prefix,
name="fgs",
prefix=parameters.beamline,
prefix=prefixes.beamline_prefix,
)

# Note, eiger cannot be currently waited on, see #166
eiger = EigerDetector(
parameters.detector_params,
name="eiger",
prefix=f"{parameters.beamline}-EA-EIGER-01:",
prefix=f"{prefixes.beamline_prefix}-EA-EIGER-01:",
)

artemis.log.LOGGER.info("Connecting to EPICS devices...")
fast_grid_scan_composite.wait_for_connection()
artemis.log.LOGGER.info("Connected.")


def get_plan(
parameters: FullParameters,
subscriptions: FGSCallbackCollection,
) -> Callable:
"""Create the plan to run the grid scan based on provided parameters.
Args:
parameters (FullParameters): The parameters to run the scan.
Returns:
Generator: The plan for the gridscan
"""
eiger.set_detector_parameters(parameters.detector_params)

@bpp.finalize_decorator(lambda: tidy_up_plans(fast_grid_scan_composite))
def run_gridscan_and_move_and_tidy(fgs_composite, detector, params, comms):
yield from run_gridscan_and_move(fgs_composite, detector, params, comms)
Expand All @@ -217,4 +234,6 @@ def run_gridscan_and_move_and_tidy(fgs_composite, detector, params, comms):
parameters = FullParameters(beamline=args.beamline)
subscriptions = FGSCallbackCollection.from_params(parameters)

create_devices()

RE(get_plan(parameters, subscriptions))
Loading

0 comments on commit 7912a9a

Please sign in to comment.