Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
b4dbd8e
Add mock config server fixture
jacob720 Dec 9, 2025
bf08ced
Use config server to read beamlineParmaeters
jacob720 Dec 9, 2025
91fc322
Remove tests for removed functions
jacob720 Dec 9, 2025
386b521
Convert test beamlineParameters files
jacob720 Dec 9, 2025
acd5805
Fix test
jacob720 Dec 9, 2025
28b7823
Fix lint
jacob720 Dec 9, 2025
84f0836
Fix
jacob720 Jan 12, 2026
afd4fbc
Require latest daq-config-server
jacob720 Jan 12, 2026
2d92790
PR comments WIP
jacob720 Jan 20, 2026
e3f6fad
Parameterise config server URL
jacob720 Jan 23, 2026
3dbb881
Refactor get_beamline_parameters
jacob720 Jan 23, 2026
9517b6a
Update lockfile
jacob720 Jan 23, 2026
bc858b2
Use server deployed on i03 beamline cluster for i03 config
jacob720 Jan 29, 2026
9529dc1
typo
jacob720 Feb 4, 2026
4aac691
Merge branch 'main' into mx_bluesky_1504_migrate_beamline_parameters_…
jacob720 Feb 4, 2026
cba50ab
Merge branch 'main' into mx_bluesky_1504_migrate_beamline_parameters_…
jacob720 Feb 5, 2026
eaa20a2
WIP
jacob720 Feb 11, 2026
0d29dbe
Reset cache between tests
jacob720 Feb 11, 2026
feae3b6
Merge branch 'main' into mx_bluesky_1504_migrate_beamline_parameters_…
jacob720 Feb 11, 2026
d4fda7b
Fix
jacob720 Feb 11, 2026
a947f60
Fix lint
jacob720 Feb 11, 2026
d5bd077
Fix lint
jacob720 Feb 12, 2026
4b4a3f1
Merge branch 'main' into mx_bluesky_1504_migrate_beamline_parameters_…
jacob720 Feb 13, 2026
357a6c9
PR comments and coverage
jacob720 Feb 13, 2026
286a17c
Lint
jacob720 Feb 13, 2026
3d05eb2
Merge branch 'main' into mx_bluesky_1504_migrate_beamline_parameters_…
jacob720 Feb 23, 2026
07b3d6e
Merge branch 'main' into mx_bluesky_1504_migrate_beamline_parameters_…
jacob720 Feb 27, 2026
df01066
Merge branch 'main' into mx_bluesky_1504_migrate_beamline_parameters_…
jacob720 Mar 9, 2026
7599d49
Merge branch 'main' into mx_bluesky_1504_migrate_beamline_parameters_…
jacob720 Mar 12, 2026
6c8fbfd
Use config server for OAV config json
jacob720 Nov 14, 2025
8268b27
Add fixture for config server
jacob720 Nov 14, 2025
4bd6ac7
Convert OAV test config files to json
jacob720 Nov 21, 2025
77ed7ab
Read OAV config correctly
jacob720 Nov 21, 2025
cb965ae
Use pydantic models for config where appropriate
jacob720 Dec 1, 2025
19525be
Add mock config server
jacob720 Dec 11, 2025
d095ba8
Use mock config server in tests and delete test config files
jacob720 Dec 11, 2025
8718ae3
Fix tests
jacob720 Dec 11, 2025
d5374a3
tidy
jacob720 Dec 11, 2025
9c3ba7b
Fix imports
jacob720 Dec 12, 2025
4f76bcf
Fix for github.com/DiamondLightSource/daq-config-server/pull/156
jacob720 Jan 16, 2026
2743475
Move config files back
jacob720 Mar 13, 2026
c054435
Fix display config
jacob720 Mar 13, 2026
e2eaa15
Fix system tests
jacob720 Mar 13, 2026
20d30e1
Fix lint
jacob720 Mar 13, 2026
4692851
Merge branch 'main' into mx_bluesky_1448_use_config_server
jacob720 Mar 27, 2026
dcb61d1
Fix lint
jacob720 Mar 27, 2026
1eeb265
Inject config client into OAVConfig and OAVConfigBeamCentre
jacob720 Mar 30, 2026
80d9022
Rename test file
jacob720 Mar 30, 2026
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
18 changes: 16 additions & 2 deletions src/dodal/beamlines/aithre.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from functools import cache

from daq_config_server import ConfigClient

from dodal.device_manager import DeviceManager
from dodal.devices.aithre_lasershaping.goniometer import Goniometer
from dodal.devices.aithre_lasershaping.laser_robot import LaserRobot
Expand All @@ -14,6 +18,12 @@
devices = DeviceManager()


@devices.fixture
@cache
def config_client() -> ConfigClient:
return ConfigClient()


@devices.factory()
def goniometer() -> Goniometer:
return Goniometer(
Expand All @@ -31,12 +41,16 @@ def robot() -> LaserRobot:


@devices.factory()
def oav(params: OAVConfigBeamCentre | None = None) -> OAVBeamCentreFile:
def oav(
config_client: ConfigClient, params: OAVConfigBeamCentre | None = None
) -> OAVBeamCentreFile:
config = (
params
if params is not None
else OAVConfigBeamCentre(
zoom_params_file=ZOOM_PARAMS_FILE, display_config_file=DISPLAY_CONFIG
zoom_params_file=ZOOM_PARAMS_FILE,
display_config_file=DISPLAY_CONFIG,
config_client=config_client,
)
)
return OAVBeamCentreFile(
Expand Down
4 changes: 3 additions & 1 deletion src/dodal/beamlines/i03.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,11 +205,13 @@ def panda_fast_grid_scan() -> PandAFastGridScan:

@devices.factory()
def oav(
config_client: ConfigClient,
params: OAVConfigBeamCentre | None = None,
) -> OAVBeamCentreFile:
return OAVBeamCentreFile(
prefix=f"{PREFIX.beamline_prefix}-DI-OAV-01:",
config=params or OAVConfigBeamCentre(ZOOM_PARAMS_FILE, DISPLAY_CONFIG),
config=params
or OAVConfigBeamCentre(ZOOM_PARAMS_FILE, DISPLAY_CONFIG, config_client),
)


Expand Down
12 changes: 8 additions & 4 deletions src/dodal/beamlines/i04.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,18 +216,22 @@ def zebra() -> Zebra:


@devices.factory()
def oav(params: OAVConfig | None = None) -> OAVBeamCentrePV:
def oav(
config_client: ConfigClient, params: OAVConfig | None = None
) -> OAVBeamCentrePV:
return OAVBeamCentrePV(
prefix=f"{PREFIX.beamline_prefix}-DI-OAV-01:",
config=params or OAVConfig(ZOOM_PARAMS_FILE),
config=params or OAVConfig(ZOOM_PARAMS_FILE, config_client),
)


@devices.factory()
def oav_full_screen(params: OAVConfig | None = None) -> OAVBeamCentrePV:
def oav_full_screen(
config_client: ConfigClient, params: OAVConfig | None = None
) -> OAVBeamCentrePV:
return OAVBeamCentrePV(
prefix=f"{PREFIX.beamline_prefix}-DI-OAV-01:",
config=params or OAVConfig(ZOOM_PARAMS_FILE),
config=params or OAVConfig(ZOOM_PARAMS_FILE, config_client),
overlay_channel=3,
mjpeg_prefix="XTAL",
)
Expand Down
12 changes: 11 additions & 1 deletion src/dodal/beamlines/i19_1.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from functools import cache

from daq_config_server import ConfigClient

from dodal.common.beamlines.beamline_utils import (
set_beamline as set_utils_beamline,
)
Expand Down Expand Up @@ -48,6 +52,12 @@
devices = DeviceManager()


@devices.fixture
@cache
def config_client() -> ConfigClient:
return ConfigClient()


@devices.factory()
def attenuator_motor_squad() -> AttenuatorMotorSquad:
return AttenuatorMotorSquad(
Expand All @@ -62,7 +72,7 @@ def beamstop() -> BeamStop:

@devices.fixture
def oav_config() -> OAVConfigBeamCentre:
return OAVConfigBeamCentre(ZOOM_PARAMS_FILE, DISPLAY_CONFIG)
return OAVConfigBeamCentre(ZOOM_PARAMS_FILE, DISPLAY_CONFIG, config_client())


@devices.factory()
Expand Down
10 changes: 8 additions & 2 deletions src/dodal/beamlines/i24.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ def path_provider() -> PathProvider:
)


@devices.fixture
@cache
def config_client() -> ConfigClient:
return ConfigClient()


@devices.factory()
def attenuator() -> EnumFilterAttenuator:
return EnumFilterAttenuator(
Expand Down Expand Up @@ -110,10 +116,10 @@ def pmac() -> PMAC:


@devices.factory()
def oav() -> OAVBeamCentreFile:
def oav(config_client) -> OAVBeamCentreFile:
return OAVBeamCentreFile(
prefix=f"{PREFIX.beamline_prefix}-DI-OAV-01:",
config=OAVConfigBeamCentre(ZOOM_PARAMS_FILE, DISPLAY_CONFIG),
config=OAVConfigBeamCentre(ZOOM_PARAMS_FILE, DISPLAY_CONFIG, config_client),
)


Expand Down
62 changes: 30 additions & 32 deletions src/dodal/devices/oav/oav_parameters.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import json
from abc import abstractmethod
from collections import ChainMap
from dataclasses import dataclass
from typing import Any, Generic, TypeVar
from xml.etree import ElementTree
from xml.etree.ElementTree import Element

from daq_config_server import ConfigClient
from daq_config_server.models import DisplayConfig

# GDA currently assumes this aspect ratio for the OAV window size.
# For some beamline this doesn't affect anything as the actual OAV aspect ratio
# matches. Others need to take it into account to rescale the values stored in
Expand Down Expand Up @@ -34,20 +35,24 @@ def __init__(
):
self.oav_config_json: str = oav_config_json
self.context = context
config_server = ConfigClient(url="https://daq-config.diamond.ac.uk")

self.global_params, self.context_dicts = self.load_json(self.oav_config_json)
self.global_params, self.context_dicts = self.load_json(
config_server, self.oav_config_json
)
self.active_params: ChainMap = ChainMap(
self.context_dicts[self.context], self.global_params
)
self.update_self_from_current_context()

@staticmethod
def load_json(filename: str) -> tuple[dict[str, Any], dict[str, dict]]:
"""Loads the json from the specified file, and returns a dict with all the
def load_json(
config_server: ConfigClient, filename: str
) -> tuple[dict[str, Any], dict[str, dict]]:
"""Loads the specified file from the config server, and returns a dict with all the
individual top-level k-v pairs, and one with all the subdicts.
"""
with open(filename) as f:
raw_params: dict[str, Any] = json.load(f)
raw_params: dict[str, Any] = config_server.get_file_contents(filename, dict)
global_params = {
k: raw_params.pop(k)
for k, v in list(raw_params.items())
Expand Down Expand Up @@ -116,21 +121,20 @@ class ZoomParamsCrosshair(ZoomParams):


class OAVConfigBase(Generic[ParamType]):
def __init__(self, zoom_params_file: str):
self.zoom_params = self._get_zoom_params(zoom_params_file)

def _get_zoom_params(self, zoom_params_file: str):
tree = ElementTree.parse(zoom_params_file)
root = tree.getroot()
return root.findall(".//zoomLevel")
def __init__(self, zoom_params_file: str, config_client: ConfigClient):
self.zoom_params = config_client.get_file_contents(zoom_params_file, dict)[
"JCameraManSettings"
]

def _read_zoom_params(self) -> dict:
um_per_pix = {}
for node in self.zoom_params:
zoom = str(_get_element_as_float(node, "level"))
um_pix_x = _get_element_as_float(node, "micronsPerXPixel")
um_pix_y = _get_element_as_float(node, "micronsPerYPixel")
um_per_pix[zoom] = (um_pix_x, um_pix_y)
zoom_levels: list[dict] = self.zoom_params["levels"]["zoomLevel"]
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 think I would probably have gone down the pydantic model route, but seeing as in either case we have to do this dict build to get the data structure we want from the file, I don't think it makes much difference in the end.

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 think you're right, will write an issue and do this separately so we can get this merged

for level in zoom_levels:
zoom = level["level"]
um_per_pix[zoom] = (
float(level["micronsPerXPixel"]),
float(level["micronsPerYPixel"]),
)
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 would just like to point out here that I think OAVBeamCentreFile is really badly named because it inherits from OAV not OAVConfigBase or OAVParameters.

return um_per_pix

@abstractmethod
Expand All @@ -157,23 +161,17 @@ def __init__(
self,
zoom_params_file: str,
display_config_file: str,
config_client: ConfigClient,
):
self.display_config = self._get_display_config(display_config_file)
super().__init__(zoom_params_file)

def _get_display_config(self, display_config_file: str):
with open(display_config_file) as f:
file_lines = f.readlines()
return file_lines
self.display_config = config_client.get_file_contents(
display_config_file, DisplayConfig
)
super().__init__(zoom_params_file, config_client)

def _read_display_config(self) -> dict:
crosshairs = {}
for i in range(len(self.display_config)):
if self.display_config[i].startswith("zoomLevel"):
zoom = self.display_config[i].split(" = ")[1].strip()
x = int(self.display_config[i + 1].split(" = ")[1])
y = int(self.display_config[i + 2].split(" = ")[1])
crosshairs[zoom] = (x, y)
for zoom, values in self.display_config.zoom_levels.items():
crosshairs[str(zoom)] = (values.crosshair_x, values.crosshair_y)
return crosshairs

def get_parameters(self) -> dict[str, ZoomParamsCrosshair]:
Expand Down
5 changes: 4 additions & 1 deletion system_tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
# Add run_engine to be used in tests
pytest_plugins = ["dodal.testing.fixtures.run_engine"]
pytest_plugins = [
"dodal.testing.fixtures.run_engine",
"dodal.testing.fixtures.config_server",
]
10 changes: 4 additions & 6 deletions system_tests/test_oav_system.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import bluesky.plan_stubs as bps
import pytest
from bluesky.run_engine import RunEngine
from daq_config_server import ConfigClient
from ophyd_async.core import init_devices
from tests.test_data import (
TEST_DISPLAY_CONFIG,
TEST_OAV_ZOOM_LEVELS_XML,
)
from tests.devices.oav.test_data import TEST_OAV_ZOOM_LEVELS

from dodal.devices.oav.oav_detector import OAV, OAVConfig

Expand All @@ -18,7 +16,7 @@

@pytest.fixture
async def oav() -> OAV:
oav_config = OAVConfig(TEST_OAV_ZOOM_LEVELS_XML, TEST_DISPLAY_CONFIG)
oav_config = OAVConfig(TEST_OAV_ZOOM_LEVELS, ConfigClient(""))
async with init_devices(connect=True):
oav = OAV("", config=oav_config, name="oav")
return oav
Expand All @@ -39,7 +37,7 @@ def take_snapshot_with_grid(oav: OAV, snapshot_filename, snapshot_directory):
@pytest.mark.skip(reason="Don't want to actually take snapshots during testing.")
def test_grid_overlay(run_engine: RunEngine):
beamline = "BL03I"
oav_params = OAVConfig(TEST_OAV_ZOOM_LEVELS_XML, TEST_DISPLAY_CONFIG)
oav_params = OAVConfig(TEST_OAV_ZOOM_LEVELS, ConfigClient(""))
oav = OAV(name="oav", prefix=f"{beamline}", config=oav_params)
snapshot_filename = "snapshot"
snapshot_directory = "."
Expand Down
5 changes: 2 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,16 @@
make_all_devices,
)
from tests.devices.beamlines.i10.test_data import LOOKUP_TABLE_PATH
from tests.devices.oav.test_data import TEST_DISPLAY_CONFIG, TEST_OAV_ZOOM_LEVELS
from tests.devices.test_daq_configuration import MOCK_DAQ_CONFIG_PATH
from tests.devices.test_data import TEST_LUT_TXT
from tests.test_data import (
I04_BEAMLINE_PARAMETERS,
TEST_DISPLAY_CONFIG,
TEST_OAV_ZOOM_LEVELS_XML,
)

MOCK_PATHS = [
("DAQ_CONFIGURATION_PATH", MOCK_DAQ_CONFIG_PATH),
("ZOOM_PARAMS_FILE", TEST_OAV_ZOOM_LEVELS_XML),
("ZOOM_PARAMS_FILE", TEST_OAV_ZOOM_LEVELS),
("DISPLAY_CONFIG", TEST_DISPLAY_CONFIG),
("LOOK_UPTABLE_DIR", LOOKUP_TABLE_PATH),
]
Expand Down
6 changes: 6 additions & 0 deletions tests/devices/beamlines/i03/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import pytest


@pytest.fixture(autouse=True)
def use_beamline_i03(monkeypatch):
monkeypatch.setenv("BEAMLINE", "i03")
11 changes: 7 additions & 4 deletions tests/devices/oav/conftest.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
from unittest.mock import AsyncMock

import pytest
from daq_config_server import ConfigClient
from ophyd_async.core import init_devices, set_mock_value

from dodal.devices.oav.oav_detector import OAVBeamCentreFile, OAVBeamCentrePV
from dodal.devices.oav.oav_parameters import OAVConfig, OAVConfigBeamCentre
from tests.test_data import TEST_DISPLAY_CONFIG, TEST_OAV_ZOOM_LEVELS_XML
from tests.devices.oav.test_data import TEST_DISPLAY_CONFIG, TEST_OAV_ZOOM_LEVELS


@pytest.fixture
async def oav() -> OAVBeamCentreFile:
oav_config = OAVConfigBeamCentre(TEST_OAV_ZOOM_LEVELS_XML, TEST_DISPLAY_CONFIG)
oav_config = OAVConfigBeamCentre(
TEST_OAV_ZOOM_LEVELS, TEST_DISPLAY_CONFIG, ConfigClient("")
)
async with init_devices(mock=True, connect=True):
oav = OAVBeamCentreFile("", config=oav_config, name="oav")
zoom_levels_list = ["1.0x", "3.0x", "5.0x", "7.5x", "10.0x"]
Expand All @@ -25,7 +28,7 @@ async def oav() -> OAVBeamCentreFile:

@pytest.fixture
async def oav_beam_centre_pv_roi() -> OAVBeamCentrePV:
oav_config = OAVConfig(TEST_OAV_ZOOM_LEVELS_XML)
oav_config = OAVConfig(TEST_OAV_ZOOM_LEVELS, ConfigClient(""))
async with init_devices(mock=True, connect=True):
oav = OAVBeamCentrePV("", config=oav_config, name="oav")
zoom_levels_list = ["1.0x", "3.0x", "5.0x", "7.5x", "10.0x"]
Expand All @@ -40,7 +43,7 @@ async def oav_beam_centre_pv_roi() -> OAVBeamCentrePV:

@pytest.fixture
async def oav_beam_centre_pv_fs() -> OAVBeamCentrePV:
oav_config = OAVConfig(TEST_OAV_ZOOM_LEVELS_XML)
oav_config = OAVConfig(TEST_OAV_ZOOM_LEVELS, ConfigClient(""))
async with init_devices(mock=True, connect=True):
oav = OAVBeamCentrePV(
"", config=oav_config, name="oav", mjpeg_prefix="XTAL", overlay_channel=3
Expand Down
6 changes: 5 additions & 1 deletion tests/devices/oav/test_data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@

TEST_DATA_PATH = Path(__file__).parent
TEST_OAV_CENTRING_JSON = join(TEST_DATA_PATH, "test_OAVCentring.json")
TEST_DISPLAY_CONFIG = join(TEST_DATA_PATH, "test_display.configuration")
TEST_OAV_ZOOM_LEVELS = join(TEST_DATA_PATH, "jCameraManZoomLevels.json")
OAV_SNAPSHOT_TEST_PNG = join(TEST_DATA_PATH, "oav_snapshot_test.png")
OAV_SNAPSHOT_EXPECTED_PNG = join(TEST_DATA_PATH, "oav_snapshot_expected.png")

__all__ = [
"TEST_OAV_CENTRING_JSON",
"TEST_DISPLAY_CONFIG",
"TEST_OAV_ZOOM_LEVELS",
"OAV_SNAPSHOT_EXPECTED_PNG",
"OAV_SNAPSHOT_TEST_PNG",
"TEST_OAV_CENTRING_JSON",
]
Loading
Loading