Skip to content

Commit 67e9322

Browse files
committed
Merged recent changes from 'main' branch
2 parents 3742c71 + 42a774a commit 67e9322

File tree

9 files changed

+194
-34
lines changed

9 files changed

+194
-34
lines changed

src/murfey/cli/inject_spa_processing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
SPAFeedbackParameters,
2222
SPARelionParameters,
2323
)
24-
from murfey.util.spa_params import default_spa_parameters
24+
from murfey.util.processing_params import default_spa_parameters
2525

2626

2727
def run():

src/murfey/server/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
get_machine_config,
5757
get_microscope,
5858
)
59-
from murfey.util.spa_params import default_spa_parameters
59+
from murfey.util.processing_params import default_spa_parameters
6060
from murfey.util.state import global_state
6161

6262
try:

src/murfey/server/api/__init__.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@
105105
TiltSeriesInfo,
106106
Visit,
107107
)
108-
from murfey.util.spa_params import default_spa_parameters
108+
from murfey.util.processing_params import default_spa_parameters
109109
from murfey.util.state import global_state
110110

111111
log = logging.getLogger("murfey.server.api")
@@ -1293,7 +1293,31 @@ def suggest_path(
12931293
)
12941294
if not machine_config.rsync_basepath:
12951295
raise ValueError("No rsync basepath set")
1296+
1297+
# Construct the full path to where the dataset is to be saved
12961298
check_path = machine_config.rsync_basepath / base_path
1299+
1300+
# Check previous year to account for the year rolling over during data collection
1301+
if not check_path.exists():
1302+
base_path_parts = base_path.split("/")
1303+
for part in base_path_parts:
1304+
# Find the path part corresponding to the year
1305+
if len(part) == 4 and part.isdigit():
1306+
year_idx = base_path_parts.index(part)
1307+
base_path_parts[year_idx] = str(int(part) - 1)
1308+
base_path = "/".join(base_path_parts)
1309+
check_path_prev = check_path
1310+
check_path = machine_config.rsync_basepath / base_path
1311+
1312+
# If it's not in the previous year either, it's a genuine error
1313+
if not check_path.exists():
1314+
log_message = (
1315+
"Unable to find current visit folder under "
1316+
f"{str(check_path_prev)!r} or {str(check_path)!r}"
1317+
)
1318+
log.error(log_message)
1319+
raise FileNotFoundError(log_message)
1320+
12971321
check_path_name = check_path.name
12981322
while check_path.exists():
12991323
count = count + 1 if count else 2
@@ -1490,6 +1514,26 @@ async def process_gain(
14901514
/ secure_filename(visit_name)
14911515
/ machine_config.gain_directory_name
14921516
)
1517+
1518+
# Check under previous year if the folder doesn't exist
1519+
if not filepath.exists():
1520+
filepath_prev = filepath
1521+
filepath = (
1522+
Path(machine_config.rsync_basepath)
1523+
/ (machine_config.rsync_module or "data")
1524+
/ str(datetime.datetime.now().year - 1)
1525+
/ secure_filename(visit_name)
1526+
/ machine_config.gain_directory_name
1527+
)
1528+
# If it's not in the previous year, it's a genuine error
1529+
if not filepath.exists():
1530+
log_message = (
1531+
"Unable to find gain reference directory under "
1532+
f"{str(filepath_prev)!r} or {str(filepath)}"
1533+
)
1534+
log.error(log_message)
1535+
raise FileNotFoundError(log_message)
1536+
14931537
if gain_reference_params.eer:
14941538
new_gain_ref, new_gain_ref_superres = await prepare_eer_gain(
14951539
filepath / safe_path_name,

src/murfey/server/api/clem.py

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
import re
44
import traceback
5+
from ast import literal_eval
56
from importlib.metadata import EntryPoint # type hinting only
67
from logging import getLogger
78
from pathlib import Path
8-
from typing import Optional, Type, Union
9+
from typing import Literal, Optional, Type, Union
910

1011
from backports.entry_points_selectable import entry_points
1112
from fastapi import APIRouter
13+
from pydantic import BaseModel, validator
1214
from sqlalchemy.exc import NoResultFound
1315
from sqlmodel import Session, select
1416

@@ -23,7 +25,6 @@
2325
CLEMTIFFFile,
2426
)
2527
from murfey.util.db import Session as MurfeySession
26-
from murfey.util.models import TIFFSeriesInfo
2728

2829
# Set up logger
2930
logger = getLogger("murfey.server.api.clem")
@@ -624,7 +625,7 @@ def register_image_stack(
624625
"/sessions/{session_id}/clem/preprocessing/process_raw_lifs"
625626
) # API posts to this URL
626627
def process_raw_lifs(
627-
session_id: int, # Used by the decorator
628+
session_id: int,
628629
lif_file: Path,
629630
db: Session = murfey_db,
630631
):
@@ -656,9 +657,15 @@ def process_raw_lifs(
656657
return True
657658

658659

660+
class TIFFSeriesInfo(BaseModel):
661+
series_name: str
662+
tiff_files: list[Path]
663+
series_metadata: Path
664+
665+
659666
@router.post("/sessions/{session_id}/clem/preprocessing/process_raw_tiffs")
660667
def process_raw_tiffs(
661-
session_id: int, # Used by the decorator
668+
session_id: int,
662669
tiff_info: TIFFSeriesInfo,
663670
db: Session = murfey_db,
664671
):
@@ -689,3 +696,72 @@ def process_raw_tiffs(
689696
messenger=_transport_object,
690697
)
691698
return True
699+
700+
701+
class AlignAndMergeParams(BaseModel):
702+
# Processing parameters
703+
series_name: str
704+
images: list[Path]
705+
metadata: Path
706+
# Optional processing parameters
707+
crop_to_n_frames: Optional[int] = None
708+
align_self: Literal["enabled", ""] = ""
709+
flatten: Literal["mean", "min", "max", ""] = ""
710+
align_across: Literal["enabled", ""] = ""
711+
712+
@validator(
713+
"images",
714+
pre=True,
715+
)
716+
def parse_stringified_list(cls, value):
717+
if isinstance(value, str):
718+
try:
719+
eval_result = literal_eval(value)
720+
if isinstance(eval_result, list):
721+
parent_tiffs = [Path(p) for p in eval_result]
722+
return parent_tiffs
723+
except (SyntaxError, ValueError):
724+
raise ValueError("Unable to parse input")
725+
# Return value as-is; if it fails, it fails
726+
return value
727+
728+
729+
@router.post("/sessions/{session_id}/clem/processing/align_and_merge_stacks")
730+
def align_and_merge_stacks(
731+
session_id: int,
732+
align_and_merge_params: AlignAndMergeParams,
733+
db: Session = murfey_db,
734+
):
735+
try:
736+
# Try and load relevant Murfey workflow
737+
workflow: EntryPoint = list(
738+
entry_points().select(group="murfey.workflows", name="clem.align_and_merge")
739+
)[0]
740+
except IndexError:
741+
raise RuntimeError("The relevant Murfey workflow was not found")
742+
743+
# Get instrument name from the database to load the correct config file
744+
session_row: MurfeySession = db.exec(
745+
select(MurfeySession).where(MurfeySession.id == session_id)
746+
).one()
747+
instrument_name = session_row.instrument_name
748+
749+
# Pass arguments to correct workflow
750+
workflow.load()(
751+
# Match the arguments found in murfey.workflows.clem.align_and_merge
752+
# Session parameters
753+
session_id=session_id,
754+
instrument_name=instrument_name,
755+
# Processing parameters
756+
series_name=align_and_merge_params.series_name,
757+
images=align_and_merge_params.images,
758+
metadata=align_and_merge_params.metadata,
759+
# Optional processing parameters
760+
crop_to_n_frames=align_and_merge_params.crop_to_n_frames,
761+
align_self=align_and_merge_params.align_self,
762+
flatten=align_and_merge_params.flatten,
763+
align_across=align_and_merge_params.align_across,
764+
# Optional session parameters
765+
messenger=_transport_object,
766+
)
767+
return True

src/murfey/server/demo_api.py

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
TiltSeriesInfo,
9191
Visit,
9292
)
93-
from murfey.util.spa_params import default_spa_parameters
93+
from murfey.util.processing_params import default_spa_parameters
9494
from murfey.util.state import global_state
9595

9696
log = logging.getLogger("murfey.server.demo_api")
@@ -110,7 +110,7 @@ class Settings(BaseSettings):
110110

111111
settings = Settings()
112112

113-
machine_config: dict = {}
113+
machine_config: dict[str, MachineConfig] = {}
114114
if settings.murfey_machine_configuration:
115115
microscope = get_microscope()
116116
machine_config = machine_config_from_file(
@@ -1292,12 +1292,46 @@ def suggest_path(
12921292
instrument_name = (
12931293
db.exec(select(Session).where(Session.id == session_id)).one().instrument_name
12941294
)
1295-
check_path = (
1296-
machine_config[instrument_name].rsync_basepath / params.base_path
1295+
rsync_basepath = (
1296+
machine_config[instrument_name].rsync_basepath
12971297
if machine_config
1298-
else Path(f"/dls/{get_microscope()}") / params.base_path
1298+
else Path(f"/dls/{get_microscope()}")
12991299
)
1300+
check_path = rsync_basepath / params.base_path
13001301
check_path = check_path.parent / f"{check_path.stem}{count}{check_path.suffix}"
1302+
check_path = check_path.resolve()
1303+
1304+
# Check for path traversal attempt
1305+
if not str(check_path).startswith(str(rsync_basepath)):
1306+
raise Exception(f"Path traversal attempt detected: {str(check_path)!r}")
1307+
1308+
# Check previous year to account for the year rolling over during data collection
1309+
if not sanitise_path(check_path).exists():
1310+
base_path_parts = list(params.base_path.parts)
1311+
for part in base_path_parts:
1312+
# Find the path part corresponding to the year
1313+
if len(part) == 4 and part.isdigit():
1314+
year_idx = base_path_parts.index(part)
1315+
base_path_parts[year_idx] = str(int(part) - 1)
1316+
base_path = "/".join(base_path_parts)
1317+
check_path_prev = check_path
1318+
check_path = rsync_basepath / base_path
1319+
check_path = check_path.parent / f"{check_path.stem}{count}{check_path.suffix}"
1320+
check_path = check_path.resolve()
1321+
1322+
# Check for path traversal attempt
1323+
if not str(check_path).startswith(str(rsync_basepath)):
1324+
raise Exception(f"Path traversal attempt detected: {str(check_path)!r}")
1325+
1326+
# If visit is not in the previous year either, it's a genuine error
1327+
if not check_path.exists():
1328+
log_message = sanitise(
1329+
"Unable to find current visit folder under "
1330+
f"{str(check_path_prev)!r} or {str(check_path)!r}"
1331+
)
1332+
log.error(log_message)
1333+
raise FileNotFoundError(log_message)
1334+
13011335
check_path_name = check_path.name
13021336
while sanitise_path(check_path).exists():
13031337
count = count + 1 if count else 2

src/murfey/util/models.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -147,19 +147,6 @@ class FractionationParameters(BaseModel):
147147
fractionation_file_name: str = "eer_fractionation.txt"
148148

149149

150-
"""
151-
Cryo-CLEM
152-
=========
153-
Models related to the cryo-CLEM workflow.
154-
"""
155-
156-
157-
class TIFFSeriesInfo(BaseModel):
158-
series_name: str
159-
tiff_files: List[Path]
160-
series_metadata: Path
161-
162-
163150
"""
164151
FIB
165152
===
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
1+
from typing import Literal, Optional
2+
13
from pydantic import BaseModel
24

35

6+
class CLEMAlignAndMergeParameters(BaseModel):
7+
crop_to_n_frames: Optional[int] = 50
8+
align_self: Literal["enabled", ""] = "enabled"
9+
flatten: Literal["mean", "min", "max", ""] = "mean"
10+
align_across: Literal["enabled", ""] = "enabled"
11+
12+
13+
default_clem_align_and_merge_parameters = CLEMAlignAndMergeParameters()
14+
15+
416
class SPAParameters(BaseModel):
517
nr_iter_2d: int = 25
618
nr_iter_3d: int = 25

src/murfey/workflows/clem/align_and_merge.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ def submit_cluster_request(
2525
images: list[Path],
2626
metadata: Path,
2727
# Optional processing parameters
28-
align_self: Optional[str] = None,
29-
flatten: Optional[Literal["min", "max", "mean"]] = "mean",
30-
align_across: Optional[str] = None,
28+
crop_to_n_frames: Optional[int] = None,
29+
align_self: Literal["enabled", ""] = "",
30+
flatten: Literal["mean", "min", "max", ""] = "mean",
31+
align_across: Literal["enabled", ""] = "",
3132
# Optional session parameters
3233
messenger: Optional[TransportManager] = None,
3334
):
@@ -64,6 +65,7 @@ def submit_cluster_request(
6465
"series_name": series_name,
6566
"images": [str(file) for file in images],
6667
"metadata": str(metadata),
68+
"crop_to_n_frames": crop_to_n_frames,
6769
"align_self": align_self,
6870
"flatten": flatten,
6971
"align_across": align_across,

src/murfey/workflows/clem/register_preprocessing_results.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525
CLEMTIFFFile,
2626
)
2727
from murfey.util.db import Session as MurfeySession
28+
from murfey.util.processing_params import (
29+
default_clem_align_and_merge_parameters as processing_params,
30+
)
2831
from murfey.workflows.clem import get_db_entry
2932
from murfey.workflows.clem.align_and_merge import submit_cluster_request
3033

@@ -187,9 +190,10 @@ def register_lif_preprocessing_result(
187190
series_name=result.series_name,
188191
images=image_stacks,
189192
metadata=result.metadata,
190-
align_self=None,
191-
flatten="mean",
192-
align_across=None,
193+
crop_to_n_frames=processing_params.crop_to_n_frames,
194+
align_self=processing_params.align_self,
195+
flatten=processing_params.flatten,
196+
align_across=processing_params.align_across,
193197
messenger=_transport_object,
194198
)
195199
if cluster_response is False:
@@ -369,9 +373,10 @@ def register_tiff_preprocessing_result(
369373
series_name=result.series_name,
370374
images=image_stacks,
371375
metadata=result.metadata,
372-
align_self=None,
373-
flatten="mean",
374-
align_across=None,
376+
crop_to_n_frames=processing_params.crop_to_n_frames,
377+
align_self=processing_params.align_self,
378+
flatten=processing_params.flatten,
379+
align_across=processing_params.align_across,
375380
messenger=_transport_object,
376381
)
377382
if cluster_response is False:

0 commit comments

Comments
 (0)