From a2fc5db7ea5e2792143bbf273e92b589aaa56a51 Mon Sep 17 00:00:00 2001 From: Max Rakitin Date: Thu, 23 May 2024 17:50:22 -0400 Subject: [PATCH 1/3] Implement Axis Caproto IOC --- pyproject.toml | 1 + src/srx_caproto_iocs/axis/__init__.py | 1 + src/srx_caproto_iocs/axis/caproto_ioc.py | 99 ++++++++++++++++++++++++ src/srx_caproto_iocs/utils.py | 4 + 4 files changed, 105 insertions(+) create mode 100644 src/srx_caproto_iocs/axis/__init__.py create mode 100644 src/srx_caproto_iocs/axis/caproto_ioc.py diff --git a/pyproject.toml b/pyproject.toml index 580497f..7cf71ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ dependencies = [ "h5py", "numpy", "ophyd", + "pillow", "pyepics", # does not work with 'setuptools' version higher than v66.1.1 "scikit-image[data]", ] diff --git a/src/srx_caproto_iocs/axis/__init__.py b/src/srx_caproto_iocs/axis/__init__.py new file mode 100644 index 0000000..3721a3c --- /dev/null +++ b/src/srx_caproto_iocs/axis/__init__.py @@ -0,0 +1 @@ +""""Axis Cameras Caproto IOC code.""" diff --git a/src/srx_caproto_iocs/axis/caproto_ioc.py b/src/srx_caproto_iocs/axis/caproto_ioc.py new file mode 100644 index 0000000..95f3f99 --- /dev/null +++ b/src/srx_caproto_iocs/axis/caproto_ioc.py @@ -0,0 +1,99 @@ +from __future__ import annotations + +import requests +from PIL import Image, ImageFont, ImageDraw +from io import BytesIO + +import textwrap +from enum import Enum + +from caproto import ChannelType +from caproto.server import pvproperty, run, template_arg_parser + +from ..base import CaprotoSaveIOC, check_args +from ..utils import now, save_image + +DEFAULT_MAX_LENGTH = 10_000_000 + + +class AxisSaveIOC(CaprotoSaveIOC): + """Axis caproto save IOC.""" + + common_kwargs = {"max_length": 255, "string_encoding": "utf-8"} + + key1 = pvproperty( + value="", + string_encoding="utf-8", + dtype=ChannelType.CHAR, + doc="key 1 for data 1", + max_length=255, + ) + data1 = pvproperty( + value=0, + dtype=ChannelType.DOUBLE, + doc="data 1", + max_length=DEFAULT_MAX_LENGTH, + ) + + def __init__(self, *args, camera_host=None, **kwargs): + self._camera_host = camera_host + print(f"{camera_host = }") + super().__init__(*args, **kwargs) + + async def _get_current_dataset( + self, *args, **kwargs + ): + url = f"http://{self._camera_host}/axis-cgi/jpg/image.cgi" + resp = requests.get(url) + img = Image.open(BytesIO(resp.content)) + + dataset = img + print(f"{now()}:\n{dataset.size}") + + return dataset + + @staticmethod + def saver(request_queue, response_queue): + """The saver callback for threading-based queueing.""" + while True: + received = request_queue.get() + filename = received["filename"] + data = received["data"] + # 'frame_number' is not used for this exporter. + frame_number = received["frame_number"] # noqa: F841 + try: + save_image(fname=filename, data=data, file_format="jpeg", mode="x") + print(f"{now()}: saved data into:\n {filename}") + + success = True + error_message = "" + except Exception as exc: # pylint: disable=broad-exception-caught + success = False + error_message = exc + print( + f"Cannot save file {filename!r} due to the following exception:\n{exc}" + ) + + response = {"success": success, "error_message": error_message} + response_queue.put(response) + + +if __name__ == "__main__": + parser, split_args = template_arg_parser( + default_prefix="", desc=textwrap.dedent(AxisSaveIOC.__doc__) + ) + + parser.add_argument( + "-c", "--camera-host", + help="The camera hostname, e.g., 'xf06bm-cam5'", + required=True, + type=str, + ) + + ioc_options, run_options = check_args(parser, split_args) + + print(f"{ioc_options = }") + print(f"{run_options = }") + + ioc = AxisSaveIOC(camera_host=parser.parse_args().camera_host, **ioc_options) + run(ioc.pvdb, **run_options) diff --git a/src/srx_caproto_iocs/utils.py b/src/srx_caproto_iocs/utils.py index 737fe3d..a7f3fff 100644 --- a/src/srx_caproto_iocs/utils.py +++ b/src/srx_caproto_iocs/utils.py @@ -15,6 +15,10 @@ def now(as_object=False): return _now.isoformat() +def save_image(fname, data, file_format="jpeg", mode="x"): + data.save(fname, file_format=file_format) + + def save_hdf5_zebra( fname, data, From 2eea66795df84e5fbcbd55b5203597418edf4cc1 Mon Sep 17 00:00:00 2001 From: Max Rakitin Date: Thu, 23 May 2024 17:59:31 -0400 Subject: [PATCH 2/3] STY: fix pre-commit issues --- src/srx_caproto_iocs/axis/caproto_ioc.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/srx_caproto_iocs/axis/caproto_ioc.py b/src/srx_caproto_iocs/axis/caproto_ioc.py index 95f3f99..286edd3 100644 --- a/src/srx_caproto_iocs/axis/caproto_ioc.py +++ b/src/srx_caproto_iocs/axis/caproto_ioc.py @@ -1,14 +1,12 @@ from __future__ import annotations -import requests -from PIL import Image, ImageFont, ImageDraw -from io import BytesIO - import textwrap -from enum import Enum +from io import BytesIO +import requests from caproto import ChannelType from caproto.server import pvproperty, run, template_arg_parser +from PIL import Image from ..base import CaprotoSaveIOC, check_args from ..utils import now, save_image @@ -18,7 +16,7 @@ class AxisSaveIOC(CaprotoSaveIOC): """Axis caproto save IOC.""" - + common_kwargs = {"max_length": 255, "string_encoding": "utf-8"} key1 = pvproperty( @@ -40,9 +38,7 @@ def __init__(self, *args, camera_host=None, **kwargs): print(f"{camera_host = }") super().__init__(*args, **kwargs) - async def _get_current_dataset( - self, *args, **kwargs - ): + async def _get_current_dataset(self, *args, **kwargs): url = f"http://{self._camera_host}/axis-cgi/jpg/image.cgi" resp = requests.get(url) img = Image.open(BytesIO(resp.content)) @@ -84,7 +80,8 @@ def saver(request_queue, response_queue): ) parser.add_argument( - "-c", "--camera-host", + "-c", + "--camera-host", help="The camera hostname, e.g., 'xf06bm-cam5'", required=True, type=str, From fde62d5440b4054a277314e1d83edfe8226e0c87 Mon Sep 17 00:00:00 2001 From: Max Rakitin Date: Thu, 23 May 2024 18:18:59 -0400 Subject: [PATCH 3/3] STY: fix the style + pylinting --- .pre-commit-config.yaml | 12 ++++++------ src/srx_caproto_iocs/__init__.py | 1 - src/srx_caproto_iocs/axis/__init__.py | 2 +- src/srx_caproto_iocs/axis/caproto_ioc.py | 8 +++----- src/srx_caproto_iocs/base.py | 6 ++++++ src/srx_caproto_iocs/example/__init__.py | 2 +- src/srx_caproto_iocs/sis_scaler/__init__.py | 1 - src/srx_caproto_iocs/utils.py | 3 ++- src/srx_caproto_iocs/zebra/__init__.py | 2 +- src/srx_caproto_iocs/zebra/caproto_ioc.py | 7 +++---- 10 files changed, 23 insertions(+), 21 deletions(-) delete mode 100644 src/srx_caproto_iocs/sis_scaler/__init__.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 199f43e..c306942 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: additional_dependencies: [black==23.*] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: "v4.5.0" + rev: "v4.6.0" hooks: - id: check-added-large-files - id: check-case-conflict @@ -40,7 +40,7 @@ repos: args: [--prose-wrap=always] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.2.1" + rev: "v0.4.5" hooks: - id: ruff args: ["--fix", "--show-fixes"] @@ -56,12 +56,12 @@ repos: # - pytest - repo: https://github.com/codespell-project/codespell - rev: "v2.2.6" + rev: "v2.3.0" hooks: - id: codespell - repo: https://github.com/shellcheck-py/shellcheck-py - rev: "v0.9.0.6" + rev: "v0.10.0.1" hooks: - id: shellcheck @@ -74,13 +74,13 @@ repos: exclude: .pre-commit-config.yaml - repo: https://github.com/abravalheri/validate-pyproject - rev: "v0.16" + rev: "v0.18" hooks: - id: validate-pyproject additional_dependencies: ["validate-pyproject-schema-store[all]"] - repo: https://github.com/python-jsonschema/check-jsonschema - rev: "0.28.0" + rev: "0.28.4" hooks: - id: check-dependabot - id: check-github-workflows diff --git a/src/srx_caproto_iocs/__init__.py b/src/srx_caproto_iocs/__init__.py index cdfb295..ef48611 100644 --- a/src/srx_caproto_iocs/__init__.py +++ b/src/srx_caproto_iocs/__init__.py @@ -4,7 +4,6 @@ srx-caproto-iocs: Caproto IOCs for the NSLS-II SRX beamline """ - from __future__ import annotations from ._version import version as __version__ diff --git a/src/srx_caproto_iocs/axis/__init__.py b/src/srx_caproto_iocs/axis/__init__.py index 3721a3c..e3b936f 100644 --- a/src/srx_caproto_iocs/axis/__init__.py +++ b/src/srx_caproto_iocs/axis/__init__.py @@ -1 +1 @@ -""""Axis Cameras Caproto IOC code.""" +"""Axis Cameras Caproto IOC code.""" diff --git a/src/srx_caproto_iocs/axis/caproto_ioc.py b/src/srx_caproto_iocs/axis/caproto_ioc.py index 286edd3..6b940fb 100644 --- a/src/srx_caproto_iocs/axis/caproto_ioc.py +++ b/src/srx_caproto_iocs/axis/caproto_ioc.py @@ -1,3 +1,4 @@ +# pylint: disable=duplicate-code from __future__ import annotations import textwrap @@ -17,8 +18,6 @@ class AxisSaveIOC(CaprotoSaveIOC): """Axis caproto save IOC.""" - common_kwargs = {"max_length": 255, "string_encoding": "utf-8"} - key1 = pvproperty( value="", string_encoding="utf-8", @@ -38,9 +37,9 @@ def __init__(self, *args, camera_host=None, **kwargs): print(f"{camera_host = }") super().__init__(*args, **kwargs) - async def _get_current_dataset(self, *args, **kwargs): + async def _get_current_dataset(self, *args, **kwargs): # pylint: disable=unused-argument url = f"http://{self._camera_host}/axis-cgi/jpg/image.cgi" - resp = requests.get(url) + resp = requests.get(url, timeout=10) img = Image.open(BytesIO(resp.content)) dataset = img @@ -56,7 +55,6 @@ def saver(request_queue, response_queue): filename = received["filename"] data = received["data"] # 'frame_number' is not used for this exporter. - frame_number = received["frame_number"] # noqa: F841 try: save_image(fname=filename, data=data, file_format="jpeg", mode="x") print(f"{now()}: saved data into:\n {filename}") diff --git a/src/srx_caproto_iocs/base.py b/src/srx_caproto_iocs/base.py index 412d33c..a50ccf7 100644 --- a/src/srx_caproto_iocs/base.py +++ b/src/srx_caproto_iocs/base.py @@ -151,6 +151,7 @@ async def _stage(self, instance, value): @stage.putter async def stage(self, *args, **kwargs): + """The stage method.""" return await self._stage(*args, **kwargs) async def _get_current_dataset(self, frame): @@ -244,6 +245,11 @@ class OphydDeviceWithCaprotoIOC(Device): def set(self, command): """The set method with values for staging and acquiring.""" + expected_old_value = None + expected_new_value = None + obj = None + cmd = None + # print(f"{now()}: {command = }") if command in [StageStates.STAGED.value, "stage"]: expected_old_value = StageStates.UNSTAGED.value diff --git a/src/srx_caproto_iocs/example/__init__.py b/src/srx_caproto_iocs/example/__init__.py index 5baeb5f..43189c5 100644 --- a/src/srx_caproto_iocs/example/__init__.py +++ b/src/srx_caproto_iocs/example/__init__.py @@ -1 +1 @@ -""""Example Caproto IOC code.""" +"""Example Caproto IOC code.""" diff --git a/src/srx_caproto_iocs/sis_scaler/__init__.py b/src/srx_caproto_iocs/sis_scaler/__init__.py deleted file mode 100644 index 479fe07..0000000 --- a/src/srx_caproto_iocs/sis_scaler/__init__.py +++ /dev/null @@ -1 +0,0 @@ -""""SIS scaler Caproto IOC code.""" diff --git a/src/srx_caproto_iocs/utils.py b/src/srx_caproto_iocs/utils.py index a7f3fff..c17dae2 100644 --- a/src/srx_caproto_iocs/utils.py +++ b/src/srx_caproto_iocs/utils.py @@ -15,7 +15,8 @@ def now(as_object=False): return _now.isoformat() -def save_image(fname, data, file_format="jpeg", mode="x"): +def save_image(fname, data, file_format="jpeg", mode="x"): # pylint: disable=unused-argument + """The function to export the image data (e.g., to a JPEG file.""" data.save(fname, file_format=file_format) diff --git a/src/srx_caproto_iocs/zebra/__init__.py b/src/srx_caproto_iocs/zebra/__init__.py index 19c633b..71a54ac 100644 --- a/src/srx_caproto_iocs/zebra/__init__.py +++ b/src/srx_caproto_iocs/zebra/__init__.py @@ -1 +1 @@ -""""Zebra Caproto IOC code.""" +"""Zebra Caproto IOC code.""" diff --git a/src/srx_caproto_iocs/zebra/caproto_ioc.py b/src/srx_caproto_iocs/zebra/caproto_ioc.py index c9ddd1b..398fffb 100644 --- a/src/srx_caproto_iocs/zebra/caproto_ioc.py +++ b/src/srx_caproto_iocs/zebra/caproto_ioc.py @@ -1,3 +1,4 @@ +# pylint: disable=duplicate-code from __future__ import annotations import textwrap @@ -162,9 +163,8 @@ class ZebraSaveIOC(CaprotoSaveIOC): # super().__init__(*args, **kwargs) # self._external_pvs = external_pvs - async def _get_current_dataset( - self, *args, **kwargs - ): # , frame, external_pv="enc1"): + async def _get_current_dataset(self, *args, **kwargs): # pylint: disable=unused-argument + # , frame, external_pv="enc1"): # client_context = Context() # (pvobject,) = await client_context.get_pvs(self._external_pvs[external_pv]) # print(f"{pvobject = }") @@ -192,7 +192,6 @@ def saver(request_queue, response_queue): filename = received["filename"] data = received["data"] # 'frame_number' is not used for this exporter. - frame_number = received["frame_number"] # noqa: F841 try: save_hdf5_zebra(fname=filename, data=data, mode="x") print(f"{now()}: saved data into:\n {filename}")