From 927cc3256e27248feb69cecc22727610be246228 Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Tue, 23 Jun 2026 16:44:40 +0100 Subject: [PATCH 01/27] Read xrm files for metadata on the client --- src/murfey/client/contexts/sxt.py | 149 +++++++++++++++++++++++++++++- 1 file changed, 147 insertions(+), 2 deletions(-) diff --git a/src/murfey/client/contexts/sxt.py b/src/murfey/client/contexts/sxt.py index 2e58993c0..95f1ef014 100644 --- a/src/murfey/client/contexts/sxt.py +++ b/src/murfey/client/contexts/sxt.py @@ -161,7 +161,152 @@ def post_transfer( data_suffixes = [".txrm"] - if transferred_file.suffix in data_suffixes and environment: + if transferred_file.suffix == ".xrm" and environment: + # Make sure we have a dcg for this grid + dcg_tag = ensure_dcg_exists( + collection_type="sxt", + metadata_source=self._basepath, + environment=environment, + machine_config=self._machine_config, + token=self._token, + ) + + metadata: dict[str, Any] = {} + with OleFileIO(str(transferred_file)) as xrm_ole: + if xrm_ole.exists("ImageInfo/XPosition") and xrm_ole.exists( + "ImageInfo/YPosition" + ): + x_tiles = np.frombuffer( + xrm_ole.openstream("ImageInfo/XPosition").getvalue(), np.float32 + ).tolist() + y_tiles = np.frombuffer( + xrm_ole.openstream("ImageInfo/YPosition").getvalue(), np.float32 + ).tolist() + metadata["x_position"] = x_tiles[int(len(x_tiles) / 2)] + metadata["y_position"] = y_tiles[int(len(y_tiles) / 2)] + + if xrm_ole.exists("ImageInfo/PixelSize"): + metadata["pixel_size"] = np.frombuffer( + xrm_ole.openstream("ImageInfo/PixelSize").getvalue(), np.float32 + ).tolist() + + if xrm_ole.exists("ImageInfo/ImageHeight"): + metadata["height"] = np.frombuffer( + xrm_ole.openstream("ImageInfo/ImageHeight").getvalue(), np.int32 + ).tolist() + + if xrm_ole.exists("ImageInfo/ImageWidth"): + metadata["width"] = np.frombuffer( + xrm_ole.openstream("ImageInfo/ImageWidth").getvalue(), np.intt32 + ).tolist() + + # Find images which are not mosaics (txrm spec typos this as mosiac) + if xrm_ole.exists("ImageInfo/MosiacRows") and xrm_ole.exists( + "ImageInfo/MosiacColumns" + ): + metadata["mosaic_rows"] = np.frombuffer( + xrm_ole.openstream("ImageInfo/MosiacRows").getvalue(), np.int32 + )[0] + metadata["mosiac_columns"] = np.frombuffer( + xrm_ole.openstream("ImageInfo/MosiacColumns").getvalue(), + np.int32, + )[0] + metadata["mosaic_size"] = int( + metadata["mosaic_rows"] * metadata["mosiac_columns"] + ) + + source = _get_source(transferred_file, environment=environment) + if source: + image_path = _file_transferred_to( + environment, + source, + transferred_file, + Path(self._machine_config.get("rsync_basepath", "")), + ) + converted_file_path = ( + Path(self._machine_config.get("rsync_basepath", "")) + / ( + environment.visit + if environment.visit + not in environment.default_destinations[source] + else "" + ) + / self._machine_config.get("processed_directory_name", "") + / self._machine_config.get("processed_extra_directory", "") + / f"{transferred_file.relative_to(source).stem}_Annotated.tiff" + ) + + capture_post( + base_url=str(environment.url.geturl()), + router_name="workflow.sxt_router", + function_name="convert_xrm_to_tiff", + token=self._token, + instrument_name=environment.instrument_name, + visit_name=environment.visit, + session_id=environment.murfey_session, + data={ + "txrm_file": image_path, + "converted_destination": str(converted_file_path), + "annotate": True, + }, + ) + + if ( + metadata.get("mosaic_size", 1) > 0 + and metadata.get("pixel_size", 0) > 0.1 + ): + # Large pixel size, this is an atlas + dcg_data = { + "experiment_type_id": 44, # Atlas + "tag": dcg_tag, + "atlas": str(converted_file_path), + "atlas_pixel_size": metadata.get("pixel_size", None), + "atlas_x_stage_position": metadata.get("x_position", None), + "atlas_y_stage_position": metadata.get("y_position", None), + "atlas_height": int( + metadata.get("height", 0) * metadata["mosaic_rows"] + ), + "atlas_width": int( + metadata.get("width", 0) * metadata["mosaic_columns"] + ), + } + capture_post( + base_url=str(environment.url.geturl()), + router_name="workflow.router", + function_name="register_dc_group", + token=self._token, + instrument_name=environment.instrument_name, + visit_name=environment.visit, + session_id=environment.murfey_session, + data=dcg_data, + ) + elif metadata.get("mosaic_size", 1) > 0: + # Other mosaic images are of grid squares + capture_post( + base_url=str(environment.url.geturl()), + router_name="workflow.sxt_router", + function_name="register_sxt_roi", + token=self._token, + instrument_name=environment.instrument_name, + session_id=environment.murfey_session, + sm_name=transferred_file.parent.name, + data={ + "tag": dcg_tag, + "name": transferred_file.stem, + "x_stage_position": metadata.get("x_position", None), + "y_stage_position": metadata.get("y_position", None), + "pixel_size": metadata.get("pixel_size", None), + "height": int( + metadata.get("height", 0) * metadata["mosaic_rows"] + ), + "width": int( + metadata.get("width", 0) * metadata["mosaic_columns"] + ), + "image": str(converted_file_path), + }, + ) + + elif transferred_file.suffix in data_suffixes and environment: source = _get_source(transferred_file, environment) if not source: logger.warning(f"No source found for file {transferred_file}") @@ -169,7 +314,7 @@ def post_transfer( # Read the tilt angles and pixel size from the txrm angles: list = [] - metadata: dict[str, Any] = { + metadata = { "source": str(self._basepath), "tilt_series_tag": transferred_file.stem, } From d5db21e3e59744f1949e0f709d2e9ed9e57079de Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Wed, 24 Jun 2026 09:43:59 +0100 Subject: [PATCH 02/27] Move around the sxt logic --- src/murfey/client/contexts/sxt.py | 18 ++---- src/murfey/server/api/workflow.py | 56 ++++++++++------- src/murfey/server/api/workflow_sxt.py | 60 +++++++++++++++++++ src/murfey/util/db.py | 39 ++++++++++++ src/murfey/util/route_manifest.yaml | 25 ++++---- .../register_data_collection_group.py | 2 + 6 files changed, 154 insertions(+), 46 deletions(-) create mode 100644 src/murfey/server/api/workflow_sxt.py diff --git a/src/murfey/client/contexts/sxt.py b/src/murfey/client/contexts/sxt.py index 95f1ef014..9f4a90056 100644 --- a/src/murfey/client/contexts/sxt.py +++ b/src/murfey/client/contexts/sxt.py @@ -159,8 +159,6 @@ def post_transfer( **kwargs, ) - data_suffixes = [".txrm"] - if transferred_file.suffix == ".xrm" and environment: # Make sure we have a dcg for this grid dcg_tag = ensure_dcg_exists( @@ -238,17 +236,11 @@ def post_transfer( capture_post( base_url=str(environment.url.geturl()), - router_name="workflow.sxt_router", + router_name="workflow_sxt.router", function_name="convert_xrm_to_tiff", token=self._token, instrument_name=environment.instrument_name, - visit_name=environment.visit, - session_id=environment.murfey_session, - data={ - "txrm_file": image_path, - "converted_destination": str(converted_file_path), - "annotate": True, - }, + data={"xrm_path": image_path, "tiff_path": converted_file_path}, ) if ( @@ -284,7 +276,7 @@ def post_transfer( # Other mosaic images are of grid squares capture_post( base_url=str(environment.url.geturl()), - router_name="workflow.sxt_router", + router_name="workflow_sxt.router", function_name="register_sxt_roi", token=self._token, instrument_name=environment.instrument_name, @@ -306,7 +298,7 @@ def post_transfer( }, ) - elif transferred_file.suffix in data_suffixes and environment: + elif transferred_file.suffix == ".txrm" and environment: source = _get_source(transferred_file, environment) if not source: logger.warning(f"No source found for file {transferred_file}") @@ -449,7 +441,7 @@ def post_transfer( reference_file_transferred_to = None capture_post( base_url=str(environment.url.geturl()), - router_name="workflow.sxt_router", + router_name="workflow_sxt.router", function_name="process_sxt_tilt_series", token=self._token, instrument_name=environment.instrument_name, diff --git a/src/murfey/server/api/workflow.py b/src/murfey/server/api/workflow.py index 467989a85..4ce3bde38 100644 --- a/src/murfey/server/api/workflow.py +++ b/src/murfey/server/api/workflow.py @@ -64,6 +64,7 @@ Session, SessionProcessingParameters, SPARelionParameters, + SxtRoi, Tilt, TiltSeries, ) @@ -78,10 +79,6 @@ motion_corrected_mrc, ) from murfey.util.tomo import midpoint -from murfey.workflows.sxt.process_sxt_tilt_series import ( - SXTTiltSeriesInfo, - process_sxt_tilt_series_workflow, -) from murfey.workflows.tomo.tomo_metadata import register_search_map_in_database logger = getLogger("murfey.server.api.workflow") @@ -101,6 +98,8 @@ class DCGroupParameters(BaseModel): atlas: str = "" sample: Optional[int] = None atlas_pixel_size: float = 0 + atlas_x_stage_position: float | None = None + atlas_y_stage_position: float | None = None create_smartem_grid: bool = False acquisition_uuid: Optional[str] = None @@ -175,6 +174,18 @@ def register_dc_group( dcg_instance.atlas_pixel_size = ( dcg_params.atlas_pixel_size or dcg_instance.atlas_pixel_size ) + dcg_instance.atlas_x_stage_position = ( + dcg_params.atlas_x_stage_position or dcg_instance.atlas_x_stage_position + ) + dcg_instance.atlas_y_stage_position = ( + dcg_params.atlas_y_stage_position or dcg_instance.atlas_y_stage_position + ) + dcg_instance.atlas_height = ( + dcg_params.atlas_height or dcg_instance.atlas_height + ) + dcg_instance.atlas_width = ( + dcg_params.atlas_width or dcg_instance.atlas_width + ) if smartem_grid_uuid: dcg_instance.smartem_grid_uuid = smartem_grid_uuid @@ -216,6 +227,22 @@ def register_dc_group( register_search_map_in_database( session_id, sm.name, search_map_params, db, close_db=False ) + + sxt_rois = db.exec( + select(SxtRoi) + .where(SxtRoi.session_id == session_id) + .where(SxtRoi.tag == dcg_params.tag) + ).all() + for roi in sxt_rois: + if _transport_object: + _transport_object.send( + _transport_object.feedback_queue, + { + "register": "register_sxt_roi", + "session_id": session_id, + **roi.model_dump(), + }, + ) db.close() elif dcg_murfey := db.exec( select(DataCollectionGroup) @@ -255,6 +282,8 @@ def register_dc_group( "atlas": dcg_params.atlas, "sample": dcg_params.sample, "atlas_pixel_size": dcg_params.atlas_pixel_size, + "atlas_x_stage_position": dcg_params.atlas_x_stage_position, + "atlas_y_stage_position": dcg_params.atlas_y_stage_position, } if _transport_object: @@ -1072,25 +1101,6 @@ def _add_tilt(): _add_tilt() -sxt_router = APIRouter( - prefix="/workflow/sxt", - dependencies=[Depends(validate_instrument_token)], - tags=["Workflows: Soft x-ray tomography"], -) - - -@sxt_router.post("/visits/{visit_name}/sessions/{session_id}/sxt_tilt_series") -def process_sxt_tilt_series( - visit_name: str, - session_id: MurfeySessionID, - tilt_series_info: SXTTiltSeriesInfo, - db=murfey_db, -): - return process_sxt_tilt_series_workflow( - visit_name, session_id, tilt_series_info, db - ) - - correlative_router = APIRouter( prefix="/workflow/correlative", dependencies=[Depends(validate_instrument_token)], diff --git a/src/murfey/server/api/workflow_sxt.py b/src/murfey/server/api/workflow_sxt.py new file mode 100644 index 000000000..24a7f4a48 --- /dev/null +++ b/src/murfey/server/api/workflow_sxt.py @@ -0,0 +1,60 @@ +from logging import getLogger +from pathlib import Path + +from fastapi import APIRouter, Depends +from pydantic import BaseModel + +from murfey.server import _transport_object +from murfey.server.api.auth import ( + MurfeySessionIDInstrument as MurfeySessionID, + validate_instrument_token, +) +from murfey.server.murfey_db import murfey_db +from murfey.workflows.sxt.process_sxt_tilt_series import ( + SXTTiltSeriesInfo, + process_sxt_tilt_series_workflow, +) + +logger = getLogger("murfey.server.api.workflow_sxt") + + +router = APIRouter( + prefix="/workflow/sxt", + dependencies=[Depends(validate_instrument_token)], + tags=["Workflows: Soft x-ray tomography"], +) + + +@router.post("/visits/{visit_name}/sessions/{session_id}/sxt_tilt_series") +def process_sxt_tilt_series( + visit_name: str, + session_id: MurfeySessionID, + tilt_series_info: SXTTiltSeriesInfo, + db=murfey_db, +): + return process_sxt_tilt_series_workflow( + visit_name, session_id, tilt_series_info, db + ) + + +class XrmFile(BaseModel): + xrm_path: Path + tiff_path: Path + + +@router.post("/convert_xrm_to_tiff") +def convert_xrm_to_tiff( + xrm_file: XrmFile, + db=murfey_db, +): + if _transport_object: + logger.info("Sending xrm conversion to images service") + _transport_object.send( + "images", + { + "txrm_file": str(xrm_file.xrm_path), + "converted_destination": str(xrm_file.tiff_path), + "annotate": True, + }, + new_connection=True, + ) diff --git a/src/murfey/util/db.py b/src/murfey/util/db.py index 189f98e06..952f42475 100644 --- a/src/murfey/util/db.py +++ b/src/murfey/util/db.py @@ -186,6 +186,10 @@ class DataCollectionGroup(SQLModel, table=True): # type: ignore atlas_id: Optional[int] = None atlas_pixel_size: Optional[float] = None atlas: str = "" + atlas_x_stage_position: float | None = None + atlas_y_stage_position: float | None = None + atlas_height: int | None = None + atlas_width: int | None = None sample: Optional[int] = None smartem_grid_uuid: Optional[str] = None @@ -1138,6 +1142,41 @@ class CryoemInitialModel(SQLModel, table=True): # type: ignore ) +""" +======================================================================================= +SXT WORKFLOW +======================================================================================= +""" + + +class SxtRoi(SQLModel, table=True): # type: ignore + id: Optional[int] = Field(primary_key=True, default=None) + session_id: int = Field(foreign_key="session.id") + name: str + tag: str + x_stage_position: Optional[float] + y_stage_position: Optional[float] + thumbnail_size_x: Optional[int] + thumbnail_size_y: Optional[int] + pixel_size: Optional[float] = None + image: str = "" + atlas_id: Optional[int] = Field( + foreign_key="datacollectiongroup.dataCollectionGroupId" + ) + x_location: Optional[int] = None + y_location: Optional[int] = None + height: Optional[int] = None + width: Optional[int] = None + + # ------------- + # Relationships + # ------------- + session: Optional[Session] = Relationship(back_populates="sxt_rois") + data_collection_group: Optional["DataCollectionGroup"] = Relationship( + back_populates="sxt_rois" + ) + + """ ======================================================================================= FUNCTIONS diff --git a/src/murfey/util/route_manifest.yaml b/src/murfey/util/route_manifest.yaml index 4fd3b9985..3640bb8c3 100644 --- a/src/murfey/util/route_manifest.yaml +++ b/src/murfey/util/route_manifest.yaml @@ -1336,16 +1336,6 @@ murfey.server.api.workflow.spa_router: type: int methods: - POST -murfey.server.api.workflow.sxt_router: - - path: /workflow/sxt/visits/{visit_name}/sessions/{session_id}/sxt_tilt_series - function: process_sxt_tilt_series - path_params: - - name: visit_name - type: str - - name: session_id - type: int - methods: - - POST murfey.server.api.workflow.tomo_router: - path: /workflow/tomo/sessions/{session_id}/tomography_processing_parameters function: register_tomo_proc_params @@ -1455,3 +1445,18 @@ murfey.server.api.workflow_fib.router: type: int methods: - POST +murfey.server.api.workflow_sxt.router: + - path: /workflow/sxt/visits/{visit_name}/sessions/{session_id}/sxt_tilt_series + function: process_sxt_tilt_series + path_params: + - name: visit_name + type: str + - name: session_id + type: int + methods: + - POST + - path: /workflow/sxt/convert_xrm_to_tiff + function: convert_xrm_to_tiff + path_params: [] + methods: + - POST diff --git a/src/murfey/workflows/register_data_collection_group.py b/src/murfey/workflows/register_data_collection_group.py index 0908e769b..04ff39145 100644 --- a/src/murfey/workflows/register_data_collection_group.py +++ b/src/murfey/workflows/register_data_collection_group.py @@ -84,6 +84,8 @@ def run(message: dict, murfey_db: SQLModelSession) -> dict[str, bool]: atlas_id=atlas_id, atlas=message.get("atlas", ""), atlas_pixel_size=message.get("atlas_pixel_size"), + atlas_x_stage_position=message.get("atlas_x_stage_position"), + atlas_y_stage_position=message.get("atlas_y_stage_position"), sample=message.get("sample"), session_id=message["session_id"], tag=message.get("tag"), From 1b5c534493bfec5dba2cb7594a887290d2080372 Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Wed, 24 Jun 2026 10:10:52 +0100 Subject: [PATCH 03/27] Add dummy router for xrm position info --- src/murfey/server/api/workflow_sxt.py | 32 ++++++++++++++++++++++----- src/murfey/util/route_manifest.yaml | 9 ++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/murfey/server/api/workflow_sxt.py b/src/murfey/server/api/workflow_sxt.py index 24a7f4a48..9bb740403 100644 --- a/src/murfey/server/api/workflow_sxt.py +++ b/src/murfey/server/api/workflow_sxt.py @@ -43,18 +43,38 @@ class XrmFile(BaseModel): @router.post("/convert_xrm_to_tiff") -def convert_xrm_to_tiff( - xrm_file: XrmFile, - db=murfey_db, -): +def convert_xrm_to_tiff(xrm_file: XrmFile, db=murfey_db): if _transport_object: logger.info("Sending xrm conversion to images service") _transport_object.send( "images", { - "txrm_file": str(xrm_file.xrm_path), - "converted_destination": str(xrm_file.tiff_path), + "image_command": "xrm_to_jpeg", + "xrm_file": str(xrm_file.xrm_path), + "tiff_destination": str(xrm_file.tiff_path), "annotate": True, }, new_connection=True, ) + + +class SxtRoiInfo(BaseModel): + tag: str + name: str + x_stage_position: float + y_stage_position: float + pixel_size: float + height: int + width: int + image: Path + + +@router.post("/visits/{visit_name}/sessions/{session_id}/register_sxt_roi") +def register_sxt_roi( + visit_name: str, + session_id: MurfeySessionID, + tilt_series_info: SXTTiltSeriesInfo, + db=murfey_db, +): + # TODO + return diff --git a/src/murfey/util/route_manifest.yaml b/src/murfey/util/route_manifest.yaml index 3640bb8c3..80dff1a7d 100644 --- a/src/murfey/util/route_manifest.yaml +++ b/src/murfey/util/route_manifest.yaml @@ -1460,3 +1460,12 @@ murfey.server.api.workflow_sxt.router: path_params: [] methods: - POST + - path: /workflow/sxt/visits/{visit_name}/sessions/{session_id}/register_sxt_roi + function: register_sxt_roi + path_params: + - name: visit_name + type: str + - name: session_id + type: int + methods: + - POST From 2ce7055cbc0e38a7ae44cfcce940a1c70c22bc7f Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Wed, 24 Jun 2026 10:12:11 +0100 Subject: [PATCH 04/27] Change router name in main --- src/murfey/server/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/murfey/server/main.py b/src/murfey/server/main.py index 6c96e617b..c14be9f2c 100644 --- a/src/murfey/server/main.py +++ b/src/murfey/server/main.py @@ -27,6 +27,7 @@ import murfey.server.api.workflow import murfey.server.api.workflow_clem import murfey.server.api.workflow_fib +import murfey.server.api.workflow_sxt from murfey.server import template_files from murfey.util.config import get_security_config @@ -96,10 +97,10 @@ class Settings(BaseSettings): app.include_router(murfey.server.api.workflow.router) app.include_router(murfey.server.api.workflow.correlative_router) app.include_router(murfey.server.api.workflow.spa_router) -app.include_router(murfey.server.api.workflow.sxt_router) app.include_router(murfey.server.api.workflow.tomo_router) app.include_router(murfey.server.api.workflow_clem.router) app.include_router(murfey.server.api.workflow_fib.router) +app.include_router(murfey.server.api.workflow_sxt.router) app.include_router(murfey.server.api.prometheus.router) From 095195f1717df866457f825af137c338c8c245f2 Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Wed, 24 Jun 2026 10:23:57 +0100 Subject: [PATCH 05/27] Ispyb insert function for sxt roi --- src/murfey/server/ispyb.py | 90 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/src/murfey/server/ispyb.py b/src/murfey/server/ispyb.py index 75525dd0c..962cc051c 100644 --- a/src/murfey/server/ispyb.py +++ b/src/murfey/server/ispyb.py @@ -543,6 +543,96 @@ def do_update_search_map( ) return {"success": False, "return_value": None} + def do_insert_sxt_roi( + self, + atlas_id: int, + roi_parameters: SearchMapParameters, + ): + if ( + roi_parameters.pixel_size + and roi_parameters.height + and roi_parameters.height_on_atlas + ): + roi_parameters.pixel_size *= ( + roi_parameters.height / roi_parameters.height_on_atlas + ) + roi_parameters.x_location = ( + int(roi_parameters.x_location8) if roi_parameters.x_location else None + ) + roi_parameters.y_location = ( + int(roi_parameters.y_location) if roi_parameters.y_location else None + ) + record = GridSquare( + atlasId=atlas_id, + gridSquareImage=roi_parameters.image, + pixelLocationX=roi_parameters.x_location, + pixelLocationY=roi_parameters.y_location, + height=roi_parameters.height_on_atlas, + width=roi_parameters.width_on_atlas, + angle=0, + stageLocationX=roi_parameters.x_stage_position, + stageLocationY=roi_parameters.y_stage_position, + pixelSize=roi_parameters.pixel_size, + ) + try: + with ISPyBSession() as db: + db.add(record) + db.commit() + log.info(f"Created SXT ROI (GridSquare) {record.gridSquareId}") + return {"success": True, "return_value": record.gridSquareId} + except ispyb.ISPyBException as e: + log.error( + "Inserting SXT ROI (GridSquare) entry caused exception '%s'.", + e, + exc_info=True, + ) + return {"success": False, "return_value": None} + + def do_update_sxt_roi(self, roi_id, roi_parameters: SearchMapParameters): + try: + with ISPyBSession() as db: + grid_square = ( + db.query(GridSquare).filter(GridSquare.gridSquareId == roi_id).one() + ) + if ( + roi_parameters.pixel_size + and roi_parameters.height + and roi_parameters.height_on_atlas + ): + roi_parameters.pixel_size *= ( + roi_parameters.height / roi_parameters.height_on_atlas + ) + grid_square.gridSquareImage = ( + roi_parameters.image or grid_square.gridSquareImage + ) + if roi_parameters.x_location: + grid_square.pixelLocationX = int(roi_parameters.x_location) + if roi_parameters.y_location: + grid_square.pixelLocationY = int(roi_parameters.y_location) + if roi_parameters.height_on_atlas: + grid_square.height = int(roi_parameters.height_on_atlas) + if roi_parameters.width_on_atlas: + grid_square.width = int(roi_parameters.width_on_atlas) + grid_square.stageLocationX = ( + roi_parameters.x_stage_position or grid_square.stageLocationX + ) + grid_square.stageLocationY = ( + roi_parameters.y_stage_position or grid_square.stageLocationY + ) + grid_square.pixelSize = ( + roi_parameters.pixel_size or grid_square.pixelSize + ) + db.add(grid_square) + db.commit() + return {"success": True, "return_value": grid_square.gridSquareId} + except ispyb.ISPyBException as e: + log.error( + "Updating SXT ROI (GridSquare) entry caused exception '%s'.", + e, + exc_info=True, + ) + return {"success": False, "return_value": None} + def send(self, queue: str, message: dict, new_connection: bool = False): if self.transport: if not self.transport.is_connected(): From 3b064fe82cea210eedee6bf774ed2bf88da26aac Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Tue, 7 Jul 2026 09:23:36 +0100 Subject: [PATCH 06/27] Add code to register metadata --- pyproject.toml | 1 + src/murfey/client/contexts/sxt.py | 3 +- src/murfey/server/api/workflow_sxt.py | 32 +++--- src/murfey/util/route_manifest.yaml | 4 +- src/murfey/workflows/sxt/sxt_metadata.py | 124 +++++++++++++++++++++++ 5 files changed, 144 insertions(+), 20 deletions(-) create mode 100644 src/murfey/workflows/sxt/sxt_metadata.py diff --git a/pyproject.toml b/pyproject.toml index fe7a69b8b..506cf8358 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -127,6 +127,7 @@ TomographyMetadataContext = "murfey.client.contexts.tomo_metadata:TomographyMeta "spa.flush_spa_preprocess" = "murfey.workflows.spa.flush_spa_preprocess:flush_spa_preprocess" "spa.motion_corrected" = "murfey.workflows.spa.motion_correction:motion_corrected" "sxt.process_tilt_series" = "murfey.workflows.sxt.process_sxt_tilt_series:run" +"sxt.register_roi" = "murfey.workflows.sxt.sxt_metadata:run" [tool.setuptools] package-dir = {"" = "src"} diff --git a/src/murfey/client/contexts/sxt.py b/src/murfey/client/contexts/sxt.py index da5732b44..6b1751535 100644 --- a/src/murfey/client/contexts/sxt.py +++ b/src/murfey/client/contexts/sxt.py @@ -303,11 +303,10 @@ def post_transfer( function_name="register_sxt_roi", token=self._token, instrument_name=environment.instrument_name, - visit_name=environment.visit, session_id=environment.murfey_session, + roi_name=transferred_file.stem, data={ "tag": dcg_tag, - "name": transferred_file.stem, "x_stage_position": metadata.get("x_position", None), "y_stage_position": metadata.get("y_position", None), "pixel_size": round(metadata.get("pixel_size", 0), 2), diff --git a/src/murfey/server/api/workflow_sxt.py b/src/murfey/server/api/workflow_sxt.py index 61059f9b9..ee003fc77 100644 --- a/src/murfey/server/api/workflow_sxt.py +++ b/src/murfey/server/api/workflow_sxt.py @@ -10,6 +10,7 @@ validate_instrument_token, ) from murfey.server.murfey_db import murfey_db +from murfey.util.models import SearchMapParameters from murfey.workflows.sxt.process_sxt_tilt_series import SXTTiltSeriesInfo logger = getLogger("murfey.server.api.workflow_sxt") @@ -62,23 +63,22 @@ def convert_xrm_to_tiff(xrm_file: XrmFile, db=murfey_db): ) -class SxtRoiInfo(BaseModel): - tag: str - name: str - x_stage_position: float - y_stage_position: float - pixel_size: float - height: int - width: int - image: Path - - -@router.post("/visits/{visit_name}/sessions/{session_id}/register_sxt_roi") +@router.post("/sessions/{session_id}/sxt_roi/{roi_name}") def register_sxt_roi( - visit_name: str, session_id: MurfeySessionID, - tilt_series_info: SXTTiltSeriesInfo, + roi_name: str, + roi_info: SearchMapParameters, db=murfey_db, ): - # TODO - return + if _transport_object: + logger.info(f"Registering SXT region {roi_info.name}") + _transport_object.send( + _transport_object.feedback_queue, + { + "register": "sxt.register_roi", + "session_id": session_id, + "roi_name": roi_name, + "roi_info": roi_info.model_dump(mode="json"), + }, + new_connection=True, + ) diff --git a/src/murfey/util/route_manifest.yaml b/src/murfey/util/route_manifest.yaml index 01527f335..e80e1237b 100644 --- a/src/murfey/util/route_manifest.yaml +++ b/src/murfey/util/route_manifest.yaml @@ -1460,10 +1460,10 @@ murfey.server.api.workflow_sxt.router: path_params: [] methods: - POST - - path: /workflow/sxt/visits/{visit_name}/sessions/{session_id}/register_sxt_roi + - path: /workflow/sxt/sessions/{session_id}/sxt_roi/{roi_name} function: register_sxt_roi path_params: - - name: visit_name + - name: roi_name type: str - name: session_id type: int diff --git a/src/murfey/workflows/sxt/sxt_metadata.py b/src/murfey/workflows/sxt/sxt_metadata.py new file mode 100644 index 000000000..966045746 --- /dev/null +++ b/src/murfey/workflows/sxt/sxt_metadata.py @@ -0,0 +1,124 @@ +import logging + +from sqlmodel.orm.session import Session as SQLModelSession, select + +from murfey.server import _transport_object +from murfey.util import sanitise +from murfey.util.db import ( + DataCollectionGroup, + SxtRoi, +) +from murfey.util.models import SearchMapParameters + +logger = logging.getLogger("murfey.workflows.sxt.sxt_metadata") + + +def register_sxt_roi( + session_id: int, + roi_name: str, + roi_parameters: SearchMapParameters, + murfey_db: SQLModelSession, +) -> dict[str, bool]: + dcg = murfey_db.exec( + select(DataCollectionGroup) + .where(DataCollectionGroup.session_id == session_id) + .where(DataCollectionGroup.tag == roi_parameters.tag) + ).one() + roi_query = murfey_db.exec( + select(SxtRoi) + .where(SxtRoi.name == roi_name) + .where(SxtRoi.tag == roi_parameters.tag) + .where(SxtRoi.session_id == SxtRoi) + ).all() + if roi_query: + # See if there is already a search map with this name and update if so + roi = roi_query[0] + roi.x_stage_position = roi_parameters.x_stage_position or roi.x_stage_position + roi.y_stage_position = roi_parameters.y_stage_position or roi.y_stage_position + roi.height = roi_parameters.height or roi.height + roi.width = roi_parameters.width or roi.width + roi.pixel_size = roi_parameters.pixel_size or roi.pixel_size + roi.image = roi_parameters.image or roi.image + if _transport_object: + _transport_object.do_update_sxt_roi(roi.id, roi_parameters) + else: + logger.info(f"Registering new sxt roi {sanitise(roi_name)}") + if _transport_object: + roi_ispyb_response = _transport_object.do_insert_sxt_roi( + dcg.atlas_id, roi_parameters + ) + else: + # mock up response so that below still works + roi_ispyb_response = {"success": False, "return_value": None} + # Register new search map + roi = SxtRoi( + id=( + roi_ispyb_response["return_value"] + if roi_ispyb_response["success"] + else None + ), + name=roi_name, + session_id=session_id, + tag=roi_parameters.tag, + x_stage_position=roi_parameters.x_stage_position, + y_stage_position=roi_parameters.y_stage_position, + pixel_size=roi_parameters.pixel_size, + width=roi_parameters.width, + height=roi_parameters.height, + thumbnail_size_x=1024, + thumbnail_size_y=1024, + image=roi_parameters.image or "", + ) + + if all( + [ + roi.x_stage_position, + roi.y_stage_position, + roi.pixel_size, + dcg.atlas_pixel_size, + dcg.atlas_stage_x, + dcg.atlas_height, + ] + ): + # Convert from stage position to pixel locations + roi.x_location = ( + roi.x_stage_position - dcg.atlas_stage_x + ) / dcg.atlas_pixel_size + roi.y_location = ( + roi.y_stage_position - dcg.atlas_stage_y + ) / dcg.atlas_pixel_size + + # Scaling from different pixel size of atlas and roi, and atlas thumbnailing + roi_parameters.x_location = roi.x_location + roi_parameters.y_location = roi.y_location + roi_parameters.height_on_atlas = ( + roi.height + * (roi.pixel_size / dcg.atlas_pixel_size) + * (1024 / dcg.atlas_height) + ) + roi_parameters.width_on_atlas = ( + roi.width + * (roi.pixel_size / dcg.atlas_pixel_size) + * (1024 / dcg.atlas_width) + ) + if _transport_object: + _transport_object.do_update_sxt_roi(roi.id, roi_parameters) + else: + logger.info( + f"Unable to register roi {sanitise(roi.name)} position yet: " + f"roi pixel size {sanitise(str(roi.pixel_size))}, " + f"atlas pixel size {sanitise(str(dcg.atlas_pixel_size))}" + ) + murfey_db.add(roi) + murfey_db.commit() + murfey_db.close() + return {"success": True} + + +def run(message: dict, murfey_db: SQLModelSession) -> dict[str, bool]: + return register_sxt_roi( + message["session_id"], + message["roi_name"], + SearchMapParameters(**message["roi_info"]), + murfey_db, + ) From c67c870bb6e8beb40d16181205256585f2827664 Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Tue, 7 Jul 2026 09:28:58 +0100 Subject: [PATCH 07/27] Can just use searchmap --- src/murfey/util/db.py | 35 ------------------------ src/murfey/workflows/sxt/sxt_metadata.py | 14 ++++------ 2 files changed, 6 insertions(+), 43 deletions(-) diff --git a/src/murfey/util/db.py b/src/murfey/util/db.py index 952f42475..baa478056 100644 --- a/src/murfey/util/db.py +++ b/src/murfey/util/db.py @@ -1142,41 +1142,6 @@ class CryoemInitialModel(SQLModel, table=True): # type: ignore ) -""" -======================================================================================= -SXT WORKFLOW -======================================================================================= -""" - - -class SxtRoi(SQLModel, table=True): # type: ignore - id: Optional[int] = Field(primary_key=True, default=None) - session_id: int = Field(foreign_key="session.id") - name: str - tag: str - x_stage_position: Optional[float] - y_stage_position: Optional[float] - thumbnail_size_x: Optional[int] - thumbnail_size_y: Optional[int] - pixel_size: Optional[float] = None - image: str = "" - atlas_id: Optional[int] = Field( - foreign_key="datacollectiongroup.dataCollectionGroupId" - ) - x_location: Optional[int] = None - y_location: Optional[int] = None - height: Optional[int] = None - width: Optional[int] = None - - # ------------- - # Relationships - # ------------- - session: Optional[Session] = Relationship(back_populates="sxt_rois") - data_collection_group: Optional["DataCollectionGroup"] = Relationship( - back_populates="sxt_rois" - ) - - """ ======================================================================================= FUNCTIONS diff --git a/src/murfey/workflows/sxt/sxt_metadata.py b/src/murfey/workflows/sxt/sxt_metadata.py index 966045746..cf1642624 100644 --- a/src/murfey/workflows/sxt/sxt_metadata.py +++ b/src/murfey/workflows/sxt/sxt_metadata.py @@ -6,7 +6,7 @@ from murfey.util import sanitise from murfey.util.db import ( DataCollectionGroup, - SxtRoi, + SearchMap, ) from murfey.util.models import SearchMapParameters @@ -25,10 +25,10 @@ def register_sxt_roi( .where(DataCollectionGroup.tag == roi_parameters.tag) ).one() roi_query = murfey_db.exec( - select(SxtRoi) - .where(SxtRoi.name == roi_name) - .where(SxtRoi.tag == roi_parameters.tag) - .where(SxtRoi.session_id == SxtRoi) + select(SearchMap) + .where(SearchMap.name == roi_name) + .where(SearchMap.tag == roi_parameters.tag) + .where(SearchMap.session_id == SearchMap) ).all() if roi_query: # See if there is already a search map with this name and update if so @@ -51,7 +51,7 @@ def register_sxt_roi( # mock up response so that below still works roi_ispyb_response = {"success": False, "return_value": None} # Register new search map - roi = SxtRoi( + roi = SearchMap( id=( roi_ispyb_response["return_value"] if roi_ispyb_response["success"] @@ -65,8 +65,6 @@ def register_sxt_roi( pixel_size=roi_parameters.pixel_size, width=roi_parameters.width, height=roi_parameters.height, - thumbnail_size_x=1024, - thumbnail_size_y=1024, image=roi_parameters.image or "", ) From d714c16d737bde3e68283d57e36e777b997e6666 Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Tue, 7 Jul 2026 09:38:46 +0100 Subject: [PATCH 08/27] Fix naming --- src/murfey/workflows/sxt/sxt_metadata.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/murfey/workflows/sxt/sxt_metadata.py b/src/murfey/workflows/sxt/sxt_metadata.py index cf1642624..5bc8c005d 100644 --- a/src/murfey/workflows/sxt/sxt_metadata.py +++ b/src/murfey/workflows/sxt/sxt_metadata.py @@ -74,19 +74,19 @@ def register_sxt_roi( roi.y_stage_position, roi.pixel_size, dcg.atlas_pixel_size, - dcg.atlas_stage_x, + dcg.atlas_x_stage_position, dcg.atlas_height, ] ): # Convert from stage position to pixel locations roi.x_location = ( - roi.x_stage_position - dcg.atlas_stage_x + roi.x_stage_position - dcg.atlas_x_stage_position ) / dcg.atlas_pixel_size roi.y_location = ( - roi.y_stage_position - dcg.atlas_stage_y + roi.y_stage_position - dcg.atlas_y_stage_position ) / dcg.atlas_pixel_size - # Scaling from different pixel size of atlas and roi, and atlas thumbnailing + # Scaling from different pixel size of atlas and roi, and atlas thumbnail size roi_parameters.x_location = roi.x_location roi_parameters.y_location = roi.y_location roi_parameters.height_on_atlas = ( From da39351ebca5dd26c5b8fe58fb6c9176a926f07a Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Tue, 7 Jul 2026 09:58:32 +0100 Subject: [PATCH 09/27] Catch different tomo and sxt registrations --- src/murfey/server/api/workflow.py | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/murfey/server/api/workflow.py b/src/murfey/server/api/workflow.py index 81e5ed53f..6dc4ae27a 100644 --- a/src/murfey/server/api/workflow.py +++ b/src/murfey/server/api/workflow.py @@ -64,7 +64,6 @@ Session, SessionProcessingParameters, SPARelionParameters, - SxtRoi, Tilt, TiltSeries, ) @@ -122,15 +121,15 @@ def register_dc_group( db.exec(select(Session).where(Session.id == session_id)).one().instrument_name ) logger.info(f"Registering data collection group on microscope {instrument_name}") + machine_config = get_machine_config(instrument_name=instrument_name)[ + instrument_name + ] smartem_grid_uuid = None if ( dcg_params.create_smartem_grid and SMARTEM_ACTIVE and dcg_params.acquisition_uuid ): - machine_config = get_machine_config(instrument_name=instrument_name)[ - instrument_name - ] if machine_config.smartem_api_url: try: smartem_client = SmartEMAPIClient( @@ -226,23 +225,19 @@ def register_dc_group( ).all() search_map_params = SearchMapParameters(tag=dcg_params.tag) for sm in search_maps: - register_search_map_in_database( - session_id, sm.name, search_map_params, db, close_db=False - ) - - sxt_rois = db.exec( - select(SxtRoi) - .where(SxtRoi.session_id == session_id) - .where(SxtRoi.tag == dcg_params.tag) - ).all() - for roi in sxt_rois: - if _transport_object: + # Different behaviour for tomo and SXT + if "tomo" in machine_config.acquisition_software: + register_search_map_in_database( + session_id, sm.name, search_map_params, db, close_db=False + ) + elif _transport_object: _transport_object.send( _transport_object.feedback_queue, { - "register": "register_sxt_roi", + "register": "sxt.register_roi", "session_id": session_id, - **roi.model_dump(), + "roi_name": sm.name, + "roi_info": search_map_params.model_dump(mode="json"), }, ) db.close() From e639893143054a6c4582a9efd179891a7fd8bda1 Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Tue, 7 Jul 2026 10:51:05 +0100 Subject: [PATCH 10/27] Use imaging site to avoid db changes --- src/murfey/server/api/workflow.py | 37 +++++++++++++------ src/murfey/util/db.py | 4 -- .../register_data_collection_group.py | 17 +++++++-- src/murfey/workflows/sxt/sxt_metadata.py | 36 ++++++++---------- 4 files changed, 55 insertions(+), 39 deletions(-) diff --git a/src/murfey/server/api/workflow.py b/src/murfey/server/api/workflow.py index 6dc4ae27a..366fba630 100644 --- a/src/murfey/server/api/workflow.py +++ b/src/murfey/server/api/workflow.py @@ -57,6 +57,7 @@ DataCollectionGroup, FoilHole, GridSquare, + ImagingSite, Movie, PreprocessStash, ProcessingJob, @@ -175,21 +176,31 @@ def register_dc_group( dcg_instance.atlas_pixel_size = ( dcg_params.atlas_pixel_size or dcg_instance.atlas_pixel_size ) - dcg_instance.atlas_x_stage_position = ( - dcg_params.atlas_x_stage_position or dcg_instance.atlas_x_stage_position - ) - dcg_instance.atlas_y_stage_position = ( - dcg_params.atlas_y_stage_position or dcg_instance.atlas_y_stage_position - ) - dcg_instance.atlas_height = ( - dcg_params.atlas_height or dcg_instance.atlas_height - ) - dcg_instance.atlas_width = ( - dcg_params.atlas_width or dcg_instance.atlas_width - ) if smartem_grid_uuid: dcg_instance.smartem_grid_uuid = smartem_grid_uuid + # Update any imaging sites + atlas_sites = db.exec( + select(ImagingSite).where(ImagingSite.dcg_id == dcg_instance.dcg_id) + ).all() + for atlas_instance in atlas_sites: + atlas_instance.pos_x = ( + dcg_params.atlas_x_stage_position or atlas_instance.pos_x + ) + atlas_instance.pos_y = ( + dcg_params.atlas_y_stage_position or atlas_instance.pos_y + ) + atlas_instance.image_pixels_x = ( + dcg_params.atlas_width or atlas_instance.image_pixels_x + ) + atlas_instance.image_pixels_y = ( + dcg_params.atlas_height or atlas_instance.image_pixels_y + ) + atlas_instance.image_pixel_size = ( + dcg_params.atlas_pixel_size or atlas_instance.image_pixel_size + ) + db.add(atlas_instance) + if _transport_object: if dcg_instance.atlas_id is not None: _transport_object.send( @@ -281,6 +292,8 @@ def register_dc_group( "atlas_pixel_size": dcg_params.atlas_pixel_size, "atlas_x_stage_position": dcg_params.atlas_x_stage_position, "atlas_y_stage_position": dcg_params.atlas_y_stage_position, + "atlas_width": dcg_params.atlas_width, + "atlas_height": dcg_params.atlas_height, } if _transport_object: diff --git a/src/murfey/util/db.py b/src/murfey/util/db.py index baa478056..189f98e06 100644 --- a/src/murfey/util/db.py +++ b/src/murfey/util/db.py @@ -186,10 +186,6 @@ class DataCollectionGroup(SQLModel, table=True): # type: ignore atlas_id: Optional[int] = None atlas_pixel_size: Optional[float] = None atlas: str = "" - atlas_x_stage_position: float | None = None - atlas_y_stage_position: float | None = None - atlas_height: int | None = None - atlas_width: int | None = None sample: Optional[int] = None smartem_grid_uuid: Optional[str] = None diff --git a/src/murfey/workflows/register_data_collection_group.py b/src/murfey/workflows/register_data_collection_group.py index 04ff39145..5de64a443 100644 --- a/src/murfey/workflows/register_data_collection_group.py +++ b/src/murfey/workflows/register_data_collection_group.py @@ -9,7 +9,7 @@ from murfey.server import _transport_object from murfey.server.ispyb import ISPyBSession, get_session_id -from murfey.util.db import DataCollectionGroup +from murfey.util.db import DataCollectionGroup, ImagingSite logger = logging.getLogger("murfey.workflows.register_data_collection_group") @@ -84,13 +84,24 @@ def run(message: dict, murfey_db: SQLModelSession) -> dict[str, bool]: atlas_id=atlas_id, atlas=message.get("atlas", ""), atlas_pixel_size=message.get("atlas_pixel_size"), - atlas_x_stage_position=message.get("atlas_x_stage_position"), - atlas_y_stage_position=message.get("atlas_y_stage_position"), sample=message.get("sample"), session_id=message["session_id"], tag=message.get("tag"), smartem_grid_uuid=message.get("smartem_grid_uuid"), ) + if message.get("atlas_x_stage_position"): + atlas_site = ImagingSite( + dcg_id=dcgid, + session_id=message["session_id"], + site_name=message.get("tag"), + data_type="atlas", + pos_x=message.get("atlas_x_stage_position"), + pos_y=message.get("atlas_y_stage_position"), + image_pixels_x=message.get("atlas_width"), + image_pixels_y=message.get("atlas_height"), + image_pixel_size=message.get("atlas_pixel_size"), + ) + murfey_db.add(atlas_site) murfey_db.add(murfey_dcg) murfey_db.commit() murfey_db.close() diff --git a/src/murfey/workflows/sxt/sxt_metadata.py b/src/murfey/workflows/sxt/sxt_metadata.py index 5bc8c005d..55e718475 100644 --- a/src/murfey/workflows/sxt/sxt_metadata.py +++ b/src/murfey/workflows/sxt/sxt_metadata.py @@ -4,10 +4,7 @@ from murfey.server import _transport_object from murfey.util import sanitise -from murfey.util.db import ( - DataCollectionGroup, - SearchMap, -) +from murfey.util.db import DataCollectionGroup, ImagingSite, SearchMap from murfey.util.models import SearchMapParameters logger = logging.getLogger("murfey.workflows.sxt.sxt_metadata") @@ -68,36 +65,35 @@ def register_sxt_roi( image=roi_parameters.image or "", ) + atlas_sites = murfey_db.exec( + select(ImagingSite).where(ImagingSite.dcg_id == dcg.id) + ).all() + if all( [ roi.x_stage_position, roi.y_stage_position, roi.pixel_size, - dcg.atlas_pixel_size, - dcg.atlas_x_stage_position, - dcg.atlas_height, + atlas_sites, ] ): + atlas = atlas_sites[0] # Convert from stage position to pixel locations - roi.x_location = ( - roi.x_stage_position - dcg.atlas_x_stage_position - ) / dcg.atlas_pixel_size - roi.y_location = ( - roi.y_stage_position - dcg.atlas_y_stage_position - ) / dcg.atlas_pixel_size + roi.x_location = (roi.x_stage_position - atlas.pos_x) / atlas.image_pixel_size + roi.y_location = (roi.y_stage_position - atlas.pox_y) / atlas.image_pixel_size # Scaling from different pixel size of atlas and roi, and atlas thumbnail size roi_parameters.x_location = roi.x_location roi_parameters.y_location = roi.y_location - roi_parameters.height_on_atlas = ( - roi.height - * (roi.pixel_size / dcg.atlas_pixel_size) - * (1024 / dcg.atlas_height) - ) roi_parameters.width_on_atlas = ( roi.width - * (roi.pixel_size / dcg.atlas_pixel_size) - * (1024 / dcg.atlas_width) + * (roi.pixel_size / atlas.image_pixel_size) + * (1024 / atlas.image_pixels_x) + ) + roi_parameters.height_on_atlas = ( + roi.height + * (roi.pixel_size / atlas.image_pixel_size) + * (1024 / atlas.image_pixels_y) ) if _transport_object: _transport_object.do_update_sxt_roi(roi.id, roi_parameters) From f20417f4067efc898bdd97e267d169ed201f6abb Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Tue, 7 Jul 2026 15:13:39 +0100 Subject: [PATCH 11/27] Registration fixes from testing --- src/murfey/client/contexts/sxt.py | 11 +++-- src/murfey/server/api/workflow.py | 51 +++++++++++++++--------- src/murfey/server/api/workflow_sxt.py | 2 +- src/murfey/workflows/sxt/sxt_metadata.py | 27 ++++++++----- 4 files changed, 56 insertions(+), 35 deletions(-) diff --git a/src/murfey/client/contexts/sxt.py b/src/murfey/client/contexts/sxt.py index 6b1751535..31f1cc4b4 100644 --- a/src/murfey/client/contexts/sxt.py +++ b/src/murfey/client/contexts/sxt.py @@ -246,7 +246,7 @@ def post_transfer( / environment.visit ) destination_extra = "" - converted_file_path = ( + converted_tiff_path = ( Path(self._machine_config.get("rsync_basepath", "")) / destination_base / self._machine_config.get("processed_directory_name", "") @@ -254,6 +254,9 @@ def post_transfer( / destination_extra / f"{transferred_file.relative_to(source).stem}_Annotated.tiff" ) + thumbnail_path = converted_tiff_path.parent / ( + converted_tiff_path.stem + "_thumbnail.jpg" + ) capture_post( base_url=str(environment.url.geturl()), router_name="workflow_sxt.router", @@ -262,7 +265,7 @@ def post_transfer( instrument_name=environment.instrument_name, data={ "xrm_path": str(image_path), - "tiff_path": str(converted_file_path), + "tiff_path": str(converted_tiff_path), }, ) @@ -274,7 +277,7 @@ def post_transfer( dcg_data = { "experiment_type_id": 44, # Atlas "tag": dcg_tag, - "atlas": str(converted_file_path), + "atlas": str(thumbnail_path), "atlas_pixel_size": round(metadata.get("pixel_size", 0), 2), "atlas_x_stage_position": metadata.get("x_position", None), "atlas_y_stage_position": metadata.get("y_position", None), @@ -316,7 +319,7 @@ def post_transfer( "width": int( metadata.get("width", 0) * metadata["mosaic_columns"] ), - "image": str(converted_file_path), + "image": str(thumbnail_path), }, ) diff --git a/src/murfey/server/api/workflow.py b/src/murfey/server/api/workflow.py index 366fba630..28202faf4 100644 --- a/src/murfey/server/api/workflow.py +++ b/src/murfey/server/api/workflow.py @@ -179,25 +179,38 @@ def register_dc_group( if smartem_grid_uuid: dcg_instance.smartem_grid_uuid = smartem_grid_uuid - # Update any imaging sites - atlas_sites = db.exec( - select(ImagingSite).where(ImagingSite.dcg_id == dcg_instance.dcg_id) - ).all() - for atlas_instance in atlas_sites: - atlas_instance.pos_x = ( - dcg_params.atlas_x_stage_position or atlas_instance.pos_x - ) - atlas_instance.pos_y = ( - dcg_params.atlas_y_stage_position or atlas_instance.pos_y - ) - atlas_instance.image_pixels_x = ( - dcg_params.atlas_width or atlas_instance.image_pixels_x - ) - atlas_instance.image_pixels_y = ( - dcg_params.atlas_height or atlas_instance.image_pixels_y - ) - atlas_instance.image_pixel_size = ( - dcg_params.atlas_pixel_size or atlas_instance.image_pixel_size + # Update any atlases which are registered as imaging sites + if atlas_sites := db.exec( + select(ImagingSite).where(ImagingSite.dcg_id == dcg_instance.id) + ).all(): + for atlas_instance in atlas_sites: + atlas_instance.pos_x = ( + dcg_params.atlas_x_stage_position or atlas_instance.pos_x + ) + atlas_instance.pos_y = ( + dcg_params.atlas_y_stage_position or atlas_instance.pos_y + ) + atlas_instance.image_pixels_x = ( + dcg_params.atlas_width or atlas_instance.image_pixels_x + ) + atlas_instance.image_pixels_y = ( + dcg_params.atlas_height or atlas_instance.image_pixels_y + ) + atlas_instance.image_pixel_size = ( + dcg_params.atlas_pixel_size or atlas_instance.image_pixel_size + ) + db.add(atlas_instance) + elif dcg_params.atlas_x_stage_position: + atlas_instance = ImagingSite( + dcg_id=dcg_instance.id, + session_id=session_id, + site_name=dcg_instance.tag, + data_type="atlas", + pos_x=dcg_params.atlas_x_stage_position, + pos_y=dcg_params.atlas_y_stage_position, + image_pixels_x=dcg_params.atlas_width, + image_pixels_y=dcg_params.atlas_height, + image_pixel_size=dcg_params.atlas_pixel_size, ) db.add(atlas_instance) diff --git a/src/murfey/server/api/workflow_sxt.py b/src/murfey/server/api/workflow_sxt.py index ee003fc77..fd8f214fa 100644 --- a/src/murfey/server/api/workflow_sxt.py +++ b/src/murfey/server/api/workflow_sxt.py @@ -71,7 +71,7 @@ def register_sxt_roi( db=murfey_db, ): if _transport_object: - logger.info(f"Registering SXT region {roi_info.name}") + logger.info(f"Registering SXT region {roi_name}") _transport_object.send( _transport_object.feedback_queue, { diff --git a/src/murfey/workflows/sxt/sxt_metadata.py b/src/murfey/workflows/sxt/sxt_metadata.py index 55e718475..487b2a468 100644 --- a/src/murfey/workflows/sxt/sxt_metadata.py +++ b/src/murfey/workflows/sxt/sxt_metadata.py @@ -1,6 +1,7 @@ import logging -from sqlmodel.orm.session import Session as SQLModelSession, select +from sqlmodel import select +from sqlmodel.orm.session import Session as SQLModelSession from murfey.server import _transport_object from murfey.util import sanitise @@ -25,7 +26,7 @@ def register_sxt_roi( select(SearchMap) .where(SearchMap.name == roi_name) .where(SearchMap.tag == roi_parameters.tag) - .where(SearchMap.session_id == SearchMap) + .where(SearchMap.session_id == session_id) ).all() if roi_query: # See if there is already a search map with this name and update if so @@ -68,32 +69,35 @@ def register_sxt_roi( atlas_sites = murfey_db.exec( select(ImagingSite).where(ImagingSite.dcg_id == dcg.id) ).all() - - if all( + if atlas_sites and all( [ roi.x_stage_position, roi.y_stage_position, roi.pixel_size, - atlas_sites, + atlas_sites[0].pos_x, + atlas_sites[0].pos_y, + atlas_sites[0].image_pixel_size, + atlas_sites[0].image_pixels_x, + atlas_sites[0].image_pixels_y, ] ): atlas = atlas_sites[0] # Convert from stage position to pixel locations roi.x_location = (roi.x_stage_position - atlas.pos_x) / atlas.image_pixel_size - roi.y_location = (roi.y_stage_position - atlas.pox_y) / atlas.image_pixel_size + roi.y_location = (roi.y_stage_position - atlas.pos_y) / atlas.image_pixel_size # Scaling from different pixel size of atlas and roi, and atlas thumbnail size - roi_parameters.x_location = roi.x_location - roi_parameters.y_location = roi.y_location + roi_parameters.x_location = roi.x_location * (512 / atlas.image_pixels_x) + 256 + roi_parameters.y_location = 256 - roi.y_location * (512 / atlas.image_pixels_y) roi_parameters.width_on_atlas = ( roi.width * (roi.pixel_size / atlas.image_pixel_size) - * (1024 / atlas.image_pixels_x) + * (512 / atlas.image_pixels_x) ) roi_parameters.height_on_atlas = ( roi.height * (roi.pixel_size / atlas.image_pixel_size) - * (1024 / atlas.image_pixels_y) + * (512 / atlas.image_pixels_y) ) if _transport_object: _transport_object.do_update_sxt_roi(roi.id, roi_parameters) @@ -101,7 +105,8 @@ def register_sxt_roi( logger.info( f"Unable to register roi {sanitise(roi.name)} position yet: " f"roi pixel size {sanitise(str(roi.pixel_size))}, " - f"atlas pixel size {sanitise(str(dcg.atlas_pixel_size))}" + f"atlas pixel size {sanitise(str(dcg.atlas_pixel_size))}, " + f"roi count {len(atlas_sites)}" ) murfey_db.add(roi) murfey_db.commit() From a74ab7eeee333f81b5de9992a8701d3154447444 Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Wed, 8 Jul 2026 16:01:03 +0100 Subject: [PATCH 12/27] Add a test --- tests/workflows/sxt/test_sxt_metadata.py | 130 +++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 tests/workflows/sxt/test_sxt_metadata.py diff --git a/tests/workflows/sxt/test_sxt_metadata.py b/tests/workflows/sxt/test_sxt_metadata.py new file mode 100644 index 000000000..55fcfddc8 --- /dev/null +++ b/tests/workflows/sxt/test_sxt_metadata.py @@ -0,0 +1,130 @@ +import copy +from unittest import mock + +from sqlmodel import Session, select + +from murfey.util.db import ( + DataCollectionGroup, + ImagingSite, + SearchMap, +) +from murfey.util.models import SearchMapParameters +from murfey.workflows.sxt import sxt_metadata +from tests.conftest import ExampleVisit, get_or_create_db_entry + + +def set_up_db(murfey_db_session: Session): + # Insert common elements needed in all tests + dcg_entry: DataCollectionGroup = get_or_create_db_entry( + murfey_db_session, + DataCollectionGroup, + lookup_kwargs={ + "id": 0, + "session_id": ExampleVisit.murfey_session_id, + "tag": "/path/to/tomogram_source", + "atlas_id": 10, + }, + ) + get_or_create_db_entry( + murfey_db_session, + ImagingSite, + lookup_kwargs={ + "dcg_id": dcg_entry.dcg_id, + "pos_x": 2, + "pos_y": 3, + "image_pixel_size": 0.5, + "image_pixels_x": 400, + "image_pixels_y": 500, + }, + ) + return dcg_entry.id + + +@mock.patch("murfey.workflows.sxt.process_sxt_tilt_series._transport_object") +def test_register_new_sxt_roi(mock_transport, murfey_db_session: Session, tmp_path): + set_up_db(murfey_db_session) + + roi_params = SearchMapParameters( + tag="/path/to/tomogram_source", + x_stage_position=10, + y_stage_position=20, + pixel_size=None, + width=None, + height=None, + image="/path/to/image.jpg", + ) + return_dict = sxt_metadata.register_sxt_roi( + ExampleVisit.murfey_session_id, "roi_1", roi_params, murfey_db_session + ) + assert return_dict.get("success") + + # Check the ispyb message + mock_transport.do_insert_sxt_roi.assert_called_once_with(10, roi_params) + + # Check the database insert + roi_entry = murfey_db_session.exec(select(SearchMap)).one() + assert roi_entry.session_id == ExampleVisit.murfey_session_id + assert roi_entry.name == "roi_1" + assert roi_entry.tag == "/path/to/tomogram_source" + assert roi_entry.x_stage_position == 10 + assert roi_entry.y_stage_position == 2 + assert not roi_entry.pixel_size + assert not roi_entry.width + assert not roi_entry.height + assert roi_entry.image == "/path/to/image.jpg" + + +@mock.patch("murfey.workflows.sxt.process_sxt_tilt_series._transport_object") +def test_update_sxt_roi(mock_transport, murfey_db_session: Session, tmp_path): + set_up_db(murfey_db_session) + + get_or_create_db_entry( + murfey_db_session, + SearchMap, + lookup_kwargs={ + "session_id": ExampleVisit.murfey_session_id, + "name": "roi_1", + "tag": "/path/to/tomogram_source", + }, + ) + + roi_params = SearchMapParameters( + id=2, + tag="/path/to/tomogram_source", + x_stage_position=10, + y_stage_position=20, + pixel_size=0.025, + width=200, + height=400, + image="/path/to/image.jpg", + ) + return_dict = sxt_metadata.register_sxt_roi( + ExampleVisit.murfey_session_id, + "roi_1", + copy.deepcopy(roi_params), + murfey_db_session, + ) + assert return_dict.get("success") + + # Check the ispyb message + mock_transport.do_update_sxt_roi.assert_any_call(2, roi_params) + + # Check the second update + roi_params.x_location = 16 * (512 / 400) + 256 + roi_params.y_location = 256 - 34 * (512 / 500) + 256 + roi_params.width_on_atlas = 2 * 512 / 400 + roi_params.height_on_atlas = 4 * 512 / 500 + + # Check the database insert + roi_entry = murfey_db_session.exec(select(SearchMap)).one() + assert roi_entry.session_id == ExampleVisit.murfey_session_id + assert roi_entry.name == "roi_1" + assert roi_entry.tag == "/path/to/tomogram_source" + assert roi_entry.x_stage_position == 10 + assert roi_entry.y_stage_position == 2 + assert roi_entry.pixel_size == 0.025 + assert roi_entry.width == 200 + assert roi_entry.height == 400 + assert roi_entry.image == "/path/to/image.jpg" + assert roi_entry.x_location == 16 + assert roi_entry.y_location == 34 From 597b011df1f462ff1cb65d054e75241644f48c63 Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Wed, 8 Jul 2026 16:08:27 +0100 Subject: [PATCH 13/27] Fix some tests --- tests/client/contexts/test_sxt.py | 7 +++---- tests/workflows/sxt/test_sxt_metadata.py | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/client/contexts/test_sxt.py b/tests/client/contexts/test_sxt.py index bdbe6876d..e0beba0b8 100644 --- a/tests/client/contexts/test_sxt.py +++ b/tests/client/contexts/test_sxt.py @@ -82,7 +82,7 @@ def test_sxt_context_xrm_atlas(mock_ole_file, mock_post, tmp_path): json={ "experiment_type_id": 44, "tag": f"{tmp_path}/cm12345-6/grid1", - "atlas": "/path/to/dest/cm12345-6/processed/grid1/example_atlas_Annotated.tiff", + "atlas": "/path/to/dest/cm12345-6/processed/grid1/example_atlas_Annotated_thumbnail.jpg", "atlas_pixel_size": 0.3, "atlas_x_stage_position": 1, "atlas_y_stage_position": -1, @@ -158,16 +158,15 @@ def test_sxt_context_xrm_roi(mock_ole_file, mock_post, tmp_path): headers={"Authorization": "Bearer "}, ) mock_post.assert_any_call( - "http://localhost:8000/workflow/sxt/visits/cm12345-6/sessions/1/register_sxt_roi", + "http://localhost:8000/workflow/sxt/sessions/1/sxt_roi/example_roi", json={ "tag": f"{tmp_path}/cm12345-6/grid1", - "name": "example_roi", "x_stage_position": 1, "y_stage_position": -1, "pixel_size": 0.03, "height": 6000, "width": 4500, - "image": "/path/to/dest/cm12345-6/processed/grid1/example_roi_Annotated.tiff", + "image": "/path/to/dest/cm12345-6/processed/grid1/example_roi_Annotated_thumbnail.jpg", }, headers={"Authorization": "Bearer "}, ) diff --git a/tests/workflows/sxt/test_sxt_metadata.py b/tests/workflows/sxt/test_sxt_metadata.py index 55fcfddc8..2c2930fd7 100644 --- a/tests/workflows/sxt/test_sxt_metadata.py +++ b/tests/workflows/sxt/test_sxt_metadata.py @@ -29,7 +29,7 @@ def set_up_db(murfey_db_session: Session): murfey_db_session, ImagingSite, lookup_kwargs={ - "dcg_id": dcg_entry.dcg_id, + "dcg_id": dcg_entry.id, "pos_x": 2, "pos_y": 3, "image_pixel_size": 0.5, From 730ab98e09500b200b3bc6f09afbe4546a419934 Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Wed, 8 Jul 2026 16:44:57 +0100 Subject: [PATCH 14/27] More test fixes --- tests/server/api/test_workflow.py | 18 +++++++++++++++++- tests/workflows/sxt/test_sxt_metadata.py | 1 + 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/server/api/test_workflow.py b/tests/server/api/test_workflow.py index a1ec8336c..3e7d83d0a 100644 --- a/tests/server/api/test_workflow.py +++ b/tests/server/api/test_workflow.py @@ -6,6 +6,7 @@ DCGroupParameters, register_dc_group, ) +from murfey.util.config import MachineConfig from murfey.util.db import DataCollectionGroup, SearchMap from tests.conftest import ExampleVisit @@ -42,6 +43,10 @@ def test_register_dc_group_new_dcg(mock_transport, murfey_db_session: Session): "atlas": "/path/to/Sample10/Atlas/Atlas_1.jpg", "sample": 10, "atlas_pixel_size": 1e-5, + "atlas_x_stage_position": None, + "atlas_y_stage_position": None, + "atlas_width": None, + "atlas_height": None, "microscope": "", "proposal_code": ExampleVisit.proposal_code, "proposal_number": str(ExampleVisit.proposal_number), @@ -263,6 +268,10 @@ def test_register_dc_group_new_dcg_old_atlas( "atlas": "/path/to/Sample10/Atlas/Atlas_1.jpg", "sample": 10, "atlas_pixel_size": 1e-5, + "atlas_x_stage_position": None, + "atlas_y_stage_position": None, + "atlas_width": None, + "atlas_height": None, "microscope": "", "proposal_code": ExampleVisit.proposal_code, "proposal_number": str(ExampleVisit.proposal_number), @@ -328,14 +337,21 @@ def test_register_dc_group_new_atlas(mock_transport, murfey_db_session: Session) @mock.patch("murfey.server.api.workflow._transport_object") @mock.patch("murfey.server.api.workflow.register_search_map_in_database") +@mock.patch("murfey.server.api.workflow.get_machine_config") def test_register_dc_group_new_atlas_with_searchmaps( - mock_register_search_map, mock_transport, murfey_db_session: Session + mock_machine_config, + mock_register_search_map, + mock_transport, + murfey_db_session: Session, ): """ Test the request to update an existing data collection group by adding an atlas, using the same tag, and also update search maps """ mock_transport.feedback_queue = "mock_feedback_queue" + mock_machine_config.return_value = { + "i01": MachineConfig(acquisition_software=["tomo"]) + } # Make sure dcg is present with an atlas id dcg = DataCollectionGroup( diff --git a/tests/workflows/sxt/test_sxt_metadata.py b/tests/workflows/sxt/test_sxt_metadata.py index 2c2930fd7..129ce26f0 100644 --- a/tests/workflows/sxt/test_sxt_metadata.py +++ b/tests/workflows/sxt/test_sxt_metadata.py @@ -30,6 +30,7 @@ def set_up_db(murfey_db_session: Session): ImagingSite, lookup_kwargs={ "dcg_id": dcg_entry.id, + "name": "site", "pos_x": 2, "pos_y": 3, "image_pixel_size": 0.5, From c9f2bfd557a360b656ef981647d38ef321820c62 Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Wed, 8 Jul 2026 16:53:19 +0100 Subject: [PATCH 15/27] More test fixes --- tests/server/api/test_workflow.py | 2 +- tests/workflows/sxt/test_sxt_metadata.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/server/api/test_workflow.py b/tests/server/api/test_workflow.py index 3e7d83d0a..2626694ad 100644 --- a/tests/server/api/test_workflow.py +++ b/tests/server/api/test_workflow.py @@ -350,7 +350,7 @@ def test_register_dc_group_new_atlas_with_searchmaps( """ mock_transport.feedback_queue = "mock_feedback_queue" mock_machine_config.return_value = { - "i01": MachineConfig(acquisition_software=["tomo"]) + "": MachineConfig(acquisition_software=["tomo"]) } # Make sure dcg is present with an atlas id diff --git a/tests/workflows/sxt/test_sxt_metadata.py b/tests/workflows/sxt/test_sxt_metadata.py index 129ce26f0..60504b4ea 100644 --- a/tests/workflows/sxt/test_sxt_metadata.py +++ b/tests/workflows/sxt/test_sxt_metadata.py @@ -30,7 +30,7 @@ def set_up_db(murfey_db_session: Session): ImagingSite, lookup_kwargs={ "dcg_id": dcg_entry.id, - "name": "site", + "site_name": "site", "pos_x": 2, "pos_y": 3, "image_pixel_size": 0.5, From f0c30b550501de49141b0193be324fb618f7d767 Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Thu, 9 Jul 2026 08:59:11 +0100 Subject: [PATCH 16/27] Maybe mocking wrong thing --- tests/workflows/sxt/test_sxt_metadata.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/workflows/sxt/test_sxt_metadata.py b/tests/workflows/sxt/test_sxt_metadata.py index 60504b4ea..8d533617b 100644 --- a/tests/workflows/sxt/test_sxt_metadata.py +++ b/tests/workflows/sxt/test_sxt_metadata.py @@ -41,7 +41,7 @@ def set_up_db(murfey_db_session: Session): return dcg_entry.id -@mock.patch("murfey.workflows.sxt.process_sxt_tilt_series._transport_object") +@mock.patch("murfey.workflows.sxt.sxt_metadata._transport_object") def test_register_new_sxt_roi(mock_transport, murfey_db_session: Session, tmp_path): set_up_db(murfey_db_session) @@ -75,7 +75,7 @@ def test_register_new_sxt_roi(mock_transport, murfey_db_session: Session, tmp_pa assert roi_entry.image == "/path/to/image.jpg" -@mock.patch("murfey.workflows.sxt.process_sxt_tilt_series._transport_object") +@mock.patch("murfey.workflows.sxt.sxt_metadata._transport_object") def test_update_sxt_roi(mock_transport, murfey_db_session: Session, tmp_path): set_up_db(murfey_db_session) From ada1f85132bb23b87af367e0677b800b4e029ff5 Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Thu, 9 Jul 2026 09:30:27 +0100 Subject: [PATCH 17/27] search map ids diff --- tests/workflows/sxt/test_sxt_metadata.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/workflows/sxt/test_sxt_metadata.py b/tests/workflows/sxt/test_sxt_metadata.py index 8d533617b..b0ec941bb 100644 --- a/tests/workflows/sxt/test_sxt_metadata.py +++ b/tests/workflows/sxt/test_sxt_metadata.py @@ -44,6 +44,7 @@ def set_up_db(murfey_db_session: Session): @mock.patch("murfey.workflows.sxt.sxt_metadata._transport_object") def test_register_new_sxt_roi(mock_transport, murfey_db_session: Session, tmp_path): set_up_db(murfey_db_session) + mock_transport.do_update_sxt_roi.return_value = {"success": True, "return_value": 2} roi_params = SearchMapParameters( tag="/path/to/tomogram_source", @@ -64,6 +65,7 @@ def test_register_new_sxt_roi(mock_transport, murfey_db_session: Session, tmp_pa # Check the database insert roi_entry = murfey_db_session.exec(select(SearchMap)).one() + assert roi_entry.id == 2 assert roi_entry.session_id == ExampleVisit.murfey_session_id assert roi_entry.name == "roi_1" assert roi_entry.tag == "/path/to/tomogram_source" @@ -90,7 +92,6 @@ def test_update_sxt_roi(mock_transport, murfey_db_session: Session, tmp_path): ) roi_params = SearchMapParameters( - id=2, tag="/path/to/tomogram_source", x_stage_position=10, y_stage_position=20, @@ -108,13 +109,14 @@ def test_update_sxt_roi(mock_transport, murfey_db_session: Session, tmp_path): assert return_dict.get("success") # Check the ispyb message - mock_transport.do_update_sxt_roi.assert_any_call(2, roi_params) + mock_transport.do_update_sxt_roi.assert_any_call(1, roi_params) # Check the second update roi_params.x_location = 16 * (512 / 400) + 256 roi_params.y_location = 256 - 34 * (512 / 500) + 256 roi_params.width_on_atlas = 2 * 512 / 400 roi_params.height_on_atlas = 4 * 512 / 500 + mock_transport.do_update_sxt_roi.assert_any_call(1, roi_params) # Check the database insert roi_entry = murfey_db_session.exec(select(SearchMap)).one() From 869596640e098701ababc178583c470c67c9aed1 Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Thu, 9 Jul 2026 09:43:39 +0100 Subject: [PATCH 18/27] Do rounding as int --- src/murfey/workflows/sxt/sxt_metadata.py | 20 ++++++++++++-------- tests/workflows/sxt/test_sxt_metadata.py | 13 +++++-------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/murfey/workflows/sxt/sxt_metadata.py b/src/murfey/workflows/sxt/sxt_metadata.py index 487b2a468..13f60f7f3 100644 --- a/src/murfey/workflows/sxt/sxt_metadata.py +++ b/src/murfey/workflows/sxt/sxt_metadata.py @@ -89,15 +89,19 @@ def register_sxt_roi( # Scaling from different pixel size of atlas and roi, and atlas thumbnail size roi_parameters.x_location = roi.x_location * (512 / atlas.image_pixels_x) + 256 roi_parameters.y_location = 256 - roi.y_location * (512 / atlas.image_pixels_y) - roi_parameters.width_on_atlas = ( - roi.width - * (roi.pixel_size / atlas.image_pixel_size) - * (512 / atlas.image_pixels_x) + roi_parameters.width_on_atlas = int( + round( + roi.width + * (roi.pixel_size / atlas.image_pixel_size) + * (512 / atlas.image_pixels_x) + ) ) - roi_parameters.height_on_atlas = ( - roi.height - * (roi.pixel_size / atlas.image_pixel_size) - * (512 / atlas.image_pixels_y) + roi_parameters.height_on_atlas = int( + round( + roi.height + * (roi.pixel_size / atlas.image_pixel_size) + * (512 / atlas.image_pixels_y) + ) ) if _transport_object: _transport_object.do_update_sxt_roi(roi.id, roi_parameters) diff --git a/tests/workflows/sxt/test_sxt_metadata.py b/tests/workflows/sxt/test_sxt_metadata.py index b0ec941bb..5e75c88b2 100644 --- a/tests/workflows/sxt/test_sxt_metadata.py +++ b/tests/workflows/sxt/test_sxt_metadata.py @@ -44,7 +44,7 @@ def set_up_db(murfey_db_session: Session): @mock.patch("murfey.workflows.sxt.sxt_metadata._transport_object") def test_register_new_sxt_roi(mock_transport, murfey_db_session: Session, tmp_path): set_up_db(murfey_db_session) - mock_transport.do_update_sxt_roi.return_value = {"success": True, "return_value": 2} + mock_transport.do_insert_sxt_roi.return_value = {"success": True, "return_value": 2} roi_params = SearchMapParameters( tag="/path/to/tomogram_source", @@ -108,14 +108,11 @@ def test_update_sxt_roi(mock_transport, murfey_db_session: Session, tmp_path): ) assert return_dict.get("success") - # Check the ispyb message - mock_transport.do_update_sxt_roi.assert_any_call(1, roi_params) - # Check the second update - roi_params.x_location = 16 * (512 / 400) + 256 - roi_params.y_location = 256 - 34 * (512 / 500) + 256 - roi_params.width_on_atlas = 2 * 512 / 400 - roi_params.height_on_atlas = 4 * 512 / 500 + roi_params.x_location = 16 * 512 / 400 + 256 + roi_params.y_location = 256 - 34 * 512 / 500 + roi_params.width_on_atlas = int(round(200 * 0.05 * 512 / 400)) + roi_params.height_on_atlas = int(round(400 * 0.05 * 512 / 500)) mock_transport.do_update_sxt_roi.assert_any_call(1, roi_params) # Check the database insert From e6026210b782905afe2dacc45e7baf50a02fba21 Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Thu, 9 Jul 2026 09:50:05 +0100 Subject: [PATCH 19/27] typo --- tests/workflows/sxt/test_sxt_metadata.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/workflows/sxt/test_sxt_metadata.py b/tests/workflows/sxt/test_sxt_metadata.py index 5e75c88b2..7519abcc8 100644 --- a/tests/workflows/sxt/test_sxt_metadata.py +++ b/tests/workflows/sxt/test_sxt_metadata.py @@ -70,7 +70,7 @@ def test_register_new_sxt_roi(mock_transport, murfey_db_session: Session, tmp_pa assert roi_entry.name == "roi_1" assert roi_entry.tag == "/path/to/tomogram_source" assert roi_entry.x_stage_position == 10 - assert roi_entry.y_stage_position == 2 + assert roi_entry.y_stage_position == 20 assert not roi_entry.pixel_size assert not roi_entry.width assert not roi_entry.height @@ -121,7 +121,7 @@ def test_update_sxt_roi(mock_transport, murfey_db_session: Session, tmp_path): assert roi_entry.name == "roi_1" assert roi_entry.tag == "/path/to/tomogram_source" assert roi_entry.x_stage_position == 10 - assert roi_entry.y_stage_position == 2 + assert roi_entry.y_stage_position == 20 assert roi_entry.pixel_size == 0.025 assert roi_entry.width == 200 assert roi_entry.height == 400 From 9a876db7cfed9d4f7abe69cdbb5aef0c968d6fd1 Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Thu, 9 Jul 2026 11:09:24 +0100 Subject: [PATCH 20/27] Test for im site insert --- .../register_data_collection_group.py | 26 +- tests/server/api/test_workflow.py | 226 +++++++++++++++++- .../test_register_data_collection_group.py | 80 +++++-- 3 files changed, 299 insertions(+), 33 deletions(-) diff --git a/src/murfey/workflows/register_data_collection_group.py b/src/murfey/workflows/register_data_collection_group.py index 5de64a443..148d9dbaa 100644 --- a/src/murfey/workflows/register_data_collection_group.py +++ b/src/murfey/workflows/register_data_collection_group.py @@ -89,20 +89,20 @@ def run(message: dict, murfey_db: SQLModelSession) -> dict[str, bool]: tag=message.get("tag"), smartem_grid_uuid=message.get("smartem_grid_uuid"), ) - if message.get("atlas_x_stage_position"): - atlas_site = ImagingSite( - dcg_id=dcgid, - session_id=message["session_id"], - site_name=message.get("tag"), - data_type="atlas", - pos_x=message.get("atlas_x_stage_position"), - pos_y=message.get("atlas_y_stage_position"), - image_pixels_x=message.get("atlas_width"), - image_pixels_y=message.get("atlas_height"), - image_pixel_size=message.get("atlas_pixel_size"), - ) - murfey_db.add(atlas_site) murfey_db.add(murfey_dcg) + if dcgid is not None and message.get("atlas_x_stage_position"): + atlas_site = ImagingSite( + dcg_id=dcgid, + session_id=message["session_id"], + site_name=message.get("tag"), + data_type="atlas", + pos_x=message.get("atlas_x_stage_position"), + pos_y=message.get("atlas_y_stage_position"), + image_pixels_x=message.get("atlas_width"), + image_pixels_y=message.get("atlas_height"), + image_pixel_size=message.get("atlas_pixel_size"), + ) + murfey_db.add(atlas_site) murfey_db.commit() murfey_db.close() diff --git a/tests/server/api/test_workflow.py b/tests/server/api/test_workflow.py index 2626694ad..db46a485c 100644 --- a/tests/server/api/test_workflow.py +++ b/tests/server/api/test_workflow.py @@ -7,7 +7,8 @@ register_dc_group, ) from murfey.util.config import MachineConfig -from murfey.util.db import DataCollectionGroup, SearchMap +from murfey.util.db import DataCollectionGroup, ImagingSite, SearchMap +from murfey.util.models import SearchMapParameters from tests.conftest import ExampleVisit @@ -446,3 +447,226 @@ def test_register_dc_group_new_atlas_with_searchmaps( murfey_db_session, close_db=False, ) + + +@mock.patch("murfey.server.api.workflow._transport_object") +@mock.patch("murfey.server.api.workflow.get_machine_config") +def test_register_dc_group_new_atlas_with_sxt_roi( + mock_machine_config, + mock_transport, + murfey_db_session: Session, +): + """ + Test the request to update an existing data collection group + by adding an atlas, using the same tag, and also update sxt rois + """ + mock_transport.feedback_queue = "mock_feedback_queue" + mock_machine_config.return_value = { + "": MachineConfig(acquisition_software=["tomo"]) + } + + # Make sure dcg is present with an atlas id + dcg = DataCollectionGroup( + id=1, + session_id=ExampleVisit.murfey_session_id, + tag="processing_tag", + atlas_id=90, + atlas_pixel_size=1e-5, + sample=10, + atlas="/path/to/Sample10/Atlas/Atlas_1.jpg", + ) + murfey_db_session.add(dcg) + murfey_db_session.commit() + + # Add some search maps with the dcg tag and one with a different tag + sm1 = SearchMap( + id=1, + session_id=ExampleVisit.murfey_session_id, + tag="processing_tag", + name="searchmap1", + ) + sm2 = SearchMap( + id=2, + session_id=ExampleVisit.murfey_session_id, + tag="processing_tag", + name="searchmap2", + ) + sm3 = SearchMap( + id=3, + session_id=ExampleVisit.murfey_session_id, + tag="different_tag", + name="searchmap3", + ) + murfey_db_session.add(sm1) + murfey_db_session.add(sm2) + murfey_db_session.add(sm3) + murfey_db_session.commit() + + # Request new dcg registration with new atlas tag and sample + dcg_params = DCGroupParameters( + experiment_type_id=37, + tag="processing_tag", + atlas="/path/to/Sample12/Atlas/Atlas_2.jpg", + sample=12, + atlas_pixel_size=1e-4, + atlas_x_stage_position=10, + atlas_y_stage_position=20, + atlas_width=200, + atlas_height=300, + ) + register_dc_group( + visit_name="cm12345-6", + session_id=ExampleVisit.murfey_session_id, + dcg_params=dcg_params, + db=murfey_db_session, + ) + + # Check request to ispyb for updating the experiment type + mock_transport.send.assert_any_call( + "mock_feedback_queue", + { + "register": "atlas_update", + "atlas_id": 90, + "atlas": "/path/to/Sample12/Atlas/Atlas_2.jpg", + "sample": 12, + "atlas_pixel_size": 1e-4, + "dcgid": 1, + "session_id": ExampleVisit.murfey_session_id, + "tag": "processing_tag", + }, + ) + + # Check the data collection group atlas was updated + new_dcg = murfey_db_session.exec( + select(DataCollectionGroup).where(DataCollectionGroup.id == dcg.id) + ).one() + assert new_dcg.atlas == "/path/to/Sample12/Atlas/Atlas_2.jpg" + assert new_dcg.sample == 12 + assert new_dcg.atlas_pixel_size == 1e-4 + assert new_dcg.tag == "processing_tag" + assert new_dcg.atlas_id == 90 + + # Check the imaging site insert + image_site = murfey_db_session.exec( + select(ImagingSite).where(ImagingSite.dcg_id == dcg.id) + ).one() + assert image_site.site_name == "processing_tag" + assert image_site.pox_x == 10 + assert image_site.image_pixels_x == 200 + assert image_site.image_pixe_size == 1e-4 + + # Check search map row updates + assert mock_transport.send.call_count == 3 + mock_transport.send.assert_any_call( + "mock_feedback_queue", + { + "register": "sxt.register_roi", + "session_id": ExampleVisit.murfey_session_id, + "roi_name": "searchmap1", + "roi_info": SearchMapParameters(tag="processing_tag").model_dump( + mode="json" + ), + }, + ) + mock_transport.send.assert_any_call( + "mock_feedback_queue", + { + "register": "sxt.register_roi", + "session_id": ExampleVisit.murfey_session_id, + "roi_name": "searchmap2", + "roi_info": SearchMapParameters(tag="processing_tag").model_dump( + mode="json" + ), + }, + ) + + +@mock.patch("murfey.server.api.workflow._transport_object") +@mock.patch("murfey.server.api.workflow.get_machine_config") +def test_register_dc_group_roi_update( + mock_machine_config, + mock_transport, + murfey_db_session: Session, +): + """ + Test the request to update an existing data collection group + by adding an atlas, using the same tag, and also update sxt rois + """ + mock_transport.feedback_queue = "mock_feedback_queue" + mock_machine_config.return_value = { + "": MachineConfig(acquisition_software=["tomo"]) + } + + # Make sure dcg is present with an atlas id + dcg = DataCollectionGroup( + id=1, + session_id=ExampleVisit.murfey_session_id, + tag="processing_tag", + atlas_id=90, + atlas_pixel_size=1e-5, + sample=10, + atlas="/path/to/Sample10/Atlas/Atlas_1.jpg", + ) + murfey_db_session.add(dcg) + murfey_db_session.commit() + + # Add some search maps with the dcg tag and one with a different tag + im1 = ImagingSite( + dcg_id=dcg.id, + site_name="processing_tag", + ) + murfey_db_session.add(im1) + murfey_db_session.commit() + + # Request new dcg registration with new atlas tag and sample + dcg_params = DCGroupParameters( + experiment_type_id=37, + tag="processing_tag", + atlas="/path/to/Sample12/Atlas/Atlas_2.jpg", + sample=12, + atlas_pixel_size=1e-4, + atlas_x_stage_position=10, + atlas_y_stage_position=20, + atlas_width=200, + atlas_height=300, + ) + register_dc_group( + visit_name="cm12345-6", + session_id=ExampleVisit.murfey_session_id, + dcg_params=dcg_params, + db=murfey_db_session, + ) + + # Check request to ispyb for updating the experiment type + mock_transport.send.assert_called_once_with( + "mock_feedback_queue", + { + "register": "atlas_update", + "atlas_id": 90, + "atlas": "/path/to/Sample12/Atlas/Atlas_2.jpg", + "sample": 12, + "atlas_pixel_size": 1e-4, + "dcgid": 1, + "session_id": ExampleVisit.murfey_session_id, + "tag": "processing_tag", + }, + ) + + # Check the data collection group atlas was updated + new_dcg = murfey_db_session.exec( + select(DataCollectionGroup).where(DataCollectionGroup.id == dcg.id) + ).one() + assert new_dcg.atlas == "/path/to/Sample12/Atlas/Atlas_2.jpg" + assert new_dcg.sample == 12 + assert new_dcg.atlas_pixel_size == 1e-4 + assert new_dcg.tag == "processing_tag" + assert new_dcg.atlas_id == 90 + + # Check the imaging site insert + image_site = murfey_db_session.exec( + select(ImagingSite).where(ImagingSite.dcg_id == dcg.id) + ).one() + assert image_site.site_name == "processing_tag" + assert image_site.pox_x == 10 + assert image_site.image_pixels_x == 200 + assert image_site.image_pixe_size == 1e-4 diff --git a/tests/workflows/test_register_data_collection_group.py b/tests/workflows/test_register_data_collection_group.py index 9d2783f70..6a565bc04 100644 --- a/tests/workflows/test_register_data_collection_group.py +++ b/tests/workflows/test_register_data_collection_group.py @@ -3,6 +3,7 @@ import pytest from pytest_mock import MockerFixture +from murfey.util.db import DataCollectionGroup, ImagingSite from murfey.workflows.register_data_collection_group import run from tests.conftest import ExampleVisit @@ -10,31 +11,33 @@ @pytest.mark.parametrize( "test_params", ( - # ISPyB session ID | # DCG search result | # DCG insert result | # Atlas insert result - (0, 0, 0, 0), - (0, 0, 0, None), - (0, 0, None, 0), - (0, 0, None, None), - (0, None, 0, 0), - (0, None, 0, None), - (0, None, None, 0), - (0, None, None, None), - (None, 0, 0, 0), - (None, 0, 0, None), - (None, 0, None, 0), - (None, 0, None, None), - (None, None, 0, 0), - (None, None, 0, None), - (None, None, None, 0), - (None, None, None, None), + # ISPyB session ID | # DCG search result | # DCG insert result | # Atlas insert result | # Atlas with location + (0, 0, 0, 0, False), + (0, 0, 0, None, False), + (0, 0, None, 0, False), + (0, 0, None, None, False), + (0, None, 0, 0, True), + (0, None, 0, None, False), + (0, None, None, 0, False), + (0, None, None, None, False), + (None, 0, 0, 0, True), + (None, 0, 0, None, False), + (None, 0, None, 0, False), + (None, 0, None, None, False), + (None, None, 0, 0, False), + (None, None, 0, None, False), + (None, None, None, 0, False), + (None, None, None, None, False), ), ) def test_run( mocker: MockerFixture, - test_params: tuple[int | None, int | None, int | None, int | None], + test_params: tuple[int | None, int | None, int | None, int | None, bool], ): # Unpack test params - (ispyb_session_id, dcg_result, insert_dcg, insert_atlas) = test_params + (ispyb_session_id, dcg_result, insert_dcg, insert_atlas, atlas_location) = ( + test_params + ) # Mock the transport object functions mock_transport_object = mocker.patch( @@ -72,6 +75,11 @@ def test_run( "atlas_pixel_size": 1e-9, "sample": 0, } + if atlas_location: + message["atlas_x_stage_position"] = 10 + message["atlas_y_stage_position"] = 20 + message["atlas_width"] = 200 + message["atlas_height"] = 400 result = run(message=message, murfey_db=mock_murfey_db) if dcg_result is not None: assert result == {"success": True} @@ -80,8 +88,42 @@ def test_run( mock_transport_object.do_insert_data_collection_group.assert_called_once() if insert_dcg is not None: mock_transport_object.do_insert_atlas.assert_called_once() + mock_murfey_db.add.assert_any_call( + DataCollectionGroup( + id=insert_dcg, + session_id=ExampleVisit.murfey_session_id, + tag="some_text", + smartem_grid_uuid=None, + atlas_id=insert_atlas, + atlas_pixel_size=1e-9, + atlas="some_file", + sample=0, + ) + ) assert result == {"success": True} else: assert result == {"success": False, "requeue": True} else: + mock_murfey_db.add.assert_any_call( + DataCollectionGroup( + session_id=ExampleVisit.murfey_session_id, + tag="some_text", + smartem_grid_uuid=None, + ) + ) assert result == {"success": True} + + if atlas_location: + mock_murfey_db.add.assert_any_call( + ImagingSite( + dcg_id=insert_dcg, + session_id=ExampleVisit.murfey_session_id, + site_name="some_text", + data_type="atlas", + pos_x=10, + pos_y=20, + image_pixels_x=200, + image_pixels_y=400, + image_pixel_size=1e-9, + ) + ) From f312d9260c55ac584f64b2d987ec880952608bcb Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Thu, 9 Jul 2026 13:26:38 +0100 Subject: [PATCH 21/27] typo --- tests/server/api/test_workflow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/server/api/test_workflow.py b/tests/server/api/test_workflow.py index db46a485c..c4db00dc9 100644 --- a/tests/server/api/test_workflow.py +++ b/tests/server/api/test_workflow.py @@ -551,7 +551,7 @@ def test_register_dc_group_new_atlas_with_sxt_roi( select(ImagingSite).where(ImagingSite.dcg_id == dcg.id) ).one() assert image_site.site_name == "processing_tag" - assert image_site.pox_x == 10 + assert image_site.pos_x == 10 assert image_site.image_pixels_x == 200 assert image_site.image_pixe_size == 1e-4 @@ -667,6 +667,6 @@ def test_register_dc_group_roi_update( select(ImagingSite).where(ImagingSite.dcg_id == dcg.id) ).one() assert image_site.site_name == "processing_tag" - assert image_site.pox_x == 10 + assert image_site.pos_x == 10 assert image_site.image_pixels_x == 200 assert image_site.image_pixe_size == 1e-4 From 02704b3a3945a743dabcc655541ace3e0af5ff7b Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Thu, 9 Jul 2026 16:05:45 +0100 Subject: [PATCH 22/27] typo --- tests/server/api/test_workflow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/server/api/test_workflow.py b/tests/server/api/test_workflow.py index c4db00dc9..abc27f4b9 100644 --- a/tests/server/api/test_workflow.py +++ b/tests/server/api/test_workflow.py @@ -553,7 +553,7 @@ def test_register_dc_group_new_atlas_with_sxt_roi( assert image_site.site_name == "processing_tag" assert image_site.pos_x == 10 assert image_site.image_pixels_x == 200 - assert image_site.image_pixe_size == 1e-4 + assert image_site.image_pixel_size == 1e-4 # Check search map row updates assert mock_transport.send.call_count == 3 @@ -669,4 +669,4 @@ def test_register_dc_group_roi_update( assert image_site.site_name == "processing_tag" assert image_site.pos_x == 10 assert image_site.image_pixels_x == 200 - assert image_site.image_pixe_size == 1e-4 + assert image_site.image_pixel_size == 1e-4 From 42c232bcab9767107dd02bd3b20f5aac1df6a00d Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Thu, 9 Jul 2026 16:14:10 +0100 Subject: [PATCH 23/27] change software name --- tests/server/api/test_workflow.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/server/api/test_workflow.py b/tests/server/api/test_workflow.py index abc27f4b9..78219d5d4 100644 --- a/tests/server/api/test_workflow.py +++ b/tests/server/api/test_workflow.py @@ -461,9 +461,7 @@ def test_register_dc_group_new_atlas_with_sxt_roi( by adding an atlas, using the same tag, and also update sxt rois """ mock_transport.feedback_queue = "mock_feedback_queue" - mock_machine_config.return_value = { - "": MachineConfig(acquisition_software=["tomo"]) - } + mock_machine_config.return_value = {"": MachineConfig(acquisition_software=["sxt"])} # Make sure dcg is present with an atlas id dcg = DataCollectionGroup( From c9fc226c3e910537ac7b81a4da67cb05f25bd2d4 Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Thu, 9 Jul 2026 16:22:13 +0100 Subject: [PATCH 24/27] sanitise string --- src/murfey/server/api/workflow_sxt.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/murfey/server/api/workflow_sxt.py b/src/murfey/server/api/workflow_sxt.py index fd8f214fa..a08e3417d 100644 --- a/src/murfey/server/api/workflow_sxt.py +++ b/src/murfey/server/api/workflow_sxt.py @@ -10,6 +10,7 @@ validate_instrument_token, ) from murfey.server.murfey_db import murfey_db +from murfey.util import sanitise from murfey.util.models import SearchMapParameters from murfey.workflows.sxt.process_sxt_tilt_series import SXTTiltSeriesInfo @@ -71,7 +72,7 @@ def register_sxt_roi( db=murfey_db, ): if _transport_object: - logger.info(f"Registering SXT region {roi_name}") + logger.info(f"Registering SXT region {sanitise(roi_name)}") _transport_object.send( _transport_object.feedback_queue, { From b26a956220c8415845682055b535c158c9cea23b Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Mon, 13 Jul 2026 16:29:44 +0100 Subject: [PATCH 25/27] Typo --- src/murfey/server/ispyb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/murfey/server/ispyb.py b/src/murfey/server/ispyb.py index 962cc051c..eead79075 100644 --- a/src/murfey/server/ispyb.py +++ b/src/murfey/server/ispyb.py @@ -557,7 +557,7 @@ def do_insert_sxt_roi( roi_parameters.height / roi_parameters.height_on_atlas ) roi_parameters.x_location = ( - int(roi_parameters.x_location8) if roi_parameters.x_location else None + int(roi_parameters.x_location) if roi_parameters.x_location else None ) roi_parameters.y_location = ( int(roi_parameters.y_location) if roi_parameters.y_location else None From 45bba8d1dd43a0c5743bd1866b9fd1c72011d9c1 Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Mon, 13 Jul 2026 16:43:14 +0100 Subject: [PATCH 26/27] Switch from all to one_or_none --- src/murfey/server/api/workflow.py | 37 ++++++++++++------------ src/murfey/workflows/sxt/sxt_metadata.py | 26 ++++++++--------- 2 files changed, 30 insertions(+), 33 deletions(-) diff --git a/src/murfey/server/api/workflow.py b/src/murfey/server/api/workflow.py index 28202faf4..a91dcd3ee 100644 --- a/src/murfey/server/api/workflow.py +++ b/src/murfey/server/api/workflow.py @@ -180,26 +180,25 @@ def register_dc_group( dcg_instance.smartem_grid_uuid = smartem_grid_uuid # Update any atlases which are registered as imaging sites - if atlas_sites := db.exec( + if atlas_instance := db.exec( select(ImagingSite).where(ImagingSite.dcg_id == dcg_instance.id) - ).all(): - for atlas_instance in atlas_sites: - atlas_instance.pos_x = ( - dcg_params.atlas_x_stage_position or atlas_instance.pos_x - ) - atlas_instance.pos_y = ( - dcg_params.atlas_y_stage_position or atlas_instance.pos_y - ) - atlas_instance.image_pixels_x = ( - dcg_params.atlas_width or atlas_instance.image_pixels_x - ) - atlas_instance.image_pixels_y = ( - dcg_params.atlas_height or atlas_instance.image_pixels_y - ) - atlas_instance.image_pixel_size = ( - dcg_params.atlas_pixel_size or atlas_instance.image_pixel_size - ) - db.add(atlas_instance) + ).one_or_none(): + atlas_instance.pos_x = ( + dcg_params.atlas_x_stage_position or atlas_instance.pos_x + ) + atlas_instance.pos_y = ( + dcg_params.atlas_y_stage_position or atlas_instance.pos_y + ) + atlas_instance.image_pixels_x = ( + dcg_params.atlas_width or atlas_instance.image_pixels_x + ) + atlas_instance.image_pixels_y = ( + dcg_params.atlas_height or atlas_instance.image_pixels_y + ) + atlas_instance.image_pixel_size = ( + dcg_params.atlas_pixel_size or atlas_instance.image_pixel_size + ) + db.add(atlas_instance) elif dcg_params.atlas_x_stage_position: atlas_instance = ImagingSite( dcg_id=dcg_instance.id, diff --git a/src/murfey/workflows/sxt/sxt_metadata.py b/src/murfey/workflows/sxt/sxt_metadata.py index 13f60f7f3..71135eb87 100644 --- a/src/murfey/workflows/sxt/sxt_metadata.py +++ b/src/murfey/workflows/sxt/sxt_metadata.py @@ -22,15 +22,14 @@ def register_sxt_roi( .where(DataCollectionGroup.session_id == session_id) .where(DataCollectionGroup.tag == roi_parameters.tag) ).one() - roi_query = murfey_db.exec( + roi = murfey_db.exec( select(SearchMap) .where(SearchMap.name == roi_name) .where(SearchMap.tag == roi_parameters.tag) .where(SearchMap.session_id == session_id) - ).all() - if roi_query: + ).one_or_none() + if roi: # See if there is already a search map with this name and update if so - roi = roi_query[0] roi.x_stage_position = roi_parameters.x_stage_position or roi.x_stage_position roi.y_stage_position = roi_parameters.y_stage_position or roi.y_stage_position roi.height = roi_parameters.height or roi.height @@ -66,22 +65,21 @@ def register_sxt_roi( image=roi_parameters.image or "", ) - atlas_sites = murfey_db.exec( + atlas = murfey_db.exec( select(ImagingSite).where(ImagingSite.dcg_id == dcg.id) - ).all() - if atlas_sites and all( + ).one_or_none() + if atlas and all( [ roi.x_stage_position, roi.y_stage_position, roi.pixel_size, - atlas_sites[0].pos_x, - atlas_sites[0].pos_y, - atlas_sites[0].image_pixel_size, - atlas_sites[0].image_pixels_x, - atlas_sites[0].image_pixels_y, + atlas.pos_x, + atlas.pos_y, + atlas.image_pixel_size, + atlas.image_pixels_x, + atlas.image_pixels_y, ] ): - atlas = atlas_sites[0] # Convert from stage position to pixel locations roi.x_location = (roi.x_stage_position - atlas.pos_x) / atlas.image_pixel_size roi.y_location = (roi.y_stage_position - atlas.pos_y) / atlas.image_pixel_size @@ -110,7 +108,7 @@ def register_sxt_roi( f"Unable to register roi {sanitise(roi.name)} position yet: " f"roi pixel size {sanitise(str(roi.pixel_size))}, " f"atlas pixel size {sanitise(str(dcg.atlas_pixel_size))}, " - f"roi count {len(atlas_sites)}" + f"roi pixel size {sanitise(str(atlas.image_pixel_size))}" ) murfey_db.add(roi) murfey_db.commit() From 97cee9f93694e51ee7651d9dceb12abe7c7836dc Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Mon, 13 Jul 2026 16:45:05 +0100 Subject: [PATCH 27/27] Log missing transport objects --- src/murfey/server/api/workflow_sxt.py | 2 ++ src/murfey/workflows/sxt/sxt_metadata.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/murfey/server/api/workflow_sxt.py b/src/murfey/server/api/workflow_sxt.py index a08e3417d..6f5e0a9e3 100644 --- a/src/murfey/server/api/workflow_sxt.py +++ b/src/murfey/server/api/workflow_sxt.py @@ -83,3 +83,5 @@ def register_sxt_roi( }, new_connection=True, ) + else: + logger.warning("No transport object for register_sxt_roi") diff --git a/src/murfey/workflows/sxt/sxt_metadata.py b/src/murfey/workflows/sxt/sxt_metadata.py index 71135eb87..642257fc5 100644 --- a/src/murfey/workflows/sxt/sxt_metadata.py +++ b/src/murfey/workflows/sxt/sxt_metadata.py @@ -103,6 +103,8 @@ def register_sxt_roi( ) if _transport_object: _transport_object.do_update_sxt_roi(roi.id, roi_parameters) + else: + logger.warning("No transport object for register_sxt_roi") else: logger.info( f"Unable to register roi {sanitise(roi.name)} position yet: "