From c43f4e7cc1535356b140176eb4804a9344d2bca5 Mon Sep 17 00:00:00 2001 From: Eu Pin Tien Date: Mon, 5 Oct 2026 12:53:10 +0100 Subject: [PATCH 1/6] Added logic to update coordinates of existing lamella GridSquare entries when updating the atlas --- src/murfey/workflows/fib/register_atlas.py | 209 ++++++++++++++++++++- 1 file changed, 207 insertions(+), 2 deletions(-) diff --git a/src/murfey/workflows/fib/register_atlas.py b/src/murfey/workflows/fib/register_atlas.py index a38de6466..d721b61f8 100644 --- a/src/murfey/workflows/fib/register_atlas.py +++ b/src/murfey/workflows/fib/register_atlas.py @@ -1,4 +1,5 @@ import logging +import math from importlib.metadata import entry_points from pathlib import Path from typing import Any, cast @@ -7,10 +8,11 @@ from pydantic import BaseModel from sqlmodel import Session, select +import murfey.server import murfey.util.db as MurfeyDB from murfey.util.config import get_machine_config from murfey.util.fib import number_from_name -from murfey.util.models import FIBImageMetadata +from murfey.util.models import FIBImageMetadata, GridSquareParameters from murfey.workflows.fib.shared import ( parse_image_metadata, populate_fib_imaging_site_entry, @@ -166,6 +168,201 @@ def _register_dcg_and_atlas( murfey_db.add(imaging_site) murfey_db.commit() + return imaging_site + + +def _update_grid_squares( + session_id: int, + atlas: MurfeyDB.ImagingSite, + murfey_db: Session, +): + """ + Searches for any grid square-type ImagingSites associated with this atlas, and + uses them to recalculate the relative positions of the images on the current + atlas image. + """ + # Early errors if variables are not set correctly + if murfey.server._transport_object is None: + raise RuntimeError("No TransportManager object was set up") + dcg_name = atlas.dcg_name + if dcg_name is None: + raise ValueError( + f"'dcg_name' field in ImagingSite entry for {atlas.image_path} is empty" + ) + + # Early exit if no lamella images have been registered + lamella_imaging_sites = murfey_db.exec( + select(MurfeyDB.ImagingSite) + .where(MurfeyDB.ImagingSite.session_id == session_id) + .where(MurfeyDB.ImagingSite.dcg_name == atlas.dcg_name) + .where(MurfeyDB.ImagingSite.data_type == "grid_square") + ).all() + if not lamella_imaging_sites: + logger.info( + "No lamella imaging sites associated with this atlas have been registered" + ) + return atlas + + # Check if the atlas has the required values for the GridSquare registration + if not ( + atlas.pos_x is not None + and atlas.pos_y is not None + and atlas.pos_z is not None + and atlas.rotation is not None + and atlas.tilt_alpha is not None + and atlas.len_x is not None + and atlas.len_y is not None + and atlas.thumbnail_pixels_x is not None + and atlas.thumbnail_pixels_y is not None + ): + logger.warning(f"Atlas {atlas.image_path} not populated with required values") + return atlas + + atlas_x1 = atlas.pos_x + (atlas.len_x / 2) + atlas_y0 = atlas.pos_y - (atlas.len_y / 2) + + # Iterate across lamella imaging sites and register/update them + for lamella in lamella_imaging_sites: + # Extract site number from site name + try: + site_number = int(lamella.site_name.split("lamella_")[-1]) + except Exception: + logger.warning( + "Unable to get lamella number from site name of " + f"ImagingSite entry {lamella.image_path}", + exc_info=True, + ) + continue + + # Check that the lamella ImagingSite entry has the required values + if not ( + lamella.pos_x is not None + and lamella.pos_y is not None + and lamella.pos_z is not None + and lamella.rotation is not None + and lamella.tilt_alpha is not None + and lamella.len_x is not None + and lamella.len_y is not None + ): + logger.warning( + f"ImagingSite for {lamella.image_path} not populated with required values" + ) + continue + + # Transform the imaging site coordinates into the atlas' frame of reference + # NOTE: This will require further investigation and tweaking, given the + # many axes and centres of rotation present in the FIB stage system. + # We start with a simple 2D rotation for now, and will adjust it as we observe + # the alignment accuracy + theta = math.radians(lamella.rotation - atlas.rotation) + sin = math.sin(theta) + cos = math.cos(theta) + x_transformed = (lamella.pos_x * cos) - (lamella.pos_y * sin) + y_transformed = (lamella.pos_x * sin) + (lamella.pos_y * cos) + + # Find the pixel coordinates of the image on the atlas + # NOTE: On the atlas image, positive directions are LEFT (x) and DOWN (y) + x_mid_px = int( + round((atlas_x1 - x_transformed) / atlas.len_x * atlas.thumbnail_pixels_x) + or 1 + ) + y_mid_px = int( + round((y_transformed - atlas_y0) / atlas.len_y * atlas.thumbnail_pixels_y) + or 1 + ) + + # Find the pixel width and height of the lamella image on the atlas + width_scaled = int( + round((lamella.len_x / atlas.len_x) * atlas.thumbnail_pixels_x) or 1 + ) + height_scaled = int( + round((lamella.len_y / atlas.len_y) * atlas.thumbnail_pixels_y) or 1 + ) + + # Populate GridSquareParameters model + grid_square_params = GridSquareParameters( + tag=dcg_name, + x_location=x_transformed, + x_location_scaled=x_mid_px, + y_location=y_transformed, + y_location_scaled=y_mid_px, + readout_area_x=lamella.image_pixels_x, + readout_area_y=lamella.image_pixels_y, + thumbnail_size_x=lamella.thumbnail_pixels_x, + thumbnail_size_y=lamella.thumbnail_pixels_y, + width=lamella.image_pixels_x, + width_scaled=width_scaled, + height=lamella.image_pixels_y, + height_scaled=height_scaled, + x_stage_position=x_transformed, + y_stage_position=y_transformed, + pixel_size=lamella.image_pixel_size, + image=lamella.thumbnail_path, + ) + + # Register or update the grid square entry as required + if grid_square_entry := murfey_db.exec( + select(MurfeyDB.GridSquare) + .where(MurfeyDB.GridSquare.name == site_number) + .where(MurfeyDB.GridSquare.session_id == session_id) + .where(MurfeyDB.GridSquare.tag == grid_square_params.tag) + ).one_or_none(): + # Update existing grid square entry on Murfey + grid_square_entry.x_location = grid_square_params.x_location + grid_square_entry.y_location = grid_square_params.y_location + grid_square_entry.x_stage_position = grid_square_params.x_stage_position + grid_square_entry.y_stage_position = grid_square_params.y_stage_position + grid_square_entry.readout_area_x = grid_square_params.readout_area_x + grid_square_entry.readout_area_y = grid_square_params.readout_area_y + grid_square_entry.thumbnail_size_x = grid_square_params.thumbnail_size_x + grid_square_entry.thumbnail_size_y = grid_square_params.thumbnail_size_y + grid_square_entry.pixel_size = grid_square_params.pixel_size + grid_square_entry.image = grid_square_params.image + + # Update existing entry on ISPyB + murfey.server._transport_object.do_update_grid_square( + grid_square_id=grid_square_entry.id, + grid_square_parameters=grid_square_params, + ) + else: + # Look up data collection group for current series + dcg_entry = murfey_db.exec( + select(MurfeyDB.DataCollectionGroup) + .where(MurfeyDB.DataCollectionGroup.session_id == session_id) + .where(MurfeyDB.DataCollectionGroup.tag == grid_square_params.tag) + ).one() + # Register to ISPyB + grid_square_ispyb_result = ( + murfey.server._transport_object.do_insert_grid_square( + atlas_id=dcg_entry.atlas_id, + grid_square_id=site_number, + grid_square_parameters=grid_square_params, + ) + ) + # Create matching record in Murfey + grid_square_entry = MurfeyDB.GridSquare( + id=grid_square_ispyb_result.get("return_value", None), + name=site_number, + session_id=session_id, + tag=grid_square_params.tag, + x_location=grid_square_params.x_location, + y_location=grid_square_params.y_location, + x_stage_position=grid_square_params.x_stage_position, + y_stage_position=grid_square_params.y_stage_position, + readout_area_x=grid_square_params.readout_area_x, + readout_area_y=grid_square_params.readout_area_y, + thumbnail_size_x=grid_square_params.thumbnail_size_x, + thumbnail_size_y=grid_square_params.thumbnail_size_y, + pixel_size=grid_square_params.pixel_size, + image=grid_square_params.image, + ) + murfey_db.add(grid_square_entry) + + # Commit all changes at the end + murfey_db.commit() + + return atlas + class FIBAtlasRegistrationInfo(BaseModel): session_id: int @@ -219,7 +416,7 @@ def run( ) # Register data collection group and atlas in ISPyB - _register_dcg_and_atlas( + fib_imaging_site = _register_dcg_and_atlas( session_id=fib_info.session_id, instrument_name=murfey_session.instrument_name, visit_name=murfey_session.visit, @@ -227,4 +424,12 @@ def run( metadata=metadata, murfey_db=murfey_db, ) + + # Update any existing lammella image (grid square) entries + fib_imaging_site = _update_grid_squares( + session_id=fib_info.session_id, + atlas=fib_imaging_site, + murfey_db=murfey_db, + ) + return {"success": True, "requeue": False} From 67c295222a181130c50965e8ad8d8a4e97e27668 Mon Sep 17 00:00:00 2001 From: Eu Pin Tien Date: Mon, 5 Oct 2026 15:50:54 +0100 Subject: [PATCH 2/6] Migrated ISPyB GridSquare creation/updating logic into the FIB workflow shared module --- src/murfey/workflows/fib/register_atlas.py | 162 ++-------------- .../fib/register_lamella_evaluation_image.py | 153 ++------------- src/murfey/workflows/fib/shared.py | 183 +++++++++++++++++- 3 files changed, 206 insertions(+), 292 deletions(-) diff --git a/src/murfey/workflows/fib/register_atlas.py b/src/murfey/workflows/fib/register_atlas.py index d721b61f8..1dffc3c6a 100644 --- a/src/murfey/workflows/fib/register_atlas.py +++ b/src/murfey/workflows/fib/register_atlas.py @@ -1,5 +1,4 @@ import logging -import math from importlib.metadata import entry_points from pathlib import Path from typing import Any, cast @@ -12,9 +11,10 @@ import murfey.util.db as MurfeyDB from murfey.util.config import get_machine_config from murfey.util.fib import number_from_name -from murfey.util.models import FIBImageMetadata, GridSquareParameters +from murfey.util.models import FIBImageMetadata from murfey.workflows.fib.shared import ( parse_image_metadata, + populate_fib_grid_square_entry, populate_fib_imaging_site_entry, ) from murfey.workflows.register_data_collection_group import register_dcg @@ -203,161 +203,27 @@ def _update_grid_squares( ) return atlas - # Check if the atlas has the required values for the GridSquare registration - if not ( - atlas.pos_x is not None - and atlas.pos_y is not None - and atlas.pos_z is not None - and atlas.rotation is not None - and atlas.tilt_alpha is not None - and atlas.len_x is not None - and atlas.len_y is not None - and atlas.thumbnail_pixels_x is not None - and atlas.thumbnail_pixels_y is not None - ): - logger.warning(f"Atlas {atlas.image_path} not populated with required values") - return atlas - - atlas_x1 = atlas.pos_x + (atlas.len_x / 2) - atlas_y0 = atlas.pos_y - (atlas.len_y / 2) - # Iterate across lamella imaging sites and register/update them for lamella in lamella_imaging_sites: - # Extract site number from site name try: - site_number = int(lamella.site_name.split("lamella_")[-1]) + grid_square_entry = populate_fib_grid_square_entry( + session_id=session_id, + dcg_name=dcg_name, + atlas=atlas, + lamella=lamella, + lamella_number=int(lamella.site_name.split("lamella_")[-1]), + transport_object=murfey.server._transport_object, + murfey_db=murfey_db, + ) + murfey_db.add(grid_square_entry) except Exception: - logger.warning( - "Unable to get lamella number from site name of " + logger.error( + "Error registering/updating GridSquare entry for " f"ImagingSite entry {lamella.image_path}", exc_info=True, ) continue - # Check that the lamella ImagingSite entry has the required values - if not ( - lamella.pos_x is not None - and lamella.pos_y is not None - and lamella.pos_z is not None - and lamella.rotation is not None - and lamella.tilt_alpha is not None - and lamella.len_x is not None - and lamella.len_y is not None - ): - logger.warning( - f"ImagingSite for {lamella.image_path} not populated with required values" - ) - continue - - # Transform the imaging site coordinates into the atlas' frame of reference - # NOTE: This will require further investigation and tweaking, given the - # many axes and centres of rotation present in the FIB stage system. - # We start with a simple 2D rotation for now, and will adjust it as we observe - # the alignment accuracy - theta = math.radians(lamella.rotation - atlas.rotation) - sin = math.sin(theta) - cos = math.cos(theta) - x_transformed = (lamella.pos_x * cos) - (lamella.pos_y * sin) - y_transformed = (lamella.pos_x * sin) + (lamella.pos_y * cos) - - # Find the pixel coordinates of the image on the atlas - # NOTE: On the atlas image, positive directions are LEFT (x) and DOWN (y) - x_mid_px = int( - round((atlas_x1 - x_transformed) / atlas.len_x * atlas.thumbnail_pixels_x) - or 1 - ) - y_mid_px = int( - round((y_transformed - atlas_y0) / atlas.len_y * atlas.thumbnail_pixels_y) - or 1 - ) - - # Find the pixel width and height of the lamella image on the atlas - width_scaled = int( - round((lamella.len_x / atlas.len_x) * atlas.thumbnail_pixels_x) or 1 - ) - height_scaled = int( - round((lamella.len_y / atlas.len_y) * atlas.thumbnail_pixels_y) or 1 - ) - - # Populate GridSquareParameters model - grid_square_params = GridSquareParameters( - tag=dcg_name, - x_location=x_transformed, - x_location_scaled=x_mid_px, - y_location=y_transformed, - y_location_scaled=y_mid_px, - readout_area_x=lamella.image_pixels_x, - readout_area_y=lamella.image_pixels_y, - thumbnail_size_x=lamella.thumbnail_pixels_x, - thumbnail_size_y=lamella.thumbnail_pixels_y, - width=lamella.image_pixels_x, - width_scaled=width_scaled, - height=lamella.image_pixels_y, - height_scaled=height_scaled, - x_stage_position=x_transformed, - y_stage_position=y_transformed, - pixel_size=lamella.image_pixel_size, - image=lamella.thumbnail_path, - ) - - # Register or update the grid square entry as required - if grid_square_entry := murfey_db.exec( - select(MurfeyDB.GridSquare) - .where(MurfeyDB.GridSquare.name == site_number) - .where(MurfeyDB.GridSquare.session_id == session_id) - .where(MurfeyDB.GridSquare.tag == grid_square_params.tag) - ).one_or_none(): - # Update existing grid square entry on Murfey - grid_square_entry.x_location = grid_square_params.x_location - grid_square_entry.y_location = grid_square_params.y_location - grid_square_entry.x_stage_position = grid_square_params.x_stage_position - grid_square_entry.y_stage_position = grid_square_params.y_stage_position - grid_square_entry.readout_area_x = grid_square_params.readout_area_x - grid_square_entry.readout_area_y = grid_square_params.readout_area_y - grid_square_entry.thumbnail_size_x = grid_square_params.thumbnail_size_x - grid_square_entry.thumbnail_size_y = grid_square_params.thumbnail_size_y - grid_square_entry.pixel_size = grid_square_params.pixel_size - grid_square_entry.image = grid_square_params.image - - # Update existing entry on ISPyB - murfey.server._transport_object.do_update_grid_square( - grid_square_id=grid_square_entry.id, - grid_square_parameters=grid_square_params, - ) - else: - # Look up data collection group for current series - dcg_entry = murfey_db.exec( - select(MurfeyDB.DataCollectionGroup) - .where(MurfeyDB.DataCollectionGroup.session_id == session_id) - .where(MurfeyDB.DataCollectionGroup.tag == grid_square_params.tag) - ).one() - # Register to ISPyB - grid_square_ispyb_result = ( - murfey.server._transport_object.do_insert_grid_square( - atlas_id=dcg_entry.atlas_id, - grid_square_id=site_number, - grid_square_parameters=grid_square_params, - ) - ) - # Create matching record in Murfey - grid_square_entry = MurfeyDB.GridSquare( - id=grid_square_ispyb_result.get("return_value", None), - name=site_number, - session_id=session_id, - tag=grid_square_params.tag, - x_location=grid_square_params.x_location, - y_location=grid_square_params.y_location, - x_stage_position=grid_square_params.x_stage_position, - y_stage_position=grid_square_params.y_stage_position, - readout_area_x=grid_square_params.readout_area_x, - readout_area_y=grid_square_params.readout_area_y, - thumbnail_size_x=grid_square_params.thumbnail_size_x, - thumbnail_size_y=grid_square_params.thumbnail_size_y, - pixel_size=grid_square_params.pixel_size, - image=grid_square_params.image, - ) - murfey_db.add(grid_square_entry) - # Commit all changes at the end murfey_db.commit() diff --git a/src/murfey/workflows/fib/register_lamella_evaluation_image.py b/src/murfey/workflows/fib/register_lamella_evaluation_image.py index ed00a596a..18c422198 100644 --- a/src/murfey/workflows/fib/register_lamella_evaluation_image.py +++ b/src/murfey/workflows/fib/register_lamella_evaluation_image.py @@ -1,6 +1,5 @@ import json import logging -import math import re from datetime import datetime from pathlib import Path @@ -13,9 +12,10 @@ import murfey.server import murfey.util.db as MurfeyDB from murfey.util.config import get_machine_config -from murfey.util.models import FIBImageMetadata, GridSquareParameters +from murfey.util.models import FIBImageMetadata from murfey.workflows.fib.shared import ( parse_image_metadata, + populate_fib_grid_square_entry, populate_fib_imaging_site_entry, ) from murfey.workflows.register_data_collection_group import register_dcg @@ -185,7 +185,7 @@ def _register_dcg( def _register_grid_square( session_id: int, imaging_site: MurfeyDB.ImagingSite, - site_number: int, + lamella_number: int, murfey_db: SQLModelSession, ): """ @@ -216,143 +216,16 @@ def _register_grid_square( return imaging_site atlas = atlas_search[-1] - # Check if the atlas has the required values for the GridSquare registration - if not ( - atlas.pos_x is not None - and atlas.pos_y is not None - and atlas.pos_z is not None - and atlas.rotation is not None - and atlas.tilt_alpha is not None - and atlas.len_x is not None - and atlas.len_y is not None - and atlas.thumbnail_pixels_x is not None - and atlas.thumbnail_pixels_y is not None - ): - logger.warning(f"Atlas {atlas.image_path} not populated with required values") - return imaging_site - atlas_x1 = atlas.pos_x + (atlas.len_x / 2) - atlas_y0 = atlas.pos_y - (atlas.len_y / 2) - - # Check that imaging site has the required values for registration - if not ( - imaging_site.pos_x is not None - and imaging_site.pos_y is not None - and imaging_site.pos_z is not None - and imaging_site.rotation is not None - and imaging_site.tilt_alpha is not None - and imaging_site.len_x is not None - and imaging_site.len_y is not None - ): - logger.warning( - f"ImagingSite for {imaging_site.image_path} not populated with required values" - ) - return imaging_site - - # Transform the imaging site coordinates into the atlas' frame of reference - # NOTE: This will require further investigation and tweaking, given the - # many axes and centres of rotation present in the FIB stage system. - # We start with a simple 2D rotation for now, and will adjust it as we observe - # the alignment accuracy - theta = math.radians(imaging_site.rotation - atlas.rotation) - sin = math.sin(theta) - cos = math.cos(theta) - x_transformed = (imaging_site.pos_x * cos) - (imaging_site.pos_y * sin) - y_transformed = (imaging_site.pos_x * sin) + (imaging_site.pos_y * cos) - - # Find the pixel coordinates of the image on the atlas - # NOTE: On the atlas image, positive directions are LEFT (x) and DOWN (y) - x_mid_px = int( - round((atlas_x1 - x_transformed) / atlas.len_x * atlas.thumbnail_pixels_x) or 1 - ) - y_mid_px = int( - round((y_transformed - atlas_y0) / atlas.len_y * atlas.thumbnail_pixels_y) or 1 - ) - - # Find the pixel width and height of the lamella image on the atlas - width_scaled = int( - round((imaging_site.len_x / atlas.len_x) * atlas.thumbnail_pixels_x) or 1 - ) - height_scaled = int( - round((imaging_site.len_y / atlas.len_y) * atlas.thumbnail_pixels_y) or 1 - ) - - # Populate GridSquareParameters model - grid_square_params = GridSquareParameters( - tag=dcg_name, - x_location=x_transformed, - x_location_scaled=x_mid_px, - y_location=y_transformed, - y_location_scaled=y_mid_px, - readout_area_x=imaging_site.image_pixels_x, - readout_area_y=imaging_site.image_pixels_y, - thumbnail_size_x=imaging_site.thumbnail_pixels_x, - thumbnail_size_y=imaging_site.thumbnail_pixels_y, - width=imaging_site.image_pixels_x, - width_scaled=width_scaled, - height=imaging_site.image_pixels_y, - height_scaled=height_scaled, - x_stage_position=x_transformed, - y_stage_position=y_transformed, - pixel_size=imaging_site.image_pixel_size, - image=imaging_site.thumbnail_path, + # Create/update the GridSquare entry in ISPyB + grid_square_entry = populate_fib_grid_square_entry( + session_id=session_id, + dcg_name=dcg_name, + atlas=atlas, + lamella=imaging_site, + lamella_number=lamella_number, + transport_object=murfey.server._transport_object, + murfey_db=murfey_db, ) - - # Register or update the grid square entry as required - if grid_square_entry := murfey_db.exec( - select(MurfeyDB.GridSquare) - .where(MurfeyDB.GridSquare.name == site_number) - .where(MurfeyDB.GridSquare.session_id == session_id) - .where(MurfeyDB.GridSquare.tag == grid_square_params.tag) - ).one_or_none(): - # Update existing grid square entry on Murfey - grid_square_entry.x_location = grid_square_params.x_location - grid_square_entry.y_location = grid_square_params.y_location - grid_square_entry.x_stage_position = grid_square_params.x_stage_position - grid_square_entry.y_stage_position = grid_square_params.y_stage_position - grid_square_entry.readout_area_x = grid_square_params.readout_area_x - grid_square_entry.readout_area_y = grid_square_params.readout_area_y - grid_square_entry.thumbnail_size_x = grid_square_params.thumbnail_size_x - grid_square_entry.thumbnail_size_y = grid_square_params.thumbnail_size_y - grid_square_entry.pixel_size = grid_square_params.pixel_size - grid_square_entry.image = grid_square_params.image - - # Update existing entry on ISPyB - murfey.server._transport_object.do_update_grid_square( - grid_square_id=grid_square_entry.id, - grid_square_parameters=grid_square_params, - ) - else: - # Look up data collection group for current series - dcg_entry = murfey_db.exec( - select(MurfeyDB.DataCollectionGroup) - .where(MurfeyDB.DataCollectionGroup.session_id == session_id) - .where(MurfeyDB.DataCollectionGroup.tag == grid_square_params.tag) - ).one() - # Register to ISPyB - grid_square_ispyb_result = ( - murfey.server._transport_object.do_insert_grid_square( - atlas_id=dcg_entry.atlas_id, - grid_square_id=site_number, - grid_square_parameters=grid_square_params, - ) - ) - # Create matching record in Murfey - grid_square_entry = MurfeyDB.GridSquare( - id=grid_square_ispyb_result.get("return_value", None), - name=site_number, - session_id=session_id, - tag=grid_square_params.tag, - x_location=grid_square_params.x_location, - y_location=grid_square_params.y_location, - x_stage_position=grid_square_params.x_stage_position, - y_stage_position=grid_square_params.y_stage_position, - readout_area_x=grid_square_params.readout_area_x, - readout_area_y=grid_square_params.readout_area_y, - thumbnail_size_x=grid_square_params.thumbnail_size_x, - thumbnail_size_y=grid_square_params.thumbnail_size_y, - pixel_size=grid_square_params.pixel_size, - image=grid_square_params.image, - ) murfey_db.add(grid_square_entry) # Add grid square ID to existing CLEM image series entry @@ -434,7 +307,7 @@ def run( fib_img_site = _register_grid_square( session_id=fib_info.session_id, imaging_site=fib_img_site, - site_number=metadata.lamella_number, + lamella_number=metadata.lamella_number, murfey_db=murfey_db, ) diff --git a/src/murfey/workflows/fib/shared.py b/src/murfey/workflows/fib/shared.py index a0ee133ff..14b6036b2 100644 --- a/src/murfey/workflows/fib/shared.py +++ b/src/murfey/workflows/fib/shared.py @@ -5,14 +5,18 @@ import math import xml.etree.ElementTree as ET from pathlib import Path -from typing import Any +from typing import TYPE_CHECKING, Any import numpy as np import PIL.Image +from sqlmodel import Session as SQLModelSession, select -from murfey.util.db import ImagingSite +import murfey.util.db as MurfeyDB from murfey.util.fib import get_slot_number, number_from_name -from murfey.util.models import FIBImageMetadata +from murfey.util.models import FIBImageMetadata, GridSquareParameters + +if TYPE_CHECKING: + from murfey.server.ispyb import TransportManager def parse_image_metadata( @@ -116,7 +120,7 @@ def parse_image_metadata( def populate_fib_imaging_site_entry( - imaging_site: ImagingSite, + imaging_site: MurfeyDB.ImagingSite, metadata: FIBImageMetadata, ): imaging_site.image_path = str(metadata.file) @@ -140,3 +144,174 @@ def populate_fib_imaging_site_entry( imaging_site.thumbnail_pixel_size = metadata.pixel_size / scale return imaging_site + + +def populate_fib_grid_square_entry( + session_id: int, + dcg_name: str, + atlas: MurfeyDB.ImagingSite, + lamella: MurfeyDB.ImagingSite, + lamella_number: int, + transport_object: TransportManager, + murfey_db: SQLModelSession, +): + """ + Helper function to create/update an ISPyB GridSquare entry for a FIB lamella + image so that its coordinates on the parent atlas image are up-to-date. Returns + the corresponding Murfey GridSquare entry, which can then be added and committed + to the database in the parent function. + """ + # Check if the atlas has the required values for the GridSquare registration + if not ( + atlas.pos_x is not None + and atlas.pos_y is not None + and atlas.pos_z is not None + and atlas.rotation is not None + and atlas.tilt_alpha is not None + and atlas.len_x is not None + and atlas.len_y is not None + and atlas.thumbnail_pixels_x is not None + and atlas.thumbnail_pixels_y is not None + ): + raise ValueError( + f"Atlas {atlas.image_path} missing one or more required values: \n" + " 'pos_x'\n" + " 'pos_y'\n" + " 'pos_z'\n" + " 'rotation'\n" + " 'tilt_alpha'\n" + " 'len_x'\n" + " 'len_y'\n" + " 'thumbnail_pixels_x'\n" + " 'thumbnail_pixels_x'" + ) + atlas_x1 = atlas.pos_x + (atlas.len_x / 2) + atlas_y0 = atlas.pos_y - (atlas.len_y / 2) + + # Check that the lamella ImagingSite entry has the required values + if not ( + lamella.pos_x is not None + and lamella.pos_y is not None + and lamella.pos_z is not None + and lamella.rotation is not None + and lamella.tilt_alpha is not None + and lamella.len_x is not None + and lamella.len_y is not None + ): + raise ValueError( + f"ImagingSite for {lamella.image_path} missing one or more required values: \n" + " 'pos_x'\n" + " 'pos_y'\n" + " 'pos_z'\n" + " 'rotation'\n" + " 'tilt_alpha'\n" + " 'len_x'\n" + " 'len_y'" + ) + + # Transform the imaging site coordinates into the atlas' frame of reference + # NOTE: This will require further investigation and tweaking, given the + # many axes and centres of rotation present in the FIB stage system. + # We start with a simple 2D rotation for now, and will adjust it as we observe + # the alignment accuracy + theta = math.radians(lamella.rotation - atlas.rotation) + sin = math.sin(theta) + cos = math.cos(theta) + x_transformed = (lamella.pos_x * cos) - (lamella.pos_y * sin) + y_transformed = (lamella.pos_x * sin) + (lamella.pos_y * cos) + + # Find the pixel coordinates of the image on the atlas + # NOTE: On the atlas image, positive directions are LEFT (x) and DOWN (y) + x_mid_px = int( + round((atlas_x1 - x_transformed) / atlas.len_x * atlas.thumbnail_pixels_x) or 1 + ) + y_mid_px = int( + round((y_transformed - atlas_y0) / atlas.len_y * atlas.thumbnail_pixels_y) or 1 + ) + + # Find the pixel width and height of the lamella image on the atlas + width_scaled = int( + round((lamella.len_x / atlas.len_x) * atlas.thumbnail_pixels_x) or 1 + ) + height_scaled = int( + round((lamella.len_y / atlas.len_y) * atlas.thumbnail_pixels_y) or 1 + ) + + # Populate GridSquareParameters model + grid_square_params = GridSquareParameters( + tag=dcg_name, + x_location=x_transformed, + x_location_scaled=x_mid_px, + y_location=y_transformed, + y_location_scaled=y_mid_px, + readout_area_x=lamella.image_pixels_x, + readout_area_y=lamella.image_pixels_y, + thumbnail_size_x=lamella.thumbnail_pixels_x, + thumbnail_size_y=lamella.thumbnail_pixels_y, + width=lamella.image_pixels_x, + width_scaled=width_scaled, + height=lamella.image_pixels_y, + height_scaled=height_scaled, + x_stage_position=x_transformed, + y_stage_position=y_transformed, + pixel_size=lamella.image_pixel_size, + image=lamella.thumbnail_path, + ) + + # Register or update the grid square entry as required + grid_square_entry = murfey_db.exec( + select(MurfeyDB.GridSquare) + .where(MurfeyDB.GridSquare.name == lamella_number) + .where(MurfeyDB.GridSquare.session_id == session_id) + .where(MurfeyDB.GridSquare.tag == grid_square_params.tag) + ).one_or_none() + if grid_square_entry is not None: + # Update existing grid square entry on Murfey + grid_square_entry.x_location = grid_square_params.x_location + grid_square_entry.y_location = grid_square_params.y_location + grid_square_entry.x_stage_position = grid_square_params.x_stage_position + grid_square_entry.y_stage_position = grid_square_params.y_stage_position + grid_square_entry.readout_area_x = grid_square_params.readout_area_x + grid_square_entry.readout_area_y = grid_square_params.readout_area_y + grid_square_entry.thumbnail_size_x = grid_square_params.thumbnail_size_x + grid_square_entry.thumbnail_size_y = grid_square_params.thumbnail_size_y + grid_square_entry.pixel_size = grid_square_params.pixel_size + grid_square_entry.image = grid_square_params.image + + # Update existing entry on ISPyB + transport_object.do_update_grid_square( + grid_square_id=grid_square_entry.id, + grid_square_parameters=grid_square_params, + ) + else: + # Look up data collection group for current series + dcg_entry = murfey_db.exec( + select(MurfeyDB.DataCollectionGroup) + .where(MurfeyDB.DataCollectionGroup.session_id == session_id) + .where(MurfeyDB.DataCollectionGroup.tag == grid_square_params.tag) + ).one() + # Register to ISPyB + grid_square_ispyb_result = transport_object.do_insert_grid_square( + atlas_id=dcg_entry.atlas_id, + grid_square_id=lamella_number, + grid_square_parameters=grid_square_params, + ) + # Create matching record in Murfey + grid_square_entry = MurfeyDB.GridSquare( + id=grid_square_ispyb_result.get("return_value", None), + name=lamella_number, + session_id=session_id, + tag=grid_square_params.tag, + x_location=grid_square_params.x_location, + y_location=grid_square_params.y_location, + x_stage_position=grid_square_params.x_stage_position, + y_stage_position=grid_square_params.y_stage_position, + readout_area_x=grid_square_params.readout_area_x, + readout_area_y=grid_square_params.readout_area_y, + thumbnail_size_x=grid_square_params.thumbnail_size_x, + thumbnail_size_y=grid_square_params.thumbnail_size_y, + pixel_size=grid_square_params.pixel_size, + image=grid_square_params.image, + ) + + return grid_square_entry From fdc15b216b64c26e057d26f8e2859e1911674e3c Mon Sep 17 00:00:00 2001 From: Eu Pin Tien Date: Mon, 5 Oct 2026 15:58:11 +0100 Subject: [PATCH 3/6] Need to enable dynamic type checking --- src/murfey/workflows/fib/shared.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/murfey/workflows/fib/shared.py b/src/murfey/workflows/fib/shared.py index 14b6036b2..5af8f2b2d 100644 --- a/src/murfey/workflows/fib/shared.py +++ b/src/murfey/workflows/fib/shared.py @@ -2,6 +2,8 @@ Functions shared by the FIB workflows """ +from __future__ import annotations + import math import xml.etree.ElementTree as ET from pathlib import Path From d550871f1b073530d7cbc4537e6d186ca5701c84 Mon Sep 17 00:00:00 2001 From: Eu Pin Tien Date: Mon, 5 Oct 2026 18:23:00 +0100 Subject: [PATCH 4/6] Updated test to check that lamella images are updated upon atlas update --- tests/workflows/fib/test_register_atlas.py | 209 ++++++++++++++------- 1 file changed, 140 insertions(+), 69 deletions(-) diff --git a/tests/workflows/fib/test_register_atlas.py b/tests/workflows/fib/test_register_atlas.py index 9f69074e8..3e9cb73ee 100644 --- a/tests/workflows/fib/test_register_atlas.py +++ b/tests/workflows/fib/test_register_atlas.py @@ -1,4 +1,5 @@ from pathlib import Path +from typing import Any from unittest.mock import MagicMock import ispyb.sqlalchemy as ISPyBDB @@ -16,6 +17,7 @@ from murfey.util.fib import get_slot_number, number_from_name from murfey.util.models import FIBImageMetadata from murfey.workflows.fib.register_atlas import run +from murfey.workflows.fib.shared import populate_fib_imaging_site_entry from tests.conftest import ExampleVisit session_id = 10 @@ -38,12 +40,37 @@ def test_run_with_db( mock_ispyb_credentials, ): rotation_offset = -75 - test_files = ( + atlas_files = ( visit_dir / "maps/LayersData/Layer/Electron Snapshot/Electron Snapshot.tiff", visit_dir / "maps/LayersData/Layer/Electron Snapshot/Electron Snapshot (2).tiff", ) + # Mock metadata template to use for the images + # These will be constant for all metadata + metadata: dict[str, Any] = { + "voltage": 2000, + "shift_x": 0, + "shift_y": 0, + "pos_x": 0.003, + "pos_y": 0.0003, + "pos_z": 0.01, + "rotation": -1.309, + "tilt_alpha": 0.8, + "tilt_beta": 0, + "pixels_x": 1500, + "pixels_y": 1000, + "thumbnail_pixels_x": 512, + "thumbnail_pixels_y": 341, + } + slot_number = get_slot_number( + x=metadata["pos_x"], + y=metadata["pos_y"], + rotation=metadata["rotation"], + rotation_offset=rotation_offset, + ) + metadata["slot_number"] = slot_number + # Add a test visit to the database if not ( murfey_session := murfey_db_session.exec( @@ -56,6 +83,70 @@ def test_run_with_db( murfey_session.instrument_name = instrument_name murfey_db_session.add(murfey_session) + + # Mock the atlas metadata + atlas_metadata_dict = metadata.copy() + atlas_metadata_dict["len_x"] = 0.003000 + atlas_metadata_dict["len_y"] = 0.002000 + atlas_metadata_dict["pixel_size_x"] = ( + atlas_metadata_dict["len_x"] / atlas_metadata_dict["pixels_x"] + ) + atlas_metadata_dict["pixel_size_y"] = ( + atlas_metadata_dict["len_y"] / atlas_metadata_dict["pixels_y"] + ) + mock_atlas_metadata = [ + FIBImageMetadata( + visit_name=visit_name, + file=test_file, + **atlas_metadata_dict, + ) + for test_file in atlas_files + ] + + # Add a test lamella image to the database + lamella_metadata_dict = metadata.copy() + lamella_metadata_dict["file"] = ( + visit_dir + / "autotem" + / visit_name + / "Sites" + / "Lamella" + / "LamellaEvaluationImages" + / "2026-04-30-15-11-43_drift_corrected_image_Polishing 2 - Electron Image.png" + ) + lamella_metadata_dict["thumbnail_path"] = ( + visit_dir + / "processed" + / visit_name + / f"grid_{slot_number}" + / "lamella_evaluation_images" + / "2026-04-30-15-11-43_polishing_2.png" + ) + lamella_metadata_dict["lamella_number"] = 1 + lamella_metadata_dict["len_x"] = 0.000900 + lamella_metadata_dict["len_y"] = 0.000600 + lamella_metadata_dict["pixel_size_x"] = ( + lamella_metadata_dict["len_x"] / lamella_metadata_dict["pixels_x"] + ) + lamella_metadata_dict["pixel_size_y"] = ( + lamella_metadata_dict["len_y"] / lamella_metadata_dict["pixels_y"] + ) + lamella_metadata = FIBImageMetadata(**lamella_metadata_dict) + + lamella_site = MurfeyDB.ImagingSite( + session_id=session_id, + site_name=lamella_metadata.site_name, + data_type="grid_square", + dcg_name=mock_atlas_metadata[0].site_name, + ) + lamella_site = populate_fib_imaging_site_entry( + lamella_site, + metadata=lamella_metadata, + ) + + murfey_db_session.add(lamella_site) + + # Commit all database changes murfey_db_session.commit() # Mock the MachineConfig @@ -94,42 +185,10 @@ def test_run_with_db( "murfey.server._transport_object", new=TransportManager("PikaTransport") ) - # Mock the metadata returned from the image file - extracted = { - "voltage": 2000, - "shift_x": 0, - "shift_y": 0, - "len_x": 0.003072, - "len_y": 0.002048, - "pos_x": 0.003, - "pos_y": 0.0003, - "pos_z": 0.01, - "rotation": -1.309, - "tilt_alpha": 0.8, - "tilt_beta": 0, - "pixels_x": 1500, - "pixels_y": 1000, - "pixel_size_x": 1e-6, - "pixel_size_y": 1e-6, - } - slot_number = get_slot_number( - x=extracted["pos_x"], - y=extracted["pos_y"], - rotation=extracted["rotation"], - rotation_offset=rotation_offset, - ) - extracted["slot_number"] = slot_number - mock_metadata = [ - FIBImageMetadata( - visit_name=visit_name, - file=test_file, - **extracted, - ) - for test_file in test_files - ] + # Mock the metadata returned from the atlas file mock_parse = mocker.patch( "murfey.workflows.fib.register_atlas.parse_image_metadata", - return_value=extracted, + return_value=atlas_metadata_dict, ) # Mock 'PIL.Image.open' and create a test image @@ -139,7 +198,7 @@ def test_run_with_db( ) # Build the name of the expected test images thumbnails: list[Path] = [] - for file in test_files: + for file in atlas_files: image_number = number_from_name(file.stem) thumbnail = ( visit_dir @@ -164,7 +223,7 @@ def test_run_with_db( ) # Run the function and check that it's run through to completion - for test_file in test_files: + for test_file in atlas_files: run( message={ "register": "fib.register_atlas", @@ -173,55 +232,67 @@ def test_run_with_db( }, murfey_db=murfey_db_session, ) - assert mock_parse.call_count == len(test_files) - assert spy_thumbnail.call_count == len(test_files) + assert mock_parse.call_count == len(atlas_files) + assert spy_thumbnail.call_count == len(atlas_files) for thumbnail in thumbnails: assert thumbnail.is_file() - assert spy_register.call_count == len(test_files) + assert spy_register.call_count == len(atlas_files) - # Murfey's ImagingSite should have an entry - search_results = murfey_db_session.exec( - sm_select(MurfeyDB.ImagingSite).where( - MurfeyDB.ImagingSite.session_id == session_id - ) - ).all() - assert len(search_results) == 1 - assert search_results[0].image_path == str(mock_metadata[-1].file) - - # Murfey's DataCollectionGroup should have an entry - murfey_dcg_search = murfey_db_session.exec( - sm_select(MurfeyDB.DataCollectionGroup).where( - MurfeyDB.DataCollectionGroup.session_id == session_id - ) - ).all() - assert len(murfey_dcg_search) == 1 + # Murfey's ImagingSite table should have an entry + atlas = murfey_db_session.exec( + sm_select(MurfeyDB.ImagingSite) + .where(MurfeyDB.ImagingSite.session_id == session_id) + .where(MurfeyDB.ImagingSite.data_type == "atlas") + ).one() + assert atlas.image_path == str(mock_atlas_metadata[-1].file) - # ISPyB's DataCollectionGroup should have an entry - murfey_dcg = murfey_dcg_search[0] - ispyb_dcg_search = ( + # Murfey's DataCollectionGroup table should have an entry + murfey_dcg = murfey_db_session.exec( + sm_select(MurfeyDB.DataCollectionGroup) + .where(MurfeyDB.DataCollectionGroup.session_id == session_id) + .where(MurfeyDB.DataCollectionGroup.tag == mock_atlas_metadata[-1].site_name) + ).one() + + # Murfey's GridSquare table should have an entry + murfey_gs = murfey_db_session.exec( + sm_select(MurfeyDB.GridSquare) + .where(MurfeyDB.GridSquare.session_id == session_id) + .where(MurfeyDB.GridSquare.tag == mock_atlas_metadata[-1].site_name) + ).one() + + # ISPyB's DataCollectionGroup table should have an entry + ispyb_dcg = ( ispyb_db_session.execute( sa_select(ISPyBDB.DataCollectionGroup).where( ISPyBDB.DataCollectionGroup.dataCollectionGroupId == murfey_dcg.id ) ) .scalars() - .all() + .one() ) - assert len(ispyb_dcg_search) == 1 - # Atlas should have an entry - ispyb_dcg = ispyb_dcg_search[0] - ispyb_atlas_search = ( + # ISPyB's Atlas table should have an entry + ispyb_atlas = ( ispyb_db_session.execute( sa_select(ISPyBDB.Atlas).where( ISPyBDB.Atlas.dataCollectionGroupId == ispyb_dcg.dataCollectionGroupId ) ) .scalars() - .all() + .one() ) - assert len(ispyb_atlas_search) == 1 - ispyb_atlas_entry = ispyb_atlas_search[0] - assert ispyb_atlas_entry.atlasImage.endswith( - f"atlas_{str(mock_metadata[-1].slot_number).zfill(2)}.png" + assert ispyb_atlas.atlasImage.endswith( + f"atlas_{str(mock_atlas_metadata[-1].slot_number).zfill(2)}.png" + ) + + # ISPyB's GridSquare table should have an entry + ispyb_gs = ( + ispyb_db_session.execute( + sa_select(ISPyBDB.GridSquare).where( + ISPyBDB.GridSquare.gridSquareId == murfey_gs.id + ) + ) + .scalars() + .one() ) + assert ispyb_gs.gridSquareImage == str(lamella_metadata_dict["thumbnail_path"]) From 2e9e1d5d6b13a6c25840c39f1eed1e022b8ab3ed Mon Sep 17 00:00:00 2001 From: Eu Pin Tien Date: Mon, 5 Oct 2026 18:35:21 +0100 Subject: [PATCH 5/6] Forgot to include visit name when constructing test lamella metadata --- tests/workflows/fib/test_register_atlas.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/workflows/fib/test_register_atlas.py b/tests/workflows/fib/test_register_atlas.py index 3e9cb73ee..f2a9a273a 100644 --- a/tests/workflows/fib/test_register_atlas.py +++ b/tests/workflows/fib/test_register_atlas.py @@ -97,10 +97,10 @@ def test_run_with_db( mock_atlas_metadata = [ FIBImageMetadata( visit_name=visit_name, - file=test_file, + file=file, **atlas_metadata_dict, ) - for test_file in atlas_files + for file in atlas_files ] # Add a test lamella image to the database @@ -131,7 +131,7 @@ def test_run_with_db( lamella_metadata_dict["pixel_size_y"] = ( lamella_metadata_dict["len_y"] / lamella_metadata_dict["pixels_y"] ) - lamella_metadata = FIBImageMetadata(**lamella_metadata_dict) + lamella_metadata = FIBImageMetadata(visit_name=visit_name, **lamella_metadata_dict) lamella_site = MurfeyDB.ImagingSite( session_id=session_id, From 83dd816774f4a6d224a462e2f0234bce3a2fbd35 Mon Sep 17 00:00:00 2001 From: Eu Pin Tien Date: Mon, 5 Oct 2026 18:55:03 +0100 Subject: [PATCH 6/6] Parametrise test to check behaviour when lamella images are present or not --- tests/workflows/fib/test_register_atlas.py | 141 ++++++++++++--------- 1 file changed, 83 insertions(+), 58 deletions(-) diff --git a/tests/workflows/fib/test_register_atlas.py b/tests/workflows/fib/test_register_atlas.py index f2a9a273a..21023a85e 100644 --- a/tests/workflows/fib/test_register_atlas.py +++ b/tests/workflows/fib/test_register_atlas.py @@ -32,8 +32,10 @@ def visit_dir(tmp_path: Path): return visit_dir +@pytest.mark.parametrize("has_lamella", (True, False)) def test_run_with_db( mocker: MockerFixture, + has_lamella: bool, visit_dir: Path, murfey_db_session: SQLModelSession, ispyb_db_session: SQLAlchemySession, @@ -103,48 +105,53 @@ def test_run_with_db( for file in atlas_files ] - # Add a test lamella image to the database - lamella_metadata_dict = metadata.copy() - lamella_metadata_dict["file"] = ( - visit_dir - / "autotem" - / visit_name - / "Sites" - / "Lamella" - / "LamellaEvaluationImages" - / "2026-04-30-15-11-43_drift_corrected_image_Polishing 2 - Electron Image.png" - ) - lamella_metadata_dict["thumbnail_path"] = ( - visit_dir - / "processed" - / visit_name - / f"grid_{slot_number}" - / "lamella_evaluation_images" - / "2026-04-30-15-11-43_polishing_2.png" - ) - lamella_metadata_dict["lamella_number"] = 1 - lamella_metadata_dict["len_x"] = 0.000900 - lamella_metadata_dict["len_y"] = 0.000600 - lamella_metadata_dict["pixel_size_x"] = ( - lamella_metadata_dict["len_x"] / lamella_metadata_dict["pixels_x"] - ) - lamella_metadata_dict["pixel_size_y"] = ( - lamella_metadata_dict["len_y"] / lamella_metadata_dict["pixels_y"] - ) - lamella_metadata = FIBImageMetadata(visit_name=visit_name, **lamella_metadata_dict) + # Construct test lamella metadata if needed + lamella_metadata_dict: dict[str, Any] = {} + if has_lamella: + lamella_metadata_dict = metadata.copy() + lamella_metadata_dict["file"] = ( + visit_dir + / "autotem" + / visit_name + / "Sites" + / "Lamella" + / "LamellaEvaluationImages" + / "2026-04-30-15-11-43_drift_corrected_image_Polishing 2 - Electron Image.png" + ) + lamella_metadata_dict["thumbnail_path"] = ( + visit_dir + / "processed" + / visit_name + / f"grid_{slot_number}" + / "lamella_evaluation_images" + / "2026-04-30-15-11-43_polishing_2.png" + ) + lamella_metadata_dict["lamella_number"] = 1 + lamella_metadata_dict["len_x"] = 0.000900 + lamella_metadata_dict["len_y"] = 0.000600 + lamella_metadata_dict["pixel_size_x"] = ( + lamella_metadata_dict["len_x"] / lamella_metadata_dict["pixels_x"] + ) + lamella_metadata_dict["pixel_size_y"] = ( + lamella_metadata_dict["len_y"] / lamella_metadata_dict["pixels_y"] + ) + lamella_metadata = FIBImageMetadata( + visit_name=visit_name, **lamella_metadata_dict + ) - lamella_site = MurfeyDB.ImagingSite( - session_id=session_id, - site_name=lamella_metadata.site_name, - data_type="grid_square", - dcg_name=mock_atlas_metadata[0].site_name, - ) - lamella_site = populate_fib_imaging_site_entry( - lamella_site, - metadata=lamella_metadata, - ) + # Create and populate the lamella entry + lamella_site = MurfeyDB.ImagingSite( + session_id=session_id, + site_name=lamella_metadata.site_name, + data_type="grid_square", + dcg_name=mock_atlas_metadata[0].site_name, + ) + lamella_site = populate_fib_imaging_site_entry( + lamella_site, + metadata=lamella_metadata, + ) - murfey_db_session.add(lamella_site) + murfey_db_session.add(lamella_site) # Commit all database changes murfey_db_session.commit() @@ -239,12 +246,12 @@ def test_run_with_db( assert spy_register.call_count == len(atlas_files) # Murfey's ImagingSite table should have an entry - atlas = murfey_db_session.exec( + murfey_atlas = murfey_db_session.exec( sm_select(MurfeyDB.ImagingSite) .where(MurfeyDB.ImagingSite.session_id == session_id) .where(MurfeyDB.ImagingSite.data_type == "atlas") ).one() - assert atlas.image_path == str(mock_atlas_metadata[-1].file) + assert murfey_atlas.image_path == str(mock_atlas_metadata[-1].file) # Murfey's DataCollectionGroup table should have an entry murfey_dcg = murfey_db_session.exec( @@ -253,13 +260,6 @@ def test_run_with_db( .where(MurfeyDB.DataCollectionGroup.tag == mock_atlas_metadata[-1].site_name) ).one() - # Murfey's GridSquare table should have an entry - murfey_gs = murfey_db_session.exec( - sm_select(MurfeyDB.GridSquare) - .where(MurfeyDB.GridSquare.session_id == session_id) - .where(MurfeyDB.GridSquare.tag == mock_atlas_metadata[-1].site_name) - ).one() - # ISPyB's DataCollectionGroup table should have an entry ispyb_dcg = ( ispyb_db_session.execute( @@ -285,14 +285,39 @@ def test_run_with_db( f"atlas_{str(mock_atlas_metadata[-1].slot_number).zfill(2)}.png" ) - # ISPyB's GridSquare table should have an entry - ispyb_gs = ( - ispyb_db_session.execute( - sa_select(ISPyBDB.GridSquare).where( - ISPyBDB.GridSquare.gridSquareId == murfey_gs.id + # Different GridSquare verification logic depending on if there was a lamella image + murfey_gs = murfey_db_session.exec( + sm_select(MurfeyDB.GridSquare) + .where(MurfeyDB.GridSquare.session_id == session_id) + .where(MurfeyDB.GridSquare.tag == mock_atlas_metadata[-1].site_name) + ).one_or_none() + if has_lamella: + # Murfey's GridSquare table should have an entry + assert murfey_gs is not None + + # ISPyB's GridSquare table should have an entry + ispyb_gs = ( + ispyb_db_session.execute( + sa_select(ISPyBDB.GridSquare).where( + ISPyBDB.GridSquare.gridSquareId == murfey_gs.id + ) ) + .scalars() + .one() ) - .scalars() - .one() - ) - assert ispyb_gs.gridSquareImage == str(lamella_metadata_dict["thumbnail_path"]) + assert ispyb_gs.gridSquareImage == str(lamella_metadata_dict["thumbnail_path"]) + else: + # Murfey's GridSquare table should be empty + assert murfey_gs is None + + # ISPyB's GridSquare table should be empty + ispyb_gs = ( + ispyb_db_session.execute( + sa_select(ISPyBDB.GridSquare).where( + ISPyBDB.GridSquare.atlasId == ispyb_atlas.atlasId + ) + ) + .scalars() + .one_or_none() + ) + assert ispyb_gs is None