Skip to content

Commit b75e508

Browse files
committed
Merge main
2 parents 707a91f + bc3278d commit b75e508

File tree

8 files changed

+634
-468
lines changed

8 files changed

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

15158

152-
def _grid_square_from_file(f: Path) -> int:
153-
for p in f.parts:
154-
if p.startswith("GridSquare"):
155-
return int(p.split("_")[1])
156-
raise ValueError(f"Grid square ID could not be determined from path {f}")
157-
158-
159-
def _foil_hole_from_file(f: Path) -> int:
160-
return int(f.name.split("_")[1])
161-
162-
16359
def _grid_square_metadata_file(
16460
f: Path, data_directories: List[Path], visit: str, grid_square: int
16561
) -> Path:
@@ -179,96 +75,6 @@ def _grid_square_metadata_file(
17975
)
18076

18177

182-
def _grid_square_data(xml_path: Path, grid_square: int, session_id: int) -> GridSquare:
183-
image_paths = list(
184-
(xml_path.parent.parent).glob(
185-
f"Images-Disc*/GridSquare_{grid_square}/GridSquare_*.jpg"
186-
)
187-
)
188-
if image_paths:
189-
image_paths.sort(key=lambda x: x.stat().st_ctime)
190-
image_path = image_paths[-1]
191-
with open(Path(image_path).with_suffix(".xml")) as gs_xml:
192-
gs_xml_data = xmltodict.parse(gs_xml.read())
193-
readout_area = gs_xml_data["MicroscopeImage"]["microscopeData"]["acquisition"][
194-
"camera"
195-
]["ReadoutArea"]
196-
pixel_size = gs_xml_data["MicroscopeImage"]["SpatialScale"]["pixelSize"]["x"][
197-
"numericValue"
198-
]
199-
full_size = (int(readout_area["a:width"]), int(readout_area["a:height"]))
200-
return GridSquare(
201-
id=grid_square,
202-
session_id=session_id,
203-
readout_area_x=full_size[0] if image_path else None,
204-
readout_area_y=full_size[1] if image_path else None,
205-
thumbnail_size_x=int((512 / max(full_size)) * full_size[0]),
206-
thumbnail_size_y=int((512 / max(full_size)) * full_size[1]),
207-
pixel_size=float(pixel_size) if image_path else None,
208-
image=str(image_path),
209-
)
210-
return GridSquare(id=grid_square, session_id=session_id)
211-
212-
213-
def _foil_hole_data(
214-
xml_path: Path, foil_hole: int, grid_square: int, session_id: int
215-
) -> FoilHole:
216-
with open(xml_path, "r") as xml:
217-
for_parsing = xml.read()
218-
data = xmltodict.parse(for_parsing)
219-
data = data["GridSquareXml"]
220-
serialization_array = data["TargetLocations"]["TargetLocationsEfficient"][
221-
"a:m_serializationArray"
222-
]
223-
required_key = ""
224-
for key in serialization_array.keys():
225-
if key.startswith("b:KeyValuePairOfintTargetLocation"):
226-
required_key = key
227-
break
228-
if required_key:
229-
image_paths = list(
230-
(xml_path.parent.parent).glob(
231-
f"Images-Disc*/GridSquare_{grid_square}/FoilHoles/FoilHole_{foil_hole}_*.jpg"
232-
)
233-
)
234-
image_paths.sort(key=lambda x: x.stat().st_ctime)
235-
image_path: Path | str = image_paths[-1] if image_paths else ""
236-
if image_path:
237-
with open(Path(image_path).with_suffix(".xml")) as fh_xml:
238-
fh_xml_data = xmltodict.parse(fh_xml.read())
239-
readout_area = fh_xml_data["MicroscopeImage"]["microscopeData"][
240-
"acquisition"
241-
]["camera"]["ReadoutArea"]
242-
pixel_size = fh_xml_data["MicroscopeImage"]["SpatialScale"]["pixelSize"][
243-
"x"
244-
]["numericValue"]
245-
full_size = (int(readout_area["a:width"]), int(readout_area["a:height"]))
246-
for fh_block in serialization_array[required_key]:
247-
pix = fh_block["b:value"]["PixelCenter"]
248-
stage = fh_block["b:value"]["StagePosition"]
249-
diameter = fh_block["b:value"]["PixelWidthHeight"]["c:width"]
250-
if int(fh_block["b:key"]) == foil_hole:
251-
return FoilHole(
252-
id=foil_hole,
253-
grid_square_id=grid_square,
254-
x_location=float(pix["c:x"]),
255-
y_location=float(pix["c:y"]),
256-
x_stage_position=float(stage["c:X"]),
257-
y_stage_position=float(stage["c:Y"]),
258-
readout_area_x=full_size[0] if image_path else None,
259-
readout_area_y=full_size[1] if image_path else None,
260-
thumbnail_size_x=None,
261-
thumbnail_size_y=None,
262-
pixel_size=float(pixel_size) if image_path else None,
263-
image=str(image_path),
264-
diameter=diameter,
265-
)
266-
logger.warning(
267-
f"Foil hole positions could not be determined from metadata file {xml_path} for foil hole {foil_hole}"
268-
)
269-
return FoilHole(id=foil_hole, grid_square_id=grid_square)
270-
271-
27278
def _get_source(file_path: Path, environment: MurfeyInstanceEnvironment) -> Path | None:
27379
for s in environment.sources:
27480
if file_path.is_relative_to(s):
@@ -564,8 +370,8 @@ def _position_analysis(
564370
environment: MurfeyInstanceEnvironment,
565371
source: Path,
566372
machine_config: dict,
567-
) -> int:
568-
grid_square = _grid_square_from_file(transferred_file)
373+
) -> Optional[int]:
374+
grid_square = grid_square_from_file(transferred_file)
569375
grid_square_metadata_file = _grid_square_metadata_file(
570376
transferred_file,
571377
[Path(p) for p in machine_config["data_directories"]],
@@ -594,6 +400,9 @@ def _position_analysis(
594400
.json()
595401
.get(str(source), {})
596402
)
403+
if not data_collection_group:
404+
logger.info("Data collection group has not yet been made")
405+
return None
597406
if data_collection_group.get("atlas"):
598407
visit_path = ""
599408
for p in transferred_file.parts:
@@ -604,15 +413,14 @@ def _position_analysis(
604413
local_atlas_path = (
605414
Path(visit_path) / environment.samples[source].atlas
606415
)
607-
gs_pix_position = _get_grid_square_atlas_positions(
416+
gs_pix_position = get_grid_square_atlas_positions(
608417
local_atlas_path,
609418
grid_square=str(grid_square),
610419
)[str(grid_square)]
611420
gs_url = f"{str(environment.url.geturl())}/sessions/{environment.murfey_session}/grid_square/{grid_square}"
612-
gs = _grid_square_data(
421+
gs = grid_square_data(
613422
grid_square_metadata_file,
614423
grid_square,
615-
environment.murfey_session,
616424
)
617425
metadata_source = Path(
618426
(
@@ -645,18 +453,17 @@ def _position_analysis(
645453
"angle": gs_pix_position[6],
646454
},
647455
)
648-
foil_hole = _foil_hole_from_file(transferred_file)
456+
foil_hole = foil_hole_from_file(transferred_file)
649457
if foil_hole not in self._foil_holes[grid_square]:
650458
fh_url = f"{str(environment.url.geturl())}/sessions/{environment.murfey_session}/grid_square/{grid_square}/foil_hole"
651459
if (
652460
grid_square_metadata_file.is_file()
653461
and environment.murfey_session is not None
654462
):
655-
fh = _foil_hole_data(
463+
fh = foil_hole_data(
656464
grid_square_metadata_file,
657465
foil_hole,
658466
grid_square,
659-
environment.murfey_session,
660467
)
661468
metadata_source = Path(
662469
(

src/murfey/client/contexts/spa_metadata.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,17 @@
66
import xmltodict
77

88
from murfey.client.context import Context
9-
from murfey.client.contexts.spa import (
10-
FoilHole,
11-
_get_grid_square_atlas_positions,
12-
_get_source,
13-
)
9+
from murfey.client.contexts.spa import _get_source
1410
from murfey.client.instance_environment import MurfeyInstanceEnvironment, SampleInfo
1511
from murfey.util import authorised_requests, capture_post, get_machine_config_client
12+
from murfey.util.spa_metadata import FoilHoleInfo, get_grid_square_atlas_positions
1613

1714
logger = logging.getLogger("murfey.client.contexts.spa_metadata")
1815

1916
requests.get, requests.post, requests.put, requests.delete = authorised_requests()
2017

2118

22-
def _foil_hole_positions(xml_path: Path, grid_square: int) -> Dict[str, FoilHole]:
19+
def _foil_hole_positions(xml_path: Path, grid_square: int) -> Dict[str, FoilHoleInfo]:
2320
with open(xml_path, "r") as xml:
2421
for_parsing = xml.read()
2522
data = xmltodict.parse(for_parsing)
@@ -54,7 +51,7 @@ def _foil_hole_positions(xml_path: Path, grid_square: int) -> Dict[str, FoilHole
5451
pix_loc = fh_block["b:value"]["PixelCenter"]
5552
stage = fh_block["b:value"]["StagePosition"]
5653
diameter = fh_block["b:value"]["PixelWidthHeight"]["c:width"]
57-
foil_holes[fh_block["b:key"]] = FoilHole(
54+
foil_holes[fh_block["b:key"]] = FoilHoleInfo(
5855
id=int(fh_block["b:key"]),
5956
grid_square_id=grid_square,
6057
x_location=int(float(pix_loc["c:x"])),
@@ -165,7 +162,7 @@ def post_transfer(
165162
"atlas_pixel_size": atlas_pixel_size,
166163
}
167164
capture_post(url, json=dcg_data)
168-
gs_pix_positions = _get_grid_square_atlas_positions(
165+
gs_pix_positions = get_grid_square_atlas_positions(
169166
_atlas_destination(environment, source, transferred_file)
170167
/ environment.samples[source].atlas
171168
)

0 commit comments

Comments
 (0)