diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 923a8d7..2b02a61 100755 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -47,7 +47,7 @@ jobs: - name: Type checking with mypy run: | - mypy src + mypy src tests test: runs-on: ubuntu-latest diff --git a/.mypy.ini b/.mypy.ini new file mode 100644 index 0000000..6e4c0ac --- /dev/null +++ b/.mypy.ini @@ -0,0 +1,24 @@ +[mypy] +python_version = 3.11 +mypy_path = src +ignore_errors = True +ignore_missing_imports = True +disallow_untyped_defs = False + +[mypy-als_tiled.bl733.adapters.*] +ignore_errors = False +ignore_missing_imports = False +check_untyped_defs = True +disallow_untyped_defs = True +disallow_incomplete_defs = True +disallow_untyped_calls = True + +[mypy-tests.*] +ignore_errors = False +disallow_untyped_defs = False + +[mypy-fabio] +ignore_missing_imports = True + +[mypy-tiled.*] +ignore_missing_imports = True diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 613f192..39d2c11 100755 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v6.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -10,19 +10,25 @@ repos: - id: debug-statements - repo: https://github.com/psf/black - rev: 23.9.1 + rev: 26.3.1 hooks: - id: black language_version: python3 - repo: https://github.com/pycqa/isort - rev: 5.12.0 + rev: 8.0.1 hooks: - id: isort args: ["--profile", "black"] - repo: https://github.com/pycqa/flake8 - rev: 6.1.0 + rev: 7.3.0 hooks: - id: flake8 args: [--max-line-length=88, --extend-ignore=E203] + + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.19.1 + hooks: + - id: mypy + additional_dependencies: [types-cachetools] diff --git a/Containerfile b/Containerfile index 8d5f187..7ac25f2 100755 --- a/Containerfile +++ b/Containerfile @@ -1,4 +1,4 @@ -FROM ghcr.io/bluesky/tiled:0.2.3 AS base +FROM ghcr.io/bluesky/tiled:0.2.8 AS base USER root @@ -9,4 +9,4 @@ USER app COPY --chown=app:app pyproject.toml README.md ./ COPY --chown=app:app src/ ./src/ RUN python -m ensurepip -RUN python -m pip install --upgrade --no-cache-dir . +RUN python -m pip install --upgrade --no-cache-dir ".[bl733]" diff --git a/example_configs/bl733/config.yaml b/example_configs/bl733/config.yaml new file mode 100644 index 0000000..16d2b89 --- /dev/null +++ b/example_configs/bl733/config.yaml @@ -0,0 +1,21 @@ +file_extensions: + edf: application/x-edf + gb: application/x-gb + +# Placeholder for future exporters that would allow download of arrays as edf or gb +# To be determined if this should include download of metadata as txt files +#media_types: +# array: +# application/x-edf: als_tiled.bl733.adapters.edf:export_edf +# application/x-gb: als_tiled.bl733.adapters.gb:export_gb + +trees: + - path: / + tree: tiled.catalog:from_uri + args: + uri: ./data/catalog.db + readable_storage: [./data] + init_if_not_exists: true + adapters_by_mimetype: + application/x-edf: als_tiled.bl733.adapters.edf:EDFAdapter + application/x-gb: als_tiled.bl733.adapters.gb:GeneralBinaryPilatus2MAdapter diff --git a/pyproject.toml b/pyproject.toml index 04688b3..beae133 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,13 +22,14 @@ classifiers = [ ] requires-python = ">=3.11" dependencies = [ - + "tiled[client]>=0.2.8", ] [project.optional-dependencies] dev = [ "pytest>=7.0", "pytest-cov", + "pytest-asyncio", "black", "isort", "flake8", @@ -38,14 +39,14 @@ dev = [ test = [ "pytest>=7.0", "pytest-cov", + "pytest-asyncio", + "als_tiled[bl733,tiled-all]", ] - -tiled_all = [ - "tiled[all]" +tiled-all = [ + "tiled[all]>=0.2.8", ] - -tiled_client = [ - "tiled[client]" +bl733 = [ + "fabio", ] [project.urls] @@ -85,12 +86,6 @@ profile = "black" multi_line_output = 3 line_length = 88 -[tool.mypy] -python_version = "3.11" -warn_return_any = true -warn_unused_configs = true -disallow_untyped_defs = true - [tool.pytest.ini_options] minversion = "7.0" addopts = "-ra -q --cov=als_tiled --cov-report=term-missing" diff --git a/src/als_tiled/bl733/__init__.py b/src/als_tiled/bl733/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/src/als_tiled/bl733/adapters/__init__.py b/src/als_tiled/bl733/adapters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/als_tiled/bl733/adapters/edf.py b/src/als_tiled/bl733/adapters/edf.py new file mode 100644 index 0000000..e48d02d --- /dev/null +++ b/src/als_tiled/bl733/adapters/edf.py @@ -0,0 +1,76 @@ +import logging +from datetime import datetime +from typing import Any, Optional + +import fabio +from tiled.adapters.array import ArrayAdapter +from tiled.adapters.utils import init_adapter_from_catalog +from tiled.catalog.orm import Node +from tiled.structures.array import ArrayStructure +from tiled.structures.core import Spec, StructureFamily +from tiled.structures.data_source import DataSource +from tiled.type_aliases import JSON +from tiled.utils import path_from_uri + +from als_tiled.bl733.adapters.metadata import parse_txt_accompanying_edf + +logger = logging.getLogger(__name__) + + +class EDFAdapter(ArrayAdapter): + structure_family = StructureFamily.array + + def __init__( + self, + data_uri: str, + structure: Optional[ArrayStructure] = None, + metadata: Optional[JSON] = None, + specs: Optional[list[Spec]] = None, + **kwargs: Optional[Any], + ) -> None: + """Adapter for `.edf` files (e.g. PILATUS3 2M) at ALS beamline 7.3.3.""" + filepath = path_from_uri(data_uri) + logger.debug("Loading EDF file produced by ALS beamline 7.3.3: %s", filepath) + + with fabio.open(filepath) as edf_file: + array = edf_file.data + metadata_edf = edf_file.header + + date = datetime.strptime(metadata_edf["Date"], "%a %b %d %H:%M:%S %Y") + metadata_edf["Date"] = date.isoformat() + + metadata = { + **(metadata or {}), + **metadata_edf, + **parse_txt_accompanying_edf(filepath), + } + + edf_spec = Spec("als-bl733-edf", version="1.0") + specs = list(specs or []) + if edf_spec not in specs: + specs.append(edf_spec) + super().__init__( + array=array, + structure=structure or ArrayStructure.from_array(array), + metadata=metadata, + specs=specs, + **kwargs, + ) + + @classmethod + def from_catalog( + cls, + data_source: DataSource, + node: Node, + /, + **kwargs: Optional[Any], + ) -> "EDFAdapter": + return init_adapter_from_catalog(cls, data_source, node, **kwargs) + + @classmethod + def from_uris( + cls, + data_uri: str, + **kwargs: Optional[Any], + ) -> "EDFAdapter": + return cls(data_uri, **kwargs) diff --git a/src/als_tiled/bl733/adapters/gb.py b/src/als_tiled/bl733/adapters/gb.py new file mode 100644 index 0000000..c134398 --- /dev/null +++ b/src/als_tiled/bl733/adapters/gb.py @@ -0,0 +1,146 @@ +import logging +import pathlib +from datetime import datetime +from typing import Any, Optional + +import fabio +import numpy as np +from tiled.adapters.array import ArrayAdapter +from tiled.adapters.utils import init_adapter_from_catalog +from tiled.catalog.orm import Node +from tiled.structures.array import ArrayStructure +from tiled.structures.core import Spec, StructureFamily +from tiled.structures.data_source import DataSource +from tiled.type_aliases import JSON +from tiled.utils import path_from_uri + +from als_tiled.bl733.adapters.metadata import parse_txt_accompanying_edf + +logger = logging.getLogger(__name__) + +# Pixel dimensions for the PILATUS 2M detector at ALS beamline 7.3.3 +PILATUS_2M_PIXELS_X = 1475 +PILATUS_2M_PIXELS_Y = 1679 + + +class GeneralBinaryPilatus2MAdapter(ArrayAdapter): + structure_family = StructureFamily.array + + def __init__( + self, + data_uri: str, + structure: Optional[ArrayStructure] = None, + metadata: Optional[JSON] = None, + specs: Optional[list[Spec]] = None, + **kwargs: Optional[Any], + ) -> None: + """Adapter for a stitched detector image .gb produced at ALS beamline 7.3.3.""" + filepath_gb = path_from_uri(data_uri) + logger.debug("Loading GB file produced by ALS beamline 7.3.3: %s", filepath_gb) + data = np.fromfile(filepath_gb, dtype=" "GeneralBinaryPilatus2MAdapter": + return init_adapter_from_catalog(cls, data_source, node, **kwargs) + + @classmethod + def from_uris( + cls, + data_uri: str, + **kwargs: Optional[Any], + ) -> "GeneralBinaryPilatus2MAdapter": + return cls(data_uri, **kwargs) + + @staticmethod + def _read_edf(filepath_edf: pathlib.Path) -> tuple[dict[str, Any], datetime | None]: + """Read one EDF file and its companion .txt, returning (metadata, date).""" + metadata_txt = parse_txt_accompanying_edf(filepath_edf) + if not filepath_edf.is_file(): + logger.warning( + f"GeneralBinary file is missing accompanying EDF file {filepath_edf}." + ) + return metadata_txt, None + header = fabio.openheader(filepath_edf).header + date = datetime.strptime(header["Date"], "%a %b %d %H:%M:%S %Y") + return {**metadata_txt, **header}, date + + @staticmethod + def _parse_accompanying_metadata(filepath_gb: pathlib.Path) -> dict[str, Any]: + """Read the hi and lo EDF companions for a .gb file and merge their metadata.""" + filepath_edf_hi = pathlib.Path( + str(filepath_gb.with_suffix(".edf")).replace("sfloat", "hi") + ) + filepath_edf_lo = pathlib.Path( + str(filepath_gb.with_suffix(".edf")).replace("sfloat", "lo") + ) + + metadata_hi, date_hi = GeneralBinaryPilatus2MAdapter._read_edf(filepath_edf_hi) + metadata_lo, date_lo = GeneralBinaryPilatus2MAdapter._read_edf(filepath_edf_lo) + + combined_metadata = GeneralBinaryPilatus2MAdapter._combine_metadata( + metadata_hi, metadata_lo + ) + + date = None + if date_hi is not None and date_lo is not None: + date = date_hi if date_hi > date_lo else date_lo + elif date_hi is not None: + date = date_hi + elif date_lo is not None: + date = date_lo + if date is not None: + combined_metadata["Date"] = date.isoformat() + + return combined_metadata + + @staticmethod + def _combine_metadata( + metadata_hi: dict[str, Any], metadata_lo: dict[str, Any] + ) -> dict[str, Any]: + """Combine metadata from hi and lo EDF files. + + Keys with identical values are kept once. Keys with different values are + suffixed with _hi and _lo. + """ + combined_metadata = {} + for key in set(metadata_hi) | set(metadata_lo): + value_hi = metadata_hi.get(key) + value_lo = metadata_lo.get(key) + if value_hi == value_lo: + combined_metadata[key] = value_hi + else: + if value_hi is not None: + combined_metadata[f"{key}_hi"] = value_hi + if value_lo is not None: + combined_metadata[f"{key}_lo"] = value_lo + return combined_metadata diff --git a/src/als_tiled/bl733/adapters/metadata.py b/src/als_tiled/bl733/adapters/metadata.py new file mode 100644 index 0000000..f9d12a0 --- /dev/null +++ b/src/als_tiled/bl733/adapters/metadata.py @@ -0,0 +1,36 @@ +import logging +import pathlib +from typing import Any + +logger = logging.getLogger(__name__) + + +def parse_txt_accompanying_edf(filepath_edf: pathlib.Path) -> dict[str, Any]: + """Parse the .txt metadata file accompanying an EDF file at ALS beamline 7.3.3. + + Parameters + ---------- + filepath_edf: + Path to the .edf file. The companion .txt is expected at the same path + with the extension replaced by .txt. + """ + filepath_txt = filepath_edf.with_suffix(".txt") + + if not filepath_txt.is_file(): + logger.warning(f"{filepath_edf} has no corresponding .txt.") + return {} + + with open(filepath_txt) as f: + lines = f.readlines() + + # Lines have the format "key: value" or are bare values with no key. + metadata: dict[str, Any] = {} + unnamed_count = 0 + for line in lines: + before, sep, after = line.partition(":") + if sep: + metadata[before.strip()] = after.strip() + elif before.strip() != "!0": + metadata[f"unnamed_{unnamed_count}"] = before.strip() + unnamed_count += 1 + return metadata diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/bl733/__init__.py b/tests/bl733/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/bl733/conftest.py b/tests/bl733/conftest.py new file mode 100644 index 0000000..e248736 --- /dev/null +++ b/tests/bl733/conftest.py @@ -0,0 +1,398 @@ +import fabio +import numpy as np +import pytest + +from als_tiled.bl733.adapters.gb import PILATUS_2M_PIXELS_X, PILATUS_2M_PIXELS_Y + +SAMPLE_TXT_CONTENT = """\ +401.000 +10440.000 +235561.000 +Izero: 401.000 +I1 normalization: 235561.000 +Diode normalization: 10440.000 +Normalize by: Diode +Exposure time s: 10.000 +ALS Proposal #: 00622 +ALS ESAF #: 00622-001 +PI: PILastname +Calibration image path: /spot733-data/raw/userdata +Motors: 64 +Sample X Stage: -88.245200 +Sample Y Stage: 3.128650 +Sample X Stage Large: 5.000500 +Sample Y Stage Large: 34.997600 +Sample Alpha Stage: 0.130909 +Sample Phi Stage: 0.051610 +M201 Feedback: 0.010372 +M1 Pitch: 0.202033 +M1 Bend: 48.399999 +BS X: -1.357906 +BS Y: 9.981358 +Sample Y Stage Fine XPS: 0.000000 +Sample Y Stage Labjack: 0.000000 +Sample Rotation Stage: 0.000000 +Slit1 top: 9.263500 +Slit1 bottom: 11.732500 +Slit1 right: 11.109500 +Slit1 left: 11.356000 +Exit Slit top: 7.781500 +Exit Slit bottom: 10.976500 +Exit Slit left: 6.961500 +Exit Slit right: 8.903000 +Sample Y Stage Robot: 0.000000 +Detector Horizontal: 0.000000 +Detector Vertical: 0.000000 +GIWAXS beamstop X: 0.000000 +GIWAXS beamstop Y: 0.000000 +Beamstop X: 0.000000 +Beamstop Y: 0.000000 +Detector Left Motor: 0.000000 +Detector Right Motor: 0.000000 +Motorized Lab Jack: 0.000000 +M1 Alignment Tune: 0.202033 +print head height: 0.000000 +Rotation New: 0.000000 +printer roll: 0.000000 +EZ fast tension stage: 0.000000 +Motorized Lab Jack1: 0.000000 +Sample Rotation Stage ESP: 0.000000 +Printing motor: 0.000000 +GIWAXS beamstop Y thorlabs: 0.000000 +Sample Y Stage Arthur: 0.000000 +Flight Tube Horizontal: 0.000000 +Flight Tube Vertical: 0.000000 +Hacked Ager Stage: 0.000000 +Sample Rotation Stage Miller: 0.000000 +Mono Angle: 0.000000 +Xtal2 Pico 1 Feedback: 0.000000 +Xtal2 Pico 2 Feedback: 0.000000 +Xtal2 Pico 3 Feedback: 0.000000 +Xtal2 Pico 1: 0.000000 +Xtal2 Pico 2: 0.000000 +Xtal2 Pico 3: 0.000000 +Sample Y Stage_old: 0.000000 +AO Waveform: 0.000000 +AO0Traingle: 0.000000 +AO1SquareWave: 0.000000 +Lamda Zup heater: 0.000000 +thermofeedback: 0.000000 +spinCoaterMotor: 0.000000 +BK Power Supply: 0.000000 +BK Control: 0.000000 +EPOS: 0.000000 +EPOS2: 0.000000 +DIOs: 14 +SAXS Protector: 0.000000 +Beamline Shutter Closed: 0.000000 +Beam Current Over Threshold: 1.000000 +Slit 1 in Position: 1.000000 +Slit 2 in Position: 1.000000 +Temp Beamline Shutter Open: 0.000000 +Beamline Shutter Open: 1.000000 +Feedback Interlock: 1.000000 +Beamline Pass Beam: 1.000000 +VacuumBadAtIG304: 0.000000 +Gate Shutter: 0.000000 +Bruker pulses: 0.000000 +Slit Top Good: 1.000000 +Slit Bottom Good: 1.000000 +AIs: 38 +Izero: 401.000000 +GiSAXS Beamstop: 0.521687 +slit1 top current: NaN +slit1 bottom current: NaN +Beam Current: 499.509338 +Beamline Shutter AI: 1.000000 +Beamline Pass Beam AI: 1.000000 +Vertical Beam Position: NaN +IG304Epics: 11.052951 +Izero AI: 0.000000 +I1 AI: -0.235249 +PHI Alignment Beamstop: 0.235471 +AI Channel 6: -0.238456 +AI Channel 7: 0.003273 +thermocoupleAI3: -2.696180 +Pyro: NaN +Raytec - Room Temp: 0.000000 +BK Amps: NaN +BK Volts: NaN +BK Power: NaN +BK Resistance: NaN +Pilatus 300KW trigger pulse: 0.000000 +Pilatus 1M Trigger Pulse: 0.000000 +PCO Invert: 0.000000 +Gate: 0.000000 +I1: 235561.000000 +GiSAXS Beamstop Counter: 522894.000000 +Sum of Slit Current: 10.633427 +Pilatus 100K exp out: 0.000000 +Kramer strain data: 0.000000 +Xtal2 Pico 1: NaN +Xtal2 Pico 2: NaN +Xtal2 Pico 3: NaN +M1 Pitch: 0.202057 +ABS(Vertical Beam Position): NaN +AIat6221 Channel 6: NaN +DCVoltageMonitor: 0.000000 +TC: 0.000000 +!0 +""" + +SAMPLE_TXT_CONTENT_HI = """\ +1070.000 +80.231 +1320.000 +Izero: 1070.000 +I1 normalization: 1320.000 +Diode normalization: 80.231 +Normalize by: Diode +Exposure time s: 0.100 +ALS Proposal #: ALS-00000 +ALS ESAF #: ALS-00000-000 +PI: PILastname +Calibration image path: /spot733-data/raw/userdata +Motors: 71 +Vertical Lift: 100.000000 +Sample X Stage: -6.894300 +Sample Y Stage: 12.023278 +Sample X Stage Large: -25.500000 +Sample Y Stage large: 26.053500 +Sample Alpha Stage: 0.007603 +Sample Phi Stage: 0.000000 +Kapton blocker: 0.000000 +Pinhole Vertical: 0.000000 +Pinhole Horizontal: 0.000000 +M201 Feedback: -0.898026 +M1 Pitch: 0.183040 +M1 Bend: 48.399999 +BS X: -2.540780 +BS Y: 10.369954 +Sample Y Stage Labjack: 0.000000 +Sample Rotation Stage: 0.000000 +Slit1 top: 8.854500 +Slit1 bottom: 11.534500 +Slit1 right: 10.679500 +Slit1 left: 11.434500 +Exit Slit top: 7.082500 +Exit Slit bottom: 7.384500 +Exit Slit left: 7.683000 +Exit Slit right: 8.030000 +Kapton Blocker Horizontal: 0.000000 +Kapton Blocker Vertical: 0.000000 +Sample Y Stage Robot: 0.000000 +Detector Horizontal: 0.000000 +Detector Vertical: 0.000000 +GIWAXS beamstop X: 0.000000 +GIWAXS beamstop Y: 0.000000 +Beamstop X: 0.000000 +Beamstop Y: 0.000000 +Detector Left Motor: 0.000000 +Detector Right Motor: 0.000000 +Motorized Lab Jack: 0.000000 +M1 Alignment Tune: 0.183040 +print head height: 0.000000 +Rotation New: 0.000000 +printer roll: 0.000000 +EZ fast tension stage: 0.000000 +Motorized Lab Jack1: 0.000000 +Sample Rotation Stage ESP: 0.000000 +Printing motor: 0.000000 +GIWAXS beamstop Y thorlabs: 0.000000 +Sample Y Stage Arthur: 0.000000 +Flight Tube Horizontal: 0.000000 +Flight Tube Vertical: 0.000000 +Hacked Ager Stage: 0.000000 +Sample Rotation Stage Miller: 0.000000 +Mono Angle: 0.000000 +Xtal2 Pico 1 Feedback: 0.000000 +Xtal2 Pico 2 Feedback: 0.000000 +Xtal2 Pico 3 Feedback: 0.000000 +Xtal2 Pico 1: 0.000000 +Xtal2 Pico 2: 0.000000 +Xtal2 Pico 3: 0.000000 +Sample Y Stage_old: 0.000000 +AO Waveform: 0.000000 +AO0Traingle: 0.000000 +AO1SquareWave: 0.000000 +Lamda Zup heater: 0.000000 +thermofeedback: 0.000000 +spinCoaterMotor: 0.000000 +BK Power Supply: 0.000000 +BK Control: 0.000000 +EPOS: 0.000000 +EPOS2: 0.000000 +Fake Motor 1: 0.000000 +Fake Motor 2: 0.000000 +DIOs: 14 +SAXS Protector: 0.000000 +Beamline Shutter Closed: 0.000000 +Beam Current Over Threshold: 1.000000 +Slit 1 in Position: 1.000000 +Slit 2 in Position: 1.000000 +Temp Beamline Shutter Open: 0.000000 +Beamline Shutter Open: 1.000000 +Feedback Interlock: 0.000000 +Beamline Pass Beam: 1.000000 +VacuumBadAtIG304: 0.000000 +Gate Shutter: 0.000000 +Bruker pulses: 0.000000 +Slit Top Good: 1.000000 +Slit Bottom Good: 1.000000 +AIs: 39 +Izero: 1070.000000 +GiSAXS Beamstop: 0.378450 +slit1 top current: NaN +slit1 bottom current: NaN +Beam Current: 500.487475 +Beamline Shutter AI: 1.000000 +Beamline Pass Beam AI: 1.000000 +Vertical Beam Position: NaN +IG304Epics: 59.738999 +Izero AI: 0.000000 +I1 AI: -0.124054 +PHI Alignment Beamstop: 0.117137 +AI Channel 6: -0.087247 +AI Channel 7: 0.003600 +thermocoupleAI3: -2.696388 +Pyro: NaN +Raytec - Room Temp: 0.000000 +BK Amps: NaN +BK Volts: NaN +BK Power: NaN +BK Resistance: NaN +Pilatus 300KW trigger pulse: 0.000000 +Pilatus 1M Trigger Pulse: 0.000000 +PCO Invert: 0.000000 +Gate: 0.000000 +I1: 1320.000000 +GiSAXS Beamstop Counter: 4007.000000 +Sum of Slit Current: 4.565782 +Pilatus 100K exp out: 0.000000 +Kramer strain data: 0.000000 +Xtal2 Pico 1: NaN +Xtal2 Pico 2: NaN +Xtal2 Pico 3: NaN +M1 Pitch: 0.183040 +ABS(Vertical Beam Position): NaN +AIat6221 Channel 6: NaN +DCVoltageMonitor: 0.000000 +TC: 0.000000 +Fake Motor 1: 0.000000 +!0 +""" + +# Keys that differ between hi and lo are replaced; keys only in hi or lo are appended. +# All keys in these files are shared between hi and lo — only values differ. +SAMPLE_TXT_CONTENT_LO = ( + SAMPLE_TXT_CONTENT_HI + # Unnamed header lines (Izero, Diode, I1 raw counts) + .replace("1070.000\n80.231\n1320.000\n", "1071.000\n80.199\n1325.000\n") + # Named scan metadata + .replace("Izero: 1070.000\n", "Izero: 1071.000\n") + .replace("I1 normalization: 1320.000\n", "I1 normalization: 1325.000\n") + .replace("Diode normalization: 80.231\n", "Diode normalization: 80.199\n") + # Motor readbacks that drifted between hi and lo exposures + .replace("M201 Feedback: -0.898026\n", "M201 Feedback: -0.898142\n") + .replace( + "M1 Pitch: 0.183040\n", "M1 Pitch: 0.183044\n" + ) # two occurrences, both change + .replace("M1 Alignment Tune: 0.183040\n", "M1 Alignment Tune: 0.183044\n") + # AI channel readbacks + .replace("Izero: 1070.000000\n", "Izero: 1071.000000\n") + .replace("GiSAXS Beamstop: 0.378450\n", "GiSAXS Beamstop: 0.378298\n") + .replace("Beam Current: 500.487475\n", "Beam Current: 500.640318\n") + .replace("I1 AI: -0.124054\n", "I1 AI: -0.124429\n") + .replace("PHI Alignment Beamstop: 0.117137\n", "PHI Alignment Beamstop: 0.117747\n") + .replace("AI Channel 6: -0.087247\n", "AI Channel 6: -0.087482\n") + .replace("AI Channel 7: 0.003600\n", "AI Channel 7: 0.003118\n") + .replace("thermocoupleAI3: -2.696388\n", "thermocoupleAI3: -2.696346\n") + .replace("I1: 1320.000000\n", "I1: 1325.000000\n") + .replace( + "GiSAXS Beamstop Counter: 4007.000000\n", + "GiSAXS Beamstop Counter: 3988.000000\n", + ) + .replace("Sum of Slit Current: 4.565782\n", "Sum of Slit Current: 4.569032\n") +) + +SAMPLE_EDF_HEADER = { + "HeaderID": "EH:000001:000000:000000", + "Image": "1", + "VersionNumber": "0.10", + "ByteOrder": "LowByteFirst", + "DataType": "SignedInteger", + "Dim_1": "1475", + "Dim_2": "1679", + "Date": "Mon Mar 25 17:06:51 2024", + "count_time": "10.000000000", + "title": "# Pixel_size 172e-6 m x 172e-6 m", + "run": "0", +} + +# Hi is acquired after lo; the GB adapter selects the later date. +SAMPLE_EDF_HEADER_HI = { + **SAMPLE_EDF_HEADER, + "count_time": "0.100000001", + "Date": "Wed Oct 29 20:15:23 2025", +} +SAMPLE_EDF_HEADER_LO = { + **SAMPLE_EDF_HEADER, + "count_time": "0.100000001", + "Date": "Wed Oct 29 20:15:16 2025", +} + +SAMPLE_EDF_DATA = np.arange( + PILATUS_2M_PIXELS_X * PILATUS_2M_PIXELS_Y, dtype=np.int32 +).reshape(PILATUS_2M_PIXELS_Y, PILATUS_2M_PIXELS_X) + +SAMPLE_GB_DATA = np.arange( + PILATUS_2M_PIXELS_X * PILATUS_2M_PIXELS_Y, dtype=" dict[str, str]: """Sample data for testing.""" return {"test": "data"} diff --git a/tests/test_main.py b/tests/test_main.py deleted file mode 100644 index 4b766ab..0000000 --- a/tests/test_main.py +++ /dev/null @@ -1,2 +0,0 @@ -def test_main_function(): - pass