From 927cc3256e27248feb69cecc22727610be246228 Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Tue, 23 Jun 2026 16:44:40 +0100 Subject: [PATCH 01/10] 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/10] 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/10] 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/10] 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 e05e44001c622565e611d704ef943d6767364cd8 Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Wed, 24 Jun 2026 10:28:52 +0100 Subject: [PATCH 05/10] Revert db changes for later pr --- src/murfey/server/api/workflow.py | 33 ++------------------------ src/murfey/util/db.py | 39 ------------------------------- 2 files changed, 2 insertions(+), 70 deletions(-) diff --git a/src/murfey/server/api/workflow.py b/src/murfey/server/api/workflow.py index 4ce3bde38..b228dbda0 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, ) @@ -100,6 +99,8 @@ class DCGroupParameters(BaseModel): atlas_pixel_size: float = 0 atlas_x_stage_position: float | None = None atlas_y_stage_position: float | None = None + atlas_width: int | None = None + atlas_height: int | None = None create_smartem_grid: bool = False acquisition_uuid: Optional[str] = None @@ -174,18 +175,6 @@ 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 @@ -227,22 +216,6 @@ 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) @@ -282,8 +255,6 @@ 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: diff --git a/src/murfey/util/db.py b/src/murfey/util/db.py index 952f42475..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 @@ -1142,41 +1138,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 From efaffae8e0664fe338f011d50667048772b2d845 Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Wed, 24 Jun 2026 10:30:02 +0100 Subject: [PATCH 06/10] Another revert --- src/murfey/workflows/register_data_collection_group.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/murfey/workflows/register_data_collection_group.py b/src/murfey/workflows/register_data_collection_group.py index 04ff39145..0908e769b 100644 --- a/src/murfey/workflows/register_data_collection_group.py +++ b/src/murfey/workflows/register_data_collection_group.py @@ -84,8 +84,6 @@ 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 ef04d9356a3094d7c6c20bec263d5c539a56a509 Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Fri, 3 Jul 2026 11:16:26 +0100 Subject: [PATCH 07/10] Improved path structuring and tests --- src/murfey/client/contexts/sxt.py | 54 ++++++++++++----- tests/client/contexts/test_sxt.py | 98 ++++++++++++++++++++++++++++--- 2 files changed, 128 insertions(+), 24 deletions(-) diff --git a/src/murfey/client/contexts/sxt.py b/src/murfey/client/contexts/sxt.py index 2bf7e696f..86b6725ae 100644 --- a/src/murfey/client/contexts/sxt.py +++ b/src/murfey/client/contexts/sxt.py @@ -184,17 +184,17 @@ def post_transfer( if xrm_ole.exists("ImageInfo/PixelSize"): metadata["pixel_size"] = np.frombuffer( xrm_ole.openstream("ImageInfo/PixelSize").getvalue(), np.float32 - ).tolist() + ).tolist()[0] if xrm_ole.exists("ImageInfo/ImageHeight"): metadata["height"] = np.frombuffer( xrm_ole.openstream("ImageInfo/ImageHeight").getvalue(), np.int32 - ).tolist() + ).tolist()[0] if xrm_ole.exists("ImageInfo/ImageWidth"): metadata["width"] = np.frombuffer( - xrm_ole.openstream("ImageInfo/ImageWidth").getvalue(), np.intt32 - ).tolist() + xrm_ole.openstream("ImageInfo/ImageWidth").getvalue(), np.int32 + ).tolist()[0] # Find images which are not mosaics (txrm spec typos this as mosiac) if xrm_ole.exists("ImageInfo/MosiacRows") and xrm_ole.exists( @@ -203,12 +203,12 @@ def post_transfer( metadata["mosaic_rows"] = np.frombuffer( xrm_ole.openstream("ImageInfo/MosiacRows").getvalue(), np.int32 )[0] - metadata["mosiac_columns"] = np.frombuffer( + metadata["mosaic_columns"] = np.frombuffer( xrm_ole.openstream("ImageInfo/MosiacColumns").getvalue(), np.int32, )[0] metadata["mosaic_size"] = int( - metadata["mosaic_rows"] * metadata["mosiac_columns"] + metadata["mosaic_rows"] * metadata["mosaic_columns"] ) source = _get_source(transferred_file, environment=environment) @@ -219,26 +219,48 @@ def post_transfer( transferred_file, Path(self._machine_config.get("rsync_basepath", "")), ) + if ( + environment.visit + in Path(environment.default_destinations[source]).parts + ): + # Split either side of the raw directory + visit_idx = Path( + environment.default_destinations[source] + ).parts.index(environment.visit) + destination_base = "/".join( + Path(environment.default_destinations[source]).parts[ + : visit_idx + 1 + ] + ) + destination_extra = "/".join( + Path(environment.default_destinations[source]).parts[ + visit_idx + 2 : + ] + ) + else: + destination_base = str( + Path(environment.default_destinations[source]) + / environment.visit + ) + destination_extra = "" converted_file_path = ( Path(self._machine_config.get("rsync_basepath", "")) - / ( - environment.visit - if environment.visit - not in environment.default_destinations[source] - else "" - ) + / destination_base / self._machine_config.get("processed_directory_name", "") / self._machine_config.get("processed_extra_directory", "") + / destination_extra / 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, - data={"xrm_path": image_path, "tiff_path": converted_file_path}, + data={ + "xrm_path": str(image_path), + "tiff_path": str(converted_file_path), + }, ) if ( @@ -250,7 +272,7 @@ def post_transfer( "experiment_type_id": 44, # Atlas "tag": dcg_tag, "atlas": str(converted_file_path), - "atlas_pixel_size": metadata.get("pixel_size", None), + "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), "atlas_height": int( @@ -278,8 +300,8 @@ 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, - sm_name=transferred_file.parent.name, data={ "tag": dcg_tag, "name": transferred_file.stem, diff --git a/tests/client/contexts/test_sxt.py b/tests/client/contexts/test_sxt.py index 4e620d2f5..d63346899 100644 --- a/tests/client/contexts/test_sxt.py +++ b/tests/client/contexts/test_sxt.py @@ -15,26 +15,108 @@ def test_sxt_context_initialisation(tmp_path): @patch("requests.post") -def test_sxt_context_xrm(mock_post, tmp_path): - """Currently nothing happens with an xrm file""" +@patch("murfey.client.contexts.sxt.OleFileIO") +def test_sxt_context_xrm_atlas(mock_ole_file, mock_post, tmp_path): + """xrm files contain metadata, test atlas-mag case""" + mock_post().status_code = 200 + mock_ole_file().__enter__().exists.return_value = True + # Motor position names + mock_ole_file().__enter__().openstream().read.return_value = ( + "\x00Val1\x00\x00Energy\x00".encode() + ) + # Metadata encoded arrays + mock_ole_file().__enter__().openstream().getvalue.side_effect = [ + np.array([-1, 0, 1, 2, 3], dtype=np.float32).tobytes(), # x tile positions + np.array([-3, -2, -1, 0, 1], dtype=np.float32).tobytes(), # y tile positions + np.array([0.3], dtype=np.float32).tobytes(), # Pixel size + np.array([1000], dtype=np.int32).tobytes(), # Image Height + np.array([900], dtype=np.int32).tobytes(), # Image Width + np.array([6], dtype=np.int32).tobytes(), # Mosaic size + np.array([5], dtype=np.int32).tobytes(), # Mosaic size + ] + env = MurfeyInstanceEnvironment( url=urlparse("http://localhost:8000"), client_id=0, - sources=[tmp_path], - default_destinations={tmp_path: str(tmp_path)}, + sources=[tmp_path / "cm12345-6/grid1"], + default_destinations={f"{tmp_path}/cm12345-6/grid1": "cm12345-6/raw/grid1"}, instrument_name="", - visit="test", + visit="cm12345-6", murfey_session=1, ) - context = SXTContext("zeiss", tmp_path, {}, "") + context = SXTContext( + "zeiss", + tmp_path / "cm12345-6/grid1", + {"rsync_basepath": "/path/to/dest", "processed_directory_name": "processed"}, + "", + ) return_value = context.post_transfer( - tmp_path / "example.xrm", + tmp_path / "cm12345-6/grid1/example_atlas.xrm", required_position_files=[], required_strings=["fractions"], environment=env, ) assert return_value - mock_post.assert_not_called() + + mock_ole_file.assert_any_call(str(tmp_path / "cm12345-6/grid1/example_atlas.xrm")) + + # assert mock_post.call_count == 4 + mock_post.assert_any_call( + "http://localhost:8000/workflow/visits/cm12345-6/sessions/1/register_data_collection_group", + json={ + "experiment_type_id": 47, + "tag": f"{tmp_path}/cm12345-6/grid1", + }, + headers={"Authorization": "Bearer "}, + ) + mock_post.assert_any_call( + "http://localhost:8000/workflow/sxt/convert_xrm_to_tiff", + json={ + "xrm_path": "/path/to/dest/cm12345-6/raw/grid1/example_atlas.xrm", + "tiff_path": "/path/to/dest/cm12345-6/processed/grid1/example_atlas_Annotated.tiff", + }, + headers={"Authorization": "Bearer "}, + ) + mock_post.assert_any_call( + "http://localhost:8000/workflow/visits/cm12345-6/sessions/1/register_data_collection_group", + 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_pixel_size": 0.3, + "atlas_x_stage_position": 1, + "atlas_y_stage_position": -1, + "atlas_height": 6000, + "atlas_width": 4500, + }, + headers={"Authorization": "Bearer "}, + ) + """ + 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), + }, + )""" @patch("requests.post") From e0e427fef40f09dc4f0831464df71fc32e4425d5 Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Fri, 3 Jul 2026 11:27:02 +0100 Subject: [PATCH 08/10] Test for roi mag --- src/murfey/client/contexts/sxt.py | 2 +- tests/client/contexts/test_sxt.py | 106 ++++++++++++++++++++++-------- 2 files changed, 81 insertions(+), 27 deletions(-) diff --git a/src/murfey/client/contexts/sxt.py b/src/murfey/client/contexts/sxt.py index 86b6725ae..3433ee279 100644 --- a/src/murfey/client/contexts/sxt.py +++ b/src/murfey/client/contexts/sxt.py @@ -307,7 +307,7 @@ def post_transfer( "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), + "pixel_size": round(metadata.get("pixel_size", 0), 2), "height": int( metadata.get("height", 0) * metadata["mosaic_rows"] ), diff --git a/tests/client/contexts/test_sxt.py b/tests/client/contexts/test_sxt.py index d63346899..bdbe6876d 100644 --- a/tests/client/contexts/test_sxt.py +++ b/tests/client/contexts/test_sxt.py @@ -91,32 +91,86 @@ def test_sxt_context_xrm_atlas(mock_ole_file, mock_post, tmp_path): }, headers={"Authorization": "Bearer "}, ) - """ - 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), - }, - )""" + + +@patch("requests.post") +@patch("murfey.client.contexts.sxt.OleFileIO") +def test_sxt_context_xrm_roi(mock_ole_file, mock_post, tmp_path): + """xrm files contain metadata, test roi-mag case""" + mock_post().status_code = 200 + mock_ole_file().__enter__().exists.return_value = True + # Motor position names + mock_ole_file().__enter__().openstream().read.return_value = ( + "\x00Val1\x00\x00Energy\x00".encode() + ) + # Metadata encoded arrays + mock_ole_file().__enter__().openstream().getvalue.side_effect = [ + np.array([-1, 0, 1, 2, 3], dtype=np.float32).tobytes(), # x tile positions + np.array([-3, -2, -1, 0, 1], dtype=np.float32).tobytes(), + # y tile positions + np.array([0.03], dtype=np.float32).tobytes(), # Pixel size, smaller than atlas + np.array([1000], dtype=np.int32).tobytes(), # Image Height + np.array([900], dtype=np.int32).tobytes(), # Image Width + np.array([6], dtype=np.int32).tobytes(), # Mosaic size + np.array([5], dtype=np.int32).tobytes(), # Mosaic size + ] + + env = MurfeyInstanceEnvironment( + url=urlparse("http://localhost:8000"), + client_id=0, + sources=[tmp_path / "cm12345-6/grid1"], + default_destinations={f"{tmp_path}/cm12345-6/grid1": "cm12345-6/raw/grid1"}, + instrument_name="", + visit="cm12345-6", + murfey_session=1, + ) + context = SXTContext( + "zeiss", + tmp_path / "cm12345-6/grid1", + {"rsync_basepath": "/path/to/dest", "processed_directory_name": "processed"}, + "", + ) + return_value = context.post_transfer( + tmp_path / "cm12345-6/grid1/example_roi.xrm", + required_position_files=[], + required_strings=["fractions"], + environment=env, + ) + assert return_value + + mock_ole_file.assert_any_call(str(tmp_path / "cm12345-6/grid1/example_roi.xrm")) + + # assert mock_post.call_count == 4 + mock_post.assert_any_call( + "http://localhost:8000/workflow/visits/cm12345-6/sessions/1/register_data_collection_group", + json={ + "experiment_type_id": 47, + "tag": f"{tmp_path}/cm12345-6/grid1", + }, + headers={"Authorization": "Bearer "}, + ) + mock_post.assert_any_call( + "http://localhost:8000/workflow/sxt/convert_xrm_to_tiff", + json={ + "xrm_path": "/path/to/dest/cm12345-6/raw/grid1/example_roi.xrm", + "tiff_path": "/path/to/dest/cm12345-6/processed/grid1/example_roi_Annotated.tiff", + }, + headers={"Authorization": "Bearer "}, + ) + mock_post.assert_any_call( + "http://localhost:8000/workflow/sxt/visits/cm12345-6/sessions/1/register_sxt_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", + }, + headers={"Authorization": "Bearer "}, + ) @patch("requests.post") From 124ba36f3e8e70011d04930fbc1a922711bb021f Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Fri, 3 Jul 2026 17:16:12 +0100 Subject: [PATCH 09/10] Break out ole read function --- src/murfey/client/contexts/sxt.py | 82 +++++++++++++++---------------- 1 file changed, 39 insertions(+), 43 deletions(-) diff --git a/src/murfey/client/contexts/sxt.py b/src/murfey/client/contexts/sxt.py index 3433ee279..7aebbd559 100644 --- a/src/murfey/client/contexts/sxt.py +++ b/src/murfey/client/contexts/sxt.py @@ -58,6 +58,10 @@ def _find_reference(txrm_file: Path) -> Path | None: return None +def _get_ole_header_value(ole_file, title: str, dtype: np.dtype): + return np.frombuffer(ole_file.openstream(title).getvalue(), dtype) + + class SXTContext(Context): def __init__( self, @@ -156,6 +160,7 @@ def post_transfer( environment=environment, **kwargs, ) + metadata: dict[str, Any] = {} if transferred_file.suffix == ".xrm" and environment: # Make sure we have a dcg for this grid @@ -167,45 +172,43 @@ def post_transfer( 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() + x_tiles = _get_ole_header_value( + xrm_ole, "ImageInfo/XPosition", np.float32 + ).tolist()[0] + y_tiles = _get_ole_header_value( + xrm_ole, "ImageInfo/YPosition", np.float32 + ).tolist()[0] 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 + metadata["pixel_size"] = _get_ole_header_value( + xrm_ole, "ImageInfo/PixelSize", np.float32 ).tolist()[0] if xrm_ole.exists("ImageInfo/ImageHeight"): - metadata["height"] = np.frombuffer( - xrm_ole.openstream("ImageInfo/ImageHeight").getvalue(), np.int32 + metadata["height"] = _get_ole_header_value( + xrm_ole, "ImageInfo/ImageHeight", np.int32 ).tolist()[0] if xrm_ole.exists("ImageInfo/ImageWidth"): - metadata["width"] = np.frombuffer( - xrm_ole.openstream("ImageInfo/ImageWidth").getvalue(), np.int32 + metadata["width"] = _get_ole_header_value( + xrm_ole, "ImageInfo/ImageWidth", np.int32 ).tolist()[0] # 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 + metadata["mosaic_rows"] = _get_ole_header_value( + xrm_ole, "ImageInfo/MosiacRows", np.int32 )[0] - metadata["mosaic_columns"] = np.frombuffer( - xrm_ole.openstream("ImageInfo/MosiacColumns").getvalue(), - np.int32, + metadata["mosaic_columns"] = _get_ole_header_value( + xrm_ole, "ImageInfo/MosiacColumns", np.int32 )[0] metadata["mosaic_size"] = int( metadata["mosaic_rows"] * metadata["mosaic_columns"] @@ -326,58 +329,52 @@ def post_transfer( # Read the tilt angles and pixel size from the txrm angles: list = [] - metadata = { - "source": str(self._basepath), - "tilt_series_tag": transferred_file.stem, - } + metadata["source"] = str(self._basepath) + metadata["tilt_series_tag"] = transferred_file.stem with OleFileIO(str(transferred_file)) as txrm_ole: if txrm_ole.exists("ReferenceData/Image"): metadata["has_reference"] = True if txrm_ole.exists("ImageInfo/Angles"): - angles = np.frombuffer( - txrm_ole.openstream("ImageInfo/Angles").getvalue(), np.float32 + angles = _get_ole_header_value( + txrm_ole, "ImageInfo/Angles", np.float32 ).tolist() metadata["minimum_angle"] = min(angles) metadata["maximum_angle"] = max(angles) if txrm_ole.exists("ImageInfo/PixelSize"): - pixel_size_txrm = np.frombuffer( - txrm_ole.openstream("ImageInfo/PixelSize").getvalue(), - np.float32, + pixel_size_txrm = _get_ole_header_value( + txrm_ole, "ImageInfo/PixelSize", np.float32 ).tolist() metadata["pixel_size"] = pixel_size_txrm[0] * 1e4 if txrm_ole.exists("ImageInfo/ImageWidth"): - image_width_txrm = np.frombuffer( - txrm_ole.openstream("ImageInfo/ImageWidth").getvalue(), np.int32 + image_width_txrm = _get_ole_header_value( + txrm_ole, "ImageInfo/ImageWidth", np.int32 ).tolist() metadata["image_size_x"] = image_width_txrm[0] if txrm_ole.exists("ImageInfo/ImageHeight"): - image_height_txrm = np.frombuffer( - txrm_ole.openstream("ImageInfo/ImageHeight").getvalue(), - np.int32, + image_height_txrm = _get_ole_header_value( + txrm_ole, "ImageInfo/ImageHeight", np.int32 ).tolist() metadata["image_size_y"] = image_height_txrm[0] if txrm_ole.exists("ImageInfo/ExpTimes"): - exposure_time_txrm = np.frombuffer( - txrm_ole.openstream("ImageInfo/ExpTimes").getvalue(), np.float32 + exposure_time_txrm = _get_ole_header_value( + txrm_ole, "ImageInfo/ExpTimes", np.float32 ).tolist() metadata["exposure_time"] = exposure_time_txrm[0] if txrm_ole.exists("ImageInfo/XrayMagnification"): - magnification_txrm = np.frombuffer( - txrm_ole.openstream("ImageInfo/XrayMagnification").getvalue(), - np.float32, + magnification_txrm = _get_ole_header_value( + txrm_ole, "ImageInfo/XrayMagnification", np.float32 ).tolist() metadata["magnification"] = magnification_txrm[0] if txrm_ole.exists("ImageInfo/ImagesTaken"): - tilt_count_txrm = np.frombuffer( - txrm_ole.openstream("ImageInfo/ImagesTaken").getvalue(), - np.int32, + tilt_count_txrm = _get_ole_header_value( + txrm_ole, "ImageInfo/ImagesTaken", np.int32 ).tolist() metadata["tilt_series_length"] = tilt_count_txrm[0] @@ -394,9 +391,8 @@ def post_transfer( .split("\x00") if i ] - axis_values = np.frombuffer( - txrm_ole.openstream("PositionInfo/MotorPositions").getvalue(), - np.float32, + axis_values = _get_ole_header_value( + txrm_ole, "PositionInfo/MotorPositions", np.float32 ) if "Energy" in axis_names: energy_index = list(np.array(axis_names) == "Energy").index( From c12f36fa746ab89744de2ebc209284757d5fd671 Mon Sep 17 00:00:00 2001 From: yxd92326 Date: Fri, 3 Jul 2026 17:17:37 +0100 Subject: [PATCH 10/10] Wrongly introduced indexes --- src/murfey/client/contexts/sxt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/murfey/client/contexts/sxt.py b/src/murfey/client/contexts/sxt.py index 7aebbd559..da5732b44 100644 --- a/src/murfey/client/contexts/sxt.py +++ b/src/murfey/client/contexts/sxt.py @@ -178,10 +178,10 @@ def post_transfer( ): x_tiles = _get_ole_header_value( xrm_ole, "ImageInfo/XPosition", np.float32 - ).tolist()[0] + ).tolist() y_tiles = _get_ole_header_value( xrm_ole, "ImageInfo/YPosition", np.float32 - ).tolist()[0] + ).tolist() metadata["x_position"] = x_tiles[int(len(x_tiles) / 2)] metadata["y_position"] = y_tiles[int(len(y_tiles) / 2)]