Skip to content

Commit bc3278d

Browse files
Register missed grid squares and foil holes during SPA processing flush (#454)
Co-authored-by: Daniel Hatton <[email protected]>
1 parent a49b3f9 commit bc3278d

File tree

7 files changed

+625
-458
lines changed

7 files changed

+625
-458
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ murfey = "murfey.client:run"
107107
"clem.register_align_and_merge_result" = "murfey.workflows.clem.register_align_and_merge_results:register_align_and_merge_result"
108108
"clem.register_lif_preprocessing_result" = "murfey.workflows.clem.register_preprocessing_results:register_lif_preprocessing_result"
109109
"clem.register_tiff_preprocessing_result" = "murfey.workflows.clem.register_preprocessing_results:register_tiff_preprocessing_result"
110+
"spa.flush_spa_preprocess" = "murfey.workflows.spa.flush_spa_preprocess:flush_spa_preprocess"
110111

111112
[tool.setuptools]
112113
package-dir = {"" = "src"}

src/murfey/client/contexts/spa.py

Lines changed: 17 additions & 212 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from datetime import datetime
55
from itertools import count
66
from pathlib import Path
7-
from typing import Any, Dict, List, NamedTuple, Optional, OrderedDict, Tuple
7+
from typing import Any, Dict, List, Optional, OrderedDict, Tuple
88

99
import requests
1010
import xmltodict
@@ -21,113 +21,19 @@
2121
capture_post,
2222
get_machine_config_client,
2323
)
24+
from murfey.util.spa_metadata import (
25+
foil_hole_data,
26+
foil_hole_from_file,
27+
get_grid_square_atlas_positions,
28+
grid_square_data,
29+
grid_square_from_file,
30+
)
2431

2532
logger = logging.getLogger("murfey.client.contexts.spa")
2633

2734
requests.get, requests.post, requests.put, requests.delete = authorised_requests()
2835

2936

30-
class FoilHole(NamedTuple):
31-
session_id: int
32-
id: int
33-
grid_square_id: int
34-
x_location: Optional[float] = None
35-
y_location: Optional[float] = None
36-
x_stage_position: Optional[float] = None
37-
y_stage_position: Optional[float] = None
38-
readout_area_x: Optional[int] = None
39-
readout_area_y: Optional[int] = None
40-
thumbnail_size_x: Optional[int] = None
41-
thumbnail_size_y: Optional[int] = None
42-
pixel_size: Optional[float] = None
43-
image: str = ""
44-
diameter: Optional[float] = None
45-
46-
47-
class GridSquare(NamedTuple):
48-
session_id: int
49-
id: int
50-
x_location: Optional[float] = None
51-
y_location: Optional[float] = None
52-
x_stage_position: Optional[float] = None
53-
y_stage_position: Optional[float] = None
54-
readout_area_x: Optional[int] = None
55-
readout_area_y: Optional[int] = None
56-
thumbnail_size_x: Optional[int] = None
57-
thumbnail_size_y: Optional[int] = None
58-
pixel_size: Optional[float] = None
59-
image: str = ""
60-
tag: str = ""
61-
62-
63-
def _get_grid_square_atlas_positions(xml_path: Path, grid_square: str = "") -> Dict[
64-
str,
65-
Tuple[
66-
Optional[int],
67-
Optional[int],
68-
Optional[float],
69-
Optional[float],
70-
Optional[int],
71-
Optional[int],
72-
Optional[float],
73-
],
74-
]:
75-
with open(
76-
xml_path,
77-
"r",
78-
) as dm:
79-
atlas_data = xmltodict.parse(dm.read())
80-
tile_info = atlas_data["AtlasSessionXml"]["Atlas"]["TilesEfficient"]["_items"][
81-
"TileXml"
82-
]
83-
gs_pix_positions: Dict[
84-
str,
85-
Tuple[
86-
Optional[int],
87-
Optional[int],
88-
Optional[float],
89-
Optional[float],
90-
Optional[int],
91-
Optional[int],
92-
Optional[float],
93-
],
94-
] = {}
95-
for ti in tile_info:
96-
try:
97-
nodes = ti["Nodes"]["KeyValuePairs"]
98-
except KeyError:
99-
continue
100-
required_key = ""
101-
for key in nodes.keys():
102-
if key.startswith("KeyValuePairOfintNodeXml"):
103-
required_key = key
104-
break
105-
if not required_key:
106-
continue
107-
for gs in nodes[required_key]:
108-
if not isinstance(gs, dict):
109-
continue
110-
if not grid_square or gs["key"] == grid_square:
111-
gs_pix_positions[gs["key"]] = (
112-
int(float(gs["value"]["b:PositionOnTheAtlas"]["c:Center"]["d:x"])),
113-
int(float(gs["value"]["b:PositionOnTheAtlas"]["c:Center"]["d:y"])),
114-
float(gs["value"]["b:PositionOnTheAtlas"]["c:Physical"]["d:x"])
115-
* 1e9,
116-
float(gs["value"]["b:PositionOnTheAtlas"]["c:Physical"]["d:y"])
117-
* 1e9,
118-
int(
119-
float(gs["value"]["b:PositionOnTheAtlas"]["c:Size"]["d:width"])
120-
),
121-
int(
122-
float(gs["value"]["b:PositionOnTheAtlas"]["c:Size"]["d:height"])
123-
),
124-
float(gs["value"]["b:PositionOnTheAtlas"]["c:Rotation"]),
125-
)
126-
if grid_square:
127-
break
128-
return gs_pix_positions
129-
130-
13137
def _file_transferred_to(
13238
environment: MurfeyInstanceEnvironment, source: Path, file_path: Path
13339
):
@@ -150,17 +56,6 @@ def _file_transferred_to(
15056
)
15157

15258

153-
def _grid_square_from_file(f: Path) -> int:
154-
for p in f.parts:
155-
if p.startswith("GridSquare"):
156-
return int(p.split("_")[1])
157-
raise ValueError(f"Grid square ID could not be determined from path {f}")
158-
159-
160-
def _foil_hole_from_file(f: Path) -> int:
161-
return int(f.name.split("_")[1])
162-
163-
16459
def _grid_square_metadata_file(
16560
f: Path, data_directories: List[Path], visit: str, grid_square: int
16661
) -> Path:
@@ -180,97 +75,6 @@ def _grid_square_metadata_file(
18075
)
18176

18277

183-
def _grid_square_data(xml_path: Path, grid_square: int, session_id: int) -> GridSquare:
184-
image_paths = list(
185-
(xml_path.parent.parent).glob(
186-
f"Images-Disc*/GridSquare_{grid_square}/GridSquare_*.jpg"
187-
)
188-
)
189-
if image_paths:
190-
image_paths.sort(key=lambda x: x.stat().st_ctime)
191-
image_path = image_paths[-1]
192-
with open(Path(image_path).with_suffix(".xml")) as gs_xml:
193-
gs_xml_data = xmltodict.parse(gs_xml.read())
194-
readout_area = gs_xml_data["MicroscopeImage"]["microscopeData"]["acquisition"][
195-
"camera"
196-
]["ReadoutArea"]
197-
pixel_size = gs_xml_data["MicroscopeImage"]["SpatialScale"]["pixelSize"]["x"][
198-
"numericValue"
199-
]
200-
full_size = (int(readout_area["a:width"]), int(readout_area["a:height"]))
201-
return GridSquare(
202-
id=grid_square,
203-
session_id=session_id,
204-
readout_area_x=full_size[0] if image_path else None,
205-
readout_area_y=full_size[1] if image_path else None,
206-
thumbnail_size_x=int((512 / max(full_size)) * full_size[0]),
207-
thumbnail_size_y=int((512 / max(full_size)) * full_size[1]),
208-
pixel_size=float(pixel_size) if image_path else None,
209-
image=str(image_path),
210-
)
211-
return GridSquare(id=grid_square, session_id=session_id)
212-
213-
214-
def _foil_hole_data(
215-
xml_path: Path, foil_hole: int, grid_square: int, session_id: int
216-
) -> FoilHole:
217-
with open(xml_path, "r") as xml:
218-
for_parsing = xml.read()
219-
data = xmltodict.parse(for_parsing)
220-
data = data["GridSquareXml"]
221-
serialization_array = data["TargetLocations"]["TargetLocationsEfficient"][
222-
"a:m_serializationArray"
223-
]
224-
required_key = ""
225-
for key in serialization_array.keys():
226-
if key.startswith("b:KeyValuePairOfintTargetLocation"):
227-
required_key = key
228-
break
229-
if required_key:
230-
image_paths = list(
231-
(xml_path.parent.parent).glob(
232-
f"Images-Disc*/GridSquare_{grid_square}/FoilHoles/FoilHole_{foil_hole}_*.jpg"
233-
)
234-
)
235-
image_paths.sort(key=lambda x: x.stat().st_ctime)
236-
image_path: Path | str = image_paths[-1] if image_paths else ""
237-
if image_path:
238-
with open(Path(image_path).with_suffix(".xml")) as fh_xml:
239-
fh_xml_data = xmltodict.parse(fh_xml.read())
240-
readout_area = fh_xml_data["MicroscopeImage"]["microscopeData"][
241-
"acquisition"
242-
]["camera"]["ReadoutArea"]
243-
pixel_size = fh_xml_data["MicroscopeImage"]["SpatialScale"]["pixelSize"][
244-
"x"
245-
]["numericValue"]
246-
full_size = (int(readout_area["a:width"]), int(readout_area["a:height"]))
247-
for fh_block in serialization_array[required_key]:
248-
pix = fh_block["b:value"]["PixelCenter"]
249-
stage = fh_block["b:value"]["StagePosition"]
250-
diameter = fh_block["b:value"]["PixelWidthHeight"]["c:width"]
251-
if int(fh_block["b:key"]) == foil_hole:
252-
return FoilHole(
253-
id=foil_hole,
254-
grid_square_id=grid_square,
255-
session_id=session_id,
256-
x_location=float(pix["c:x"]),
257-
y_location=float(pix["c:y"]),
258-
x_stage_position=float(stage["c:X"]),
259-
y_stage_position=float(stage["c:Y"]),
260-
readout_area_x=full_size[0] if image_path else None,
261-
readout_area_y=full_size[1] if image_path else None,
262-
thumbnail_size_x=None,
263-
thumbnail_size_y=None,
264-
pixel_size=float(pixel_size) if image_path else None,
265-
image=str(image_path),
266-
diameter=diameter,
267-
)
268-
logger.warning(
269-
f"Foil hole positions could not be determined from metadata file {xml_path} for foil hole {foil_hole}"
270-
)
271-
return FoilHole(id=foil_hole, grid_square_id=grid_square, session_id=session_id)
272-
273-
27478
def _get_source(file_path: Path, environment: MurfeyInstanceEnvironment) -> Path | None:
27579
for s in environment.sources:
27680
if file_path.is_relative_to(s):
@@ -566,8 +370,8 @@ def _position_analysis(
566370
environment: MurfeyInstanceEnvironment,
567371
source: Path,
568372
machine_config: dict,
569-
) -> int:
570-
grid_square = _grid_square_from_file(transferred_file)
373+
) -> Optional[int]:
374+
grid_square = grid_square_from_file(transferred_file)
571375
grid_square_metadata_file = _grid_square_metadata_file(
572376
transferred_file,
573377
[Path(p) for p in machine_config["data_directories"]],
@@ -596,6 +400,9 @@ def _position_analysis(
596400
.json()
597401
.get(str(source), {})
598402
)
403+
if not data_collection_group:
404+
logger.info("Data collection group has not yet been made")
405+
return None
599406
if data_collection_group.get("atlas"):
600407
visit_path = ""
601408
for p in transferred_file.parts:
@@ -606,15 +413,14 @@ def _position_analysis(
606413
local_atlas_path = (
607414
Path(visit_path) / environment.samples[source].atlas
608415
)
609-
gs_pix_position = _get_grid_square_atlas_positions(
416+
gs_pix_position = get_grid_square_atlas_positions(
610417
local_atlas_path,
611418
grid_square=str(grid_square),
612419
)[str(grid_square)]
613420
gs_url = f"{str(environment.url.geturl())}/sessions/{environment.murfey_session}/grid_square/{grid_square}"
614-
gs = _grid_square_data(
421+
gs = grid_square_data(
615422
grid_square_metadata_file,
616423
grid_square,
617-
environment.murfey_session,
618424
)
619425
metadata_source = Path(
620426
(
@@ -647,18 +453,17 @@ def _position_analysis(
647453
"angle": gs_pix_position[6],
648454
},
649455
)
650-
foil_hole = _foil_hole_from_file(transferred_file)
456+
foil_hole = foil_hole_from_file(transferred_file)
651457
if foil_hole not in self._foil_holes[grid_square]:
652458
fh_url = f"{str(environment.url.geturl())}/sessions/{environment.murfey_session}/grid_square/{grid_square}/foil_hole"
653459
if (
654460
grid_square_metadata_file.is_file()
655461
and environment.murfey_session is not None
656462
):
657-
fh = _foil_hole_data(
463+
fh = foil_hole_data(
658464
grid_square_metadata_file,
659465
foil_hole,
660466
grid_square,
661-
environment.murfey_session,
662467
)
663468
metadata_source = Path(
664469
(

src/murfey/client/contexts/spa_metadata.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
import xmltodict
77

88
from murfey.client.context import Context
9-
from murfey.client.contexts.spa import _get_grid_square_atlas_positions, _get_source
9+
from murfey.client.contexts.spa import _get_source
1010
from murfey.client.instance_environment import MurfeyInstanceEnvironment, SampleInfo
1111
from murfey.util import authorised_requests, capture_post, get_machine_config_client
12+
from murfey.util.spa_metadata import get_grid_square_atlas_positions
1213

1314
logger = logging.getLogger("murfey.client.contexts.spa_metadata")
1415

@@ -116,7 +117,7 @@ def post_transfer(
116117
.get(str(source), [])
117118
)
118119
if registered_grid_squares:
119-
gs_pix_positions = _get_grid_square_atlas_positions(
120+
gs_pix_positions = get_grid_square_atlas_positions(
120121
source_visit_dir / partial_path
121122
)
122123
for gs in registered_grid_squares:

0 commit comments

Comments
 (0)