From a22af6fed9e05ac594631acf1332ffb34a9c943c Mon Sep 17 00:00:00 2001 From: Eu Pin Tien Date: Wed, 16 Sep 2026 17:35:57 +0100 Subject: [PATCH 1/5] Add 'lamella_number' as an optional field to the 'FIBImageMetadata' Pydantic model; updated site name generation logic so that it uses '/' instead of '--' as the section separators --- src/murfey/util/models.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/murfey/util/models.py b/src/murfey/util/models.py index f7ad71905..8822c5fea 100644 --- a/src/murfey/util/models.py +++ b/src/murfey/util/models.py @@ -248,6 +248,7 @@ class FIBImageMetadata(BaseModel): visit_name: str file: Path thumbnail_path: Path | None = None + lamella_number: int | None = None # Acceleration voltage (V) voltage: float # Beam shifts (m) @@ -309,7 +310,10 @@ def site_name(self) -> str: Create a site name for the current image based on the project name and its slot number. """ - return f"{self.project_name}--slot_{self.slot_number}" + site_name = f"{self.project_name}/slot_{self.slot_number}" + if self.lamella_number is not None: + site_name += f"/lamella_{self.lamella_number}" + return site_name """ From 2bca197b4a941a7cc8c40671edc2a01f625fd003 Mon Sep 17 00:00:00 2001 From: Eu Pin Tien Date: Wed, 16 Sep 2026 18:03:37 +0100 Subject: [PATCH 2/5] Updated 'parse_image_metadata' helper function so that it also populates the 'lamella_number' field --- src/murfey/workflows/fib/shared.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/murfey/workflows/fib/shared.py b/src/murfey/workflows/fib/shared.py index 444721c14..a0ee133ff 100644 --- a/src/murfey/workflows/fib/shared.py +++ b/src/murfey/workflows/fib/shared.py @@ -11,7 +11,7 @@ import PIL.Image from murfey.util.db import ImagingSite -from murfey.util.fib import get_slot_number +from murfey.util.fib import get_slot_number, number_from_name from murfey.util.models import FIBImageMetadata @@ -101,6 +101,13 @@ def parse_image_metadata( ), # Convert to degrees rotation_offset=rotation_offset, ) + # Determine the lamella number + try: + parts = file.parts + site_idx = parts.index("Sites") + 1 + extracted["lamella_number"] = number_from_name(parts[site_idx]) + except ValueError: + extracted["lamella_number"] = None case _: raise NotImplementedError( f"Unsupported metadata format {metadata_format!r}" From 23cb908d1fde764b4d6f50afee72769e92a968c6 Mon Sep 17 00:00:00 2001 From: Eu Pin Tien Date: Wed, 16 Sep 2026 18:12:59 +0100 Subject: [PATCH 3/5] Updated tests to also test the case of a TIFF file --- tests/workflows/fib/test_fib_shared.py | 78 ++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 11 deletions(-) diff --git a/tests/workflows/fib/test_fib_shared.py b/tests/workflows/fib/test_fib_shared.py index 67369af18..b2c7d04b5 100644 --- a/tests/workflows/fib/test_fib_shared.py +++ b/tests/workflows/fib/test_fib_shared.py @@ -5,6 +5,7 @@ import pytest from pytest_mock import MockerFixture +from murfey.util.fib import number_from_name from murfey.util.models import FIBImageMetadata from murfey.workflows.fib.shared import parse_image_metadata from tests.conftest import ExampleVisit @@ -134,8 +135,10 @@ def create_image_metadata( "test_params", ( ( + "autotem", "Metadata", # Tag key "2026-04-15-21-50-14_drift_corrected_image_Finer Milling - Electron Image.png", + "Lamella", # Lamella folder "some_project", 2000, # Voltage 0, # Beam shift X @@ -156,8 +159,34 @@ def create_image_metadata( 1e-6, # Y ), ( + "autotem", "Metadata", # Tag key "2026-04-16-02-39-40_drift_corrected_image_Polishing 2 - Electron Image.png", + "Lamella (23)", # Lamella folder + "another_project", + 2000, # Voltage + 0, # Beam shift X + 0, # Y + 0.003072, # Field of view X + 0.002048, # Y + -0.003, # Stage X + 0.0003, # Y + 0.01, # Z + 1.833, # Rotation + -75, # Rotation offset + 0, # Alpha tilt + 0, # Beta tilt + 2, # Expected slot number + 3072, # Image size X + 2048, # Y + 1e-6, # Pixel size X + 1e-6, # Y + ), + ( + "maps", + 34683, # Tag key + "Electron Snapshot.tiff", + "", # Lamella folder "another_project", 2000, # Voltage 0, # Beam shift X @@ -182,6 +211,8 @@ def create_image_metadata( def test_parse_metadata( mocker: MockerFixture, test_params: tuple[ + str, + str | int, str, str, str, @@ -207,8 +238,10 @@ def test_parse_metadata( ): # Unpack test params ( + workflow_name, tag_key, image_name, + lamella_folder, project_name, voltage, shift_x, @@ -229,13 +262,25 @@ def test_parse_metadata( pixel_size_y, ) = test_params file = ( - visit_dir - / "autotem" - / project_name - / "Sites" - / "Lamella" - / "LamellaEvaluationImages" - / image_name + ( + visit_dir + / "autotem" + / project_name + / "Sites" + / lamella_folder + / "LamellaEvaluationImages" + / image_name + ) + if workflow_name == "autotem" + else ( + visit_dir + / "maps" + / project_name + / "LayersData" + / "Layer" + / "Electron Snapshot" + / image_name + ) ) # Mock the results of opening an image file @@ -256,9 +301,15 @@ def test_parse_metadata( pixel_size_x, pixel_size_y, ) - tags = dict.fromkeys(["Metadata", "MetadataAsINI"], 0) - tags[tag_key] = xml_string - mock_image = MagicMock(text=tags) + tags: dict[str | int, str | int] + if workflow_name == "autotem": + tags = dict.fromkeys(["Metadata", "MetadataAsINI"], 0) + tags[tag_key] = xml_string + mock_image = MagicMock(text=tags) + else: + tags = dict.fromkeys([34682, 34683], 0) + tags[tag_key] = xml_string + mock_image = MagicMock(tag_v2=tags) mocker.patch( "murfey.workflows.fib.shared.PIL.Image.open", return_value=mock_image, @@ -273,6 +324,8 @@ def test_parse_metadata( assert parsed.visit_name == visit_name assert parsed.file == file + lamella_number = number_from_name(lamella_folder) if lamella_folder else None + assert parsed.lamella_number == lamella_number assert parsed.voltage == voltage assert parsed.shift_x == shift_x assert parsed.shift_y == shift_y @@ -289,5 +342,8 @@ def test_parse_metadata( assert parsed.pixel_size_x == pixel_size_x assert parsed.pixel_size_y == pixel_size_y assert parsed.slot_number == expected_slot_number - assert parsed.site_name == f"{project_name}--slot_{expected_slot_number}" + expected_site_name = f"{project_name}/slot_{expected_slot_number}" + if workflow_name == "autotem": + expected_site_name += f"/lamella_{lamella_number}" + assert parsed.site_name == expected_site_name assert parsed.pixel_size == 0.5 * (pixel_size_x + pixel_size_y) From 55e2811480cf27e435eb85e51bfb674717782990 Mon Sep 17 00:00:00 2001 From: Eu Pin Tien Date: Wed, 16 Sep 2026 18:14:59 +0100 Subject: [PATCH 4/5] Replaced other instances where data collection group uses the old site name format --- src/murfey/workflows/fib/register_milling_progress.py | 2 +- tests/workflows/fib/test_register_milling_progress.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/murfey/workflows/fib/register_milling_progress.py b/src/murfey/workflows/fib/register_milling_progress.py index b958cc6b9..294cda9ed 100644 --- a/src/murfey/workflows/fib/register_milling_progress.py +++ b/src/murfey/workflows/fib/register_milling_progress.py @@ -43,7 +43,7 @@ def _ensure_prerequisites( """ # Construct the DataCollectionGroup and GridSquare lookup tags - dcg_tag = f"{project_name}--slot_{slot_number}" + dcg_tag = f"{project_name}/slot_{slot_number}" # Determine variables to register data collection group and atlas with proposal_code = "".join(char for char in visit_name.split("-")[0] if char.isalpha()) diff --git a/tests/workflows/fib/test_register_milling_progress.py b/tests/workflows/fib/test_register_milling_progress.py index fd7a82c2b..1cc6f4d51 100644 --- a/tests/workflows/fib/test_register_milling_progress.py +++ b/tests/workflows/fib/test_register_milling_progress.py @@ -414,7 +414,7 @@ def test_run_with_db( sm_select(MurfeyDB.DataCollectionGroup) .where(MurfeyDB.DataCollectionGroup.session_id == session_id) .where( - MurfeyDB.DataCollectionGroup.tag == f"{site_info['project_name']}--slot_1" + MurfeyDB.DataCollectionGroup.tag == f"{site_info['project_name']}/slot_1" ) ).one_or_none() assert dcg_murfey is not None From 37ebde56bda0b8f5e2ead4cf78595a92d5b2fbea Mon Sep 17 00:00:00 2001 From: Eu Pin Tien Date: Wed, 16 Sep 2026 18:31:07 +0100 Subject: [PATCH 5/5] Use 'grid' instead of 'slot' as part of the site name --- src/murfey/util/models.py | 2 +- src/murfey/workflows/fib/register_milling_progress.py | 2 +- tests/workflows/fib/test_fib_shared.py | 2 +- tests/workflows/fib/test_register_milling_progress.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/murfey/util/models.py b/src/murfey/util/models.py index 8822c5fea..9070bea3a 100644 --- a/src/murfey/util/models.py +++ b/src/murfey/util/models.py @@ -310,7 +310,7 @@ def site_name(self) -> str: Create a site name for the current image based on the project name and its slot number. """ - site_name = f"{self.project_name}/slot_{self.slot_number}" + site_name = f"{self.project_name}/grid_{self.slot_number}" if self.lamella_number is not None: site_name += f"/lamella_{self.lamella_number}" return site_name diff --git a/src/murfey/workflows/fib/register_milling_progress.py b/src/murfey/workflows/fib/register_milling_progress.py index 294cda9ed..c3c4b1673 100644 --- a/src/murfey/workflows/fib/register_milling_progress.py +++ b/src/murfey/workflows/fib/register_milling_progress.py @@ -43,7 +43,7 @@ def _ensure_prerequisites( """ # Construct the DataCollectionGroup and GridSquare lookup tags - dcg_tag = f"{project_name}/slot_{slot_number}" + dcg_tag = f"{project_name}/grid_{slot_number}" # Determine variables to register data collection group and atlas with proposal_code = "".join(char for char in visit_name.split("-")[0] if char.isalpha()) diff --git a/tests/workflows/fib/test_fib_shared.py b/tests/workflows/fib/test_fib_shared.py index b2c7d04b5..4e6a21743 100644 --- a/tests/workflows/fib/test_fib_shared.py +++ b/tests/workflows/fib/test_fib_shared.py @@ -342,7 +342,7 @@ def test_parse_metadata( assert parsed.pixel_size_x == pixel_size_x assert parsed.pixel_size_y == pixel_size_y assert parsed.slot_number == expected_slot_number - expected_site_name = f"{project_name}/slot_{expected_slot_number}" + expected_site_name = f"{project_name}/grid_{expected_slot_number}" if workflow_name == "autotem": expected_site_name += f"/lamella_{lamella_number}" assert parsed.site_name == expected_site_name diff --git a/tests/workflows/fib/test_register_milling_progress.py b/tests/workflows/fib/test_register_milling_progress.py index 1cc6f4d51..aad13cd35 100644 --- a/tests/workflows/fib/test_register_milling_progress.py +++ b/tests/workflows/fib/test_register_milling_progress.py @@ -414,7 +414,7 @@ def test_run_with_db( sm_select(MurfeyDB.DataCollectionGroup) .where(MurfeyDB.DataCollectionGroup.session_id == session_id) .where( - MurfeyDB.DataCollectionGroup.tag == f"{site_info['project_name']}/slot_1" + MurfeyDB.DataCollectionGroup.tag == f"{site_info['project_name']}/grid_1" ) ).one_or_none() assert dcg_murfey is not None