Skip to content

Commit 383823a

Browse files
Register CLEM colours (#739)
* Added logic to optionally insert collection mode and colour flags for the Atlas and GridSquare tables in ISPyB for the CLEM workflow * Added column to CLEMImageSeries table to keep track of collection mode used for dataset * Register colours and collection mode in CLEMImageSeries table upon arrival of message, then translate it across to Atlas and GridSquare tables in ISPyB * Fixed error with calculating the location of ROIs on the atlas image --------- Co-authored-by: Daniel Hatton <daniel.hatton@diamond.ac.uk>
1 parent ad961f2 commit 383823a

9 files changed

Lines changed: 305 additions & 62 deletions

src/murfey/server/ispyb.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,13 +186,21 @@ def do_update_atlas(
186186
atlas_image: str,
187187
pixel_size: float,
188188
slot: int | None,
189+
collection_mode: str | None = None,
190+
color_flags: dict[str, str | int] | None = None,
189191
):
192+
color_flags = color_flags or {}
190193
try:
191194
with ISPyBSession() as db:
192195
atlas = db.query(Atlas).filter(Atlas.atlasId == atlas_id).one()
193196
atlas.atlasImage = atlas_image or atlas.atlasImage
194197
atlas.pixelSize = pixel_size or atlas.pixelSize
195198
atlas.cassetteSlot = slot or atlas.cassetteSlot
199+
atlas.mode = collection_mode or atlas.mode
200+
# Optionally insert colour flags if present
201+
if color_flags:
202+
for col_name, value in color_flags.items():
203+
setattr(atlas, col_name, value)
196204
db.add(atlas)
197205
db.commit()
198206
return {"success": True, "return_value": atlas.atlasId}
@@ -209,7 +217,9 @@ def do_insert_grid_square(
209217
atlas_id: int,
210218
grid_square_id: int,
211219
grid_square_parameters: GridSquareParameters,
220+
color_flags: dict[str, int] | None = None,
212221
):
222+
color_flags = color_flags or {}
213223
# most of this is for mypy
214224
if (
215225
grid_square_parameters.pixel_size is not None
@@ -234,7 +244,12 @@ def do_insert_grid_square(
234244
stageLocationX=grid_square_parameters.x_stage_position,
235245
stageLocationY=grid_square_parameters.y_stage_position,
236246
pixelSize=grid_square_parameters.pixel_size,
247+
mode=grid_square_parameters.collection_mode,
237248
)
249+
# Optionally insert colour flags
250+
if color_flags:
251+
for col_name, value in color_flags.items():
252+
setattr(record, col_name, value)
238253
try:
239254
with ISPyBSession() as db:
240255
db.add(record)
@@ -250,8 +265,12 @@ def do_insert_grid_square(
250265
return {"success": False, "return_value": None}
251266

252267
def do_update_grid_square(
253-
self, grid_square_id: int, grid_square_parameters: GridSquareParameters
268+
self,
269+
grid_square_id: int,
270+
grid_square_parameters: GridSquareParameters,
271+
color_flags: dict[str, int] | None = None,
254272
):
273+
color_flags = color_flags or {}
255274
try:
256275
with ISPyBSession() as db:
257276
grid_square: GridSquare = (
@@ -290,6 +309,12 @@ def do_update_grid_square(
290309
grid_square.stageLocationY = grid_square_parameters.y_stage_position
291310
if grid_square_parameters.pixel_size:
292311
grid_square.pixelSize = grid_square_parameters.pixel_size
312+
if grid_square_parameters.collection_mode:
313+
grid_square.mode = grid_square_parameters.collection_mode
314+
# Optionally insert colour flags
315+
if color_flags:
316+
for col_name, value in color_flags.items():
317+
setattr(grid_square, col_name, value)
293318
db.add(grid_square)
294319
db.commit()
295320
return {"success": True, "return_value": grid_square.gridSquareId}

src/murfey/util/db.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,14 @@ class CLEMImageSeries(SQLModel, table=True): # type: ignore
294294
sa_relationship_kwargs={"cascade": "delete"},
295295
) # One to many
296296
number_of_members: Optional[int] = Field(default=None)
297+
has_grey: Optional[bool] = Field(default=None)
298+
has_red: Optional[bool] = Field(default=None)
299+
has_green: Optional[bool] = Field(default=None)
300+
has_blue: Optional[bool] = Field(default=None)
301+
has_cyan: Optional[bool] = Field(default=None)
302+
has_magenta: Optional[bool] = Field(default=None)
303+
has_yellow: Optional[bool] = Field(default=None)
304+
collection_mode: Optional[str] = Field(default=None)
297305

298306
# Shape and resolution information
299307
image_pixels_x: Optional[int] = Field(default=None)

src/murfey/util/models.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,9 @@ class GridSquareParameters(BaseModel):
157157
pixel_size: Optional[float] = None
158158
angle: Optional[float] = None
159159

160+
# Collection mode
161+
collection_mode: Optional[str] = None
162+
160163

161164
class FoilHoleParameters(BaseModel):
162165
tag: str

src/murfey/workflows/clem/register_preprocessing_results.py

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import logging
1212
import re
1313
import traceback
14+
from collections.abc import Collection
1415
from importlib.metadata import entry_points
1516
from pathlib import Path
1617
from typing import Literal, Optional
@@ -66,6 +67,27 @@ def _is_clem_atlas(result: CLEMPreprocessingResult):
6667
)
6768

6869

70+
COLOR_FLAGS_MURFEY = {
71+
"gray": "has_grey",
72+
"red": "has_red",
73+
"green": "has_green",
74+
"blue": "has_blue",
75+
"cyan": "has_cyan",
76+
"magenta": "has_magenta",
77+
"yellow": "has_yellow",
78+
}
79+
80+
81+
def _get_color_flags(
82+
colors: Collection[str] | None = None,
83+
):
84+
colors = colors or []
85+
color_flags = dict.fromkeys(COLOR_FLAGS_MURFEY.values(), False)
86+
for color in colors:
87+
color_flags[COLOR_FLAGS_MURFEY[color]] = True
88+
return color_flags
89+
90+
6991
def _register_clem_image_series(
7092
session_id: int,
7193
result: CLEMPreprocessingResult,
@@ -159,6 +181,11 @@ def _register_clem_image_series(
159181
clem_img_series.image_search_string = str(output_file.parent / "*tiff")
160182
clem_img_series.data_type = "atlas" if _is_clem_atlas(result) else "grid_square"
161183
clem_img_series.number_of_members = result.number_of_members
184+
for col_name, value in _get_color_flags(result.output_files.keys()).items():
185+
setattr(clem_img_series, col_name, value)
186+
clem_img_series.collection_mode = _determine_collection_mode(
187+
result.output_files.keys()
188+
)
162189
clem_img_series.image_pixels_x = result.pixels_x
163190
clem_img_series.image_pixels_y = result.pixels_y
164191
clem_img_series.image_pixel_size = result.pixel_size
@@ -186,6 +213,31 @@ def _register_clem_image_series(
186213
logger.info(f"CLEM preprocessing results registered for {result.series_name!r} ")
187214

188215

216+
def _determine_collection_mode(
217+
colors: Collection[str] | None = None,
218+
):
219+
if not colors:
220+
logger.warning("No colours were present in returned result")
221+
return None
222+
if "gray" in colors:
223+
if len(colors) == 1:
224+
return "Bright Field"
225+
else:
226+
return "Bright Field and Fluorescent"
227+
else:
228+
return "Fluorescent"
229+
230+
231+
def _snake_to_camel_case(string: str):
232+
parts = string.split("_")
233+
return parts[0] + "".join(part.capitalize() for part in parts[1:])
234+
235+
236+
COLOR_FLAGS_MURFEY_TO_ISPYB = {
237+
value: _snake_to_camel_case(value) for value in COLOR_FLAGS_MURFEY.values()
238+
}
239+
240+
189241
def _register_dcg_and_atlas(
190242
session_id: int,
191243
instrument_name: str,
@@ -225,9 +277,17 @@ def _register_dcg_and_atlas(
225277
else:
226278
atlas_name = str(output_file.parent / "*.tiff")
227279
atlas_pixel_size = result.pixel_size
280+
# Translate colour flags into ISPyB convention
281+
color_flags = {
282+
COLOR_FLAGS_MURFEY_TO_ISPYB[key]: int(value)
283+
for key, value in _get_color_flags(result.output_files.keys()).items()
284+
}
285+
collection_mode = _determine_collection_mode(result.output_files.keys())
228286
else:
229287
atlas_name = ""
230288
atlas_pixel_size = 0.0
289+
color_flags = None
290+
collection_mode = None
231291

232292
if dcg_search := murfey_db.exec(
233293
select(MurfeyDB.DataCollectionGroup)
@@ -245,6 +305,8 @@ def _register_dcg_and_atlas(
245305
"atlas": atlas_name,
246306
"atlas_pixel_size": atlas_pixel_size,
247307
"sample": dcg_entry.sample,
308+
"color_flags": color_flags,
309+
"collection_mode": collection_mode,
248310
}
249311
if entry_point_result := entry_points(
250312
group="murfey.workflows", name="atlas_update"
@@ -269,6 +331,8 @@ def _register_dcg_and_atlas(
269331
"atlas": atlas_name,
270332
"atlas_pixel_size": atlas_pixel_size,
271333
"sample": None,
334+
"color_flags": color_flags,
335+
"collection_mode": collection_mode,
272336
}
273337
if entry_point_result := entry_points(
274338
group="murfey.workflows", name="data_collection_group"
@@ -342,6 +406,8 @@ def _register_grid_square(
342406
and atlas_entry.x1 is not None
343407
and atlas_entry.y0 is not None
344408
and atlas_entry.y1 is not None
409+
and atlas_entry.thumbnail_pixels_x is not None
410+
and atlas_entry.thumbnail_pixels_y is not None
345411
):
346412
atlas_width_real = atlas_entry.x1 - atlas_entry.x0
347413
atlas_height_real = atlas_entry.y1 - atlas_entry.y0
@@ -356,34 +422,31 @@ def _register_grid_square(
356422
and clem_img_series.x1 is not None
357423
and clem_img_series.y0 is not None
358424
and clem_img_series.y1 is not None
359-
and clem_img_series.thumbnail_pixels_x is not None
360-
and clem_img_series.thumbnail_pixels_y is not None
361-
and clem_img_series.thumbnail_pixel_size is not None
362425
):
363426
# Find pixel corresponding to image midpoint on atlas
364427
x_mid_real = (
365428
0.5 * (clem_img_series.x0 + clem_img_series.x1) - atlas_entry.x0
366429
)
367430
x_mid_px = int(
368-
x_mid_real / atlas_width_real * clem_img_series.thumbnail_pixels_x
431+
x_mid_real / atlas_width_real * atlas_entry.thumbnail_pixels_x
369432
)
370433
y_mid_real = (
371434
0.5 * (clem_img_series.y0 + clem_img_series.y1) - atlas_entry.y0
372435
)
373436
y_mid_px = int(
374-
y_mid_real / atlas_height_real * clem_img_series.thumbnail_pixels_y
437+
y_mid_real / atlas_height_real * atlas_entry.thumbnail_pixels_y
375438
)
376439

377-
# Find the size of the image, in pixels, when overlaid the atlas
440+
# Find the size of the image, in pixels, when overlaid on the atlas
378441
width_scaled = int(
379442
(clem_img_series.x1 - clem_img_series.x0)
380443
/ atlas_width_real
381-
* clem_img_series.thumbnail_pixels_x
444+
* atlas_entry.thumbnail_pixels_x
382445
)
383446
height_scaled = int(
384447
(clem_img_series.y1 - clem_img_series.y0)
385448
/ atlas_height_real
386-
* clem_img_series.thumbnail_pixels_y
449+
* atlas_entry.thumbnail_pixels_y
387450
)
388451
else:
389452
logger.warning(
@@ -410,7 +473,13 @@ def _register_grid_square(
410473
y_stage_position=0.5 * (clem_img_series.y0 + clem_img_series.y1),
411474
pixel_size=clem_img_series.image_pixel_size,
412475
image=clem_img_series.thumbnail_search_string,
476+
collection_mode=clem_img_series.collection_mode,
413477
)
478+
# Construct colour flags for ISPyB
479+
color_flags = {
480+
ispyb_color_flags: int(getattr(clem_img_series, murfey_color_flags, 0))
481+
for murfey_color_flags, ispyb_color_flags in COLOR_FLAGS_MURFEY_TO_ISPYB.items()
482+
}
414483
# Register or update the grid square entry as required
415484
if grid_square_result := murfey_db.exec(
416485
select(MurfeyDB.GridSquare)
@@ -435,6 +504,7 @@ def _register_grid_square(
435504
_transport_object.do_update_grid_square(
436505
grid_square_id=grid_square_entry.id,
437506
grid_square_parameters=grid_square_params,
507+
color_flags=color_flags,
438508
)
439509
else:
440510
# Look up data collection group for current series
@@ -448,6 +518,7 @@ def _register_grid_square(
448518
atlas_id=dcg_entry.atlas_id,
449519
grid_square_id=clem_img_series.id,
450520
grid_square_parameters=grid_square_params,
521+
color_flags=color_flags,
451522
)
452523
# Register to Murfey
453524
grid_square_entry = MurfeyDB.GridSquare(

src/murfey/workflows/register_atlas_update.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@ def run(
1919
logger.info(f"Registering updated atlas: \n{message}")
2020

2121
_transport_object.do_update_atlas(
22-
message["atlas_id"],
23-
message["atlas"],
24-
message["atlas_pixel_size"],
25-
message["sample"],
22+
atlas_id=message["atlas_id"],
23+
atlas_image=message["atlas"],
24+
pixel_size=message["atlas_pixel_size"],
25+
slot=message["sample"],
26+
# Extract optional parameters
27+
collection_mode=message.get("collection_mode"),
28+
color_flags=message.get("color_flags", {}),
2629
)
2730
if dcg_hooks := entry_points(group="murfey.hooks", name="data_collection_group"):
2831
try:

src/murfey/workflows/register_data_collection_group.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ def run(message: dict, murfey_db: SQLModelSession) -> dict[str, bool]:
6767
pixelSize=message.get("atlas_pixel_size", 0),
6868
cassetteSlot=message.get("sample"),
6969
)
70+
# Optionally set the collection mode and color flags
71+
if collection_mode := message.get("collection_mode"):
72+
atlas_record.mode = collection_mode
73+
if color_flags := message.get("color_flags", {}):
74+
for col_name, value in color_flags.items():
75+
setattr(atlas_record, col_name, value)
7076
atlas_id = _transport_object.do_insert_atlas(atlas_record).get(
7177
"return_value", None
7278
)

0 commit comments

Comments
 (0)