Skip to content

Commit

Permalink
feat: add GridWidthHeight (#129)
Browse files Browse the repository at this point in the history
* feat: add GridWidthHeight

* add exports

* don't warn

* docs: fix docs

* add test
  • Loading branch information
tlambert03 authored Aug 16, 2023
1 parent 7652953 commit 56f3394
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 16 deletions.
6 changes: 4 additions & 2 deletions docs/schema/axes.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,12 @@ Ways to describe a z-stack acquisition sequence.

Ways to describe a grid acquisition sequence.

::: useq.GridRelative
::: useq.GridRowsColumns
options:
members: []
::: useq.GridWidthHeight
options:
members: []

::: useq.GridFromEdges
options:
members: []
24 changes: 23 additions & 1 deletion src/useq/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
from typing import Any

from useq._actions import AcquireImage, Action, HardwareAutofocus
from useq._channel import Channel
from useq._grid import AnyGridPlan, GridFromEdges, GridRelative
from useq._grid import AnyGridPlan, GridFromEdges, GridRowsColumns, GridWidthHeight
from useq._hardware_autofocus import AnyAutofocusPlan, AutoFocusPlan, AxesBasedAF
from useq._mda_event import MDAEvent, PropertyTuple
from useq._mda_sequence import MDASequence
from useq._position import Position
from useq._time import (
AnyTimePlan,
MultiPhaseTimePlan,
TDurationLoops,
TIntervalDuration,
TIntervalLoops,
)
from useq._z import (
AnyZPlan,
ZAboveBelow,
ZAbsolutePositions,
ZRangeAround,
Expand All @@ -24,11 +28,15 @@
"Action",
"AnyAutofocusPlan",
"AnyGridPlan",
"AnyTimePlan",
"AnyZPlan",
"AutoFocusPlan",
"AxesBasedAF",
"Channel",
"GridFromEdges",
"GridRelative",
"GridRowsColumns",
"GridWidthHeight",
"HardwareAutofocus",
"MDAEvent",
"MDASequence",
Expand All @@ -49,3 +57,17 @@
# type ignores because pydantic-compat consumes the kwargs
MDAEvent.model_rebuild(MDASequence=MDASequence) # type: ignore [call-arg]
Position.model_rebuild(MDASequence=MDASequence) # type: ignore [call-arg]


def __getattr__(name: str) -> Any:
if name == "GridRelative":
from useq._grid import GridRowsColumns

# warnings.warn(
# "useq.GridRelative has been renamed to useq.GridFromEdges",
# DeprecationWarning,
# stacklevel=2,
# )

return GridRowsColumns
raise AttributeError(f"module {__name__} has no attribute {name}")
56 changes: 52 additions & 4 deletions src/useq/_grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ def _offset_y(self, dy: float) -> float:
return max(self.top, self.bottom)


class GridRelative(_GridPlan):
class GridRowsColumns(_GridPlan):
"""Yield relative delta increments to build a grid acquisition.
Attributes
Expand All @@ -275,8 +275,8 @@ class GridRelative(_GridPlan):
"""

# everything but fov_width and fov_height is immutable
rows: int = Field(..., frozen=True)
columns: int = Field(..., frozen=True)
rows: int = Field(..., frozen=True, ge=1)
columns: int = Field(..., frozen=True, ge=1)
relative_to: RelativeTo = Field(RelativeTo.center, frozen=True)

@property
Expand All @@ -302,4 +302,52 @@ def _offset_y(self, dy: float) -> float:
)


AnyGridPlan = Union[GridFromEdges, GridRelative]
GridRelative = GridRowsColumns


class GridWidthHeight(_GridPlan):
"""Yield relative delta increments to build a grid acquisition.
Attributes
----------
width: float
Minimum total width of the grid, in microns. (may be larger based on fov_width)
height: float
Minimum total height of the grid, in microns. (may be larger based on
fov_height)
relative_to : RelativeTo
Point in the grid to which the coordinates are relative. If "center", the grid
is centered around the origin. If "top_left", the grid is positioned such that
the top left corner is at the origin.
"""

width: float = Field(..., frozen=True, gt=0)
height: float = Field(..., frozen=True, gt=0)
relative_to: RelativeTo = Field(RelativeTo.center, frozen=True)

@property
def is_relative(self) -> bool:
return True

def _nrows(self, dy: float) -> int:
return math.ceil(self.height / dy)

def _ncolumns(self, dx: float) -> int:
return math.ceil(self.width / dx)

def _offset_x(self, dx: float) -> float:
return (
-((self._ncolumns(dx) - 1) * dx) / 2
if self.relative_to == RelativeTo.center
else 0.0
)

def _offset_y(self, dy: float) -> float:
return (
((self._nrows(dy) - 1) * dy) / 2
if self.relative_to == RelativeTo.center
else 0.0
)


AnyGridPlan = Union[GridFromEdges, GridRowsColumns, GridWidthHeight]
30 changes: 21 additions & 9 deletions tests/test_grid.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pytest

from useq import GridFromEdges, GridRelative
from useq import GridFromEdges, GridRowsColumns, GridWidthHeight
from useq._grid import OrderMode, _rect_indices, _spiral_indices

EXPECT = {
Expand Down Expand Up @@ -39,26 +39,26 @@ def test_spiral_indices() -> None:

def test_position_equality():
"""Order of grid positions should only change the order in which they are yielded"""
t1 = GridRelative(rows=3, columns=3, mode=OrderMode.spiral)
t1 = GridRowsColumns(rows=3, columns=3, mode=OrderMode.spiral)
spiral_pos = set(t1.iter_grid_positions(1, 1))

t2 = GridRelative(rows=3, columns=3, mode=OrderMode.row_wise)
t2 = GridRowsColumns(rows=3, columns=3, mode=OrderMode.row_wise)
row_pos = set(t2.iter_grid_positions(1, 1))

t3 = GridRelative(rows=3, columns=3, mode="row_wise_snake")
t3 = GridRowsColumns(rows=3, columns=3, mode="row_wise_snake")
snake_row_pos = set(t3.iter_grid_positions(1, 1))

t4 = GridRelative(rows=3, columns=3, mode=OrderMode.column_wise)
t4 = GridRowsColumns(rows=3, columns=3, mode=OrderMode.column_wise)
col_pos = set(t4.iter_grid_positions(1, 1))

t5 = GridRelative(rows=3, columns=3, mode=OrderMode.column_wise_snake)
t5 = GridRowsColumns(rows=3, columns=3, mode=OrderMode.column_wise_snake)
snake_col_pos = set(t5.iter_grid_positions(1, 1))

assert spiral_pos == row_pos == snake_row_pos == col_pos == snake_col_pos


def test_grid_type():
g1 = GridRelative(rows=2, columns=3)
g1 = GridRowsColumns(rows=2, columns=3)
assert [(g.x, g.y) for g in g1.iter_grid_positions(1, 1)] == [
(-1.0, 0.5),
(0.0, 0.5),
Expand All @@ -67,8 +67,19 @@ def test_grid_type():
(0.0, -0.5),
(-1.0, -0.5),
]
g2 = GridFromEdges(top=1, left=-1, bottom=-1, right=2)
assert [(g.x, g.y) for g in g2.iter_grid_positions(1, 1)] == [
assert g1.is_relative
g2 = GridWidthHeight(width=3, height=2, fov_height=1, fov_width=1)
assert [(g.x, g.y) for g in g2.iter_grid_positions()] == [
(-1.0, 0.5),
(0.0, 0.5),
(1.0, 0.5),
(1.0, -0.5),
(0.0, -0.5),
(-1.0, -0.5),
]
assert g2.is_relative
g3 = GridFromEdges(top=1, left=-1, bottom=-1, right=2)
assert [(g.x, g.y) for g in g3.iter_grid_positions(1, 1)] == [
(-1.0, 1.0),
(0.0, 1.0),
(1.0, 1.0),
Expand All @@ -82,6 +93,7 @@ def test_grid_type():
(1.0, -1.0),
(2.0, -1.0),
]
assert not g3.is_relative


def test_num_position_error() -> None:
Expand Down

0 comments on commit 56f3394

Please sign in to comment.