Skip to content

Commit 0187914

Browse files
authored
CLEM workflow fixes (#784)
* Fixed broken logic when registering align-and-merge processing results * Do not pass align-and-merge processing parameters via Murfey; set them directly in the recipe * Fixed broken tests and added test for the align-and-merge registration workflow
1 parent fe9abef commit 0187914

6 files changed

Lines changed: 165 additions & 57 deletions

File tree

src/murfey/util/processing_params.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from datetime import datetime
22
from functools import lru_cache
33
from pathlib import Path
4-
from typing import Literal, Optional
54

65
from pydantic import BaseModel
76
from werkzeug.utils import secure_filename
@@ -60,12 +59,6 @@ class CLEMProcessingParameters(BaseModel):
6059
# Atlas vs GridSquare registration threshold
6160
atlas_threshold: float = 0.0015 # in m
6261

63-
# Image alignment and merging-specific parameters
64-
crop_to_n_frames: Optional[int] = 50
65-
align_self: Literal["enabled", ""] = "enabled"
66-
flatten: Literal["mean", "min", "max", ""] = "mean"
67-
align_across: Literal["enabled", ""] = "enabled"
68-
6962

7063
default_clem_processing_parameters = CLEMProcessingParameters()
7164

src/murfey/workflows/clem/align_and_merge.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from __future__ import annotations
77

88
from pathlib import Path
9-
from typing import Literal, Optional
9+
from typing import Optional
1010

1111
from murfey.util.config import get_machine_config
1212

@@ -24,11 +24,6 @@ def run(
2424
series_name: str,
2525
images: list[Path],
2626
metadata: Path,
27-
# Optional processing parameters
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", ""] = "",
3227
# Optional session parameters
3328
messenger: Optional[TransportManager] = None,
3429
):
@@ -65,10 +60,6 @@ def run(
6560
"series_name": series_name,
6661
"images": [str(file) for file in images],
6762
"metadata": str(metadata),
68-
"crop_to_n_frames": crop_to_n_frames,
69-
"align_self": align_self,
70-
"flatten": flatten,
71-
"align_across": align_across,
7263
# Other recipe parameters
7364
"session_dir": str(session_dir),
7465
"session_id": session_id,

src/murfey/workflows/clem/register_align_and_merge_results.py

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
import logging
55
import traceback
66
from ast import literal_eval
7+
from functools import cached_property
78
from pathlib import Path
89
from typing import Optional
910

10-
from pydantic import BaseModel, field_validator
11+
from pydantic import BaseModel, computed_field, field_validator
1112
from sqlmodel import Session, select
1213

1314
from murfey.util.db import ImagingSite
@@ -25,6 +26,27 @@ class AlignAndMergeResult(BaseModel):
2526
thumbnail: Optional[Path] = None
2627
thumbnail_size: Optional[tuple[int, int]] = None
2728

29+
# Valid Pydantic decorator not supported by MyPy
30+
@computed_field # type: ignore
31+
@cached_property
32+
def is_denoised(self) -> bool:
33+
"""
34+
The "_Lng_LVCC" suffix appended to a CLEM dataset's position name indicates
35+
that it's a denoised image set of the same position. These results should
36+
override or supersede the original ones once they're available.
37+
"""
38+
return "_Lng_LVCC" in self.series_name
39+
40+
# Valid Pydantic decorator not supported by MyPy
41+
@computed_field # type: ignore
42+
@cached_property
43+
def site_name(self) -> str:
44+
"""
45+
Extract just the name of the site by removing the "_Lng_LVCC" suffix from
46+
the series name.
47+
"""
48+
return self.series_name.replace("_Lng_LVCC", "")
49+
2850
@field_validator("image_stacks", mode="before")
2951
@classmethod
3052
def parse_stringified_list(cls, value):
@@ -78,26 +100,36 @@ def run(message: dict, murfey_db: Session) -> dict[str, bool]:
78100

79101
# Outer try-finally block for tidying up database-related section of function
80102
try:
81-
# Register items in database if not already present
82103
try:
83-
if not (
84-
clem_img_series := murfey_db.exec(
85-
select(ImagingSite)
86-
.where(ImagingSite.session_id == session_id)
87-
.where(ImagingSite.site_name == result.series_name)
88-
).one_or_none()
89-
):
90-
clem_img_series = ImagingSite(
91-
session_id=session_id, series_name=result.series_name
104+
clem_img_site = murfey_db.exec(
105+
select(ImagingSite)
106+
.where(ImagingSite.session_id == session_id)
107+
.where(ImagingSite.site_name == result.site_name)
108+
).one()
109+
110+
# Update the stored entry only if the incoming one matches it
111+
if clem_img_site.image_path is not None and (
112+
# Denoised dataset results should be registered regardless
113+
result.is_denoised
114+
or (
115+
# Raw dataset result should only be considered
116+
# If the current entry is also a raw dataset
117+
not result.is_denoised
118+
and "_Lng_LVCC" not in clem_img_site.image_path
92119
)
93-
clem_img_series.composite_created = True
94-
murfey_db.add(clem_img_series)
95-
murfey_db.commit()
120+
):
121+
clem_img_site.composite_created = True
122+
murfey_db.add(clem_img_site)
123+
murfey_db.commit()
96124

97-
logger.info(
98-
"Align-and-merge processing result registered for "
99-
f"{result.series_name!r} series"
100-
)
125+
logger.info(
126+
"Align-and-merge processing result registered for "
127+
f"{result.series_name!r} series"
128+
)
129+
else:
130+
logger.info(
131+
"Skipping database registration as incoming result doesn't match stored entry"
132+
)
101133

102134
except Exception:
103135
logger.error(

src/murfey/workflows/clem/register_preprocessing_results.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -658,10 +658,6 @@ def run(message: dict, murfey_db: Session) -> dict[str, bool]:
658658
series_name=result.series_name,
659659
images=image_combo,
660660
metadata=result.metadata,
661-
crop_to_n_frames=processing_params.crop_to_n_frames,
662-
align_self=processing_params.align_self,
663-
flatten=processing_params.flatten,
664-
align_across=processing_params.align_across,
665661
messenger=_transport_object,
666662
)
667663
except Exception:

tests/workflows/clem/test_align_and_merge.py

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from pathlib import Path
2-
from typing import Literal
3-
from unittest.mock import MagicMock, patch
2+
from unittest.mock import MagicMock
43

54
import pytest
5+
from pytest_mock import MockerFixture
66

77
from murfey.server.ispyb import TransportManager
88
from murfey.util.config import MachineConfig
@@ -23,12 +23,6 @@
2323
]
2424
feedback_queue = "murfey_feedback"
2525

26-
# Align and merge settings
27-
crop_to_n_frames = 20
28-
align_self: Literal["enabled", ""] = "enabled"
29-
flatten: Literal["mean", "min", "max", ""] = "max"
30-
align_across: Literal["enabled", ""] = "enabled"
31-
3226

3327
@pytest.fixture
3428
def processed_dir(tmp_path: Path):
@@ -62,9 +56,8 @@ def metadata(processed_dir: Path):
6256
return metadata
6357

6458

65-
@patch("murfey.workflows.clem.align_and_merge.get_machine_config")
6659
def test_run(
67-
mock_get_machine_config,
60+
mocker: MockerFixture,
6861
image_stacks: list[Path],
6962
metadata: Path,
7063
processed_dir: Path,
@@ -81,6 +74,9 @@ def test_run(
8174
# Construct a mock MachineConfig object for use within the function
8275
mock_machine_config = MagicMock(spec=MachineConfig)
8376
mock_machine_config.processed_directory_name = processed_folder
77+
mock_get_machine_config = mocker.patch(
78+
"murfey.workflows.clem.align_and_merge.get_machine_config"
79+
)
8480
mock_get_machine_config.return_value = {
8581
instrument_name: mock_machine_config,
8682
}
@@ -92,10 +88,6 @@ def test_run(
9288
series_name=series_name_long,
9389
images=image_stacks,
9490
metadata=metadata,
95-
crop_to_n_frames=crop_to_n_frames,
96-
align_self=align_self,
97-
flatten=flatten,
98-
align_across=align_across,
9991
messenger=mock_transport,
10092
)
10193

@@ -107,10 +99,6 @@ def test_run(
10799
"series_name": series_name_long,
108100
"images": [str(file) for file in image_stacks],
109101
"metadata": str(metadata),
110-
"crop_to_n_frames": crop_to_n_frames,
111-
"align_self": align_self,
112-
"flatten": flatten,
113-
"align_across": align_across,
114102
# Other recipe parameters
115103
"session_dir": str(processed_dir.parent),
116104
"session_id": session_id,
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
from pathlib import Path
2+
3+
import pytest
4+
from sqlmodel import Session as SQLModelSession, select
5+
6+
import murfey.util.db as MurfeyDB
7+
from murfey.workflows.clem.register_align_and_merge_results import run
8+
from tests.conftest import ExampleVisit, get_or_create_db_entry
9+
10+
session_id = ExampleVisit.murfey_session_id + 1
11+
visit_name = f"{ExampleVisit.proposal_code}{ExampleVisit.proposal_number}-{ExampleVisit.visit_number}"
12+
processed_dir_name = "processed"
13+
project_name = "Grid_1"
14+
15+
16+
@pytest.mark.parametrize(
17+
"test_params",
18+
[ # Registered data | Incoming data
19+
["_Lng_LVCC", ""],
20+
["_Lng_LVCC", "_Lng_LVCC"],
21+
["", ""],
22+
["", "_Lng_LVCC"],
23+
],
24+
)
25+
def test_run(
26+
test_params: tuple[str, str], murfey_db_session: SQLModelSession, tmp_path: Path
27+
):
28+
# Unpack test params
29+
registered_type, incoming_type = test_params
30+
31+
# Create a session to insert for this test
32+
murfey_session: MurfeyDB.Session = get_or_create_db_entry(
33+
murfey_db_session,
34+
MurfeyDB.Session,
35+
lookup_kwargs={
36+
"id": session_id,
37+
"name": visit_name,
38+
"visit": visit_name,
39+
"instrument_name": ExampleVisit.instrument_name,
40+
},
41+
)
42+
43+
# Create an ImagingSite entry using the existing values
44+
registered_position_name = "Position_1" + registered_type
45+
image_path = (
46+
tmp_path
47+
/ visit_name
48+
/ "processed"
49+
/ project_name
50+
/ "TileScan_1"
51+
/ registered_position_name
52+
/ "*.tiff"
53+
)
54+
registered_series_name = f"{project_name}--TileScan_1--{registered_position_name}"
55+
site_name = registered_series_name.rstrip(registered_type)
56+
get_or_create_db_entry(
57+
murfey_db_session,
58+
MurfeyDB.ImagingSite,
59+
lookup_kwargs={
60+
"session_id": murfey_session.id,
61+
"site_name": site_name,
62+
"image_path": str(image_path),
63+
},
64+
)
65+
66+
# Create the incoming message
67+
incoming_position_name = "Position_1" + incoming_type
68+
incoming_series_name = f"{project_name}--TileScan_1--{incoming_position_name}"
69+
# The site names should match
70+
assert site_name == incoming_series_name.rstrip(incoming_type)
71+
72+
message = {
73+
"session_id": murfey_session.id,
74+
"result": {
75+
"series_name": incoming_series_name,
76+
"image_stacks": [],
77+
"align_self": None,
78+
"flatten": None,
79+
"align_across": None,
80+
"output_file": tmp_path / "dummy",
81+
"thumbnail": None,
82+
"thumbnail_size": None,
83+
},
84+
}
85+
86+
# Run the function and check that the expected values were created
87+
result = run(message, murfey_db_session)
88+
assert result["success"]
89+
90+
imaging_site = murfey_db_session.exec(
91+
select(MurfeyDB.ImagingSite)
92+
.where(MurfeyDB.ImagingSite.session_id == murfey_session.id)
93+
.where(
94+
MurfeyDB.ImagingSite.site_name == incoming_series_name.rstrip(incoming_type)
95+
)
96+
).one()
97+
98+
# Check that 'composite_created' is updated correctly
99+
if (
100+
# Both data types match
101+
(registered_type and incoming_type)
102+
or (not registered_type and not incoming_type)
103+
# Registered type is raw data, while incoming is denoised
104+
or (not registered_type and incoming_type)
105+
):
106+
assert imaging_site.composite_created
107+
else:
108+
assert not imaging_site.composite_created

0 commit comments

Comments
 (0)