Skip to content

Commit 62abcff

Browse files
authored
Support ICC computational clearing mode for CLEM datasets (#823)
* Use a TypeAlias to represent the full list of colour channel string Literals supported * Updated logic on how the clearing mode used is determined, and added support for the 'ICC' mode * Updated tests to consider the different CC suffixes present in the CLEM workflow * Replace denoising-related terms with 'computational clearing' to more accurately indicate what has happened with the dataset
1 parent 95769c0 commit 62abcff

2 files changed

Lines changed: 49 additions & 34 deletions

File tree

src/murfey/workflows/clem/register_preprocessing_results.py

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from functools import cached_property
1515
from importlib.metadata import entry_points
1616
from pathlib import Path
17-
from typing import Literal, Optional
17+
from typing import Literal, Optional, TypeAlias
1818

1919
from pydantic import BaseModel, computed_field
2020
from sqlmodel import Session, select
@@ -29,24 +29,24 @@
2929

3030
logger = logging.getLogger("murfey.workflows.clem.register_preprocessing_results")
3131

32+
ColorChannels: TypeAlias = Literal[
33+
"gray", "red", "green", "blue", "cyan", "magenta", "yellow"
34+
]
35+
36+
CC_MODES = ("_ICC", "_Lng_LVCC", "_Lng_SVCC")
37+
3238

3339
class CLEMPreprocessingResult(BaseModel):
3440
series_name: str
3541
number_of_members: int
3642
is_stack: bool
3743
is_montage: bool
38-
output_files: dict[
39-
Literal["gray", "red", "green", "blue", "cyan", "magenta", "yellow"], Path
40-
]
41-
thumbnails: dict[
42-
Literal["gray", "red", "green", "blue", "cyan", "magenta", "yellow"], Path
43-
] = {}
44+
output_files: dict[ColorChannels, Path]
45+
thumbnails: dict[ColorChannels, Path] = {}
4446
thumbnail_size: Optional[tuple[int, int]] = None # height, width
4547
metadata: Path
4648
parent_lif: Optional[Path] = None
47-
parent_tiffs: dict[
48-
Literal["gray", "red", "green", "blue", "cyan", "magenta", "yellow"], list[Path]
49-
] = {}
49+
parent_tiffs: dict[ColorChannels, list[Path]] = {}
5050
pixels_x: int
5151
pixels_y: int
5252
units: str
@@ -57,25 +57,37 @@ class CLEMPreprocessingResult(BaseModel):
5757
# Valid Pydantic decorator not supported by MyPy
5858
@computed_field # type: ignore
5959
@cached_property
60-
def is_denoised(self) -> bool:
60+
def is_cc(self) -> bool:
6161
"""
62-
The "_Lng_LVCC" and "_Lng_SVCC" suffixes appended to a CLEM dataset's position
63-
name indicate that it's a denoised image set of the same position. They should
64-
override or supersede the original ones if they're present
62+
The "_ICC", "_Lng_LVCC", and "_Lng_SVCC" suffixes appended to a CLEM dataset's
63+
position name indicate that it's a computationally cleared image set of the
64+
same position. They should override or supersede the original ones if present.
6565
"""
66-
return any(
67-
pattern in self.series_name for pattern in ("_Lng_LVCC", "_Lng_SVCC")
68-
)
66+
return any(self.series_name.endswith(pattern) for pattern in CC_MODES)
67+
68+
# Valid Pydantic decorator not supported by MyPy
69+
@computed_field # type: ignore
70+
@cached_property
71+
def cc_mode(self) -> str | None:
72+
"""
73+
Store the computational clearing mode used as an attribute
74+
"""
75+
for pattern in CC_MODES:
76+
if self.series_name.endswith(pattern):
77+
return pattern[1:]
78+
return None
6979

7080
# Valid Pydantic decorator not supported by MyPy
7181
@computed_field # type: ignore
7282
@cached_property
7383
def site_name(self) -> str:
7484
"""
75-
Extract just the name of the site by removing the "_Lng_LVCC" suffix from
85+
Extract just the name of the site by removing the clearing mode suffix from
7686
the series name.
7787
"""
78-
return self.series_name.replace("_Lng_LVCC", "").replace("_Lng_SVCC", "")
88+
if self.cc_mode is not None:
89+
return self.series_name[: -(len(self.cc_mode) + 1)]
90+
return self.series_name
7991

8092
# Valid Pydantic decorator not supported by MyPy
8193
@computed_field # type: ignore
@@ -123,7 +135,7 @@ def _register_clem_imaging_site(
123135
"""
124136
Creates an ImagingSite database entry for the current CLEM preprocessing result
125137
if one doesn't already exist, or modifies the existing one if it does. Each entry
126-
corresponds to a unique site on the sample grid, and results containing denoised
138+
corresponds to a unique site on the sample grid, and results containing cleared
127139
data will supersede existing rows for the same position that contain only raw
128140
data. Returns the created/queried entry.
129141
"""
@@ -186,8 +198,8 @@ def _populate(
186198
)
187199
clem_img_site = _populate(clem_img_site, result)
188200

189-
# Prepare to overwrite existing entry if current result is a denoised dataset
190-
if result.is_denoised:
201+
# Prepare to overwrite existing entry if current result is a cleared dataset
202+
if result.is_cc:
191203
# Proceed with overwrite if current result is different from existing entry
192204
output_file = list(result.output_files.values())[0]
193205
if str(output_file.parent / "*.tiff") != clem_img_site.image_path:

tests/workflows/clem/test_register_preprocessing_results.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def generate_preprocessing_messages(
3737
rsync_basepath: Path,
3838
session_id: int,
3939
colors: list[str],
40+
denoising_suffix: str,
4041
):
4142
# Make directory to where data for current grid is stored
4243
visit_dir = rsync_basepath / "2020" / visit_name
@@ -86,7 +87,7 @@ def generate_preprocessing_messages(
8687
datasets.extend(
8788
[
8889
(
89-
grid_dir / "TileScan 1" / f"Position {n + 1}_Lng_LVCC",
90+
grid_dir / "TileScan 1" / f"Position {n + 1}{denoising_suffix}",
9091
True,
9192
False,
9293
(2048, 2048),
@@ -286,6 +287,7 @@ def test_run(
286287
rsync_basepath=rsync_basepath,
287288
session_id=ExampleVisit.murfey_session_id,
288289
colors=colors,
290+
denoising_suffix="_Lng_LVCC",
289291
)
290292
for message in preprocessing_messages:
291293
result = run(
@@ -305,13 +307,13 @@ def test_run(
305307
@pytest.mark.parametrize(
306308
"test_params",
307309
(
308-
# Reverse list order? | Colors
309-
(False, ["gray"]),
310-
(True, ["gray"]),
311-
(False, ["red", "green", "blue"]),
312-
(True, ["cyan", "magenta", "yellow"]),
313-
(False, ["gray", "red", "green", "blue"]),
314-
(True, ["gray", "cyan", "magenta", "yellow"]),
310+
# Reverse list order? | Colors | Denoising suffix
311+
(False, ["gray"], "_Lng_LVCC"),
312+
(True, ["gray"], "_Lng_SVCC"),
313+
(False, ["red", "green", "blue"], "_ICC"),
314+
(True, ["cyan", "magenta", "yellow"], "_Lng_LVCC"),
315+
(False, ["gray", "red", "green", "blue"], "_Lng_SVCC"),
316+
(True, ["gray", "cyan", "magenta", "yellow"], "_ICC"),
315317
),
316318
)
317319
def test_run_with_db(
@@ -320,10 +322,10 @@ def test_run_with_db(
320322
mock_ispyb_credentials,
321323
murfey_db_session: SQLModelSession,
322324
ispyb_db_session: SQLAlchemySession,
323-
test_params: tuple[bool, list[str]],
325+
test_params: tuple[bool, list[str], str],
324326
):
325327
# Unpack test params
326-
(shuffle_message, colors) = test_params
328+
(shuffle_message, colors, denoising_suffix) = test_params
327329

328330
# Create a session to insert for this test
329331
murfey_session: MurfeyDB.Session = get_or_create_db_entry(
@@ -381,6 +383,7 @@ def test_run_with_db(
381383
rsync_basepath=rsync_basepath,
382384
session_id=murfey_session.id,
383385
colors=colors,
386+
denoising_suffix=denoising_suffix,
384387
)
385388
if shuffle_message:
386389
preprocessing_messages.reverse()
@@ -465,8 +468,8 @@ def test_run_with_db(
465468
)
466469
assert len(ispyb_gs_search) == (len(preprocessing_messages) - 2) // 2
467470
for gs in ispyb_gs_search:
468-
# Check that all entries point to the denoised images ("_Lng_LVCC")
469-
assert gs.gridSquareImage is not None and "_Lng_LVCC" in gs.gridSquareImage
471+
# Check that all entries point to the denoised images
472+
assert gs.gridSquareImage is not None and denoising_suffix in gs.gridSquareImage
470473

471474
# Check that the GridSquare color flags and collection mode are set correctly
472475
for flag, value in color_flags.items():

0 commit comments

Comments
 (0)