Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
7b50bb6
Used Optional[] as type hint for Python 3.9 compatibility
tieneupin Oct 2, 2024
e796663
Raise Exception if no TransportManager instance is found when running…
tieneupin Oct 2, 2024
0167f96
Corrected database call in 'validate_and_sanitise' function; simplifi…
tieneupin Oct 2, 2024
6dac704
Added functions to post requests to the server to register LIF and TI…
tieneupin Oct 2, 2024
bbd3e29
Used Path object to resolve file path in 'validate_and_sanitise'; add…
tieneupin Oct 3, 2024
b6a4297
Corrected returned booleans at the end of different workflow branches
tieneupin Oct 9, 2024
9d03fa2
Corrected inconsistent returns and raises in functions
tieneupin Oct 9, 2024
dadb02d
Simplified CLEM BaseModels and updated CLEMContext file handling logi…
tieneupin Oct 9, 2024
c69a22f
Allow forward slashes in series name when validating input
tieneupin Oct 9, 2024
276ee54
Added folder to store module feedback callback workflows in
tieneupin Oct 10, 2024
f6fd56d
Added feedback callback elif-blocks for CLEM workflows
tieneupin Oct 10, 2024
5ef7218
Updated 'lif_to_stack' API endpoint to load and pass on more information
tieneupin Oct 10, 2024
d29b8ed
Updated 'lif_to_stack' workflow to handle additional zocalo wrapper p…
tieneupin Oct 10, 2024
398b09e
Notes on how 'feedback_callback' works
tieneupin Oct 30, 2024
7820501
Constructed parameters from file path; updated parameter names
tieneupin Oct 30, 2024
51c528c
Fixed comments
tieneupin Oct 30, 2024
2d6d367
Removed 'instrument_name' parameter from murfey.server.murfey_db
tieneupin Oct 31, 2024
e8447e0
Refactored CLEM Murfey workflows
tieneupin Nov 1, 2024
c4bdfc7
Missed deleting the old file
tieneupin Nov 1, 2024
a01a786
Updated TIFF Pydantic model
tieneupin Nov 1, 2024
185158f
Added Pydantic models to validate LIF and TIFF preprocessing result m…
tieneupin Nov 1, 2024
ad9a489
Changed 'color' to the more generic 'channel'
tieneupin Nov 1, 2024
c9ffa31
Refactored functions to parse CLEM preprocessing results and started …
tieneupin Nov 1, 2024
c3c78a0
Completed draft of function to register LIF preprocessing results
tieneupin Nov 4, 2024
f9858ee
Added 'number_of_members' as a parameter to register for a series
tieneupin Nov 4, 2024
ecf6d38
Added 'return None' to CLEM results registration code blocks
tieneupin Nov 4, 2024
a132b86
Merged recent changes from 'main' branch
tieneupin Nov 4, 2024
f7681c9
Rewrote 'tiff_to_stack' function to work with Zocalo wrapper
tieneupin Nov 4, 2024
480a7e4
Modified URLs for CLEM preprocesisng API endpoints
tieneupin Nov 4, 2024
09e85cc
Stringified TIFF list when submitting to cluster
tieneupin Nov 4, 2024
3cd3344
Ensured file paths in recipes are represented canonically
tieneupin Nov 4, 2024
816d434
Adjusted order of parameters
tieneupin Nov 4, 2024
8576eb1
Canonicalisation of strings appears to not be needed when submitting …
tieneupin Nov 4, 2024
4e5b202
Fixed outdated parameters to pass over to 'tiff_to_stack' workflow
tieneupin Nov 5, 2024
fbc9fe5
Fixed broken database refreshes and updated logged messages
tieneupin Nov 6, 2024
8bcd8c0
Updated 'TIFFPreprocessingResult' Pydantic model to parse stringified…
tieneupin Nov 6, 2024
2cc5624
Completed function to register TIFF preprocessing results
tieneupin Nov 6, 2024
0811cba
Missed a return statement in 'except' block
tieneupin Nov 6, 2024
3fd77b4
Rewrote CLEM preprocessing results registration workflows to make use…
tieneupin Nov 6, 2024
93c08ea
Adjusted naming convention for LIF and TIFF processing functions and …
tieneupin Nov 6, 2024
9cd58ff
Replaced 'row' with 'session_row' to make variable more explicit
tieneupin Nov 7, 2024
befd420
Rewrote function to iterate over entry point names and run the match
tieneupin Nov 7, 2024
5397c0e
Missed a print statement
tieneupin Nov 7, 2024
fa6c5c8
Reverted entry point group name to 'murfey.workflows' to facilitate g…
tieneupin Nov 7, 2024
1a0057b
Optimised code logic for LIF and TIFF processing API endpoints
tieneupin Nov 7, 2024
feade10
Added 'requeue=False' argument when nacking message
tieneupin Nov 7, 2024
36efb6f
Fixed logic when reading rsync base path from machine config
tieneupin Nov 7, 2024
aafc518
Used 'entry_points' from 'backports.entry_points_selectable' instead
tieneupin Nov 8, 2024
cb1a570
Used 'entry_points' instance from 'backports.entry_points_selectable'…
tieneupin Nov 8, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,10 @@ murfey = "murfey.client:run"
[project.entry-points."murfey.config.extraction"]
"murfey_machine" = "murfey.util.config:get_extended_machine_config"
[project.entry-points."murfey.workflows"]
"lif_to_stack" = "murfey.workflows.lif_to_stack:zocalo_cluster_request"
"tiff_to_stack" = "murfey.workflows.tiff_to_stack:zocalo_cluster_request"
"process_raw_lifs" = "murfey.workflows.clem.process_raw_lifs:zocalo_cluster_request"
"process_raw_tiffs" = "murfey.workflows.clem.process_raw_tiffs:zocalo_cluster_request"
"register_lif_preprocessing_result" = "murfey.workflows.clem.register_preprocessing_results:register_lif_preprocessing_result"
"register_tiff_preprocessing_result" = "murfey.workflows.clem.register_preprocessing_results:register_tiff_preprocessing_result"

[tool.setuptools]
package-dir = {"" = "src"}
Expand Down
4 changes: 2 additions & 2 deletions src/murfey/client/contexts/clem.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ def process_lif_file(

try:
# Construct the URL to post the request to
url = f"{str(environment.url.geturl())}/sessions/{environment.murfey_session}/lif_to_stack?lif_file={quote(str(lif_file), safe='')}"
url = f"{str(environment.url.geturl())}/sessions/{environment.murfey_session}/clem/preprocessing/process_raw_lifs?lif_file={quote(str(lif_file), safe='')}"
# Validate
if not url:
logger.error(
Expand Down Expand Up @@ -442,7 +442,7 @@ def process_tiff_series(

try:
# Construct URL for Murfey server to communicate with
url = f"{str(environment.url.geturl())}/sessions/{environment.murfey_session}/tiff_to_stack"
url = f"{str(environment.url.geturl())}/sessions/{environment.murfey_session}/clem/preprocessing/process_raw_tiffs"
if not url:
logger.error(
"URL could not be constructed from the environment and file path"
Expand Down
23 changes: 23 additions & 0 deletions src/murfey/server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@
from murfey.server.ispyb import TransportManager # Session
except AttributeError:
pass
from backports.entry_points_selectable import entry_points
from importlib_metadata import EntryPoint # For type hinting only

import murfey.util.db as db
from murfey.util import LogFilter
from murfey.util.spa_params import default_spa_parameters
Expand Down Expand Up @@ -2964,6 +2967,26 @@ def feedback_callback(header: dict, message: dict) -> None:
if _transport_object:
_transport_object.transport.ack(header)
return None
elif (
message["register"] in entry_points().select(group="murfey.workflows").names
):
# Run the workflow if a match is found
workflow: EntryPoint = list( # Returns a list of either 1 or 0
entry_points().select(
group="murfey.workflows", name=message["register"]
)
)[0]
result = workflow.load()(
message=message,
db=murfey_db,
)
if _transport_object:
if result:
_transport_object.transport.ack(header)
else:
# Send it directly to DLQ without trying to rerun it
_transport_object.transport.nack(header, requeue=False)
return None
if _transport_object:
_transport_object.transport.nack(header, requeue=False)
return None
Expand Down
128 changes: 66 additions & 62 deletions src/murfey/server/api/clem.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from __future__ import annotations

import re
import sys
import traceback
from importlib.metadata import EntryPoint # type hinting only
from logging import getLogger
from pathlib import Path
from typing import Optional, Type, Union

from backports.entry_points_selectable import entry_points
from fastapi import APIRouter
from sqlalchemy.exc import NoResultFound
from sqlmodel import Session, select
Expand All @@ -22,13 +23,7 @@
CLEMTIFFFile,
)
from murfey.util.db import Session as MurfeySession
from murfey.util.models import TiffSeriesInfo

# Use backport from importlib_metadata for Python <3.10
if sys.version_info.major == 3 and sys.version_info.minor < 10:
from importlib_metadata import EntryPoint, entry_points
else:
from importlib.metadata import EntryPoint, entry_points
from murfey.util.models import TIFFSeriesInfo

# Set up logger
logger = getLogger("murfey.server.api.clem")
Expand Down Expand Up @@ -81,23 +76,15 @@ def validate_and_sanitise(
machine_config = get_machine_config(instrument_name=instrument_name)[
instrument_name
]
rsync_basepath = machine_config.rsync_basepath
try:
base_path = list(rsync_basepath.parents)[-2].as_posix()
except IndexError:
# Print to troubleshoot
logger.warning(f"Base path {rsync_basepath!r} is too short")
base_path = rsync_basepath.as_posix()
except Exception:
raise Exception("Unexpected exception occurred when loading the file base path")
base_path = machine_config.rsync_basepath.as_posix()

# Check that full file path doesn't contain unallowed characters
# Currently allows only:
# - words (alphanumerics and "_"; \w),
# - spaces (\s),
# - periods,
# - dashes,
# - forward slashes ("/")
# Currently allows only:
# - words (alphanumerics and "_"; \w),
# - spaces (\s),
# - periods,
# - dashes,
# - forward slashes ("/")
if bool(re.fullmatch(r"^[\w\s\.\-/]+$", str(full_path))) is False:
raise ValueError(f"Unallowed characters present in {file}")

Expand Down Expand Up @@ -631,51 +618,68 @@ def register_image_stack(
"""


@router.post("/sessions/{session_id}/lif_to_stack") # API posts to this URL
def lif_to_stack(
@router.post(
"/sessions/{session_id}/clem/preprocessing/process_raw_lifs"
) # API posts to this URL
def process_raw_lifs(
session_id: int, # Used by the decorator
lif_file: Path,
db: Session = murfey_db,
):
# Get command line entry point
murfey_workflows = entry_points().select(
group="murfey.workflows", name="lif_to_stack"
)

# Use entry point if found
if len(murfey_workflows) == 1:
workflow: EntryPoint = list(murfey_workflows)[0]
workflow.load()(
# Match the arguments found in murfey.workflows.lif_to_stack
file=lif_file,
root_folder="images",
messenger=_transport_object,
)
return True
# Raise error if Murfey workflow not found
else:
try:
# Try and load relevant Murfey workflow
workflow: EntryPoint = list(
entry_points().select(group="murfey.workflows", name="process_raw_lifs")
)[0]
except IndexError:
raise RuntimeError("The relevant Murfey workflow was not found")

# Get instrument name from the database to load the correct config file
session_row: MurfeySession = db.exec(
select(MurfeySession).where(MurfeySession.id == session_id)
).one()
instrument_name = session_row.instrument_name

# Pass arguments along to the correct workflow
workflow.load()(
# Match the arguments found in murfey.workflows.clem.process_raw_lifs
file=lif_file,
root_folder="images",
session_id=session_id,
instrument_name=instrument_name,
messenger=_transport_object,
)
return True


@router.post("/sessions/{session_id}/tiff_to_stack")
def tiff_to_stack(
@router.post("/sessions/{session_id}/clem/preprocessing/process_raw_tiffs")
def process_raw_tiffs(
session_id: int, # Used by the decorator
tiff_info: TiffSeriesInfo,
tiff_info: TIFFSeriesInfo,
db: Session = murfey_db,
):
# Get command line entry point
murfey_workflows = entry_points().select(
group="murfey.workflows", name="tiff_to_stack"
)

# Use entry point if found
if murfey_workflows:
workflow: EntryPoint = list(murfey_workflows)[0]
workflow.load()(
# Match the arguments found in murfey.workflows.tiff_to_stack
file=tiff_info.tiff_files[0], # Pass it only one file from the list
root_folder="images",
metadata=tiff_info.series_metadata,
messenger=_transport_object,
)
# Raise error if Murfey workflow not found
else:
try:
# Try and load relevant Murfey workflow
workflow: EntryPoint = list(
entry_points().select(group="murfey.workflows", name="process_raw_tiffs")
)[0]
except IndexError:
raise RuntimeError("The relevant Murfey workflow was not found")

# Get instrument name from the database to load the correct config file
session_row: MurfeySession = db.exec(
select(MurfeySession).where(MurfeySession.id == session_id)
).one()
instrument_name = session_row.instrument_name

# Pass arguments to correct workflow
workflow.load()(
# Match the arguments found in murfey.workflows.clem.process_raw_tiffs
tiff_list=tiff_info.tiff_files,
root_folder="images",
session_id=session_id,
instrument_name=instrument_name,
metadata=tiff_info.series_metadata,
messenger=_transport_object,
)
return True
3 changes: 0 additions & 3 deletions src/murfey/server/murfey_db.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from __future__ import annotations

import os
from functools import partial

import yaml
Expand All @@ -11,8 +10,6 @@

from murfey.util.config import Security, get_security_config

instrument_name = os.getenv("BEAMLINE", "")


def url(security_config: Security | None = None) -> str:
security_config = security_config or get_security_config()
Expand Down
9 changes: 6 additions & 3 deletions src/murfey/util/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,9 +269,12 @@ class CLEMImageSeries(SQLModel, table=True): # type: ignore
) # One to many

# Process checklist for series
images_aligned: bool = False # Image stacks aligned to reference image
rgbs_created: bool = False # Image stacks all colorised
composite_created: bool = False # Composite flattened image created
number_of_members: int = (
0 # Expected number of image stacks belonging to this series
)
images_aligned: bool = False # Have all members been aligned?
rgbs_created: bool = False # Have all members been colourised?
composite_created: bool = False # Has a composite image been created?
composite_image: Optional[str] = None # Full path to composite image


Expand Down
39 changes: 37 additions & 2 deletions src/murfey/util/models.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from __future__ import annotations

from ast import literal_eval
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional

from pydantic import BaseModel
from pydantic import BaseModel, validator

"""
General Models
Expand Down Expand Up @@ -154,12 +155,46 @@ class FractionationParameters(BaseModel):
"""


class TiffSeriesInfo(BaseModel):
class TIFFSeriesInfo(BaseModel):
series_name: str
tiff_files: List[Path]
series_metadata: Path


class LIFPreprocessingResult(BaseModel):
image_stack: Path
metadata: Path
series_name: str
channel: str
number_of_members: int
parent_lif: Path


class TIFFPreprocessingResult(BaseModel):
image_stack: Path
metadata: Path
series_name: str
channel: str
number_of_members: int
parent_tiffs: list[Path]

@validator(
"parent_tiffs",
pre=True,
)
def parse_stringified_list(cls, value):
if isinstance(value, str):
try:
eval_result = literal_eval(value)
if isinstance(eval_result, list):
parent_tiffs = [Path(p) for p in eval_result]
return parent_tiffs
except (SyntaxError, ValueError):
raise ValueError("Unable to parse input")
# Return value as-is; if it fails, it fails
return value


"""
FIB
===
Expand Down
Empty file.
Loading