diff --git a/iohub/convert.py b/iohub/convert.py index 4000bbc3..f66f0c26 100644 --- a/iohub/convert.py +++ b/iohub/convert.py @@ -200,10 +200,10 @@ def _get_position_coords(self): if not self.reader.stage_positions: raise ValueError("Stage positions not available.") for idx, pos in enumerate(self.reader.stage_positions): - stage_pos = ( - pos.get("XYStage") or pos.get("XY") or pos.get("XY Stage") - ) - if stage_pos is None: + try: + xy_stage = pos["DefaultXYStage"] + stage_pos = pos[xy_stage] + except KeyError: raise ValueError( f"Stage position is not available for position {idx}" ) diff --git a/iohub/mm_fov.py b/iohub/mm_fov.py index 65dc8e30..e2dd2914 100644 --- a/iohub/mm_fov.py +++ b/iohub/mm_fov.py @@ -1,5 +1,6 @@ from __future__ import annotations +import re from pathlib import Path from xarray import DataArray @@ -109,42 +110,53 @@ def stage_positions(self, value): @property def hcs_position_labels(self): """Parse plate position labels generated by the HCS position generator, - e.g. 'A1-Site_0' or '1-Pos000_000', and split into row, column, and - FOV names. + e.g. 'A1-Site_0', '1-Pos000_000', or 'Pos-9-000_000', and split into + row, column, and FOV names. Returns ------- list[tuple[str, str, str]] - FOV name paths, e.g. ('A', '1', '0') or ('0', '1', '000000') + FOV name paths, e.g. ('A', '1', '0'), ('0', '1', '000000'), or + ('0', '9', '000000'). """ if not self.stage_positions: raise ValueError("Stage position metadata not available.") + try: - # Look for "'A1-Site_0', 'H12-Site_1', ... " format - labels = [ - pos["Label"].split("-Site_") for pos in self.stage_positions - ] - return [(well[0], well[1:], fov) for well, fov in labels] - except Exception: - try: - # Look for "'1-Pos000_000', '2-Pos000_001', ... " - # and split into ('1', '000_000'), ... - labels = [ - pos["Label"].split("-Pos") for pos in self.stage_positions - ] - # remove underscore from FOV name, i.e. '000_000' - # collect all wells in row '0' so output is - # ('0', '1', '000000') - return [ - ("0", col, fov.replace("_", "")) for col, fov in labels - ] - except Exception: - labels = [pos.get("Label") for pos in self.stage_positions] - raise ValueError( - "HCS position labels are in the format of " - "'A1-Site_0', 'H12-Site_1', or '1-Pos000_000' " - f"Got labels {labels}" - ) + labels = [pos["Label"] for pos in self.stage_positions] + except KeyError: + raise ValueError("Stage positions do not have labels.") + + # See https://chatgpt.com/share/e/670097cc-2854-8008-bd33-b54cad7c99b9 + pattern = re.compile( + r"([A-Z])(\d+)-Site_(\d+)|" + r"Pos-(\d+)-(\d+)_(\d+)|" + r"(\d+)-Pos(\d+)_(\d+)" + ) + row_col_fov = [] + for label in labels: + if (match := re.match(pattern, label)) is not None: + if match.group(1): # "A1-Site_0" case + row_col_fov.append( + (match.group(1), match.group(2), match.group(3)) + ) + elif match.group(4): # "Pos-5-000_005" case + row_col_fov.append( + ("0", match.group(4), match.group(5) + match.group(6)) + ) + elif match.group(7): # "1-Pos000_000" case + row_col_fov.append( + ("0", match.group(7), match.group(8) + match.group(9)) + ) + + if not row_col_fov: + raise ValueError( + "HCS position labels are in the format of " + "'A1-Site_0', 'H12-Site_1', or '1-Pos000_000', " + f"or 'Pos-1-000_000'. Got labels {labels}" + ) + + return row_col_fov @property def zyx_scale(self) -> tuple[float, float, float]: diff --git a/iohub/ndtiff.py b/iohub/ndtiff.py index 65f9da52..23d72e6a 100644 --- a/iohub/ndtiff.py +++ b/iohub/ndtiff.py @@ -167,7 +167,9 @@ def _get_summary_metadata(self): "YPosition_um_Intended", ] ): - position_metadata[img_metadata["Core-XYStage"]] = ( + xy_stage = img_metadata["Core-XYStage"] + position_metadata["DefaultXYStage"] = xy_stage + position_metadata[xy_stage] = ( img_metadata["XPosition_um_Intended"], img_metadata["YPosition_um_Intended"], ) diff --git a/tests/mmstack/test_mmstack.py b/tests/mmstack/test_mmstack.py index bd27facd..65fa0fff 100644 --- a/tests/mmstack/test_mmstack.py +++ b/tests/mmstack/test_mmstack.py @@ -68,6 +68,8 @@ def test_mmstack_metadata(ome_tiff): assert isinstance(mmstack.micromanager_metadata, dict) assert mmstack.micromanager_metadata["Summary"] assert mmstack.micromanager_summary + if mmstack.stage_positions: + assert "DefaultXYStage" in mmstack.stage_positions[0] def test_fov_axes_names(ome_tiff): diff --git a/tests/test_ndtiff.py b/tests/test_ndtiff.py index dca898ea..fc1d09b2 100644 --- a/tests/test_ndtiff.py +++ b/tests/test_ndtiff.py @@ -32,6 +32,8 @@ def test_dataset_metadata(ndtiff_dataset): assert isinstance(dataset.micromanager_metadata, dict) assert dataset.micromanager_metadata["Summary"] assert isinstance(dataset.micromanager_summary, dict) + if dataset.stage_positions: + assert "DefaultXYStage" in dataset.stage_positions[0] @pytest.mark.parametrize("ndtiff_v2", ndtiff_v2_datasets)