Skip to content
Closed
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
68 changes: 55 additions & 13 deletions src/ome_writers/_useq.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,12 +394,11 @@ def _build_well_plate_positions(plate_plan: useq.WellPlatePlan) -> list[Position
plate_column = str(col_idx + 1)
for fov_idx, well_pos in enumerate(well_positions):
pos = next(plate_iter) # grab the next AbsolutePosition in the outer loop
# NOTE: the pos.name is a useq's auto-generated name, either WellName_fovN
# for multi-fovs (e.g. A1_0000, etc) or just WellName for single fov
# (e.g. A1, B3, etc). We replace it with `fov{fov_idx}.
# Use the grid point's name from useq (set by name_pattern)
fov_name = getattr(well_pos, "name", None) or f"{fov_idx:04d}"
positions.append(
Position(
name=f"fov{fov_idx}",
name=fov_name,
plate_row=plate_row,
plate_column=plate_column,
grid_row=getattr(well_pos, "row", None),
Expand All @@ -421,6 +420,20 @@ def _row_idx_to_letter(index: int) -> str:
return name


def _plate_row_to_str(value: int | str | None) -> str | None:
"""Convert plate_row to string: int -> letter, str -> as-is, None -> None."""
if value is None:
return None
return _row_idx_to_letter(value) if isinstance(value, int) else str(value)


def _plate_col_to_str(value: int | str | None) -> str | None:
"""Convert plate_col to string: int -> 1-based str, str -> as-is, None -> None."""
if value is None:
return None
return str(value + 1) if isinstance(value, int) else str(value)


def _pos_with_grid_point(
name: str,
pos: useq.Position,
Expand Down Expand Up @@ -453,10 +466,18 @@ def _pos_with_grid_point(
name = f"{name}_{suffix}" if name else suffix
else:
name = f"{name}_g{gp_idx:04d}" if name else f"{gp_idx:04d}"
plate_row = getattr(pos, "plate_row", None)
plate_col = getattr(pos, "plate_col", None)
# When positions have plate info, use the grid point's name from useq
# (set by grid plan's name_pattern, e.g., "0000000")
if plate_row is not None and plate_col is not None:
name = getattr(gp, "name", None) or f"{gp_idx:04d}"
return Position(
name=name,
grid_row=grid_row,
grid_column=grid_col,
plate_row=_plate_row_to_str(plate_row),
plate_column=_plate_col_to_str(plate_col),
x_coord=x_coord,
y_coord=y_coord,
z_coord=pos.z,
Expand Down Expand Up @@ -498,6 +519,8 @@ def _build_stage_positions_plan(seq: useq.MDASequence) -> list[Position]:
else:
grid = global_grid or None

plate_row = getattr(pos, "plate_row", None)
plate_col = getattr(pos, "plate_col", None)
if grid:
for gp_idx, gp in enumerate(grid):
positions.append(
Expand All @@ -514,23 +537,42 @@ def _build_stage_positions_plan(seq: useq.MDASequence) -> list[Position]:
z_coord=pos.z,
grid_column=pos.col,
grid_row=pos.row,
plate_row=_plate_row_to_str(plate_row),
plate_column=_plate_col_to_str(plate_col),
)
)

return positions


def _plate_from_useq(seq: useq.MDASequence) -> Plate | None:
"""Convert a useq WellPlatePlan to an ome-writers Plate."""
"""Convert a useq WellPlatePlan or plate-annotated positions to a Plate."""
import useq

useq_plate = seq.stage_positions
if not isinstance(useq_plate, useq.WellPlatePlan):
return None
if isinstance(useq_plate, useq.WellPlatePlan):
plate = useq_plate.plate
return Plate(
row_names=[_row_idx_to_letter(i) for i in range(plate.rows)],
column_names=[str(i + 1) for i in range(plate.columns)],
name=plate.name or None,
)

plate = useq_plate.plate
return Plate(
row_names=[_row_idx_to_letter(i) for i in range(plate.rows)],
column_names=[str(i + 1) for i in range(plate.columns)],
name=plate.name or None,
)
# Check if positions have plate_row/plate_col annotations
if useq_plate:
row_names: set[str] = set()
col_names: set[str] = set()
for p in useq_plate:
pr = getattr(p, "plate_row", None)
pc = getattr(p, "plate_col", None)
if pr is not None:
row_names.add(_plate_row_to_str(pr)) # type: ignore[arg-type]
if pc is not None:
col_names.add(_plate_col_to_str(pc)) # type: ignore[arg-type]
if row_names and col_names:
return Plate(
row_names=sorted(row_names),
column_names=sorted(col_names),
)

return None
20 changes: 10 additions & 10 deletions tests/test_useq.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,10 @@ class Case:
),
expected_dim_names=["p", "t", "c", "y", "x"],
expected_positions=[
ExpectedPosition("fov0", "A", "1", grid_row=0, grid_col=0),
ExpectedPosition("fov1", "A", "1", grid_row=0, grid_col=1),
ExpectedPosition("fov0", "B", "2", grid_row=0, grid_col=0),
ExpectedPosition("fov1", "B", "2", grid_row=0, grid_col=1),
ExpectedPosition("0000", "A", "1", grid_row=0, grid_col=0),
ExpectedPosition("0001", "A", "1", grid_row=0, grid_col=1),
ExpectedPosition("0000", "B", "2", grid_row=0, grid_col=0),
ExpectedPosition("0001", "B", "2", grid_row=0, grid_col=1),
],
id="well_plate_with_points",
),
Expand Down Expand Up @@ -716,11 +716,11 @@ def test_well_plate_fov_folder_names(tmp_path: Path, zarr_backend: str) -> None:
for _ in seq:
stream.append(dummy_frame)

# Check that zarr folders have the expected "fov0", "fov1" names
# Check that zarr folders have the expected names from useq's name_pattern
# For WellPlatePlan with plate layout, the structure should be:
# test_fov_names.ome.zarr/A/1/fov0/, A/1/fov1/, B/2/fov0/, B/2/fov1/
# test_fov_names.ome.zarr/A/1/0000/, A/1/0001/, B/2/0000/, B/2/0001/
zarr_root = tmp_path / "test_fov_names.ome.zarr"
assert (zarr_root / "A" / "1" / "fov0").exists(), "Expected fov0 in well A1"
assert (zarr_root / "A" / "1" / "fov1").exists(), "Expected fov1 in well A1"
assert (zarr_root / "B" / "2" / "fov0").exists(), "Expected fov0 in well B2"
assert (zarr_root / "B" / "2" / "fov1").exists(), "Expected fov1 in well B2"
assert (zarr_root / "A" / "1" / "0000").exists(), "Expected 0000 in well A1"
assert (zarr_root / "A" / "1" / "0001").exists(), "Expected 0001 in well A1"
assert (zarr_root / "B" / "2" / "0000").exists(), "Expected 0000 in well B2"
assert (zarr_root / "B" / "2" / "0001").exists(), "Expected 0001 in well B2"
Loading