Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion src/daq_config_server/app/_file_converter_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import xmltodict

from daq_config_server.models import DisplayConfig, beamline_parameters_to_dict
from daq_config_server.models import beamline_parameters_to_dict
from daq_config_server.models.base_model import ConfigModel
from daq_config_server.models.feature_settings.hyperion_feature_settings import (
HyperionFeatureSettings,
Expand All @@ -21,6 +21,7 @@
UndulatorEnergyGapLookupTable,
parse_i09_hu_undulator_energy_gap_lut,
)
from daq_config_server.models.oav import DisplayConfig

FILE_TO_CONVERTER_MAP: dict[str, Callable[[str], ConfigModel | dict[str, Any]]] = { # type: ignore
"/tests/test_data/test_good_lut.txt": UndulatorEnergyGapLookupTable.from_contents, # For system tests # noqa: E501
Expand Down
8 changes: 1 addition & 7 deletions src/daq_config_server/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
from .base_model import ConfigModel
from .beamline_parameters import beamline_parameters_to_dict
from .display_config_models import DisplayConfig, DisplayConfigData

__all__ = [
"ConfigModel",
"beamline_parameters_to_dict",
"DisplayConfig",
"DisplayConfigData",
]
__all__ = ["ConfigModel", "beamline_parameters_to_dict"]
4 changes: 4 additions & 0 deletions src/daq_config_server/models/oav/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .display_config_models import DisplayConfig, DisplayConfigData
from .zoom_levels import ZoomLevelData, ZoomLevels

__all__ = ["DisplayConfig", "DisplayConfigData", "ZoomLevels", "ZoomLevelData"]
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
from typing import Self

from pydantic import model_validator

from daq_config_server.models.base_model import ConfigModel
from daq_config_server.models.utils import (
camel_to_snake_case,
remove_comments,
)
from daq_config_server.models.oav.per_zoom_level import PerZoomLevel
from daq_config_server.models.utils import camel_to_snake_case, remove_comments


class DisplayConfigData(ConfigModel):
Expand All @@ -18,23 +14,7 @@ class DisplayConfigData(ConfigModel):
bottom_right_y: int


class DisplayConfig(ConfigModel):
zoom_levels: dict[float, DisplayConfigData]
required_zoom_levels: set[float] | None = None

@model_validator(mode="after")
def check_zoom_levels_match_required(self):
existing_keys = set(self.zoom_levels.keys())
if (
self.required_zoom_levels is not None
and self.required_zoom_levels != existing_keys
):
raise ValueError(
f"Zoom levels {existing_keys} "
f"do not match required zoom levels: {self.required_zoom_levels}"
)
return self

class DisplayConfig(PerZoomLevel[DisplayConfigData]):
@classmethod
def from_contents(cls, contents: str) -> Self:
lines = contents.splitlines()
Expand Down
25 changes: 25 additions & 0 deletions src/daq_config_server/models/oav/per_zoom_level.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from typing import Generic, TypeVar

from pydantic import model_validator

from daq_config_server.models.base_model import ConfigModel

T = TypeVar("T", bound=ConfigModel)


class PerZoomLevel(ConfigModel, Generic[T]):
zoom_levels: dict[float, T]
required_zoom_levels: set[float] | None = None

@model_validator(mode="after")
def check_zoom_levels_match_required(self):
existing_keys = set(self.zoom_levels.keys())
if (
self.required_zoom_levels is not None
and self.required_zoom_levels != existing_keys
):
raise ValueError(
f"Zoom levels {existing_keys} "
f"do not match required zoom levels: {self.required_zoom_levels}"
)
return self
37 changes: 37 additions & 0 deletions src/daq_config_server/models/oav/zoom_levels.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from typing import Self

import xmltodict

from daq_config_server.models.base_model import ConfigModel
from daq_config_server.models.oav.per_zoom_level import PerZoomLevel
from daq_config_server.models.utils import camel_to_snake_case


class ZoomLevelData(ConfigModel):
position: int
microns_per_x_pixel: float
microns_per_y_pixel: float


class ZoomLevels(PerZoomLevel[ZoomLevelData]):
tolerance: float

@classmethod
def from_jcamera_man_zoom_levels(cls, contents: str) -> Self:
config_dict = xmltodict.parse(contents)
zoom_levels_list: list[dict[str, str]] = config_dict["JCameraManSettings"][
"levels"
]["zoomLevel"]
zoom_levels: dict[float, ZoomLevelData] = {}
for zoom_level in zoom_levels_list:
level = float(zoom_level["level"])
new_zoom_level = {
camel_to_snake_case(key): value
for key, value in zoom_level.items()
if key != "level"
}
zoom_levels[level] = ZoomLevelData.model_validate(new_zoom_level)
return cls(
zoom_levels=zoom_levels,
tolerance=config_dict["JCameraManSettings"]["tolerance"],
)
4 changes: 3 additions & 1 deletion src/daq_config_server/models/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ def remove_comments(lines: Iterable[str]) -> list[str]:


def camel_to_snake_case(value: str) -> str:
return re.sub(r"([a-z])([A-Z])", r"\1_\2", value).lower()
value = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", value)
value = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", value)
return value.lower()


DEFAULT_IGNORE_LINES_STARTING_WITH = ("Units", "ScannableUnits", "ScannableNames")
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@

from daq_config_server.models import (
ConfigModel,
DisplayConfig,
beamline_parameters_to_dict,
)
from daq_config_server.models.lookup_tables.insertion_device import (
UndulatorEnergyGapLookupTable,
)
from daq_config_server.models.oav import DisplayConfig
from tests.constants import ServerFilePaths, TestDataPaths


Expand Down
3 changes: 3 additions & 0 deletions tests/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ class TestDataPaths:
TEST_I15_1_XPDF_LOCAL_PARAMETERS = TEST_DATA_DIR_PATH.joinpath(
"test_xpdfLocalParameters.xml"
)
TEST_JCAMERA_MAN_ZOOM_LEVELS = TEST_DATA_DIR_PATH.joinpath(
"test_jCameraManZoomLevels.xml"
)


# These are the file locations accessible from the server running in a container
Expand Down
3 changes: 2 additions & 1 deletion tests/system_tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@
FILE_TO_CONVERTER_MAP,
)
from daq_config_server.app.client import ConfigClient
from daq_config_server.models import ConfigModel, DisplayConfig
from daq_config_server.models import ConfigModel
from daq_config_server.models.lookup_tables import (
BeamlinePitchLookupTable,
BeamlineRollLookupTable,
)
from daq_config_server.models.lookup_tables.insertion_device import (
UndulatorEnergyGapLookupTable,
)
from daq_config_server.models.oav import DisplayConfig
from tests.constants import (
ServerFilePaths,
TestDataPaths,
Expand Down
42 changes: 42 additions & 0 deletions tests/test_data/test_jCameraManZoomLevels.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<JCameraManSettings>
<levels>
<zoomLevel>
<level>1.0</level>
<position>0</position>
<micronsPerXPixel>2.87</micronsPerXPixel>
<micronsPerYPixel>2.87</micronsPerYPixel>
</zoomLevel>
<zoomLevel>
<level>2.5</level>
<position>10</position>
<micronsPerXPixel>2.31</micronsPerXPixel>
<micronsPerYPixel>2.31</micronsPerYPixel>
</zoomLevel>
<zoomLevel>
<level>5.0</level>
<position>25</position>
<micronsPerXPixel>1.58</micronsPerXPixel>
<micronsPerYPixel>1.58</micronsPerYPixel>
</zoomLevel>
<zoomLevel>
<level>7.5</level>
<position>50</position>
<micronsPerXPixel>0.806</micronsPerXPixel>
<micronsPerYPixel>0.806</micronsPerYPixel>
</zoomLevel>
<zoomLevel>
<level>10.0</level>
<position>75</position>
<micronsPerXPixel>0.438</micronsPerXPixel>
<micronsPerYPixel>0.438</micronsPerYPixel>
</zoomLevel>
<zoomLevel>
<level>15.0</level>
<position>90</position>
<micronsPerXPixel>0.302</micronsPerXPixel>
<micronsPerYPixel>0.302</micronsPerYPixel>
</zoomLevel>
</levels>
<tolerance>1.0</tolerance>
</JCameraManSettings>
3 changes: 2 additions & 1 deletion tests/unit_tests/app/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@
TypeConversionError,
_get_mime_type,
)
from daq_config_server.models import ConfigModel, DisplayConfig
from daq_config_server.models import ConfigModel
from daq_config_server.models.lookup_tables import (
BeamlinePitchLookupTable,
GenericLookupTable,
)
from daq_config_server.models.lookup_tables.insertion_device import (
UndulatorEnergyGapLookupTable,
)
from daq_config_server.models.oav import DisplayConfig
from daq_config_server.testing import make_test_response

test_path = Path("test")
Expand Down
33 changes: 32 additions & 1 deletion tests/unit_tests/models/test_display_config_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

import pytest

from daq_config_server.models import DisplayConfig, DisplayConfigData
from daq_config_server.models.oav import DisplayConfig, DisplayConfigData
from daq_config_server.models.oav.zoom_levels import ZoomLevelData, ZoomLevels
from tests.constants import TestDataPaths


Expand Down Expand Up @@ -58,3 +59,33 @@ def test_display_config_with_wrong_zoom_levels_causes_error():
match="Zoom levels {1.0, 2.5} do not match required zoom levels: {1.0, 3.0}",
):
DisplayConfig(zoom_levels=zoom_levels, required_zoom_levels=({1.0, 3.0}))


def test_zoom_levels_config_model_can_parse_j_camera_man_file():
with open(TestDataPaths.TEST_JCAMERA_MAN_ZOOM_LEVELS) as f:
contents = f.read()
expected = ZoomLevels(
zoom_levels={
1.0: ZoomLevelData(
position=0, microns_per_x_pixel=2.87, microns_per_y_pixel=2.87
),
2.5: ZoomLevelData(
position=10, microns_per_x_pixel=2.31, microns_per_y_pixel=2.31
),
5.0: ZoomLevelData(
position=25, microns_per_x_pixel=1.58, microns_per_y_pixel=1.58
),
7.5: ZoomLevelData(
position=50, microns_per_x_pixel=0.806, microns_per_y_pixel=0.806
),
10.0: ZoomLevelData(
position=75, microns_per_x_pixel=0.438, microns_per_y_pixel=0.438
),
15.0: ZoomLevelData(
position=90, microns_per_x_pixel=0.302, microns_per_y_pixel=0.302
),
},
tolerance=1.0,
)
result = ZoomLevels.from_jcamera_man_zoom_levels(contents)
assert result == expected
3 changes: 2 additions & 1 deletion tests/unit_tests/models/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ def test_remove_comments_works_as_expected():
("camelCase", "camel_case"),
("_Camel_Case", "_camel_case"),
("CAMELCASE", "camelcase"),
("CAMELCAsE", "camelcas_e"),
("CAMELCAsE", "camelc_as_e"),
("micronsPerXPixel", "microns_per_x_pixel"),
],
)
def test_camel_to_snake_case_works_as_expected(camel_case: str, snake_case: str):
Expand Down
Loading