diff --git a/pyproject.toml b/pyproject.toml index 4eaa19a38..8cfffeaf9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ dependencies = [ "matplotlib", "requests", "opencv-python", - "dls-dodal @ git+https://github.com/DiamondLightSource/dodal.git@main", + "dls-dodal @ git+https://github.com/DiamondLightSource/dodal.git@b01bf2f78c088b879b0f5a7d0bfc3691da505abb", ] dynamic = ["version"] license.file = "LICENSE" diff --git a/src/mx_bluesky/I24/serial/fixed_target/i24ssx_moveonclick.py b/src/mx_bluesky/I24/serial/fixed_target/i24ssx_moveonclick.py index a1fc858e5..3a153f10d 100755 --- a/src/mx_bluesky/I24/serial/fixed_target/i24ssx_moveonclick.py +++ b/src/mx_bluesky/I24/serial/fixed_target/i24ssx_moveonclick.py @@ -3,23 +3,60 @@ Robin Owen 12 Jan 2021 """ import logging +from typing import Dict +import bluesky.plan_stubs as bps import cv2 as cv +from bluesky.run_engine import RunEngine +from dodal.beamlines import i24 +from dodal.devices.oav.oav_detector import OAV +from dodal.devices.oav.oav_parameters import OAVParameters from mx_bluesky.I24.serial.fixed_target import i24ssx_Chip_Manager_py3v1 as manager +from mx_bluesky.I24.serial.parameters.constants import OAV1_CAM, OAV_CONFIG_FILES from mx_bluesky.I24.serial.setup_beamline import caput, pv logger = logging.getLogger("I24ssx.moveonclick") -# Set beam position and scale. -# TODO grab these from somewhere else, no hard coding! -beamX = 599 # 577 -beamY = 388 # 409 +# Set scale. +# TODO See https://github.com/DiamondLightSource/mx_bluesky/issues/44 zoomcalibrator = 6 # 8 seems to work well for zoom 2 +def _read_zoom_level(oav: OAV): + zoom_level = yield from bps.rd(oav.zoom_controller.level) + return float(zoom_level) + + +def _get_beam_centre(oav: OAV, oav_params: OAVParameters): + """Extract the beam centre x/y positions from the display.configuration file. + + Args: + oav (OAV): OAV device for i24. + oav_params (OAVParamters): the OAV parameters. + """ + RE = RunEngine(call_returns_result=True) + + zoom_level = RE(_read_zoom_level(oav)).plan_result + + beamX, beamY = oav_params.get_beam_position_from_zoom(zoom_level) + return beamX, beamY + + +# TODO In the future, this should be done automatically in the OAV device +# See https://github.com/DiamondLightSource/dodal/issues/224 +def get_beam_centre_from_oav(oav_config: Dict = OAV_CONFIG_FILES): + # Get I24 oav device from dodal + oav = i24.oav() + # Use xraycentering as context, not super relevant here. + oav_params = OAVParameters("xrayCentring", **oav_config) + + return _get_beam_centre(oav, oav_params) + + # Register clicks and move chip stages def onMouse(event, x, y, flags, param): + beamX, beamY = get_beam_centre_from_oav() if event == cv.EVENT_LBUTTONUP: logger.info("Clicked X and Y %s %s" % (x, y)) xmove = -1 * (beamX - x) * zoomcalibrator @@ -31,9 +68,91 @@ def onMouse(event, x, y, flags, param): caput(pv.me14e_pmac_str, ymovepmacstring) -if __name__ == "__main__": +def update_ui(frame): + # Get beam x and y values + beamX, beamY = get_beam_centre_from_oav() + + # Overlay text and beam centre + cv.ellipse( + frame, (beamX, beamY), (12, 8), 0.0, 0.0, 360, (0, 255, 255), thickness=2 + ) + # putText(frame,'text',bottomLeftCornerOfText, font, fontScale, fontColor, thickness, lineType) + cv.putText( + frame, + "Key bindings", + (20, 40), + cv.FONT_HERSHEY_COMPLEX_SMALL, + 1, + (0, 255, 255), + 1, + 1, + ) + cv.putText( + frame, + "Q / A : go to / set as f0", + (25, 70), + cv.FONT_HERSHEY_COMPLEX_SMALL, + 0.8, + (0, 255, 255), + 1, + 1, + ) + cv.putText( + frame, + "W / S : go to / set as f1", + (25, 90), + cv.FONT_HERSHEY_COMPLEX_SMALL, + 0.8, + (0, 255, 255), + 1, + 1, + ) + cv.putText( + frame, + "E / D : go to / set as f2", + (25, 110), + cv.FONT_HERSHEY_COMPLEX_SMALL, + 0.8, + (0, 255, 255), + 1, + 1, + ) + cv.putText( + frame, + "I / O : in /out of focus", + (25, 130), + cv.FONT_HERSHEY_COMPLEX_SMALL, + 0.8, + (0, 255, 255), + 1, + 1, + ) + cv.putText( + frame, + "C : Create CS", + (25, 150), + cv.FONT_HERSHEY_COMPLEX_SMALL, + 0.8, + (0, 255, 255), + 1, + 1, + ) + cv.putText( + frame, + "esc : close window", + (25, 170), + cv.FONT_HERSHEY_COMPLEX_SMALL, + 0.8, + (0, 255, 255), + 1, + 1, + ) + cv.imshow("OAV1view", frame) + + +def start_viewer(oav1: str = OAV1_CAM): # Create a video caputure from OAV1 - cap = cv.VideoCapture("http://bl24i-di-serv-01.diamond.ac.uk:8080/OAV1.mjpg.mjpg") + cap = cv.VideoCapture(oav1) # Create window named OAV1view and set onmouse to this cv.namedWindow("OAV1view") @@ -47,82 +166,7 @@ def onMouse(event, x, y, flags, param): while success: success, frame = cap.read() - # Overlay text and beam centre - cv.ellipse( - frame, (beamX, beamY), (12, 8), 0.0, 0.0, 360, (0, 255, 255), thickness=2 - ) - # putText(frame,'text',bottomLeftCornerOfText, font, fontScale, fontColor, thickness, lineType) - cv.putText( - frame, - "Key bindings", - (20, 40), - cv.FONT_HERSHEY_COMPLEX_SMALL, - 1, - (0, 255, 255), - 1, - 1, - ) - cv.putText( - frame, - "Q / A : go to / set as f0", - (25, 70), - cv.FONT_HERSHEY_COMPLEX_SMALL, - 0.8, - (0, 255, 255), - 1, - 1, - ) - cv.putText( - frame, - "W / S : go to / set as f1", - (25, 90), - cv.FONT_HERSHEY_COMPLEX_SMALL, - 0.8, - (0, 255, 255), - 1, - 1, - ) - cv.putText( - frame, - "E / D : go to / set as f2", - (25, 110), - cv.FONT_HERSHEY_COMPLEX_SMALL, - 0.8, - (0, 255, 255), - 1, - 1, - ) - cv.putText( - frame, - "I / O : in /out of focus", - (25, 130), - cv.FONT_HERSHEY_COMPLEX_SMALL, - 0.8, - (0, 255, 255), - 1, - 1, - ) - cv.putText( - frame, - "C : Create CS", - (25, 150), - cv.FONT_HERSHEY_COMPLEX_SMALL, - 0.8, - (0, 255, 255), - 1, - 1, - ) - cv.putText( - frame, - "esc : close window", - (25, 170), - cv.FONT_HERSHEY_COMPLEX_SMALL, - 0.8, - (0, 255, 255), - 1, - 1, - ) - cv.imshow("OAV1view", frame) + update_ui(frame) k = cv.waitKey(1) if k == 113: # Q @@ -165,3 +209,7 @@ def onMouse(event, x, y, flags, param): # Clear cameraCapture instance cap.release() + + +if __name__ == "__main__": + start_viewer() diff --git a/src/mx_bluesky/I24/serial/parameters/constants.py b/src/mx_bluesky/I24/serial/parameters/constants.py index 446c4507e..be437f5e0 100644 --- a/src/mx_bluesky/I24/serial/parameters/constants.py +++ b/src/mx_bluesky/I24/serial/parameters/constants.py @@ -1,5 +1,12 @@ from pathlib import Path +OAV_CONFIG_FILES = { + "zoom_params_file": "/dls_sw/i24/software/gda/config/xml/jCameraManZoomLevels.xml", + "oav_config_json": "/dls_sw/i24/software/daq_configuration/json/OAVCentring.json", + "display_config": "/dls_sw/i24/software/gda_versions/var/display.configuration", +} +OAV1_CAM = "http://bl24i-di-serv-01.diamond.ac.uk:8080/OAV1.mjpg.mjpg" + PARAM_FILE_PATH = Path("src/mx_bluesky/I24/serial/parameters").expanduser().resolve() PARAM_FILE_PATH_FT = ( Path("src/mx_bluesky/I24/serial/parameters/fixed_target").expanduser().resolve() diff --git a/tests/I24/serial/fixed_target/__init__.py b/tests/I24/serial/fixed_target/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/I24/serial/fixed-target/test_chip_manager.py b/tests/I24/serial/fixed_target/test_chip_manager.py similarity index 100% rename from tests/I24/serial/fixed-target/test_chip_manager.py rename to tests/I24/serial/fixed_target/test_chip_manager.py diff --git a/tests/I24/serial/fixed-target/test_chip_startup.py b/tests/I24/serial/fixed_target/test_chip_startup.py similarity index 100% rename from tests/I24/serial/fixed-target/test_chip_startup.py rename to tests/I24/serial/fixed_target/test_chip_startup.py diff --git a/tests/I24/serial/fixed-target/test_ft_collect.py b/tests/I24/serial/fixed_target/test_ft_collect.py similarity index 100% rename from tests/I24/serial/fixed-target/test_ft_collect.py rename to tests/I24/serial/fixed_target/test_ft_collect.py diff --git a/tests/I24/serial/fixed_target/test_moveonclick.py b/tests/I24/serial/fixed_target/test_moveonclick.py new file mode 100644 index 000000000..7fc8fb4e4 --- /dev/null +++ b/tests/I24/serial/fixed_target/test_moveonclick.py @@ -0,0 +1,66 @@ +from unittest.mock import ANY, MagicMock, call, patch + +import cv2 as cv +import pytest +from bluesky.run_engine import RunEngine +from dodal.beamlines import i24 +from dodal.devices.oav.oav_detector import OAV + +from mx_bluesky.I24.serial.fixed_target.i24ssx_moveonclick import ( + _read_zoom_level, + onMouse, + update_ui, +) + + +@pytest.fixture +def fake_oav() -> OAV: + return i24.oav(fake_with_ophyd_sim=True) + + +@pytest.mark.parametrize( + "beam_position, expected_1J, expected_2J", + [ + ((15, 10), "#1J:-90", "#2J:60"), + ((100, 150), "#1J:-600", "#2J:900"), + ((475, 309), "#1J:-2850", "#2J:1854"), + ((638, 392), "#1J:-3828", "#2J:2352"), + ], +) +@patch("mx_bluesky.I24.serial.fixed_target.i24ssx_moveonclick.caput") +@patch("mx_bluesky.I24.serial.fixed_target.i24ssx_moveonclick.get_beam_centre_from_oav") +def test_onMouse_gets_beam_position_and_sends_correct_str( + fake_get_beam_pos, + fake_caput, + beam_position, + expected_1J, + expected_2J, +): + fake_get_beam_pos.side_effect = [beam_position] + onMouse(cv.EVENT_LBUTTONUP, 0, 0, "", "") + assert fake_caput.call_count == 2 + fake_caput.assert_has_calls( + [ + call(ANY, expected_1J), + call(ANY, expected_2J), + ] + ) + + +@patch("mx_bluesky.I24.serial.fixed_target.i24ssx_moveonclick.cv") +@patch("mx_bluesky.I24.serial.fixed_target.i24ssx_moveonclick.get_beam_centre_from_oav") +def test_update_ui_uses_correct_beam_centre_for_ellipse(fake_beam_pos, fake_cv): + mock_frame = MagicMock() + fake_beam_pos.side_effect = [(15, 10)] + update_ui(mock_frame) + fake_cv.ellipse.assert_called_once() + fake_cv.ellipse.assert_has_calls( + [call(ANY, (15, 10), (12, 8), 0.0, 0.0, 360, (0, 255, 255), thickness=2)] + ) + + +def test_read_zoom_level(fake_oav): + fake_oav.zoom_controller.level.sim_put("3.0") + RE = RunEngine(call_returns_result=True) + zoom_level = RE(_read_zoom_level(fake_oav)).plan_result + assert zoom_level == 3.0