From 61df855b8e7eec0888f6d4f2abf0258e3d71846e Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Tue, 2 Jun 2026 13:23:06 +0100 Subject: [PATCH 1/2] Break out the foil hole logic --- pyproject.toml | 1 + src/murfey/client/contexts/spa_metadata.py | 185 ++++++++-------- src/murfey/server/api/session_control.py | 23 +- src/murfey/util/route_manifest.yaml | 4 +- .../workflows/spa/flush_spa_preprocess.py | 203 +++--------------- .../workflows/spa/register_foil_holes.py | 194 +++++++++++++++++ 6 files changed, 339 insertions(+), 271 deletions(-) create mode 100644 src/murfey/workflows/spa/register_foil_holes.py diff --git a/pyproject.toml b/pyproject.toml index b181e111b..3d94e5923 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -124,6 +124,7 @@ TomographyMetadataContext = "murfey.client.contexts.tomo_metadata:TomographyMeta "spa.ctf_estimated" = "murfey.workflows.spa.ctf_estimation:ctf_estimated" "spa.flush_spa_preprocess" = "murfey.workflows.spa.flush_spa_preprocess:flush_spa_preprocess" "spa.motion_corrected" = "murfey.workflows.spa.motion_correction:motion_corrected" +"spa.register_foil_holes" = "murfey.workflows.spa.register_foil_holes:register_foil_holes" [tool.setuptools] package-dir = {"" = "src"} diff --git a/src/murfey/client/contexts/spa_metadata.py b/src/murfey/client/contexts/spa_metadata.py index 4f2dd262c..31383fb6a 100644 --- a/src/murfey/client/contexts/spa_metadata.py +++ b/src/murfey/client/contexts/spa_metadata.py @@ -1,4 +1,5 @@ import logging +import xml.etree.ElementTree as ET from pathlib import Path from typing import Dict, Optional @@ -12,8 +13,8 @@ ) from murfey.client.instance_environment import MurfeyInstanceEnvironment from murfey.util.client import capture_post +from murfey.util.models import FoilHoleParameters from murfey.util.spa_metadata import ( - FoilHoleInfo, get_grid_square_atlas_positions, grid_square_data, ) @@ -21,55 +22,75 @@ logger = logging.getLogger("murfey.client.contexts.spa_metadata") -def _foil_hole_positions(xml_path: Path, grid_square: int) -> Dict[str, FoilHoleInfo]: - with open(xml_path, "r") as xml: - for_parsing = xml.read() - data = xmltodict.parse(for_parsing) - data = data["GridSquareXml"] - if "TargetLocationsEfficient" in data["TargetLocations"].keys(): - # Grids with regular foil holes - serialization_array = data["TargetLocations"]["TargetLocationsEfficient"][ - "a:m_serializationArray" - ] - elif "TargetLocations" in data["TargetLocations"].keys(): - # Lacey grids - serialization_array = data["TargetLocations"]["TargetLocations"][ - "a:m_serializationArray" - ] - else: +def _foil_hole_positions( + xml_path: Path, tag: str, grid_square: int +) -> Dict[str, FoilHoleParameters]: + tree = ET.parse(xml_path) + for elem in tree.iter(): + if "}" in elem.tag: + elem.tag = elem.tag.split("}")[1] + tree_root = tree.getroot() + target_locations = tree_root.find("TargetLocations") + serialization_array = None + if target_locations: + if eff_target_locations := target_locations.find("TargetLocationsEfficient"): + # Grids with regular foil holes + serialization_array = eff_target_locations.find("m_serializationArray") + elif eff_target_locations := target_locations.find("TargetLocations"): + # Lacey grids + serialization_array = eff_target_locations.find("m_serializationArray") + if not serialization_array: logger.warning(f"Target locations not found for {str(xml_path)}") return {} - required_key = "" - for key in serialization_array.keys(): - if key.startswith("b:KeyValuePairOfintTargetLocation"): - required_key = key - break - if not required_key: - logger.info(f"Required key not found for {str(xml_path)}") - return {} + + fh_blocks = [ + i + for i in serialization_array.iter() + if "KeyValuePairOfintTargetLocationXml" in i.tag + ] foil_holes = {} - for fh_block in serialization_array[required_key]: - if fh_block["b:value"]["IsNearGridBar"] == "false": - image_paths = list( - (xml_path.parent.parent).glob( - f"Images-Disc*/GridSquare_{grid_square}/FoilHoles/FoilHole_{fh_block['b:key']}_*.jpg" + for fh_block in fh_blocks: + fh_block_values = fh_block.find("value") + fh_key = fh_block.find("key") + if fh_key and fh_key.text and fh_block_values: + near_grid_bar = fh_block_values.find("IsNearGridBar") + if near_grid_bar and near_grid_bar.text == "false": + image_paths = list( + (xml_path.parent.parent).glob( + f"Images-Disc*/GridSquare_{grid_square}/FoilHoles/FoilHole_{fh_key.text}_*.jpg" + ) ) - ) - image_paths.sort(key=lambda x: x.stat().st_ctime) - image_path: str = str(image_paths[-1]) if image_paths else "" - pix_loc = fh_block["b:value"]["PixelCenter"] - stage = fh_block["b:value"]["StagePosition"] - diameter = fh_block["b:value"]["PixelWidthHeight"]["c:width"] - foil_holes[fh_block["b:key"]] = FoilHoleInfo( - id=int(fh_block["b:key"]), - grid_square_id=grid_square, - x_location=int(float(pix_loc["c:x"])), - y_location=int(float(pix_loc["c:y"])), - x_stage_position=float(stage["c:X"]), - y_stage_position=float(stage["c:Y"]), - image=str(image_path), - diameter=int(float(diameter)), - ) + image_paths.sort(key=lambda x: x.stat().st_ctime) + image_path: str = str(image_paths[-1]) if image_paths else "" + pix_loc = fh_block_values.find("PixelCenter") + stage = fh_block_values.find("StagePosition") + width_height = fh_block_values.find("PixelWidthHeight") + diameter = width_height.find("width") if width_height else None + if pix_loc and stage: + x_pix = pix_loc.find("x") + y_pix = pix_loc.find("y") + x_stage = stage.find("X") + y_stage = stage.find("Y") + foil_holes[fh_key.text] = FoilHoleParameters( + name=int(fh_key.text), + tag=tag, + x_location=int(float(x_pix.text)) + if (x_pix and x_pix.text) + else None, + y_location=int(float(y_pix.text)) + if (y_pix and y_pix.text) + else None, + x_stage_position=int(float(x_stage.text)) + if (x_stage and x_stage.text) + else None, + y_stage_position=int(float(y_stage.text)) + if (y_stage and y_stage.text) + else None, + image=str(image_path), + diameter=int(float(diameter.text)) + if (diameter and diameter.text) + else None, + ).model_dump() return foil_holes @@ -193,7 +214,6 @@ def post_transfer( logger.info( f"Collecting foil hole positions for {str(transferred_file)} and grid square {gs_name}" ) - fh_positions = _foil_hole_positions(transferred_file, gs_name) visitless_source_search_dir = "/".join( [part for part in source.parts if part != environment.visit] ).replace("//", "/") @@ -209,6 +229,11 @@ def post_transfer( Path(visitless_source_search_dir) / "Images-Disc1" ] visitless_source = str(visitless_source_images_dirs[-1]) + fh_positions = _foil_hole_positions( + xml_path=transferred_file, + tag=visitless_source, + grid_square=gs_name, + ) if fh_positions: gs_info = grid_square_data( @@ -244,44 +269,28 @@ def post_transfer( }, ) - if gs_name not in self._registered_squares: - for fh, fh_data in fh_positions.items(): - capture_post( - base_url=str(environment.url.geturl()), - router_name="session_control.spa_router", - function_name="register_foil_hole", - token=self._token, - instrument_name=environment.instrument_name, - session_id=environment.murfey_session, - gs_name=gs_name, - data={ - "name": fh, - "x_location": fh_data.x_location, - "y_location": fh_data.y_location, - "x_stage_position": fh_data.x_stage_position, - "y_stage_position": fh_data.y_stage_position, - "readout_area_x": fh_data.readout_area_x, - "readout_area_y": fh_data.readout_area_y, - "thumbnail_size_x": fh_data.thumbnail_size_x, - "thumbnail_size_y": fh_data.thumbnail_size_y, - "pixel_size": fh_data.pixel_size, - "diameter": fh_data.diameter, - "tag": visitless_source, - "image": fh_data.image, - }, - ) - if fh_positions: - capture_post( - base_url=str(environment.url.geturl()), - router_name="session_control.spa_router", - function_name="register_square", - token=self._token, - instrument_name=environment.instrument_name, - session_id=environment.murfey_session, - gsid=gs_name, - data={ - "tag": visitless_source, - "count": len(self._registered_squares) + 1, - }, - ) - self._registered_squares.add(gs_name) + if fh_positions and gs_name not in self._registered_squares: + capture_post( + base_url=str(environment.url.geturl()), + router_name="session_control.spa_router", + function_name="register_foil_holes", + token=self._token, + instrument_name=environment.instrument_name, + session_id=environment.murfey_session, + gs_name=gs_name, + data=fh_positions, + ) + capture_post( + base_url=str(environment.url.geturl()), + router_name="session_control.spa_router", + function_name="register_square", + token=self._token, + instrument_name=environment.instrument_name, + session_id=environment.murfey_session, + gsid=gs_name, + data={ + "tag": visitless_source, + "count": len(self._registered_squares) + 1, + }, + ) + self._registered_squares.add(gs_name) diff --git a/src/murfey/server/api/session_control.py b/src/murfey/server/api/session_control.py index bbc62a496..71603d61a 100644 --- a/src/murfey/server/api/session_control.py +++ b/src/murfey/server/api/session_control.py @@ -64,7 +64,6 @@ ) from murfey.workflows.spa.atlas import atlas_jpg_from_mrc from murfey.workflows.spa.flush_spa_preprocess import ( - register_foil_hole as _register_foil_hole, register_grid_square as _register_grid_square, ) from murfey.workflows.tomo.tomo_metadata import ( @@ -473,17 +472,25 @@ def register_grid_square( return _register_grid_square(session_id, gsid, grid_square_params, db) -@spa_router.post("/sessions/{session_id}/grid_square/{gs_name}/foil_hole") -def register_foil_hole( +@spa_router.post("/sessions/{session_id}/grid_square/{gs_name}/foil_holes") +def register_foil_holes( session_id: MurfeySessionID, gs_name: int, - foil_hole_params: FoilHoleParameters, + foil_hole_group: dict[str, FoilHoleParameters], db=murfey_db, ): - logger.info( - f"Registering foil hole {foil_hole_params.name} with position {(foil_hole_params.x_location, foil_hole_params.y_location)}" - ) - return _register_foil_hole(session_id, gs_name, foil_hole_params, db) + logger.info(f"Registering foil holes for grid square {gs_name}") + if _transport_object: + _transport_object.send( + _transport_object.feedback_queue, + { + "register": "spa.register_foil_holes", + "session_id": session_id, + "gs_name": gs_name, + "foil_hole_group": foil_hole_group, + }, + ) + return gs_name tomo_router = APIRouter( diff --git a/src/murfey/util/route_manifest.yaml b/src/murfey/util/route_manifest.yaml index ffdccaa51..c2ff84f0c 100644 --- a/src/murfey/util/route_manifest.yaml +++ b/src/murfey/util/route_manifest.yaml @@ -941,8 +941,8 @@ murfey.server.api.session_control.spa_router: type: int methods: - POST - - path: /session_control/spa/sessions/{session_id}/grid_square/{gs_name}/foil_hole - function: register_foil_hole + - path: /session_control/spa/sessions/{session_id}/grid_square/{gs_name}/foil_holes + function: register_foil_holes path_params: - name: gs_name type: int diff --git a/src/murfey/workflows/spa/flush_spa_preprocess.py b/src/murfey/workflows/spa/flush_spa_preprocess.py index e90a74bc1..dd6e77ddf 100644 --- a/src/murfey/workflows/spa/flush_spa_preprocess.py +++ b/src/murfey/workflows/spa/flush_spa_preprocess.py @@ -3,13 +3,11 @@ from typing import Optional from PIL import Image -from sqlalchemy.exc import NoResultFound from sqlmodel import Session, select try: from smartem_backend.api_client import SmartEMAPIClient from smartem_common.schemas import ( - FoilHoleData as SmartEMFoilHoleData, GridSquareData as SmartEMGridSquareData, GridSquareMetadata as SmartEMGridSquareMetadata, ) @@ -20,7 +18,7 @@ from murfey.server import _transport_object from murfey.server.feedback import _murfey_id -from murfey.util import sanitise, secure_path +from murfey.util import secure_path from murfey.util.config import get_machine_config, get_microscope from murfey.util.db import ( AutoProcProgram, @@ -45,6 +43,7 @@ grid_square_data, grid_square_from_file, ) +from murfey.workflows.spa.register_foil_holes import register_holes_on_grid logger = logging.getLogger("murfey.workflows.spa.flush_spa_preprocess") @@ -220,154 +219,6 @@ def register_grid_square( murfey_db.close() -def register_foil_hole( - session_id: int, - gs_name: int, - foil_hole_params: FoilHoleParameters, - murfey_db: Session, -) -> Optional[int]: - try: - gs = murfey_db.exec( - select(GridSquare) - .where(GridSquare.tag == foil_hole_params.tag) - .where(GridSquare.session_id == session_id) - .where(GridSquare.name == gs_name) - ).one() - gsid = gs.id - except NoResultFound: - logger.warning( - f"Foil hole {sanitise(str(foil_hole_params.name))} could not be registered as grid square {sanitise(str(gs_name))} was not found" - ) - return None - secured_foil_hole_image_path = secure_path(Path(foil_hole_params.image)) - if foil_hole_params.image and secured_foil_hole_image_path.is_file(): - jpeg_size = Image.open(secured_foil_hole_image_path).size - else: - jpeg_size = (0, 0) - foil_hole_query = murfey_db.exec( - select(FoilHole) - .where(FoilHole.name == foil_hole_params.name) - .where(FoilHole.grid_square_id == gsid) - .where(FoilHole.session_id == session_id) - ).all() - if foil_hole_query: - # Foil hole already exists in the murfey database - foil_hole = foil_hole_query[0] - foil_hole.x_location = foil_hole_params.x_location or foil_hole.x_location - foil_hole.y_location = foil_hole_params.y_location or foil_hole.y_location - foil_hole.x_stage_position = ( - foil_hole_params.x_stage_position or foil_hole.x_stage_position - ) - foil_hole.y_stage_position = ( - foil_hole_params.y_stage_position or foil_hole.y_stage_position - ) - foil_hole.readout_area_x = ( - foil_hole_params.readout_area_x or foil_hole.readout_area_x - ) - foil_hole.readout_area_y = ( - foil_hole_params.readout_area_y or foil_hole.readout_area_y - ) - foil_hole.thumbnail_size_x = ( - foil_hole_params.thumbnail_size_x or foil_hole.thumbnail_size_x - ) or jpeg_size[0] - foil_hole.thumbnail_size_y = ( - foil_hole_params.thumbnail_size_y or foil_hole.thumbnail_size_y - ) or jpeg_size[1] - foil_hole.pixel_size = foil_hole_params.pixel_size or foil_hole.pixel_size - if _transport_object and gs.readout_area_x: - _transport_object.do_update_foil_hole( - foil_hole.id, gs.thumbnail_size_x / gs.readout_area_x, foil_hole_params - ) - else: - # No existing foil hole in the murfey database - if _transport_object: - fh_ispyb_response = _transport_object.do_insert_foil_hole( - gs.id, - gs.thumbnail_size_x / gs.readout_area_x if gs.readout_area_x else None, - foil_hole_params, - ) - else: - fh_ispyb_response = {"success": False, "return_value": None} - foil_hole = FoilHole( - id=( - fh_ispyb_response["return_value"] - if fh_ispyb_response["success"] - else None - ), - name=foil_hole_params.name, - session_id=session_id, - grid_square_id=gsid, - x_location=foil_hole_params.x_location, - y_location=foil_hole_params.y_location, - x_stage_position=foil_hole_params.x_stage_position, - y_stage_position=foil_hole_params.y_stage_position, - readout_area_x=foil_hole_params.readout_area_x, - readout_area_y=foil_hole_params.readout_area_y, - thumbnail_size_x=foil_hole_params.thumbnail_size_x or jpeg_size[0], - thumbnail_size_y=foil_hole_params.thumbnail_size_y or jpeg_size[1], - pixel_size=foil_hole_params.pixel_size, - image=str(secured_foil_hole_image_path), - ) - fh_id = foil_hole.id - murfey_db.add(foil_hole) - murfey_db.commit() - - if SMARTEM_ACTIVE and gs.smartem_uuid: - try: - murfey_session = murfey_db.exec( - select(MurfeySession).where(MurfeySession.id == session_id) - ).one() - machine_config = get_machine_config( - instrument_name=murfey_session.instrument_name - )[murfey_session.instrument_name] - if machine_config.smartem_api_url: - smartem_client = SmartEMAPIClient( - base_url=machine_config.smartem_api_url, logger=logger - ) - fh_data = SmartEMFoilHoleData( - id=str(foil_hole_params.name), - gridsquare_id=str(gs.name), - gridsquare_uuid=gs.smartem_uuid, - x_location=( - int(foil_hole_params.x_location) - if foil_hole_params.x_location is not None - else None - ), - y_location=( - int(foil_hole_params.y_location) - if foil_hole_params.y_location is not None - else None - ), - x_stage_position=foil_hole_params.x_stage_position, - y_stage_position=foil_hole_params.y_stage_position, - diameter=( - int(foil_hole_params.diameter) - if foil_hole_params.diameter is not None - else None - ), - **( - {"uuid": foil_hole.smartem_uuid} - if foil_hole.smartem_uuid - else {} - ), - ) - if foil_hole.smartem_uuid: - smartem_client.update_foilhole(fh_data) - else: - responses = smartem_client.create_gridsquare_foilholes( - gs.smartem_uuid, [fh_data] - ) - if responses: - foil_hole.smartem_uuid = responses[0].uuid - murfey_db.add(foil_hole) - murfey_db.commit() - except Exception: - logger.warning("Failed to register foil hole with smartem", exc_info=True) - - murfey_db.close() - return fh_id - - def _grid_square_metadata_file(f: Path, grid_square: int) -> Optional[Path]: """Search through metadata directories to find the required grid square dm""" raw_dir = f.parent.parent.parent @@ -383,7 +234,7 @@ def _grid_square_metadata_file(f: Path, grid_square: int) -> Optional[Path]: def _flush_position_analysis( movie_path: Path, dcg_id: int, session_id: int, murfey_db: Session -) -> Optional[int]: +) -> list[int | None] | None: """Register a grid square and foil hole in the database""" data_collection_group = murfey_db.exec( select(DataCollectionGroup).where(DataCollectionGroup.id == dcg_id) @@ -440,28 +291,32 @@ def _flush_position_analysis( foil_hole, grid_square, ) - foil_hole_parameters = FoilHoleParameters( - tag=data_collection_group.tag, - name=foil_hole, - x_location=fh.x_location, - y_location=fh.y_location, - x_stage_position=fh.x_stage_position, - y_stage_position=fh.y_stage_position, - readout_area_x=fh.readout_area_x, - readout_area_y=fh.readout_area_y, - thumbnail_size_x=fh.thumbnail_size_x, - thumbnail_size_y=fh.thumbnail_size_y, - pixel_size=fh.pixel_size, - image=fh.image, - diameter=fh.diameter, - ) + foil_hole_parameters = { + str(foil_hole): FoilHoleParameters( + tag=data_collection_group.tag, + name=foil_hole, + x_location=fh.x_location, + y_location=fh.y_location, + x_stage_position=fh.x_stage_position, + y_stage_position=fh.y_stage_position, + readout_area_x=fh.readout_area_x, + readout_area_y=fh.readout_area_y, + thumbnail_size_x=fh.thumbnail_size_x, + thumbnail_size_y=fh.thumbnail_size_y, + pixel_size=fh.pixel_size, + image=fh.image, + diameter=fh.diameter, + ) + } else: - foil_hole_parameters = FoilHoleParameters( - tag=data_collection_group.tag, - name=foil_hole, - ) + foil_hole_parameters = { + str(foil_hole): FoilHoleParameters( + tag=data_collection_group.tag, + name=foil_hole, + ) + } # Insert or update this foil hole in the database - return register_foil_hole(session_id, gs.id, foil_hole_parameters, murfey_db) + return register_holes_on_grid(session_id, gs.id, foil_hole_parameters, murfey_db) def flush_spa_preprocess(message: dict, murfey_db: Session) -> dict[str, bool]: @@ -530,12 +385,14 @@ def flush_spa_preprocess(message: dict, murfey_db: Session) -> dict[str, bool]: foil_hole_id = f.foil_hole_id if not foil_hole_id: # Register grid square and foil hole if not present - foil_hole_id = _flush_position_analysis( + foil_hole_id_list = _flush_position_analysis( movie_path=Path(f.file_path), dcg_id=collected_ids[0].id, session_id=session_id, murfey_db=murfey_db, ) + if foil_hole_id_list: + foil_hole_id = foil_hole_id_list[0] except Exception as e: logger.error( f"Flushing position analysis for {f.file_path} caused exception {e}", diff --git a/src/murfey/workflows/spa/register_foil_holes.py b/src/murfey/workflows/spa/register_foil_holes.py new file mode 100644 index 000000000..63c0100fb --- /dev/null +++ b/src/murfey/workflows/spa/register_foil_holes.py @@ -0,0 +1,194 @@ +import logging +from pathlib import Path + +from PIL import Image +from sqlalchemy.exc import NoResultFound +from sqlmodel import Session, select + +try: + from smartem_backend.api_client import SmartEMAPIClient + from smartem_common.schemas import FoilHoleData as SmartEMFoilHoleData + + SMARTEM_ACTIVE = True +except ImportError: + SMARTEM_ACTIVE = False + +from murfey.server import _transport_object +from murfey.util import sanitise, secure_path +from murfey.util.config import get_machine_config +from murfey.util.db import ( + FoilHole, + GridSquare, + Session as MurfeySession, +) +from murfey.util.models import FoilHoleParameters + +logger = logging.getLogger("murfey.workflows.spa.register_foil_holes") + + +def register_holes_on_grid( + session_id: int, + gs_name: int, + foil_hole_group: dict[str, FoilHoleParameters], + murfey_db: Session, +) -> list[int | None] | None: + try: + gs = murfey_db.exec( + select(GridSquare) + .where(GridSquare.tag == next(iter(foil_hole_group.values())).tag) + .where(GridSquare.session_id == session_id) + .where(GridSquare.name == gs_name) + ).one() + gsid = gs.id + except NoResultFound: + logger.warning( + f"Foil holes could not be registered as grid square {sanitise(str(gs_name))} was not found" + ) + return None + + fh_ids: list[int | None] = [] + for fh_name, foil_hole_params in foil_hole_group.items(): + secured_foil_hole_image_path = secure_path(Path(foil_hole_params.image)) + if foil_hole_params.image and secured_foil_hole_image_path.is_file(): + jpeg_size = Image.open(secured_foil_hole_image_path).size + else: + jpeg_size = (0, 0) + foil_hole_query = murfey_db.exec( + select(FoilHole) + .where(FoilHole.name == foil_hole_params.name) + .where(FoilHole.grid_square_id == gsid) + .where(FoilHole.session_id == session_id) + ).all() + if foil_hole_query: + # Foil hole already exists in the murfey database + foil_hole = foil_hole_query[0] + foil_hole.x_location = foil_hole_params.x_location or foil_hole.x_location + foil_hole.y_location = foil_hole_params.y_location or foil_hole.y_location + foil_hole.x_stage_position = ( + foil_hole_params.x_stage_position or foil_hole.x_stage_position + ) + foil_hole.y_stage_position = ( + foil_hole_params.y_stage_position or foil_hole.y_stage_position + ) + foil_hole.readout_area_x = ( + foil_hole_params.readout_area_x or foil_hole.readout_area_x + ) + foil_hole.readout_area_y = ( + foil_hole_params.readout_area_y or foil_hole.readout_area_y + ) + foil_hole.thumbnail_size_x = ( + foil_hole_params.thumbnail_size_x or foil_hole.thumbnail_size_x + ) or jpeg_size[0] + foil_hole.thumbnail_size_y = ( + foil_hole_params.thumbnail_size_y or foil_hole.thumbnail_size_y + ) or jpeg_size[1] + foil_hole.pixel_size = foil_hole_params.pixel_size or foil_hole.pixel_size + if _transport_object and gs.readout_area_x: + _transport_object.do_update_foil_hole( + foil_hole.id, + gs.thumbnail_size_x / gs.readout_area_x, + foil_hole_params, + ) + else: + # No existing foil hole in the murfey database + if _transport_object: + fh_ispyb_response = _transport_object.do_insert_foil_hole( + gs.id, + gs.thumbnail_size_x / gs.readout_area_x + if gs.readout_area_x + else None, + foil_hole_params, + ) + else: + fh_ispyb_response = {"success": False, "return_value": None} + foil_hole = FoilHole( + id=( + fh_ispyb_response["return_value"] + if fh_ispyb_response["success"] + else None + ), + name=foil_hole_params.name, + session_id=session_id, + grid_square_id=gsid, + x_location=foil_hole_params.x_location, + y_location=foil_hole_params.y_location, + x_stage_position=foil_hole_params.x_stage_position, + y_stage_position=foil_hole_params.y_stage_position, + readout_area_x=foil_hole_params.readout_area_x, + readout_area_y=foil_hole_params.readout_area_y, + thumbnail_size_x=foil_hole_params.thumbnail_size_x or jpeg_size[0], + thumbnail_size_y=foil_hole_params.thumbnail_size_y or jpeg_size[1], + pixel_size=foil_hole_params.pixel_size, + image=str(secured_foil_hole_image_path), + ) + fh_ids.append(foil_hole.id) + murfey_db.add(foil_hole) + + if SMARTEM_ACTIVE and gs.smartem_uuid: + try: + murfey_session = murfey_db.exec( + select(MurfeySession).where(MurfeySession.id == session_id) + ).one() + machine_config = get_machine_config( + instrument_name=murfey_session.instrument_name + )[murfey_session.instrument_name] + if machine_config.smartem_api_url: + smartem_client = SmartEMAPIClient( + base_url=machine_config.smartem_api_url, logger=logger + ) + fh_data = SmartEMFoilHoleData( + id=str(foil_hole_params.name), + gridsquare_id=str(gs.name), + gridsquare_uuid=gs.smartem_uuid, + x_location=( + int(foil_hole_params.x_location) + if foil_hole_params.x_location is not None + else None + ), + y_location=( + int(foil_hole_params.y_location) + if foil_hole_params.y_location is not None + else None + ), + x_stage_position=foil_hole_params.x_stage_position, + y_stage_position=foil_hole_params.y_stage_position, + diameter=( + int(foil_hole_params.diameter) + if foil_hole_params.diameter is not None + else None + ), + **( + {"uuid": foil_hole.smartem_uuid} + if foil_hole.smartem_uuid + else {} + ), + ) + if foil_hole.smartem_uuid: + smartem_client.update_foilhole(fh_data) + else: + responses = smartem_client.create_gridsquare_foilholes( + gs.smartem_uuid, [fh_data] + ) + if responses: + foil_hole.smartem_uuid = responses[0].uuid + murfey_db.add(foil_hole) + except Exception: + logger.warning( + f"Failed to register foil hole {foil_hole.id} with smartem", + exc_info=True, + ) + + murfey_db.commit() + murfey_db.close() + return fh_ids + + +def register_foil_holes(message: dict, murfey_db: Session) -> dict[str, bool]: + session_id = message["session_id"] + gs_name = message["gs_name"] + foil_hole_group = message["foil_hole_group"] + fh_ids = register_holes_on_grid(session_id, gs_name, foil_hole_group, murfey_db) + if fh_ids is None: + logger.warning(f"Failed to register foil holes on grid square {gs_name}") + return {"success": False, "requeue": False} + return {"success": True} From ad848447e762f5c033e65cb55b783773bed1f539 Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Wed, 10 Jun 2026 13:30:41 +0100 Subject: [PATCH 2/2] Minor edits --- src/murfey/client/contexts/spa.py | 38 +++++++++++++----------- src/murfey/server/api/session_control.py | 4 ++- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/murfey/client/contexts/spa.py b/src/murfey/client/contexts/spa.py index 78f98111f..781b7219d 100644 --- a/src/murfey/client/contexts/spa.py +++ b/src/murfey/client/contexts/spa.py @@ -377,39 +377,43 @@ def _position_analysis( capture_post( base_url=str(environment.url.geturl()), router_name="session_control.spa_router", - function_name="register_foil_hole", + function_name="register_foil_holes", token=self._token, instrument_name=environment.instrument_name, session_id=environment.murfey_session, gs_name=grid_square, data={ - "name": foil_hole, - "x_location": fh.x_location, - "y_location": fh.y_location, - "x_stage_position": fh.x_stage_position, - "y_stage_position": fh.y_stage_position, - "readout_area_x": fh.readout_area_x, - "readout_area_y": fh.readout_area_y, - "thumbnail_size_x": fh.thumbnail_size_x, - "thumbnail_size_y": fh.thumbnail_size_y, - "pixel_size": fh.pixel_size, - "diameter": fh.diameter, - "tag": str(source), - "image": str(image_path), + str(foil_hole): { + "name": foil_hole, + "x_location": fh.x_location, + "y_location": fh.y_location, + "x_stage_position": fh.x_stage_position, + "y_stage_position": fh.y_stage_position, + "readout_area_x": fh.readout_area_x, + "readout_area_y": fh.readout_area_y, + "thumbnail_size_x": fh.thumbnail_size_x, + "thumbnail_size_y": fh.thumbnail_size_y, + "pixel_size": fh.pixel_size, + "diameter": fh.diameter, + "tag": str(source), + "image": str(image_path), + } }, ) else: capture_post( base_url=str(environment.url.geturl()), router_name="session_control.spa_router", - function_name="register_foil_hole", + function_name="register_foil_holes", token=self._token, instrument_name=environment.instrument_name, session_id=environment.murfey_session, gs_name=grid_square, data={ - "name": foil_hole, - "tag": str(source), + str(foil_hole): { + "name": foil_hole, + "tag": str(source), + } }, ) self._foil_holes[grid_square].append(foil_hole) diff --git a/src/murfey/server/api/session_control.py b/src/murfey/server/api/session_control.py index 71603d61a..eeadcaf5a 100644 --- a/src/murfey/server/api/session_control.py +++ b/src/murfey/server/api/session_control.py @@ -487,7 +487,9 @@ def register_foil_holes( "register": "spa.register_foil_holes", "session_id": session_id, "gs_name": gs_name, - "foil_hole_group": foil_hole_group, + "foil_hole_group": { + k: v.model_dump() for k, v in foil_hole_group.items() + }, }, ) return gs_name