Skip to content

Commit 61d48e5

Browse files
committed
Added logic to parse FIB Maps metadata and images and trigger workflow (to be determine) once both are accounted for
1 parent 6efb99b commit 61d48e5

1 file changed

Lines changed: 128 additions & 1 deletion

File tree

  • src/murfey/client/contexts

src/murfey/client/contexts/fib.py

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from __future__ import annotations
22

33
import logging
4+
import threading
45
from datetime import datetime
56
from pathlib import Path
67
from typing import Dict, List, NamedTuple, Optional
8+
from xml.etree import ElementTree as ET
79

810
import xmltodict
911

@@ -13,6 +15,8 @@
1315

1416
logger = logging.getLogger("murfey.client.contexts.fib")
1517

18+
lock = threading.Lock()
19+
1620

1721
class Lamella(NamedTuple):
1822
name: str
@@ -25,18 +29,98 @@ class MillingProgress(NamedTuple):
2529
timestamp: float
2630

2731

32+
class ElectronSnapshotMetadata(NamedTuple):
33+
image_dir: str # Partial path from EMproject.emxml parent to the image
34+
status: str
35+
x_len: float | None
36+
y_len: float | None
37+
z_len: float | None
38+
x_center: float | None
39+
y_center: float | None
40+
z_center: float | None
41+
extent: tuple[float, float, float, float] | None
42+
rotation_angle: float | None
43+
44+
2845
def _number_from_name(name: str) -> int:
2946
return int(
3047
name.strip().replace("Lamella", "").replace("(", "").replace(")", "") or 1
3148
)
3249

3350

51+
def _parse_electron_snapshot_metadata(xml_file: Path):
52+
metadata_dict = {}
53+
root = ET.parse(xml_file).getroot()
54+
datasets = root.findall(".//Datasets/Dataset")
55+
for dataset in datasets:
56+
# Extract all string-based values
57+
name, image_dir, status = [
58+
node.text
59+
if ((node := dataset.find(node_path)) is not None and node.text is not None)
60+
else ""
61+
for node_path in (
62+
".//Name",
63+
".//FinalImages",
64+
".//Status",
65+
)
66+
]
67+
68+
# Extract all float values
69+
cx, cy, cz, x_len, y_len, z_len, rotation_angle = [
70+
float(node.text)
71+
if ((node := dataset.find(node_path)) is not None and node.text is not None)
72+
else None
73+
for node_path in (
74+
".//BoxCenter/CenterX",
75+
".//BoxCenter/CenterY",
76+
".//BoxCenter/CenterZ",
77+
".//BoxSize/SizeX",
78+
".//BoxSize/SizeY",
79+
".//BoxSize/SizeZ",
80+
".//RotationAngle",
81+
)
82+
]
83+
84+
# Calculate the extent of the image
85+
extent = None
86+
if (
87+
cx is not None
88+
and cy is not None
89+
and x_len is not None
90+
and y_len is not None
91+
):
92+
extent = (
93+
x_len - (cx / 2),
94+
x_len + (cx / 2),
95+
y_len - (cy / 2),
96+
y_len - (cy / 2),
97+
)
98+
99+
# Append metadata for current site to dict
100+
metadata_dict[name] = ElectronSnapshotMetadata(
101+
status=status,
102+
image_dir=image_dir,
103+
x_len=x_len,
104+
y_len=y_len,
105+
z_len=z_len,
106+
x_center=cx,
107+
y_center=cy,
108+
z_center=cz,
109+
extent=extent,
110+
rotation_angle=rotation_angle,
111+
)
112+
return metadata_dict
113+
114+
34115
class FIBContext(Context):
35116
def __init__(self, acquisition_software: str, basepath: Path, token: str):
36117
super().__init__("FIB", acquisition_software, token)
37118
self._basepath = basepath
38119
self._milling: Dict[int, List[MillingProgress]] = {}
39120
self._lamellae: Dict[int, Lamella] = {}
121+
self._electron_snapshots: Dict[str, Path] = {}
122+
self._electron_snapshot_metadata: Dict[str, ElectronSnapshotMetadata] = {}
123+
self._electron_snapshots_submitted: set[str] = set()
40124

41125
def post_transfer(
42126
self,
@@ -130,7 +214,50 @@ def post_transfer(
130214
# Maps
131215
# -----------------------------------------------------------------------------
132216
elif self._acquisition_software == "maps":
133-
pass
217+
# Electron snapshot metadata file
218+
if transferred_file.name == "EMproject.emxml":
219+
# Extract all "Electron Snapshot" metadata and store it
220+
self._electron_snapshot_metadata = _parse_electron_snapshot_metadata(
221+
transferred_file
222+
)
223+
# If dataset hasn't been transferred, register it
224+
for dataset_name in list(self._electron_snapshot_metadata.keys()):
225+
if dataset_name not in self._electron_snapshots_submitted:
226+
if dataset_name in self._electron_snapshots:
227+
logger.info(f"Registering {dataset_name!r}")
228+
229+
## Workflow to trigger goes here
230+
231+
# Clear old entry after triggering workflow
232+
self._electron_snapshots_submitted.add(dataset_name)
233+
with lock:
234+
self._electron_snapshots.pop(dataset_name, None)
235+
self._electron_snapshot_metadata.pop(dataset_name, None)
236+
else:
237+
logger.debug(f"Waiting for image for {dataset_name}")
238+
# Electron snapshot image
239+
elif (
240+
"Electron Snapshot" in transferred_file.name
241+
and transferred_file.suffix in (".tif", ".tiff")
242+
):
243+
# Store file in Context memory
244+
dataset_name = transferred_file.stem
245+
self._electron_snapshots[dataset_name] = transferred_file
246+
247+
if dataset_name not in self._electron_snapshots_submitted:
248+
# If the metadata and image are both present, register dataset
249+
if dataset_name in list(self._electron_snapshot_metadata.keys()):
250+
logger.info(f"Registering {dataset_name!r}")
251+
252+
## Workflow to trigger goes here
253+
254+
# Clear old entry after triggering workflow
255+
self._electron_snapshots_submitted.add(dataset_name)
256+
with lock:
257+
self._electron_snapshots.pop(dataset_name, None)
258+
self._electron_snapshot_metadata.pop(dataset_name, None)
259+
else:
260+
logger.debug(f"Waiting for metadata for {dataset_name}")
134261
# -----------------------------------------------------------------------------
135262
# Meteor
136263
# -----------------------------------------------------------------------------

0 commit comments

Comments
 (0)