From 7cc1c968d25398977190753a562175d0aa6be612 Mon Sep 17 00:00:00 2001 From: Talley Lambert Date: Sun, 7 Jul 2024 08:49:38 -0400 Subject: [PATCH] refactor: slight changes to well plate registry --- src/useq/__init__.py | 10 +--- src/useq/_plate.py | 102 ++------------------------------ src/useq/_plate_registry.py | 112 ++++++++++++++++++++++++++++++++++++ tests/test_well_plate.py | 7 ++- 4 files changed, 124 insertions(+), 107 deletions(-) create mode 100644 src/useq/_plate_registry.py diff --git a/src/useq/__init__.py b/src/useq/__init__.py index 4fa372fc..afa0c625 100644 --- a/src/useq/__init__.py +++ b/src/useq/__init__.py @@ -14,12 +14,8 @@ from useq._hardware_autofocus import AnyAutofocusPlan, AutoFocusPlan, AxesBasedAF from useq._mda_event import MDAEvent, PropertyTuple from useq._mda_sequence import MDASequence -from useq._plate import ( - WellPlate, - WellPlatePlan, - known_well_plate_keys, - register_well_plates, -) +from useq._plate import WellPlate, WellPlatePlan +from useq._plate_registry import register_well_plates, registered_well_plate_keys from useq._position import Position, RelativePosition from useq._time import ( AnyTimePlan, @@ -42,6 +38,7 @@ "AcquireImage", "Action", "register_well_plates", + "registered_well_plate_keys", "AnyAutofocusPlan", "AnyGridPlan", "AnyTimePlan", @@ -54,7 +51,6 @@ "GridRowsColumns", "GridWidthHeight", "HardwareAutofocus", - "known_well_plate_keys", "MDAEvent", "MDASequence", "MultiPhaseTimePlan", diff --git a/src/useq/_plate.py b/src/useq/_plate.py index 9ace2e1a..f336fe5e 100644 --- a/src/useq/_plate.py +++ b/src/useq/_plate.py @@ -4,11 +4,10 @@ from contextlib import suppress from functools import cached_property from typing import ( - TYPE_CHECKING, Any, + Callable, Iterable, List, - Mapping, Sequence, Tuple, Union, @@ -23,20 +22,9 @@ from useq._base_model import FrozenModel from useq._grid import GridRowsColumns, RandomPoints, Shape, _PointsPlan +from useq._plate_registry import _PLATE_REGISTRY from useq._position import Position, PositionBase, RelativePosition -if TYPE_CHECKING: - from collections.abc import Callable - from typing import Required, TypedDict - - class KnownPlateKwargs(TypedDict, total=False): - rows: Required[int] - columns: Required[int] - well_spacing: Required[tuple[float, float] | float] - well_size: tuple[float, float] | float | None - circular_wells: bool - name: str - class _SliceType: @classmethod @@ -120,7 +108,7 @@ def from_str(cls, name: str) -> WellPlate: Use `useq.register_well_plates` to add new plates to the registry. """ try: - obj = _KNOWN_PLATES[name] + obj = _PLATE_REGISTRY[name] except KeyError as e: raise ValueError( f"Unknown plate name {name!r}. " @@ -140,7 +128,8 @@ class WellPlatePlan(FrozenModel, Sequence[Position]): ---------- plate : WellPlate | str | int The well-plate definition. Minimally including rows, columns, and well spacing. - If expressed as a string, it is assumed to be a key in `WellPlate.KNOWN_PLATES`. + If expressed as a string, it is assumed to be a key in + `useq.registered_well_plate_keys`. a1_center_xy : tuple[float, float] The stage coordinates in µm of the center of well A1 (top-left corner). rotation : float | None @@ -464,84 +453,3 @@ def _index_to_row_name(index: int) -> str: name = chr(index % 26 + 65) + name index = index // 26 - 1 return name - - -# ---------------------------- Known Plates ---------------------------- - -PlateOrKwargs = Union["KnownPlateKwargs", WellPlate] -_KNOWN_PLATES: dict[str, PlateOrKwargs] = { - "6-well": {"rows": 2, "columns": 3, "well_spacing": 39.12, "well_size": 34.8}, - "12-well": {"rows": 3, "columns": 4, "well_spacing": 26, "well_size": 22}, - "24-well": {"rows": 4, "columns": 6, "well_spacing": 19, "well_size": 15.6}, - "48-well": {"rows": 6, "columns": 8, "well_spacing": 13, "well_size": 11.1}, - "96-well": {"rows": 8, "columns": 12, "well_spacing": 9, "well_size": 6.4}, - "384-well": { - "rows": 16, - "columns": 24, - "well_spacing": 4.5, - "well_size": 3.4, - "circular_wells": False, - }, - "1536-well": { - "rows": 32, - "columns": 48, - "well_spacing": 2.25, - "well_size": 1.7, - "circular_wells": False, - }, -} - - -@overload -def register_well_plates( - plates: Mapping[str, PlateOrKwargs], - /, - **kwargs: PlateOrKwargs, -) -> None: ... -@overload -def register_well_plates( - plates: Iterable[tuple[str, PlateOrKwargs]], - /, - **kwargs: PlateOrKwargs, -) -> None: ... -@overload -def register_well_plates(**kwargs: PlateOrKwargs) -> None: ... -def register_well_plates( - plates: Mapping[str, PlateOrKwargs] | Iterable[tuple[str, PlateOrKwargs]] = (), - /, - **kwargs: PlateOrKwargs, -) -> None: - """Register well-plate definitions to allow lookup by key. - - Added keys will override existing keys if they already exist. - - Values may either be WellPlate instances, or dictionaries with the following keys: - - rows: Required[int] - - columns: Required[int] - - well_spacing: Required[tuple[float, float]] - - well_size: tuple[float, float] | None - - circular_wells: bool - - name: str - - Examples - -------- - >>> import useq - >>> useq.register_well_plates( - ... { - ... "custom-square-plate": { - ... "rows": 8, "columns": 8, "well_spacing": 9.3, "well_size": 7.1 - ... }, - ... "silly-plate": {"rows": 1, "columns": 1, "well_spacing": 100} - ... } - ... ) - """ - _KNOWN_PLATES.update(plates, **kwargs) - - -def known_well_plate_keys() -> set[str]: - """Return a set of all registered well-plate keys. - - These keys may be used as an argument to `WellPlatePlan.plate` to select a plate - definition. - """ - return set(_KNOWN_PLATES) diff --git a/src/useq/_plate_registry.py b/src/useq/_plate_registry.py new file mode 100644 index 00000000..03bf0497 --- /dev/null +++ b/src/useq/_plate_registry.py @@ -0,0 +1,112 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, overload + +if TYPE_CHECKING: + from typing import Iterable, Mapping, Required, TypeAlias, TypedDict + + from useq._plate import WellPlate + + class KnownPlateKwargs(TypedDict, total=False): + rows: Required[int] + columns: Required[int] + well_spacing: Required[tuple[float, float] | float] + well_size: tuple[float, float] | float | None + circular_wells: bool + name: str + + PlateOrKwargs: TypeAlias = KnownPlateKwargs | WellPlate + + +_PLATE_REGISTRY: dict[str, PlateOrKwargs] = { + "6-well": {"rows": 2, "columns": 3, "well_spacing": 39.12, "well_size": 34.8}, + "12-well": {"rows": 3, "columns": 4, "well_spacing": 26, "well_size": 22}, + "24-well": {"rows": 4, "columns": 6, "well_spacing": 19, "well_size": 15.6}, + "48-well": {"rows": 6, "columns": 8, "well_spacing": 13, "well_size": 11.1}, + "96-well": {"rows": 8, "columns": 12, "well_spacing": 9, "well_size": 6.4}, + "384-well": { + "rows": 16, + "columns": 24, + "well_spacing": 4.5, + "well_size": 3.4, + "circular_wells": False, + }, + "1536-well": { + "rows": 32, + "columns": 48, + "well_spacing": 2.25, + "well_size": 1.7, + "circular_wells": False, + }, + "coverslip-18mm-square": { + "rows": 1, + "columns": 1, + "well_spacing": 0.0, + "well_size": 18.0, + "circular_wells": False, + "name": "18mm coverslip", + }, + "coverslip-22mm-square": { + "rows": 1, + "columns": 1, + "well_spacing": 0.0, + "well_size": 22.0, + "circular_wells": False, + "name": "22mm coverslip", + }, +} + + +@overload +def register_well_plates( + plates: Mapping[str, PlateOrKwargs], + /, + **kwargs: PlateOrKwargs, +) -> None: ... +@overload +def register_well_plates( + plates: Iterable[tuple[str, PlateOrKwargs]], + /, + **kwargs: PlateOrKwargs, +) -> None: ... +@overload +def register_well_plates(**kwargs: PlateOrKwargs) -> None: ... +def register_well_plates( + plates: Mapping[str, PlateOrKwargs] | Iterable[tuple[str, PlateOrKwargs]] = (), + /, + **kwargs: PlateOrKwargs, +) -> None: + """Register well-plate definitions to allow lookup by key. + + Added keys will override existing keys if they already exist. + + Values may either be WellPlate instances, or dictionaries with the following keys: + - rows: Required[int] + - columns: Required[int] + - well_spacing: Required[tuple[float, float]] + - well_size: tuple[float, float] | None + - circular_wells: bool + - name: str + + Examples + -------- + >>> import useq + >>> useq.register_well_plates( + ... { + ... "custom-square-plate": { + ... "rows": 8, "columns": 8, "well_spacing": 9.3, "well_size": 7.1 + ... }, + ... "silly-plate": {"rows": 1, "columns": 1, "well_spacing": 100} + ... } + ... ) + """ + _PLATE_REGISTRY.update(plates, **kwargs) + + +def registered_well_plate_keys() -> set[str]: + """Return a set of all registered well-plate keys. + + These keys may be used as an argument to `WellPlatePlan.plate` to select a plate + definition. + """ + return set(_PLATE_REGISTRY) diff --git a/tests/test_well_plate.py b/tests/test_well_plate.py index 1e197310..2133a246 100644 --- a/tests/test_well_plate.py +++ b/tests/test_well_plate.py @@ -2,7 +2,7 @@ import pytest import useq -from useq import _plate +from useq import _plate, _plate_registry def test_plate_plan() -> None: @@ -68,7 +68,8 @@ def test_plate_errors() -> None: def test_custom_plate(monkeypatch: pytest.MonkeyPatch) -> None: plates: dict = {} - monkeypatch.setattr(_plate, "_KNOWN_PLATES", plates) + monkeypatch.setattr(_plate_registry, "_PLATE_REGISTRY", plates) + monkeypatch.setattr(_plate, "_PLATE_REGISTRY", plates) useq.register_well_plates( { @@ -79,7 +80,7 @@ def test_custom_plate(monkeypatch: pytest.MonkeyPatch) -> None: myplate={"rows": 8, "columns": 8, "well_spacing": 9, "well_size": 10}, ) assert "myplate" in plates - assert "silly" in useq.known_well_plate_keys() + assert "silly" in useq.registered_well_plate_keys() with pytest.raises(ValueError, match="Unknown plate name"): useq.WellPlatePlan(plate="bad", a1_center_xy=(0, 0))