Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Remove NoX plans #102

Merged
merged 9 commits into from
Jul 20, 2023
Merged
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
10 changes: 2 additions & 8 deletions src/useq/__init__.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
from useq._actions import AcquireImage, Action, HardwareAutofocus
from useq._channel import Channel
from useq._grid import AnyGridPlan, GridFromEdges, GridRelative, NoGrid
from useq._hardware_autofocus import AnyAutofocusPlan, AutoFocusPlan, AxesBasedAF, NoAF
from useq._grid import AnyGridPlan, GridFromEdges, GridRelative
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 (
MultiPhaseTimePlan,
NoT,
TDurationLoops,
TIntervalDuration,
TIntervalLoops,
)
from useq._z import (
NoZ,
ZAboveBelow,
ZAbsolutePositions,
ZRangeAround,
Expand All @@ -35,10 +33,6 @@
"MDAEvent",
"MDASequence",
"MultiPhaseTimePlan",
"NoAF",
"NoGrid",
"NoT",
"NoZ",
"Position",
"PropertyTuple",
"TDurationLoops",
Expand Down
17 changes: 1 addition & 16 deletions src/useq/_grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,6 @@ def iter_grid_positions(
for r, c in _INDEX_GENERATORS[self.mode](rows, cols):
yield GridPosition(x0 + c * dx, y0 - r * dy, r, c, self.is_relative)

def __bool__(self) -> bool:
return True

def __len__(self) -> int:
return len(list(self.iter_grid_positions(1, 1)))

Expand Down Expand Up @@ -247,16 +244,4 @@ def _offset_y(self, dy: float) -> float:
)


class NoGrid(_GridPlan):
"""Don't acquire a grid."""

def iter_grid_positions(
self, fov_width: float, fov_height: float
) -> Iterator[GridPosition]:
return iter([])

def __bool__(self) -> bool:
return False


AnyGridPlan = Union[GridFromEdges, GridRelative, NoGrid]
AnyGridPlan = Union[GridFromEdges, GridRelative]
16 changes: 2 additions & 14 deletions src/useq/_hardware_autofocus.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Optional, Tuple, Union
from typing import Any, Optional, Tuple

from pydantic import PrivateAttr

Expand Down Expand Up @@ -84,16 +84,4 @@ def should_autofocus(self, event: MDAEvent) -> bool:
)


class NoAF(AutoFocusPlan):
"""No hardware autofocus plan."""

autofocus_device_name: str = "__no_autofocus__"

def __bool__(self) -> bool:
return False

def should_autofocus(self, event: MDAEvent) -> bool:
return False


AnyAutofocusPlan = Union[AxesBasedAF, NoAF]
AnyAutofocusPlan = AxesBasedAF
65 changes: 34 additions & 31 deletions src/useq/_mda_sequence.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

from itertools import product
from typing import Any, Dict, Iterator, Optional, Sequence, Tuple, Union, cast
from typing import Any, Dict, Iterator, Optional, Sequence, Tuple, cast
from uuid import UUID, uuid4
from warnings import warn

Expand All @@ -11,13 +11,13 @@

from useq._base_model import UseqModel
from useq._channel import Channel # noqa: TCH001
from useq._grid import AnyGridPlan, GridPosition, NoGrid
from useq._hardware_autofocus import AnyAutofocusPlan, AxesBasedAF, NoAF
from useq._grid import AnyGridPlan, GridPosition # noqa: TCH001
from useq._hardware_autofocus import AnyAutofocusPlan, AxesBasedAF
from useq._mda_event import Channel as EventChannel
from useq._mda_event import MDAEvent
from useq._position import Position
from useq._time import AnyTimePlan, NoT
from useq._z import AnyZPlan, NoZ
from useq._time import AnyTimePlan # noqa: TCH001
from useq._z import AnyZPlan # noqa: TCH001

TIME = "t"
CHANNEL = "c"
Expand Down Expand Up @@ -48,23 +48,23 @@ class MDASequence(UseqModel):
stage_positions : tuple[Position, ...]
The stage positions to visit. (each with `x`, `y`, `z`, `name`, and `sequence`,
all of which are optional).
grid_plan : GridFromEdges, GridRelative, NoGrid
The grid plan to follow. One of `GridFromEdges`, `GridRelative` or `NoGrid`.
grid_plan : GridFromEdges | GridRelative | None
The grid plan to follow. One of `GridFromEdges`, `GridRelative` or `None`.
channels : tuple[Channel, ...]
The channels to acquire. see `Channel`.
time_plan : MultiPhaseTimePlan | TIntervalDuration | TIntervalLoops \
| TDurationLoops | NoT
| TDurationLoops | None
The time plan to follow. One of `TIntervalDuration`, `TIntervalLoops`,
`TDurationLoops`, `MultiPhaseTimePlan`, or `NoT`
`TDurationLoops`, `MultiPhaseTimePlan`, or `None`
z_plan : ZTopBottom | ZRangeAround | ZAboveBelow | ZRelativePositions | \
ZAbsolutePositions | NoZ
ZAbsolutePositions | None
The z plan to follow. One of `ZTopBottom`, `ZRangeAround`, `ZAboveBelow`,
`ZRelativePositions`, `ZAbsolutePositions`, or `NoZ`.
`ZRelativePositions`, `ZAbsolutePositions`, or `None`.
uid : UUID
A read-only unique identifier (uuid version 4) for the sequence. This will be
generated, do not set.
autofocus_plan : AxesBasedAF | NoAF
The hardware autofocus plan to follow. One of `AxesBasedAF` or `NoAF`.
autofocus_plan : AxesBasedAF | None
The hardware autofocus plan to follow. One of `AxesBasedAF` or `None`.

Examples
--------
Expand Down Expand Up @@ -104,11 +104,11 @@ class MDASequence(UseqModel):
metadata: Dict[str, Any] = Field(default_factory=dict)
axis_order: str = "".join(INDICES)
stage_positions: Tuple[Position, ...] = Field(default_factory=tuple)
grid_plan: AnyGridPlan = Field(default_factory=NoGrid)
grid_plan: Optional[AnyGridPlan] = None
channels: Tuple[Channel, ...] = Field(default_factory=tuple)
time_plan: AnyTimePlan = Field(default_factory=NoT)
z_plan: AnyZPlan = Field(default_factory=NoZ)
autofocus_plan: AnyAutofocusPlan = Field(default_factory=NoAF)
time_plan: Optional[AnyTimePlan] = None
z_plan: Optional[AnyZPlan] = None
autofocus_plan: Optional[AnyAutofocusPlan] = None

_uid: UUID = PrivateAttr(default_factory=uuid4)
_length: Optional[int] = PrivateAttr(default=None)
Expand All @@ -130,12 +130,12 @@ def __hash__(self) -> int:
return hash(self.uid)

@validator("z_plan", pre=True)
def validate_zplan(cls, v: Any) -> Union[dict, NoZ]:
return v or NoZ()
def validate_zplan(cls, v: Any) -> Optional[dict]:
return v or None

@validator("time_plan", pre=True)
def validate_time_plan(cls, v: Any) -> Union[dict, NoT]:
return {"phases": v} if isinstance(v, (tuple, list)) else v or NoT()
def validate_time_plan(cls, v: Any) -> Optional[dict]:
return {"phases": v} if isinstance(v, (tuple, list)) else v or None

@validator("stage_positions", pre=True)
def validate_positions(cls, v: Any) -> Any:
Expand Down Expand Up @@ -217,8 +217,7 @@ def _check_order(
if (
GRID in order
and POSITION in order
and grid_plan is not None
and not isinstance(grid_plan, NoGrid)
and grid_plan
and not grid_plan.is_relative
and len(stage_positions) > 1
):
Expand All @@ -241,7 +240,7 @@ def _check_order(
)

# Cannot use autofocus plan with absolute z_plan
if Z in order and z_plan is not None and not z_plan.is_relative:
if Z in order and z_plan and not z_plan.is_relative:
err = "Absolute Z positions cannot be used with autofocus plan."
if isinstance(autofocus_plan, AxesBasedAF):
raise ValueError(err)
Expand Down Expand Up @@ -286,15 +285,19 @@ def iter_axis(
self, axis: str
) -> Iterator[Position | Channel | float | GridPosition]:
"""Iterate over the events of a given axis."""
yield from {
plan = {
TIME: self.time_plan,
POSITION: self.stage_positions,
Z: self.z_plan,
CHANNEL: self.channels,
GRID: self.grid_plan.iter_grid_positions(
self._fov_size[0], self._fov_size[1]
GRID: (
self.grid_plan.iter_grid_positions(self._fov_size[0], self._fov_size[1])
if self.grid_plan
else ()
),
}[axis]
if plan:
yield from plan

def __iter__(self) -> Iterator[MDAEvent]: # type: ignore [override]
"""Same as `iter_events`. Supports `for event in sequence: ...` syntax."""
Expand Down Expand Up @@ -468,7 +471,7 @@ def iter_sequence(
event = MDAEvent(**event_kwargs)
if autofocus_plan:
af_event = autofocus_plan.event(event)
if af_event is not None:
if af_event:
yield af_event
yield event

Expand Down Expand Up @@ -522,7 +525,7 @@ def _should_skip(
position: Position | None,
channel: Channel | None,
index: dict[str, int],
z_plan: AnyZPlan,
z_plan: AnyZPlan | None,
) -> bool:
"""Return True if this event should be skipped."""
if channel:
Expand Down Expand Up @@ -568,15 +571,15 @@ def _should_skip(
def _xyzpos(
position: Position | None,
channel: Channel | None,
z_plan: AnyZPlan,
z_plan: AnyZPlan | None,
grid: GridPosition | None = None,
z_pos: float | None = None,
) -> MDAEventDict:
if z_pos is not None:
# combine z_pos with z_offset
if channel and channel.z_offset is not None:
z_pos += channel.z_offset
if z_plan.is_relative:
if z_plan and z_plan.is_relative:
# TODO: either disallow without position z, or add concept of "current"
z_pos += getattr(position, Z, None) or 0
elif position:
Expand Down
14 changes: 2 additions & 12 deletions src/useq/_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@ def deltas(self) -> Iterator[datetime.timedelta]:
yield current
current += self.interval # type: ignore # TODO

def __bool__(self) -> bool:
return len(self) > 0


class TIntervalLoops(TimePlan):
"""Define temporal sequence using interval and number of loops.
Expand Down Expand Up @@ -102,22 +99,15 @@ def loops(self) -> int:
return self.duration // self.interval + 1


class NoT(TimePlan):
"""Don't acquire a time sequence."""

def deltas(self) -> Iterator[datetime.timedelta]:
yield from ()


SinglePhaseTimePlan = Union[TIntervalDuration, TIntervalLoops, TDurationLoops, NoT]
SinglePhaseTimePlan = Union[TIntervalDuration, TIntervalLoops, TDurationLoops]


class MultiPhaseTimePlan(TimePlan):
"""Time sequence composed of multiple phases.

Attributes
----------
phases : Sequence[TIntervalDuration | TIntervalLoops | TDurationLoops | NoT]
phases : Sequence[TIntervalDuration | TIntervalLoops | TDurationLoops]
Sequence of time plans.
"""

Expand Down
14 changes: 1 addition & 13 deletions src/useq/_z.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,20 +155,8 @@ def is_relative(self) -> bool:
return False


class NoZ(ZPlan):
"""Don't acquire Z."""

go_up: bool = True

def positions(self) -> Sequence[float]:
return []

def __bool__(self) -> bool:
return False


# order matters... this is the order in which pydantic will try to coerce input.
# should go from most specific to least specific
AnyZPlan = Union[
ZTopBottom, ZAboveBelow, ZRangeAround, ZAbsolutePositions, ZRelativePositions, NoZ
ZTopBottom, ZAboveBelow, ZRangeAround, ZAbsolutePositions, ZRelativePositions
]
9 changes: 2 additions & 7 deletions tests/fixtures/mda.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,7 @@
{
"name": "test_name",
"sequence": {
"autofocus_plan": {
"autofocus_device_name": "__no_autofocus__",
"autofocus_motor_offset": null
},
"autofocus_plan": null,
"axis_order": "tpgcz",
"channels": [],
"grid_plan": {
Expand All @@ -78,9 +75,7 @@
},
"metadata": {},
"stage_positions": [],
"time_plan": {
"prioritize_duration": false
},
"time_plan": null,
"z_plan": {
"above": 10.0,
"below": 0.0,
Expand Down
9 changes: 0 additions & 9 deletions tests/test_sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@
GridRelative,
MDAEvent,
MDASequence,
NoGrid,
NoT,
NoZ,
Position,
TDurationLoops,
TIntervalDuration,
Expand All @@ -32,14 +29,12 @@
(ZAbsolutePositions(absolute=[0, 0.5, 5]), [0, 0.5, 5]),
(ZRelativePositions(relative=[0, 0.5, 5]), [0, 0.5, 5]),
(ZRangeAround(range=8, step=1), [-4, -3, -2, -1, 0, 1, 2, 3, 4]),
(NoZ(), []),
]
z_as_dict: _T = [
({"above": 8, "below": 4, "step": 2}, [-4, -2, 0, 2, 4, 6, 8]),
({"range": 8, "step": 1}, [-4, -3, -2, -1, 0, 1, 2, 3, 4]),
({"absolute": [0, 0.5, 5]}, [0, 0.5, 5]),
({"relative": [0, 0.5, 5]}, [0, 0.5, 5]),
(None, []),
]
z_inputs = z_as_class + z_as_dict

Expand All @@ -60,7 +55,6 @@
]

t_as_dict: _T = [
(None, []),
({"interval": 0.5, "duration": 2}, [0, 0.5, 1, 1.5, 2]),
({"loops": 5, "duration": 8}, [0, 2, 4, 6, 8]),
({"loops": 5, "interval": 0.25}, [0, 0.25, 0.5, 0.75, 1]),
Expand All @@ -70,7 +64,6 @@
),
({"loops": 5, "duration": {"milliseconds": 8}}, [0, 0.002, 0.004, 0.006, 0.008]),
({"loops": 5, "duration": {"seconds": 8}}, [0, 2, 4, 6, 8]),
(NoT(), []),
]
t_inputs = t_as_class + t_as_dict

Expand All @@ -87,7 +80,6 @@
{"overlap": 10.0, "top": 0.0, "left": 0.0, "bottom": 2.0, "right": 2.0},
[(10.0, 10.0), OrderMode.row_wise_snake, 0.0, 0.0, 2.0, 2.0],
),
({}, [(0.0, 0.0), OrderMode.row_wise_snake]),
]

g_as_class = [
Expand All @@ -99,7 +91,6 @@
GridFromEdges(overlap=10.0, top=0.0, left=0, bottom=2, right=2),
[(10.0, 10.0), OrderMode.row_wise_snake, 0.0, 0.0, 2.0, 2.0],
),
(NoGrid(), [(0.0, 0.0), OrderMode.row_wise_snake]),
]
g_inputs = g_as_class + g_as_dict

Expand Down